Wpisz w dowolną wyszukiwarkę semantyczną „auto”, a znajdzie ci dokumenty o samochodach. Nie dlatego, że ktoś ręcznie wpisał synonimy. Te dwa słowa nie mają wspólnego rdzenia, nie mają wspólnej odmiany, a mimo to maszyna traktuje je jako bliskie.
To jest jedyne miejsce w całym stosie, w którym pojawia się coś, co można uczciwie nazwać znaczeniem. I wygląda ono inaczej, niż większość ludzi zakłada.
Od numeru do wektora
W poprzednim artykule skończyliśmy na tym, że tokenizer zwraca numery. Numer to jeszcze nie znaczenie — to adres, jak numer katalogowy. „5847″ nie mówi nic o tym, czym jest 5847.
Krok dalej stoi macierz embeddingów. Wyobraź sobie gigantyczną tabelę: tyle wierszy, ile pozycji w słowniku modelu, i tyle kolumn, ile wynosi wymiar reprezentacji — zwykle od kilkuset do kilku tysięcy. Numer tokenu wskazuje wiersz. Ten wiersz to lista liczb i to właśnie ona jest embeddingiem.
Kluczowa różnica wobec tokenizera: ta tabela jest częścią modelu i powstała w treningu. Tokenizer to była lista ułożona algorytmem, bez ani jednej wyuczonej wartości. Tutaj każda liczba w każdym wierszu została wypracowana przez model. Rozbijanie było mechaniką. To jest już sieć.
Znaczenie jako położenie
Pojedyncza liczba w wektorze nie znaczy nic. Nie ma kolumny „czy to zwierzę” ani „czy to coś dużego” — nikt tych wymiarów nie zaprojektował i nie da się ich sensownie nazwać. Znaczenie nie siedzi w żadnej z liczb z osobna.
Siedzi w położeniu względem innych. Jeśli potraktować wektor jako współrzędne punktu w przestrzeni o tysiącu wymiarów, to okazuje się, że słowa o podobnym znaczeniu lądują blisko siebie, a niepodobne daleko. Podobieństwo znaczeniowe staje się odległością — czymś, co da się policzyć.
To dlatego wyszukiwanie semantyczne w ogóle działa. Nie porównuje się ciągów znaków, tylko odległości punktów. „Auto” i „samochód” są blisko, choć nie mają wspólnych liter. „Zamek” i „forteca” są blisko. „Zamek” i „suwak” też — i to jest problem, do którego zaraz wrócimy.
Skąd model wie, co jest blisko czego
Nikt tego nie wpisał. Wynika to z jednej obserwacji o języku: słowa o podobnym znaczeniu występują w podobnym towarzystwie.
„Auto” i „samochód” pojawiają się obok tych samych czasowników — zaparkować, sprzedać, ubezpieczyć — i tych samych rzeczowników: silnik, bagażnik, przegląd. Model przewidujący kolejny token musi dla obu tych słów przewidywać podobne kontynuacje. Najprostszy sposób, by to osiągnąć, to nadać im podobne reprezentacje. Bliskość w przestrzeni nie jest więc dodatkiem do modelu, tylko produktem ubocznym trenowania go do przewidywania.
Znaczenie w tym sensie jest w całości pochodną tekstów, na których model trenowano. Ze wszystkim, co w tych tekstach było — łącznie z uprzedzeniami, dysproporcjami i tym, że polszczyzny było tam znacznie mniej niż angielszczyzny.
Sławna arytmetyka i ile z niej zostało
Najczęściej powtarzany fakt o embeddingach brzmi tak: odejmij od wektora „król” wektor „mężczyzna”, dodaj „kobieta”, a wylądujesz w okolicy „królowej”. Relacje znaczeniowe są więc kierunkami w przestrzeni.
To działa i jest efektowne. Warto jednak wiedzieć, że w popularnym obiegu urosło ponad miarę. Wyniki bywają dobierane, przykłady poza kanonem sprawdzają się znacznie gorzej, a część efektu bierze się z tego, jak liczy się najbliższego sąsiada — słowa wejściowe są z wyszukiwania wykluczane, co samo w sobie podbija trafność.
Zapamiętaj więc kierunek, nie magię: struktura w tej przestrzeni istnieje, ale nie jest tak czysta, jak sugeruje najsłynniejszy przykład.
Statyczny kontra kontekstowy — najważniejsze rozróżnienie
Tu jest miejsce, w którym większość wyjaśnień się zatrzymuje, a nie powinna.
Wiersz wyciągnięty z macierzy embeddingów jest zawsze taki sam. „Zamek” dostaje ten sam wektor niezależnie od tego, czy mowa o budowli, urządzeniu w drzwiach, czy suwaku w kurtce. To embedding statyczny — i sam z siebie nie rozstrzyga żadnej wieloznaczności.
Rozstrzygnięcie przychodzi później. Wektor tokenu jest przepuszczany przez kolejne warstwy modelu i na każdej z nich aktualizowany na podstawie sąsiadów w zdaniu. Po kilku warstwach „zamek” w zdaniu o zardzewiałym mechanizmie ma już inne współrzędne niż „zamek” w zdaniu o murach obronnych. To są embeddingi kontekstowe i to one wykonują faktyczną pracę.
Macierz embeddingów daje więc punkt startowy, nie odpowiedź. Mechanizm, który ten punkt przesuwa, to attention — temat następnego artykułu.
Embedding tokenu to nie to samo co embedding tekstu
Rozróżnienie praktyczne, na którym potyka się sporo wdrożeń.
Wszystko powyżej dotyczy pojedynczych tokenów wewnątrz modelu językowego. Kiedy jednak budujesz wyszukiwanie po własnej bazie wiedzy, potrzebujesz jednego wektora dla całego akapitu albo dokumentu. Do tego służą osobne modele embeddingowe, trenowane specjalnie po to, żeby teksty o zbliżonym sensie lądowały blisko siebie.
To inne modele, z innym wymiarem wektora i innymi cenami. Z tego wynikają dwie zasady, które oszczędzają sporo kłopotu.
Wektory z różnych modeli są nieporównywalne. Zmiana modelu embeddingowego oznacza przeliczenie całej bazy od nowa — mieszanie starych i nowych wektorów daje wyniki, które wyglądają wiarygodnie i są bezużyteczne.
Model embeddingowy nie musi pochodzić od tego samego dostawcy co model generujący. To dwie niezależne decyzje i przy polskich treściach warto obie testować osobno, bo jakość dla polszczyzny bywa nierówna niezależnie od pozycji w ogólnych rankingach.
Czego embeddingi nie potrafią
Trzy ograniczenia, które w praktyce zaskakują najczęściej.
Przeciwieństwa leżą blisko siebie. „Dobry” i „zły” występują w niemal identycznym towarzystwie, więc mają podobne reprezentacje. Wyszukiwanie semantyczne potrafi z tego powodu znaleźć dokument mówiący dokładnie coś odwrotnego niż pytanie.
Negacja bywa niewidoczna. „Umowa obejmuje szkody” i „umowa nie obejmuje szkód” są w przestrzeni wektorowej niepokojąco blisko. Przy dokumentach prawnych i technicznych to realne ryzyko.
Liczby i identyfikatory nie mają sensownego położenia. Numer faktury, kwota, data — dla przestrzeni znaczeń to szum. Do wyszukiwania po nich służy zwykłe wyszukiwanie po tekście, a nie semantyczne, i dobre wdrożenia łączą oba.
Wszystkie trzy są argumentem za tym, żeby nie budować wyszukiwania wyłącznie na embeddingach. To narzędzie do podobieństwa, nie do precyzji.
Jak to sprawdzić u siebie
Poniższy kod liczy odległości między parami zdań po polsku. Model pobiera się raz i działa lokalnie.
from sentence_transformers import SentenceTransformer, util
model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")
pary = [
("Kupiłem nowe auto.", "Nabyłem samochód."),
("Kupiłem nowe auto.", "Sprzedałem stary rower."),
("Umowa obejmuje szkody.", "Umowa nie obejmuje szkód."),
("To był bardzo dobry film.", "To był bardzo zły film."),
]
for a, b in pary:
wektory = model.encode([a, b])
print(f"{util.cos_sim(*wektory).item():.3f} {a} || {b}")
Dwa pierwsze wyniki potwierdzą to, czego się spodziewasz: parafraza bez wspólnych słów wychodzi wysoko, zdanie o czymś innym nisko. Dwa ostatnie są ciekawsze — zdania o przeciwnym sensie wypadną zaskakująco blisko siebie.
Jeśli budujesz wyszukiwanie po własnych treściach, podmień przykłady na pary ze swojej dziedziny. Szczególnie warto sprawdzić pary różniące się wyłącznie negacją albo jedną liczbą, bo to tam wyszukiwanie semantyczne najczęściej zawodzi po cichu.
Co dalej
Wiemy już, że token dostaje punkt startowy w przestrzeni znaczeń i że sam z siebie ten punkt nie rozstrzyga wieloznaczności. Pozostaje pytanie, co go przesuwa — jak to się dzieje, że „zamek” w jednym zdaniu znaczy co innego niż w drugim.
O tym jest artykuł czwarty: Attention — jak model decyduje, na co patrzeć.





















