Головна / Статті / Cross-encoder у ролі судді: фільтруємо RAG-пайплайн за оцінками релевантності

Cross-encoder у ролі судді: фільтруємо RAG-пайплайн за оцінками релевантності

Як перехресна увага дає cross-encoder змогу оцінювати релевантність пари «запит — документ» від 0 до 1 і як поріг не дає слабким результатам пошуку взагалі дійти до LLM.

1529 слів

Більшість посібників з RAG вважають cross-encoder просто кроком переранжування. Але він здатен на цікавішу роботу. У ролі судді він вирішує, чи достатньо добрий знайдений текст, щоб узагалі передавати його генераторові.

Поширена альтернатива — LLM у ролі судді, але це все та сама генеративна модель з усіма звичними примхами. Суддя на базі cross-encoder опиняється у вдалій точці перетину трьох турбот: як розвивається архітектура, скільки коштує пайплайн і як довго чекають користувачі.

Чому cross-encoder — добрий суддя

Базові RAG-системи цілком спираються на bi-encoder — родину sentence-transformer, якою будують ембедінги. Bi-encoder стискає кожен текст в окремий самостійний вектор. Він вимірює, наскільки близькі два вектори в просторі, і пропускає тоншу логіку на кшталт умов, заперечень і структурних перевертань.

Є й глибше обмеження. Bi-encoder налаштовані на повноту, а не на точність. Вони знаходять документи на ту саму тему, але не можуть підтвердити, що документ відповідає на питання, яке справді поставили.

Сьогодні пошук зазвичай оцінюють так: просять LLM на зразок GPT чи Claude прочитати знайдені чанки й виставити їм оцінку. У продакшені цей підхід має три серйозні вади:

  1. Затримка. Очікування важкого API-виклику ще до початку генерації псує враження користувачеві, який чекає швидкої відповіді.
  2. Вартість. Надсилання тисяч токенів знайденого контексту моделі-судді на кожен запит роздуває рахунок за API.
  3. Нестабільність. Промпти дрейфують, а вивід моделі недетермінований, що ламає будь-який жорстко заданий поріг. Можливі й галюцинації, тож фрагмент, визнаний релевантним у понеділок, у вівторок може виявитися нерелевантним або напіврелевантним.

Cross-encoder знімає всі три проблеми. Він читає запит і документ разом за один прохід трансформера, тож кожен токен може звертати увагу на будь-який інший. На виході — точна, відтворювана оцінка від 0.0 до 1.0.

Що відбувається всередині: перехресна увага

Найпростіше зрозуміти, чому він судить краще, якщо порівняти, як він читає текст, із тим, як це робить bi-encoder.

Bi-encoder кодує запит (Q) і документ (D) окремо. Виходить один вектор для Q та інший для D. Усередині трансформера токени запиту звертають увагу лише на токени запиту, а токени документа — лише на токени документа. Дві сторони зустрічаються в самому кінці, через скалярний добуток або косинусну подібність.

Cross-encoder працює інакше. Він збирає одну вхідну послідовність, у якій лежать обидва тексти:

[CLS] User Query [SEP] Retrieved Document Chunk [SEP]

Оскільки обидва тексти перебувають в одній послідовності, модель застосовує повну перехресну увагу. Стандартна формула уваги так само діє:

Attention(Q, K, V) = softmax( (Q * Kᵀ) / √dₖ ) * V

Різниця в тому, що кожен токен запиту обчислює вагу уваги щодо кожного токена чанка документа, причому все одразу. Слова питання безпосередньо пов'язуються зі словами, які на нього відповідають або йому суперечать.

Для контрасту — схема bi-encoder, де дві сторони не бачать одна одну до фінального порівняння:

Bi-Encoder Architecture:
  Query Tokens  ──► [ Encoder A ] ──► [ Vector Q ] ──┐
                                                      ├──► Cosine Distance ──► Score
  Doc Tokens    ──► [ Encoder B ] ──► [ Vector D ] ──┘
                   (No token-to-token interaction)

Cross-Encoder Architecture:
  Query Tokens ──┐
                 ├──► [  Full Self-Attention Matrix  ] ──► [ [CLS] Token ] ──► Sigmoid ──► Relevance
  Doc Tokens   ──┘    (Every token attends to every)       (Dense Output)                   Probability

Розбір на прикладі

Ось випадок, де bi-encoder ламається, а cross-encoder дає раду.

Припустімо, у базі знань три документи:

  • Документ A: «Сильні грози й злива порушили рух усім регіоном, і аеропорт скасував усі рейси, призначені на вечір».
  • Документ B: «Попри зливу й темне небо, аеропорт працював у звичайному режимі, і жоден рейс не затримали».
  • Документ C: «Збій у програмному забезпеченні керування повітряним рухом спричинив великі затримки, і денний дощ тут був ні до чого».

Користувач питає: «Дощ спричинив затримки рейсів?»

Де помиляється bi-encoder

Стандартна модель ембедінгів сприймає запит цілком і оцінює кожен документ сам по собі. В усіх трьох текстах є сильно зважені терміни «дощ», «рейс» і «затримки». Bi-encoder вимірює лише загальну близькість векторів, тому документи B і C отримують дуже високу косинусну подібність. Вони часто опиняються поруч із документом A, а іноді й вище за нього. Ретривер бачить збіжні слова, вирішує, що збігається й тема, і вважає їх релевантними.

До чого доходить cross-encoder

Тепер передамо тих самих кандидатів судді на базі cross-encoder. Матриця перехресної уваги змушує токени запиту вишикуватися навпроти тексту документа й утворити справжні зв'язки. Результат зовсім інший:

[Query] + [Doc A] ---> Score: 0.94 (High Confidence)
Mechanics: The model explicitly links "rain" -> "caused" -> "cancel/delays".

[Query] + [Doc B] ---> Score: 0.21 (Irrelevant Noise)
Mechanics: The cross-attention catches the modifier "Despite the heavy rain... continued smoothly without a single flight delay". It recognizes a structural negation.

[Query] + [Doc C] ---> Score: 0.42 (Topical Context Only)
Mechanics: The model notes that while both events happened, the text explicitly declares them "unrelated".

Якщо вважати ці оцінки справжніми ймовірностями, документи B і C можна одразу викинути з промпту. Тоді генератор отримує один чанк із сильним сигналом — документ A. Це виключає шанс, що модель вигадає суперечливе чи хибне твердження про те, як вплинула погода.

Як виходить оцінка

Bi-encoder порівнює відстань у просторі між двома окремими векторами. Cross-encoder узагалі не створює незалежних ембедінгів. Він доходить до оцінки, аналізуючи всю послідовність спільно:

  1. Обробка токенів. Запит і знайдений документ пакуються в одну послідовність і розділяються спеціальним токеном: [CLS] User Query [SEP] Document Chunk [SEP].
  2. Матриця уваги. Поки послідовність проходить трансформером, кожен токен обох текстів обчислює своє відношення до кожного іншого токена. У підсумку всередині моделі виходить щільна матриця взаємодій.
  3. Прихований стан. Підсумкове розуміння модель збирає в токені [CLS] на початку послідовності, який слугує єдиним підсумком того, як пов'язані запит і документ.
  4. Відображення в ймовірність. Фінальний прихований стан [CLS] проходить крізь лінійний класифікаційний шар, що згортає сирі приховані виміри в одне число. Потім його перетворює сигмоїда, Score = 1 / (1 + e^-x).

Сигмоїда стискає будь-який сирий вихід у діапазон від 0.0 до 1.0. Результат читається як відкалібрована ймовірність релевантності — міра того, наскільки модель певна, що документ справді містить відповідь на питання.

Як читати оцінку

Оскільки значення обмежене й абсолютне, його можна розкласти за рівнями й діяти за кожним під час виконання:

  • Від 0.80 до 1.00 — сигнал високої впевненості. Документ закриває суть питання. Відповідь прямо є в чанку, і структура тверджень збігається із запитом.
  • Від 0.50 до 0.80 — лише тематичний контекст. Документ із потрібної галузі й згадує потрібні поняття, але певної відповіді в ньому немає або в ньому багато фонового шуму.
  • Від 0.00 до 0.50 — структурна невідповідність або шум. Текст має багато спільних із запитом ключових слів, але протилежну логіку, заперечення чи сторонню тему.

Перетворюємо суддю на автоматичний вимикач

У звичайному RAG cross-encoder — сортувальник. Він переставляє блоки тексту, перш ніж вони підуть далі. Справжня новизна ролі судді в тому, щоб перетворити його ймовірність на жорсткий шлюз, який може спрацювати як автоматичний вимикач.

Замість того щоб дозволяти генераторові читати те, що випадково опинилося першим, ви задаєте пайплайну тверду нижню планку якості, наприклад 0.75.

Припустімо, ретривер повернув двадцять документів, і суддя оцінив кожен із них. Найкраща оцінка в усій пачці — 0.42. Вимикач спрацьовує, і пайплайн одразу скасовує виклик генерації.

Величезній дорогій мовній моделі не можна лишати вгадувати з нерелевантного матеріалу, бо саме це й провокує галюцинації. Натомість система віддає користувачеві заздалегідь підготовлену запасну відповідь, щось на кшталт: «Вибачте, немає перевіреної внутрішньої документації, щоб надійно відповісти на це питання».

Відкалібровані ймовірності, використані як жорстка програмна межа, перетворюють непередбачуваний цикл генерації тексту на передбачувану програму. Генератор запускається, лише коли докази добрі, а суддя обходиться дешево, бо це невелика модель-енкодер, а не ще один раунд дорогої генерації.