Poza podobieństwem kosinusowym: porównanie pięciu technik wyszukiwania w RAG
Kosinus, BM25, hybrydowy RRF, ponowne rankowanie za pomocą kodera krzyżowego oraz MMR – wyjaśnione przy użyciu kodu, zalet i wad oraz podejście oparte na zapytaniu do wyboru odpowiedniej techniki wyszukiwania dla RAG.
Źródła: **Beyond Cosine Similarity: The 5 RAG Retrieval Techniques That Actually Move the Needle**
Dwie zdania, „Kot siedział na macie” i „Kotek odpoczywał na dywaniku”, nie zawierają ze sobą żadnych wspólnych słów, a mimo to model embeddingowy ocenia je na około 0,85. W ten sposób kosynusowa podobieństwo zyskuje swoją reputację, i właśnie tutaj kończą się większość tutoriali o RAG: pokazuje się kosynus, wkleja się fragment tekstu i przechodzi dalej.
collection.query(query_embeddings=embedding, n_results=5)
To rozwiązanie działa. Jest to jedynie jedna z pięciu technik wyszukiwania, z których każda jest przeznaczona do rozwiązywania innego typu problemów. Poleganie wyłącznie na kosinusie oznacza, że całe kategorie zapytań zawodzą bez żadnego błędu, a użytkownicy otrzymują po prostu błędne odpowiedzi. Oto, co faktycznie mierzy kosinus, w jakich sytuacjach zawodzi oraz cztery pozostałe techniki, plus sposób na decydowanie, którą z nich wybrać.
Co mierzy kosinus
Model embedding konwertuje tekst na wektor, czyli listę setek lub tysięcy liczb. Teksty o podobnym znaczeniu stają się wektorami skierowanymi w tym samym kierunku. Podobieństwo kosinusowe porównuje kąt dzielący dwa wektory, a nie odległość między nimi.
To jest kluczowy punkt. Zdanie składające się z jednej linijki i pełny akapit mogą oznaczać to samo. Jeśli ich wektory są skierowane w tym samym kierunku, kosinus pokazuje wysoką wartość. Długość, która objawia się jako amplituda, stanowi szum, natomiast kierunek to znaczenie.
Typowe wyniki wyglądają w ten sposób:
| Para | Wynik |
|---|---|
| "Kot siedział na macie" i "Kot siedział na dywanie" | 0.85 |
| ">Jak działa uwaga?" i ">Wyjaśnij mechanizm transformera" | 0.79 |
| ">Język programowania Python" i ">Python – gatunek węża" | 0.38 |
| ">Kocham poranną kawę" i ">Teoria splątania kwantowego" | 0.03 |
Pierwszy wiersz pokazuje, dlaczego kosinus jest tak imponujący – brak wspólnych słów, a wynik wynosi 0.85. Model nauczył się, że „kot” i „koty” znajdują się w bliskiej odległości w przestrzeni semantycznej, a kosinus to wykrywa bez żadnego dopasowywania słów kluczowych.
Dlaczego nie odległość euklidesowa
Oczywistą alternatywą jest odległość euklidesowa, czyli dosłownie odległość pomiędzy dwoma wektorami. Problem polega na tym, że jeśli dokument powtórzymy dziesięć razy, jego wektor staje się dziesięć razy większy, przez co wygląda zupełnie inaczej niż oryginał, mimo że treść pozostała bez zmian. Kosinus to ignoruje – liczy się tylko kierunek.
Ostrzeżenie dotyczące wyników
Wyniki oparte na kosinusie nie są uniwersalne. Wartość 0,7 uzyskana za pomocą modelu all-MiniLM-L6-v2 oznacza coś innego niż wartość 0,7 uzyskana z modelu text-embedding-3-large, ponieważ każdy model ma swój własny przestrzeń embeddingów o innej gęstości. Kalibruj progi ocen na swoim własnym zbiorze danych do testowania i nie kopiuj liczb z tutoriali, w których użyto innego modelu.
Analogia festiwalu dla osób niebędących inżynierami
Zanim przejdziemy do kodu, przygotujmy mentalny model pięciu technik. Jesteśmy na festiwalu muzycznym i szukamy przyjaciela w tłumie, mając do dyspozycji trzy strategie.
- Na podstawie kierunku (kosinus). Oboje zgadzacie się zwrócić twarzą w stronę sceny. Ten sam kierunek oznacza tę samą osobę, bez względu na to, jak daleko od siebie stojecie. Ważny jest kąt, w którym pokazujecie, a nie odległość między wami.
- Na podstawie identyfikatora imienia (szukanie według słów kluczowych, BM25). Krzyczycie „Czy jest tu ktoś o imieniu Alex?”. Metoda ta działa doskonale, jeśli identyfikator imienia jest dokładnie taki sam, ale pomija wszystkich o imionach Alexis, Al lub Sandy, którzy są tą samą osobą pod innym nazwiskiem.
- Na podstawie twarzy i identyfikatora imienia (hybrydowa). Sprawdzacie obie informacje. Osoba, która pasuje do obu kryteriów, wyróżnia się na pierwszym miejscu, a ta, która pasuje tylko do jednego, nadal zostaje zauważona. Dzięki temu uzyskujecie najszerszy zakres wykrywania.
Lekcja, którą większość poradników pomija: żaden pojedynczy metod nie pozwala znaleźć wszystkich. Wybrana technika decyduje o tym, które odpowiedzi i które osoby na zawsze przeoczycie.
Pięć technik, po jednej
1. Podobieństwo kosinowe, standard
Jak to działa. Porównuje semantyczny kierunek wektorów zapytania i dokumentu.
Kiedy go używać. Gdy zapytania są w języku naturalnym i nie wymagane są dokładne terminy: ogólne zastosowania RAG, odpowiadanie na pytania w dokumentach, wszystko, co wymaga dużego ilości parafrazowania.
Gdzie zawodzi. Dosłowne ciągi znaków. Kod produktu, numer faktury lub imię osoby, które prawie nie wystąpiły w danych treningowych, mogą sprawić, że metoda podobieństwa kosinowego całkowicie zawiedzie. Wektory „GPT-4o” i „GPT-4” mogą znajdować się na tyle blisko siebie, że generują szum zamiast sygnału.
W kodzie:
collection.query(
query_embeddings=query_embedding,
n_results=5
)
Kompromis. Znaczenie ma większe znaczenie niż dokładne sformułowanie. Jest szybki, dość precyzyjny i nie wymaga dodatkowej konfiguracji.
2. Wyszukiwanie słów kluczowych BM25 – narzędzie precyzji
Jak to działa. Ocenia dokumenty na podstawie częstotliwości występowania słów z zapytania w nich, uwzględniając jednocześnie ich powszechność w całym korpusie. Nie wykorzystuje żadnych embeddingów, tylko prostego liczenia.
Kiedy go używać. Gdy zapytanie opiera się na dokładnym identyfikatorze, który musi pasować znak po znaku, np. numerze faktury, nazwie modelu wraz z datą wydania lub odniesieniu CVE.
Czego nie uwzględnia. Żadnego sensu znaczeniowego. „Feline” nie będzie pasować do dokumentów, w których znajduje się słowo „cat”. Synonimy i parafrazy są dla niego niewidoczne.
Wartościowy wynik. W jednym małym projekcie budowanym od zera dodanie algorytmu BM25 obok metody kosinusowej zwiększyło wskaźnik odnalezienia z 0,25 do 0,75. Przyczyną było to, że rzeczywiste zapytania użytkowników zawierały dokładne nazwy produktów, które metoda kosinusowa traktowała jako niemal identyczne, zamiast dopasowywać je bezpośrednio. Należy traktować to jako jeden punkt danych, a nie wartość uniwersalną.
Kompromis. Dokładność dopasowań dokładnych kosztem braku jakiejkolwiek semantycznej generalizacji.
3. Szukanie hybrydowe – standard w produkcji
Jak to działa. Metoda ta wykorzystuje jednocześnie algorytm kosinusowy i BM25, a następnie łączy obie listy uporządkowane za pomocą techniki Reciprocal Rank Fusion (RRF). Żaden z tych metod sam w sobie nie radzi sobie ze wszystkimi typami zapytań.
Kiedy go używać. Zapytania łączą język naturalny z konkretnymi terminami, co opisuje praktycznie każdy system produkcyjny. Użytkownik pytający „Jaka jest polityka zwrotów dla zamówienia #8821?” formułuje pytanie częściowo semantycznie (polityka zwrotów) i częściowo poprzez dokładne dopasowanie (#8821).
Krok fuzji. Każda technika zwraca swój własny uporządkowany wykaz. RRF przypisuje każdemu dokumencie ocenę na podstawie jego pozycji w poszczególnych wykazach – wynosi ona 1 podzielone przez (k plus rank), gdzie k to stała, zwykle 60 – a następnie sumuje oceny uzyskane z poszczególnych technik. Dokumenty, które plasują się wysoko w obu wykazach, znajdują się na górze listy.
# Pseudocode for RRF merge
def rrf_score(rank, k=60):
return 1 / (k + rank)
def merge_results(cosine_results, bm25_results):
scores = {}
for rank, doc in enumerate(cosine_results):
scores[doc.id] = scores.get(doc.id, 0) + rrf_score(rank)
for rank, doc in enumerate(bm25_results):
scores[doc.id] = scores.get(doc.id, 0) + rrf_score(rank)
return sorted(scores.items(), key=lambda x: x[1], reverse=True)
W AWS OpenSearch obsługuje to w sposób natywny: konfiguruje się zapytanie hybrydowe z parametrem wagi, a system samodzielnie zajmuje się fuzją wyników.
Kompromis. Najlepszy ogólny wskaźnik odnalezienia, przy mniej więcej dwukrotnie wyższych kosztach wyszukiwania, co zazwyczaj jest warte poniesienia takich kosztów.
4. Ponowna klasyfikacja za pomocą cross-encodera
Jak to działa. Metoda dwuetapowa. W pierwszym etapie wybierane są 20 najlepszych kandydatów za pomocą wyszukiwania kosinowego lub hybrydowego. W drugim etapie każdy kandydat wraz z zapytaniem jest podawany do cross-encodera, który ocenia ich istotność, analizując oba jednocześnie.
Kiedy go używać. Gdy najlepsze wyniki są istotne, ale w niewłaściwej kolejności, a precyzja jest ważniejsza niż szybkość odpowiedzi. Należy go stosować w drugim etapie, a nie podczas pierwszej próby wyszukiwania.
Dlaczego dwa etapy? Kodownik krzyżowy ocenia istotność znacznie lepiej niż metoda kosinusa, ale nie można go używać na całej bazie danych. Dwadzieścia wywołań modelu zamiast jednego jest do przyjęcia. Dziesięć tysięcy wywołań podczas wyszukiwania już nie.
from sentence_transformers import CrossEncoder
reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')# Stage 1: retrieve candidates
candidates = collection.query(query_embeddings=embedding, n_results=20)# Stage 2: rerank
pairs = [(query, chunk) for chunk in candidates['documents'][0]]
scores = reranker.predict(pairs)
top_5 = sorted(zip(scores, candidates['documents'][0]), reverse=True)[:5]
Twarde ostrzeżenie. Nie używaj ponownego rankowania jako pierwszego etapu. Koszt opóźnienia jest wysoki, ponieważ zamiast jednego wyszukiwania embeddingu wykonujesz dwadzieścia pełnych inferyencji modelu.
Kompromis. Najwyższa precyzja, ale też najwyższe koszty, więc należy go stosować wyłącznie jako drugi etap.
5. Maksymalna marginalna relewność (MMR) dla różnorodności
Jak to działa. Kompromisuje między bliskością do zapytania a podobieństwem do już wybranych fragmentów. Każdy nowy wybór musi być relewny i różnić się od tego, co zostało już wybrane.
Kiedy go używać. Gdy wszystkie czołowe fragmenty mówią to samo, na przykład w przypadku szerokiego pytania podsumowującego, gdzie każdy wynik powtarza ten sam fakt.
Narzędzie lambda.
lambda = 1.0oznacza czystą relewność, tę samą co w standardowym algorytmie kosinusa
lambda = 0.0 oznacza czystą różnorodność, całkowicie ignorującą istotnośćlambda = 0.5 to zrównoważony punkt wyjścia do dalszych dostosowańdef mmr_select(query_embedding, candidates, lambda_param=0.5, k=5):
selected = []
remaining = list(candidates)
while len(selected) < k and remaining:
mmr_scores = []
for doc in remaining:
relevance = cosine_similarity(query_embedding, doc.embedding)
if selected:
redundancy = max(cosine_similarity(doc.embedding, s.embedding)
for s in selected)
else:
redundancy = 0
mmr_scores.append(lambda_param * relevance - (1 - lambda_param) * redundancy)
best = remaining[mmr_scores.index(max(mmr_scores))]
selected.append(best)
remaining.remove(best)
return selected
Gdzie sprawdza się najlepiej. Streszczenia, pytania badawcze oraz wszelkie przypadki, gdy czytelnik chce obejrzenia różnych koncepcji, a nie głębokiego omówienia jednej.
Kompromis. Usuwa redundancję, ale lambda wymaga dostosowania.
Wybór: niech decydują twoje zapytania
Zanim wybierzesz technikę, przeanalizuj 20 rzeczywistych zapytań od twoich użytkowników lub w swojej dziedzinie. Hipotetyczne nie liczą się.
- 80% lub więcej to zwykły język naturalny. Cosine to twoja domyślna opcja. Zacznij od niej, ponieważ jest szybka i dokładna, a nie wymaga dodatkowej infrastruktury.
- 30% lub więcej zawiera dokładne terminy, kody, ID lub nazwy własne. Dodaj BM25. Gdy zauważysz taki wzorzec, wyszukiwanie hybrydowe staje się domyślnym rozwiązaniem w produkcji, więc zaimplementuj fuzję RRF. Poprawa dokładności jest zazwyczaj szybka i znacząca.
Co pomijają tutoriale
Techniki mogą się ze sobą kolidować, a nie tylko uzupełniać. W jednym eksperymencie dodanie rozszerzania zapytań, które generuje kilka wariantów zapytania i wyszukuje dane dla każdego z nich, na wierzch hybrydowego wyszukiwania wydawało się pewnym rozwiązaniem. Większy zakres powinien oznaczać lepsze wyniki. Wyniki spadły z 0,807 do 0,702. Więcej sygnałów wyszukiwania jednocześnie wzmacnia szum, tak samo jak prawdziwe sygnały, a dodatkowe warianty przyciągały sąsiednie, ale błędne fragmenty danych. Hybrydowe wyszukiwanie już dobrze funkcjonowało, a rozszerzanie dodawało zakłóceń. Należy dodawać po jednej technice, mierzyć jej efekty osobno przed łączeniem ich, pamiętając, że kolejność ma znaczenie – to, co jest przydatne pojedynczo, może być szkodliwe w połączeniu.
Iloczyn skalarny to nie kosinus. Niektóre bazy danych wektorowych używają domyślnie iloczynu skalarnego. Zgodność tych dwóch metod występuje tylko wtedy, gdy wektory są znormalizowane do jednostki. Jeśli Twój model embeddingów nie normalizuje swoich wyników, oceny uzyskane dzięki iloczynowi skalarnemu nie mogą być porównywane pomiędzy różnymi zapytaniami, więc wartość 0,9 dla jednego zapytania i 0,9 dla drugiego oznaczają różne rzeczy. Sprawdź, co faktycznie oblicza Twoja baza danych. Na przykład ChromaDB jest zazwyczaj opisywany jako używający innej domyślnej metryki niż Qdrant, więc sprawdź to w aktualnej dokumentacji obu rozwiązań.
To zapytania decydują o technice, a nie dokumenty. Inżynierowie często poświęcają czas na klasyfikację swojego korpusu jako technicznego lub konwersacyjnego, długiego lub krótkiego. Ma to znaczenie przy dzieleniu tekstu na fragmenty. Przy wyborze techniki wyszukiwania analiza zapytań jest o wiele ważniejsza. Korpus techniczny wyszukiwany za pomocą pytań w języku naturalnym stanowi problem kosinusowy, natomiast ten sam korpus wyszukiwany podając numer części stanowi problem hybrydowy.
Pełny proces
Oto warstwowa struktura, jaką ostatecznie przyjmują wiele systemów RAG w produkcji:
User Query
↓
[Optional: HyDE — if query is vague]
↓
Stage 1 Retrieval: Hybrid (Cosine + BM25 → RRF merge)
→ Top 20 candidates
↓
[Optional: Cross-encoder reranking — if precision matters]
→ Top 5 candidates
↓
[Optional: MMR — if results are redundant]
→ Final k candidates
↓
LLM Context Assembly + Generation
Nie każdy system potrzebuje wszystkich warstw. Zacznij od metody kosinusowej, dodaj metodę hybrydową, gdy pojawią się błędy przy dokładnym dopasowaniu, dodaj ponowne sortowanie, gdy kolejność jest błędna, oraz MMR, gdy wyniki się powtarzają. Mierz na każdym kroku, ponieważ to, co pomaga jednemu systemowi, może zaszkodzić innemu.
Liczba, która ma znaczenie
Raporty z eksperymentów z wyszukiwaniem często pokazują różnicę w stopniu odnalezienia między wyszukiwaniem opartym wyłącznie na metodzie kosinusowej a hybrydowym w zmiennych zestawach zapytań – w jednym małym projekcie ten wskaźnik wzrósł z 0,25 do 0,75. Jeśli nawet w przybliżeniu dotyczy to twoich danych, oznacza to, że około trzy razy więcej poprawnych odpowiedzi dociera do modelu językowego przed napisaniem jakiegoś słowa, bez żadnej modyfikacji promptów, aktualizacji modelu ani przebudowy infrastruktury. Chodzi tu o dobre dokonanie wyboru metody wyszukiwania.
Pozycje pokrewne
- Dlaczego RAG zawodzi w produkcji: dzielenie na fragmenty, embeddingi, zdobywanie danych i ocena — Praktyczny przegląd sytuacji, w których generacja wzbogacona zdobywaniem danych przestaje działać poza fazą demonstracji: granice fragmentów, niewidoczne problemy z embeddingami, metoda top-K, przestarzałe dane oraz brak oceny.
- Praktyczna architektura RAG w Firebase i Vertex AI z użyciem Firestore Vectors — Budowa systemu RAG przy użyciu embeddingów z Vertex AI oraz wyszukiwania wektorowego w Firestore: grupowanie danych, filtry metadanych, izolacja użytkowników, spersonalizowane instrukcje, cacheowanie i metryki.