Ponowne szeregowanie pobranych fragmentów
Ponowne szeregowanie za pomocą cross-encodera
Ponowne szeregowanie pobranych fragmentów to bezpłatna lekcja AI Prompt Engineering na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Prompt Engineering, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Prompt Engineering zawiera 4 lekcji w sumie.
Po co w ogóle stosować reranking
Wyszukiwanie pierwszego etapu (gęste lub rzadkie) optymalizuje recall przy dużej skali: umieszcza właściwy fragment gdzieś wśród 50 najlepszych wyników. Jest szybkie, ale przybliżone. Następnie drugi etap, czyli reranker, porządkuje tę krótką listę pod kątem precyzji, umieszczając naprawdę istotne fragmenty na jej początku.
Wzorzec „wyszukuj szeroko, a następnie precyzyjnie porządkuj” stanowi podstawę zaawansowanego 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 a cross-encoder
Bi-encoder koduje zapytanie i dokument osobno do postaci wektorów i porównuje je za pomocą podobieństwa cosinusowego; jest szybki i pozwala na indeksowanie, ale nie uwzględnia interakcji między zapytaniem a dokumentem. Cross-encoder przekazuje zapytanie i kandydata razem przez model i zwraca wynik istotności, ujmując szczegółowe interakcje.
Cross-encodery są znacznie dokładniejsze, ale nie można ich wstępnie obliczyć, dlatego uruchamia się je tylko dla krótkiej listy kandydatów.
# 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 attentionPrzebieg rerankingu z użyciem cross-encodera
Reranker ocenia każdego kandydata względem zapytania, a następnie sortuje wyniki malejąco. Ponieważ cross-encoder jednocześnie analizuje zapytanie i dokument, rozstrzyga subtelne kwestie istotności pominięte przez bi-encoder: negację, potrzebę dokładnego dopasowania oraz różnicę między odpowiedzią a tematem.
Ten etap zwykle poprawia dokładność odpowiedzi bardziej niż jakiekolwiek inne pojedyncze ulepszenie 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 jako reranker
Gdy żaden wytrenowany cross-encoder nie pasuje do danej domeny, do rerankingu można użyć LLM-a. Promptowanie listwise prosi model o uporządkowanie listy fragmentów według istotności w jednym wywołaniu, natomiast pointwise polega na niezależnym ocenianiu każdego fragmentu.
Listwise dobrze uwzględnia porównania względne i oszczędza tokeny, ale należy uważać na stronniczość pozycji oraz zadbać o odporne parsowanie wyników, ponieważ model może pominąć lub powielić identyfikatory.
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]Opóźnienie a rozmiar krótkiej listy
Koszt ponownego rangowania skaluje się wraz z liczbą elementów na krótkiej liście. Cross-encoder dla 50 kandydatów jest znacznie tańszy niż dla 500. Proszę wybrać wartość pierwszego etapu k na tyle dużą, aby uwzględnić właściwy fragment (należy zweryfikować recall@k), ale na tyle małą, aby ponowne rangowanie mieściło się w budżecie opóźnienia.
Proszę grupować ocenianie par w partie i uruchamiać je na sprzęcie akcelerowanym; cross-encodery dobrze równoleglą przetwarzanie par.
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}Hybrydowy pierwszy etap i reranking
Najwyższy poziom recall zapewnia hybrydowy pierwszy etap (połączenie wyników wyszukiwania gęstego i BM25), który przekazuje rerankerowi jedną, pozbawioną duplikatów krótką listę kandydatów. Wyszukiwanie gęste odzyskuje parafrazy, wyszukiwanie rzadkie — dokładne identyfikatory, a następnie cross-encoder porządkuje ich połączenie według rzeczywistej trafności.
To połączenie jest odporne na różne typy zapytań — od pytań w języku naturalnym po dosłowne wyszukiwanie.
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]Progi i wartości odcięcia dla wyników
Wyniki rerankera można kalibrować. Zamiast zawsze wybierać pierwsze n wyników, należy zastosować próg trafności: zachować fragmenty powyżej określonego wyniku, a jeśli żaden nie spełnia tego warunku, zwrócić uczciwą odpowiedź „brak odpowiedzi”. Zapobiega to zaśmiecaniu promptu słabo trafnymi treściami.
Próg należy dostroić na zbiorze walidacyjnym, aby zachować równowagę między zakresem pytań, na które można odpowiedzieć, a uwzględnianiem rozpraszających treści.
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 keptRóżnorodność po rerankingu
Sortowanie wyłącznie według trafności może zwrócić kilka niemal identycznych fragmentów z tego samego dokumentu, marnując budżet kontekstu. Po rerankingu należy zastosować MMR lub limity na dokument, aby zapewnić, że końcowy zestaw obejmuje różne aspekty i źródła.
Ma to znaczenie w przypadku pytań wieloetapowych, których odpowiedź obejmuje kilka dokumentów.
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 outKolejność dla generatora
Po wybraniu najtrafniejszych fragmentów należy je odpowiednio umieścić, aby wykorzystać mechanizm uwagi. Ze względu na zjawisko lost-in-the-middle pojedynczy fragment o najwyższym wyniku należy umieścić na początku lub na końcu kontekstu, a nie ukrywać go wśród innych fragmentów.
Niektóre potoki porządkują fragmenty rosnąco według trafności, tak aby najlepszy znajdował się najbliżej pytania, naśladując strategię recency stosowaną w few-shot.
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 questionOcena rerankera
Rerankera należy oceniać za pomocą metryk rankingowych, przede wszystkim NDCG i MRR, na podstawie oznaczonej trafności par zapytanie–fragment, a następnie oceniać dokładność odpowiedzi w zadaniu końcowym. Reranker, który poprawia NDCG, ale nie poprawia odpowiedzi, może jedynie zmieniać kolejność fragmentów, z którymi generator już sobie radził.
Należy zawsze oceniać jakość zadania końcowego, a nie tylko metryki rankingowe.
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.0Produkcyjny potok rerankingu
Od początku do końca: należy pobrać hybrydowo krótką listę 50 kandydatów, usunąć duplikaty, wykonać reranking za pomocą cross-encodera, zastosować próg wyniku, zadbać o różnorodność dokumentów, uporządkować fragmenty pod kątem uwagi i wygenerować odpowiedź z cytowaniami. Na podstawie progu należy zdecydować, czy zwrócić uczciwą odpowiedź „brak odpowiedzi”.
Jeśli ruch się powtarza, należy buforować embeddingi i wyniki rerankera dla par (zapytanie, fragment), aby ograniczyć koszty.
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)Szybkie sprawdzenie
Wybierz właściwą architekturę rerankingu.
Podsumowanie
Najważniejsze wnioski:
- Należy wyszukiwać szeroko, aby uzyskać recall, a następnie wykonać reranking krótkiej listy kandydatów, aby zwiększyć precision.
- Cross-encodery wspólnie oceniają pary zapytanie–dokument (są dokładne, ale nie można ich indeksować); bi-encodery są szybkie, lecz mniej precyzyjne.
- Reranking listwise/pointwise za pomocą LLM-a jest rozwiązaniem awaryjnym; należy uważać na stronniczość pozycji i problemy z parsowaniem.
- Rozmiar krótkiej listy kandydatów należy dostroić tak, aby wysycenie recall mieściło się w budżecie opóźnienia; warto połączyć ją z hybrydowym wyszukiwaniem w pierwszym etapie.
- Należy stosować progi wyników, dbać o różnorodność dokumentów, porządkować fragmenty pod kątem uwagi oraz oceniać system za pomocą NDCG i dokładności odpowiedzi w zadaniu końcowym.
Często zadawane pytania
Czy lekcja „Ponowne szeregowanie pobranych fragmentów” jest bezpłatna?
Tak — pełny tekst „Ponowne szeregowanie pobranych fragmentów” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Prompt Engineering, przejdź na CoddyKit PRO. Kurs AI Prompt Engineering zawiera 4 lekcji w sumie.
Co nauczysz się w „Ponowne szeregowanie pobranych fragmentów”?
Ponowne szeregowanie za pomocą cross-encodera Ćwiczysz AI Prompt Engineering z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć AI Prompt Engineering?
Nie wymagamy żadnego doświadczenia. AI Prompt Engineering w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.
Ile czasu zajmuje lekcja „Ponowne szeregowanie pobranych fragmentów”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji AI Prompt Engineering?
Tak. Każda lekcja AI Prompt Engineering zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Poza naiwnym RAG
- Ponowne szeregowanie pobranych fragmentów
- Kompresja kontekstu
- Przepisywanie zapytań i HyDE