Strona główna / Artykuły

Znaczenie jako współrzędne: od embeddingów do baz danych wektorowych i RAG

Embeddingi, podobieństwo kosinowe, bazy danych wektorowych oraz pętla RAG wyjaśnione za pomocą czterech krótkich zdań o psach, kotach i Paryżu, aby zwiększyć liczba kliknięć w wyszukiwarkę semantyczną.

Wydawca: REACTAPP.TOP11 min czytania2466 słów

Źródła: Meaning as Coordinates: Semantic Search, Vector Databases, and RAG

Pole wyszukiwania, które nie zwraca żadnych wyników dla zapytania „dobry zwierzę domowe dla aktywnej osoby”, mimo że baza wiedzy jest pełna porad dotyczących psów, pokazuje dokładnie, gdzie zawodzi dopasowywanie słów kluczowych. Naprawa tego problemu oznacza traktowanie znaczenia jako pozycji w przestrzeni. Ta koncepcja leży u podstaw wyszukiwania semantycznego, baz danych wektorowych oraz generacji wzbogaconej o informacje z bazy danych, a jeden mały przykład wystarczy, by to wszystko zrozumieć.

Przykład używany przez cały tekst

Wyobraźmy sobie miniaturową bazę wiedzy składającą się z czterech zdań:

1. Dogs are loyal and need daily walks and exercise
2. Cats are independent pets that groom themselves
3. Golden retrievers are friendly family dogs
4. The capital of France is Paris

Użytkownik pyta:

"What's a good pet for an active person who exercises a lot?"

W pytaniu nigdy nie występują słowa „pies” ani „spacer”. Zwykłe wyszukiwanie według słów kluczowych nie dałoby żadnych użytecznych wyników. Mimo to maszyna może nadal dostarczyć właściwą odpowiedź, ponieważ porównuje pozycję tekstów w przestrzeni znaczeń, a nie te słowa, które się w nich znajdują.

Podobne znaczenia, bliskie punkty

Wektor to po prostu rząd liczb, który koduje znaczenie tekstu. Gdy podobne znaczenia tworzą podobne wektory, znajdowanie powiązanych tekstów staje się szukaniem bliskich punktów. To przekształca problem językowy w problem geometryczny, który stanowi podstawę współczesnego wyszukiwania, rekomendacji oraz techniki RAG – polegającej na dostarczaniu modelowi językowemu zewnętrznej bazy danych do konsultacji, aby przestał wymyślać rzeczy.

Gdy znaczenie to zbiór współrzędnych, praca z znaczeniem oznacza pracę z odległością i kierunkiem, co komputery robią niezwykle dobrze.

Embeddingi dla całych fragmentów tekstu

Wektory na poziomie słów nie wystarczają do wyszukiwania dokumentów. Do zapisania znaczenia zdania, akapitu lub strony potrzebny jest pojedynczy wektor. Modele typu Transformer potrafią go wygenerować: wystarczy podać fragment tekstu, a otrzymuje się wektor, zwykle nazywany embeddingiem, który podsumowuje jego znaczenie.

"Dogs need daily walks"  ──►  [ 0.91,  0.08, -0.30,  ... ]   (e.g. 384 numbers)
 "The capital is Paris"   ──►  [-0.05,  0.62,  0.44,  ... ]

Embedding zdania skompresowuje fragment tekstu na listę liczb o stałej długości. Dwa teksty na tym samym temacie dają podobne listy, natomiast teksty niespowiązane dają zupełnie inne. Poszczególne liczby nie mają żadnego znaczenia dla człowieka. Ważne jest to, jak się wzajemnie odnoszą, a nikt nie czyta ich bezpośrednio. Są one jedynie porównywane.

Obraz, który należy mieć na myśli

Wyobraź sobie każdy tekst jako punkt w przestrzeni. Teksty o podobnym znaczeniu znajdują się blisko siebie, natomiast te niespowiązane – daleko od siebie.

(a simplified 2-D "meaning space")
   dog walks •   • golden retriever          ← the "dogs" neighborhood
                \  /
                 (pets)
   cat grooming •                                    • Paris   ← far away, different topic

Na tej uproszczonej mapie dwa zdania o psach znajdują się blisko siebie, zdanie o kocie leży obok nich, ponieważ również dotyczy zwierzęcia domowego, natomiast zdanie o Paryżu znajduje się w zupełnie innym regionie. Rzeczywiste przestrzenie embedding mają setki wymiarów, ale intuicja jest identyczna: powiązane znaczenia oznaczają punkty znajdujące się blisko siebie. Wszystko, co następuje, to sposoby na znalezienie najbliższych punktów.

Wyszukiwanie według słów kluczowych kontra wyszukiwanie semantyczne

Wyszukiwanie według słów kluczowych dopasowuje słowa. Szukając „ćwiczeń”, pominięty zostanie dokument mówiący o codziennych przechadzkach, ponieważ nie ma żadnego wspólnego słowa. Jest to szybkie, ale dosłowne i pomija znaczenie.

Wyszukiwanie semantyczne dopasowuje znaczenia. Zapytanie jest przekształcane w wektor, a zwracane są wektory dokumentów najbliższe mu, niezależnie od słów, których używają. Oto ranking uzyskany dla tego przykładu, przy użyciu rzeczywistych wyników podobieństwa:

Query: "a good pet for an active person who exercises"
 0.999  #############################  Golden retrievers are friendly family dogs
 0.995  #############################  Dogs are loyal and need daily walks and exercise
 0.398  ###########                    Cats are independent pets that groom themselves
 0.059  #                              The capital of France is Paris

Zapytanie nie ma żadnych słów kluczowych wspólnych z zdaniami o psach, a mimo to oba uzyskują najwyższy wynik, około 1,0. Zdanie o kocie znajduje się pośrodku, ponieważ nadal jest to zwierzę domowe, natomiast zdanie o Paryżu ma wynik bliski zera. Wyszukiwanie według słów kluczowych nie dałoby żadnych wyników. Dlatego właśnie wyszukiwanie semantyczne wydaje się inteligentne – rozpoznaje, że aktywna osoba uprawiająca sport i pies potrzebujący codziennych przechadzek oznaczają mniej więcej to samo. Różnica pomiędzy wyszukiwaniem według słów kluczowych a semantycznym to różnica pomiędzy brakiem wyników a odpowiedzią, której faktycznie szukano.

Mierzenie bliskości za pomocą podobieństwa kosinowego

Zwykłym narzędziem do mierzenia bliskości dwóch wektorów jest podobieństwo kosinowe. Bierze ono pod uwagę kąt pomiędzy dwiema strzałkami, ignorując ich długość.

  • Strzałki skierowane w tym samym kierunku dają podobieństwo bliskie 1, co oznacza bardzo podobne znaczenie.
  • Strzałki skierowane prostopadle dają podobieństwo bliskie 0, co oznacza brak związku.
  • Strzałki wskazujące w przeciwnych kierunkach dają podobieństwo bliskie -1, co oznacza przeciwne znaczenie.

Niewielki kąt pomiędzy zapytaniem a dokumentem o psach wygląda w ten sposób:

^  query
        │ /
        │/  small angle  →  cosine ≈ 1  (similar)
        └──────► dog doc

A szeroki kąt na dokument z Paryża:

        ^ query
        │
        └──────► Paris doc      big angle  →  cosine ≈ 0  (unrelated)

Zamiast długości używa się kąta, aby teksty długie i krótkie mogły być porównywane w sprawiedliwy sposób. Obliczenia mieszczą się w jednej linii: należy pomnożyć wektory ze sobą i znormalizować wynik.

import numpy as np
def cosine(a, b):
    return a @ b / (np.linalg.norm(a) * np.linalg.norm(b))

To mała funkcja leży u podstaw każdego wyniku w powyższym rankingu. Wyniki waha się od -1 do 1, a wartość bliska 1 oznacza, że dwa teksty mają to samo znaczenie.

Bazy danych wektorowych do wyszukiwania na dużą skalę

W przykładzie znajdują się cztery dokumenty. Rzeczywiste systemy przechowują miliony lub miliardy takich elementów, takich jak każda zgłoszenie obsługi klienta, strona produktu czy akapit z dokumentacji firmy. Porównywanie zapytania z miliardem wektorów pojedynczo przy każdym wyszukiwaniu jest zbyt wolne.

Dla tego celu istnieją bazy danych wektorowych. Są to struktury zaprojektowane do wykonywania jednej zadania doskonale: przyjmowania wektora zapytania i szybkiego zwracania najbliższych przechowywanych wektorów. Opierają się one na przybliżonym wyszukiwaniu najbliższego sąsiada, rodziny sprytnych indeksów, które znajdują niemal najbliższe dopasowania, pomijając większość porównań. Niewielka strata dokładności jest kompensowana ogromną szybkością działania.

documents ─► embed ─► [ vector database ]
                              │  stores millions of vectors
                              │  + a smart index (ANN)
   query ─► embed ─────────►  │
                              ▼
                     top-k nearest matches (in milliseconds)

Dokumenty przechodzą przez model embeddingu tylko raz i są zapisywane razem z indeksem. Gdy pojawia się pytanie, ono również jest przekształcane za pomocą tego modelu, a baza danych natychmiast odpowiada, pokazując k najlepiej pasujących elementów, niezależnie od liczby przechowywanych wektorów. Do produktów w tym obszarze należą FAISS, Pinecone, Weaviate, Chroma i Milvus, a także pgvector – rozszerzenie, które wprowadza wyszukiwanie wektorowe do zwykłego PostgreSQL. Szczegóły się różnią, ale wszystkie one przechowują embeddingi i szybko znajdują odpowiednie elementy. Krótko mówiąc, baza danych wektorowych to silnik wyszukiwania znaczeń.

Co idzie nie tak z modelami języka potocznego

Wielkie modele językowe robią wrażenie, ale same w sobie mają trzy poważne słabości:

  • Halucynacje. Gdy nie są pewne, generują płynne, pewne siebie, ale błędne odpowiedzi.
  • Granica wiedzy. Model zna tylko to, co znajdowało się w danych treningowych, zamrożonych w określonej wcześniejszej dacie.
  • Brak wiedzy o twoich danych. Dokumenty wewnętrzne, prywatne notatki oraz spotkanie z zeszłego tygodnia są nieznane modelowi.
You: "What's our refund policy?"
 Plain LLM: "Typically, refunds are offered within 30 days..."   ← a guess, not YOUR policy

Gdy pytają o politykę zwrotów, prosty model odpowiada z pamięci, podając prawdopodobną odpowiedź, a nie rzeczywistą politykę firmy. Jest to ryzykowne w przypadku informacji faktycznych lub poufnych. Konieczne jest dostarczanie dokładnych faktów dokładnie w momencie, gdy model odpowiada, bez konieczności ponownego szkolenia, a RAG robi dokładnie to.

Retrieval-augmented generation

RAG łączy wszystko powyższe w jeden proces. Zamiast prosić model o odpowiedź z pamięci, najpierw poprzez wyszukiwanie semantyczne pobierane są odpowiednie dokumenty, a następnie model jest proszony o udzielenie odpowiedzi na ich podstawie. Nazwa ta jest opisem: retrieval oznacza znajdowanie istotnego tekstu, augmented oznacza dodanie go do promptu, a generation pozwala modelowi napisać odpowiedź.

1. Question ─► embed ─► search the vector DB ─► top relevant chunks
                                                        │
 2. Build a prompt:  "Using ONLY the context below, answer the question.
                      Context: [retrieved chunks]
                      Question: [user's question]"
                                                        │
 3. LLM generates an answer grounded in the retrieved text ─► with sources

Przekazanie przykładu do przetworzenia:

Question: "What's a good pet for an active person?"
   → retrieves docs 1 & 3 (the dog sentences, from Figure 3)
   → prompt: "Using this context: [Dogs need daily walks and exercise;
              Golden retrievers are friendly family dogs] — answer the question."
   → LLM: "A dog — especially a golden retriever — is a great fit, since
           they need daily walks and exercise. (Source: docs 1, 3)"

Odpowiedź jest teraz uzasadniona. Opiera się na rzeczywistych dokumentach, które kontroluje właściciel, i potrafi wskazać, skąd pochodzi dany fakt. Znacznie mniej wymyślonych odpowiedzi przedostaje się do użytkownika. RAG to wyszukiwanie semantyczne w połączeniu z modelem językowym: najpierw pobiera odpowiednie fakty, a następnie model formułuje odpowiedź.

Praktyczny proces i jego główna pułapka

RAG składa się z dwóch faz. Konfiguracja odbywa się raz, natomiast wyszukiwanie – za każdym razem, gdy ktoś zadaje pytanie.

── SETUP (once) ──                    ── QUERY (every question) ──
 documents                             user question
    │ split into chunks                    │ embed
    │ embed each chunk                     │ search vector DB
    │ store in vector DB                   │ get top-k chunks
    ▼                                      │ build prompt with chunks
 [ vector database ]  ◄───── search ──────┤ send to LLM
                                           ▼
                                     grounded answer

Dokumenty są dzielone na fragmenty, wstawiane i przechowywane tylko raz. Każde zapytanie jest wstawiane, porównywane z przechowywanymi fragmentami, a najlepsze z nich trafiają do modelu w celu sformułowania ostatecznej odpowiedzi.

Problemem jest sam proces dzielenia na fragmenty, czyli sposób, w jaki dokumenty są rozdzielane przed wstawieniem. Zbyt duże fragmenty łączą kilka tematów w jeden wektor, co powoduje nieprecyzyjne dopasowania. Zbyt małe fragmenty utrać kontekst, przez co odpowiedź może zostać przecięta na pół. Wybór odpowiedniej wielkości fragmentów należy do najważniejszych decyzji praktycznych w działającym systemie RAG i przypomina, że niepozorna praca przy przygotowaniu danych wpływa na wszystko później w procesie.

To samo w kodzie

Część systemu RAG odpowiedzialna za wyszukiwanie, czyli prosta wyszukiwarka semantyczna, wymaga zaledwie kilku linijek kodu w NumPy. Otrzymuje wektory niektórych dokumentów oraz wektor zapytania i sortuje dokumenty według podobieństwa kosinowego:

import numpy as np
def cosine(a, b):
    return a @ b / (np.linalg.norm(a) * np.linalg.norm(b))def search(query_vec, doc_vecs, docs, k=2):
    scores = [(cosine(query_vec, v), d) for v, d in zip(doc_vecs, docs)]
    return sorted(scores, reverse=True)[:k]      # top-k most similar

Gdy poda się cztery wektory zwierząt domowych i Paryża oraz zapytanie dotyczące aktywnej osoby, najpierw zwracane są dwa dokumenty o psach, zgodnie z wcześniej pokazaną rankingową kolejnością. To właśnie jest wyszukiwanie semantyczne w zaledwie kilku liniach kodu.

W rzeczywistym projekcie wektory nie są budowane ręcznie. Wykorzystuje się model embeddingowy oraz magazyn wektorów, a kod wygląda mniej więcej tak:

# Illustrative real-world usage (libraries download a model, so not run here)
from sentence_transformers import SentenceTransformer
import chromadb
model = SentenceTransformer("all-MiniLM-L6-v2")     # turns text into vectors
db = chromadb.Client().create_collection("pets")db.add(documents=my_docs, embeddings=model.encode(my_docs).tolist(),
       ids=[str(i) for i in range(len(my_docs))])q = "a good pet for an active person"
hits = db.query(query_embeddings=model.encode([q]).tolist(), n_results=2)
# hits now holds the top-2 most semantically similar documents

Biblioteki wykonują większość pracy, ale w gruncie rzeczy stosują tę samą metodę wyszukiwania podobieństwa kosinowego. Wyszukiwanie semantyczne polega na kilku wzorach matematycznych dotyczących wektorów, a prawdziwe narzędzia opakowują tę koncepcję w szybkość i skalowalność.

Cały proces

Tekst zaczyna się od znaków i tokenów, następnie przekształca się w embeddingi, potem uzyskuje kontekst dzięki mechanizmowi uwagi w transformatorze, a na końcu staje się mapą wiedzy nadającą się do wyszukiwania, co sprawia, że modele językowe są dokładne i przydatne.

raw text
   → characters / tokens / embeddings        [article 1: From Characters to Meaning]
   → attention turns them into context-aware
     vectors inside a transformer            [article 2: Attention, Explained Simply]
   → those vectors become coordinates we can
     store, search, and retrieve             [article 3: this one]
       → semantic search  (find by meaning)
       → vector databases (search at scale)
       → RAG              (ground an LLM in real data)

Główną myślą przewijającą się przez to wszystko jest rozwijająca się koncepcja: znaczenie to punkt w przestrzeni. Najpierw odnosi się to do słów, potem do zdań w kontekście, a w końcu do całych zbiorów dokumentów, które można przeszukiwać i nad którymi można rozważać.

Należy pamiętać o jeszcze jednej różnicy. RAG umożliwia modelowi dostęp do wiedzy bez zmiany samego modelu, podobnie jak podanie mu notatek do przeczytania w momencie udzielania odpowiedzi. Dostosowywanie modelu to inna metoda, przy której wewnętrzne wagi modelu są korygowane poprzez szkolenie na przykładach. RAG dostarcza wiedzę do przeczytania, natomiast dostosowywanie modelu zmienia to, czym model jest. Umiejętność wyboru odpowiedniej metody to sama w sobie praktyczna umiejętność.

Literatura pokrewna

  • Nie można odzyskać liczby, której nikt nie zapisał: OKF kontra Vector RAG — Porównanie zweryfikowanego pakietu OKF z bazą wiedzy wektorowej Bedrock na podstawie statystyk ankietowych: dlaczego obliczone wartości nie mogą być odzyskane i jak połączyć obie metody.
  • Osiem darmowych baz danych wektorowych do agentów AI w porównaniu: ograniczenia, dopasowanie i kompromisy — Porównanie ośmiu baz danych wektorowych z darmowymi wersjami przeznaczonymi do przechowywania pamięci agentów: sztywne ograniczenia, dopasowanie do warunków implementacji, współbieżność oraz wybór odpowiedniej bazy dla danego środowiska.
  • Wyszukiwanie wektorowe w Azure AI Search: HNSW, profile i Python RAG Retriever — Jak stworzyć indeks wektorowy w Azure AI Search za pomocą Pythona: HNSW kontra kompleksowe KNN, profile, embeddingi, zapytania wektorowe i prompty RAG, plus wskazówki dotyczące wyszukiwania hybrydowego.
  • Debugowanie pipeline’a, a nie modelu LLM: model mentalny do rozwiązywania problemów w RAG — Gdy bot RAG podaje błędne odpowiedzi, rzadko jest to wina modelu. Śledź błędy na etapach pobierania danych, wyszukiwania, generowania, oceny i pętli agentowych, aby znaleźć uszkodzony element.
  • Semantic Search vs Vector Search, and Where RAG and Azure AI Search Fit — Wyjaśnienie wyszukiwania słów kluczowych, wektorowego, semantycznego i hybrydowego przy użyciu przykładu polityki wyjścia i incydentu, a także sposób, w jaki Azure AI Search, Semantic Kernel i RAG są ze sobą powiązane.
  • A Reusable Semantic Search Service: Pinecone Embeddings Behind MCP — Indeksowanie plików PDF raz za pomocą dzielenia na fragmenty, batchowych embeddingów wielojęzycznych typu e5-large oraz Pinecone, a następnie umożliwienie dowolnemu agentowi wyszukiwania ich przez serwer FastMCP z ustalonym progiem oceny.