Переранжирование найденных фрагментов
Переранжирование с помощью кросс-энкодера
«Переранжирование найденных фрагментов» — бесплатный урок AI Prompt Engineering на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Prompt Engineering, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Prompt Engineering содержит 4 уроков всего.
Зачем вообще выполнять переранжирование
Поиск на первом этапе (плотный или разреженный) оптимизирует полноту в масштабе: найти эталонный фрагмент где-нибудь среди первых 50. Он быстрый, но грубый. Затем переранжировщик второго этапа перестраивает порядок этого короткого списка для повышения точности, выводя действительно релевантные фрагменты на первые позиции.
Этот шаблон — сначала широкий поиск, затем точное переранжирование — составляет основу передового RAG.
def two_stage(query, k_retrieve=50, k_final=5):
candidates = first_stage_retrieve(query, k_retrieve) # high recall
reranked = rerank(query, candidates) # high precision
return reranked[:k_final]Биэнкодер и кросс-энкодер
Биэнкодер кодирует запрос и документ по отдельности в векторы и сравнивает их по косинусному сходству; он быстр и поддерживает индексацию, но теряет взаимодействие запроса с документом. Кросс-энкодер передаёт запрос и кандидата вместе через модель и выдаёт оценку релевантности, фиксируя тонкие взаимосвязи.
Кросс-энкодеры значительно точнее, но их нельзя вычислить заранее, поэтому они запускаются только для короткого списка кандидатов.
# Bi-encoder: score = cos(enc(q), enc(d)) -> precomputable
# Cross-encoder: score = model(q, d) -> 0..1 -> per-pair, no index
def cross_encode(query, doc):
return cross_encoder.predict([(query, doc)])[0] # joint attentionЭтап переранжирования кросс-энкодером
Переранжировщик оценивает каждого кандидата относительно запроса, а затем сортирует результаты по убыванию. Поскольку кросс-энкодер совместно анализирует запрос и документ, он выявляет тонкие аспекты релевантности, которые пропустил биэнкодер: отрицание, требования точного совпадения и различие между ответом и темой.
Обычно этот этап сильнее повышает точность ответа, чем любое другое отдельное улучшение RAG.
def rerank(query, candidates):
pairs = [(query, c.text) for c in candidates]
scores = cross_encoder.predict(pairs) # batched
for c, s in zip(candidates, scores):
c.rerank_score = s
return sorted(candidates, key=lambda c: c.rerank_score, reverse=True)LLM в роли переранжировщика
Если обученный кросс-энкодер не подходит для Вашей предметной области, для переранжирования можно использовать LLM. Ранжирование списка в промпте просит модель упорядочить список фрагментов по релевантности за один вызов; поточечное ранжирование оценивает каждый фрагмент независимо.
Ранжирование списка учитывает относительные сравнения и экономит токены, но следите за позиционным смещением и убедитесь, что разбор вывода устойчив к пропуску или дублированию идентификаторов моделью.
def llm_listwise(query, candidates):
passages = '\n'.join(
'[' + str(i) + '] ' + c.text for i, c in enumerate(candidates)
)
prompt = (
'Rank the passages by relevance to the query. '
'Return only IDs, most relevant first.\nQuery: ' + query +
'\n' + passages
)
order = parse_ids(llm(prompt, temperature=0))
return [candidates[i] for i in order]Задержка и размер списка кандидатов
Стоимость повторного ранжирования растёт вместе с размером списка кандидатов. Кросс-энкодер для 50 кандидатов обходится значительно дешевле, чем для 500. Выберите достаточно большое значение k для первого этапа, чтобы находить эталонный фрагмент (проверьте полноту поиска с помощью recall@k), но достаточно малое, чтобы выполнять повторное ранжирование в пределах допустимого бюджета задержки.
Объединяйте оценку пар в пакеты и выполняйте её на аппаратном обеспечении с ускорением; кросс-энкодеры хорошо распараллеливаются между парами.
def tune_shortlist(eval_set, ks=(20, 50, 100, 200)):
# find smallest k where recall@k saturates -> rerank fewer pairs
return {k: (recall_at_k(eval_set, k), rerank_latency(k)) for k in ks}Гибридный первый этап с последующим переранжированием
Наибольшую полноту поиска обеспечивает гибридный первый этап: плотный поиск и BM25 объединяются, после чего единый список кандидатов без дубликатов передаётся переранжировщику. Плотный поиск находит перефразированные формулировки, разреженный — точные идентификаторы, а затем кросс-энкодер сортирует их объединение по истинной релевантности.
Такое сочетание надёжно работает с разными типами запросов — от вопросов на естественном языке до буквального поиска.
def hybrid_then_rerank(query, k_final=6):
dense = dense_retrieve(query, 50)
sparse = bm25_retrieve(query, 50)
fused = dedup(rrf(dense, sparse)) # reciprocal rank fusion
return rerank(query, fused)[:k_final]Пороги оценок и ограничения
Оценки переранжировщика можно калибровать. Вместо того чтобы всегда брать первые n результатов, применяйте порог релевантности: оставляйте фрагменты с оценкой выше порога, а если подходящих нет, честно возвращайте ответ «нет ответа». Это не позволяет перегружать промпт слабосвязанным содержимым.
Настройте порог на проверочном наборе, чтобы сбалансировать полноту покрытия вопросов, на которые можно ответить, и включение отвлекающих результатов.
def threshold_select(reranked, tau=0.3, max_n=8):
kept = [c for c in reranked if c.rerank_score >= tau][:max_n]
if not kept:
return None # signal: no sufficiently relevant context
return keptРазнообразие после переранжирования
Простая сортировка по релевантности может вернуть несколько почти одинаковых фрагментов из одного документа, напрасно расходуя бюджет контекста. После переранжирования применяйте MMR или ограничивайте число фрагментов на документ, чтобы в итоговый набор вошли разные аспекты и источники.
Это особенно важно для многошаговых вопросов, ответ на которые охватывает несколько документов.
def diversify(reranked, max_per_doc=2, k=6):
out, per_doc = [], {}
for c in reranked:
d = c.meta['doc_id']
if per_doc.get(d, 0) < max_per_doc:
out.append(c)
per_doc[d] = per_doc.get(d, 0) + 1
if len(out) == k:
break
return outПорядок для генератора
Выбрав лучшие фрагменты, разместите их так, чтобы использовать особенности механизма внимания. С учётом эффекта потери информации в середине поместите единственный фрагмент с наивысшей оценкой в начало или конец контекста, а не прячьте его среди остальных.
Некоторые конвейеры располагают фрагменты по возрастанию релевантности, чтобы лучший находился ближе всего к вопросу; это отражает стратегию близости к последним примерам при обучении на нескольких примерах.
def order_for_llm(chunks):
chunks = sorted(chunks, key=lambda c: c.rerank_score) # ascending
return chunks # most relevant chunk ends up last,
# nearest the trailing questionОценка переранжировщика
Оценивайте переранжировщик с помощью метрик ранжирования, прежде всего NDCG и MRR, на размеченной релевантности пар «запрос–фрагмент», а затем проверяйте конечную точность ответа. Если переранжировщик повышает NDCG, но не улучшает ответы, возможно, он лишь меняет порядок фрагментов, с которым генератор и так справлялся.
Всегда оценивайте качество конечной задачи, а не только метрики ранжирования.
import math
def ndcg_at_k(relevances, k):
dcg = sum(r / math.log2(i + 2) for i, r in enumerate(relevances[:k]))
ideal = sorted(relevances, reverse=True)
idcg = sum(r / math.log2(i + 2) for i, r in enumerate(ideal[:k]))
return dcg / idcg if idcg else 0.0Производственный конвейер переранжирования
Сквозной процесс выглядит так: выполните гибридный поиск и получите список из 50 кандидатов, удалите дубликаты, выполните переранжирование кросс-энкодером, примените порог оценки, обеспечьте разнообразие по документам, расположите фрагменты с учётом внимания и сгенерируйте ответ с цитатами. Если результаты не достигают порога, возвращайте честный ответ «нет ответа».
Кэшируйте векторные представления и оценки переранжировщика для каждой пары (запрос, фрагмент), если запросы повторяются, чтобы снизить затраты.
def pipeline(query):
shortlist = hybrid_then_rerank(query, k_final=20)
kept = threshold_select(shortlist, tau=0.3, max_n=8)
if kept is None:
return 'No relevant information found.'
ctx = order_for_llm(diversify(kept))
return generate_with_citations(query, ctx)Быстрая проверка
Выберите правильную архитектуру переранжирования.
Итоги
Главные выводы:
- Сначала выполняйте широкий поиск для повышения полноты, затем переранжируйте список кандидатов для повышения точности.
- Кросс-энкодеры совместно оценивают пары «запрос–документ» — они точны, но не поддерживают индексацию; би-энкодеры работают быстро, но дают более грубую оценку.
- Списочное и поточечное переранжирование с помощью LLM — запасной вариант; учитывайте смещение позиций и проблемы разбора.
- Настройте размер списка кандидатов так, чтобы полнота поиска достигала предела в рамках допустимой задержки; сочетайте его с гибридным поиском на первом этапе.
- Применяйте пороги оценок, обеспечивайте разнообразие по документам, располагайте фрагменты с учётом внимания и оценивайте результат с помощью NDCG и последующей точности ответа.
Часто задаваемые вопросы
Урок «Переранжирование найденных фрагментов» бесплатный?
Да — полный текст урока «Переранжирование найденных фрагментов» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Prompt Engineering, подпишись на CoddyKit PRO. Курс AI Prompt Engineering содержит 4 уроков всего.
Чему я научусь в уроке «Переранжирование найденных фрагментов»?
Переранжирование с помощью кросс-энкодера Ты практикуешь AI Prompt Engineering с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AI Prompt Engineering?
Предыдущий опыт не требуется. AI Prompt Engineering на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Переранжирование найденных фрагментов»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AI Prompt Engineering?
Да. Каждый урок AI Prompt Engineering включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- За пределами наивного RAG
- Переранжирование найденных фрагментов
- Сжатие контекста
- Переписывание запросов и HyDE