Osadzenia słów — word2vec i wektory, które coś znaczą
Wielu ludzi poznających dziś AI zaczyna od szczytu stosu — transformery, głowice uwagi, dostrajanie, model czatowy, który już zdaje się wszystko rozumieć. To świetne miejsce, żeby z tej technologii korzystać. To zbyt wysokopoziomowe miejsce, żeby się jej uczyć: wielkie modele to głębokie stosy pomysłów gromadzonych przez ponad pięćdziesiąt lat, a niemal każdy z tych pomysłów jest mniejszy i o wiele czytelniejszy sam z osobna. Żeby to zrozumieć, lepiej budować od klocków niż zaczynać od gotowego stosu — a klockiem, który liczy się najbardziej, tym, którego wariacjami są modele w stylu GPT, BERT i każdy system wyszukiwania, jest pomysł, że słowa da się zamienić w wektory, których geometria koduje znaczenie.
Zapewne widzieliście już ten słynny przykład — king − man + woman ≈ queen, prawdopodobnie najczęściej zrzucany ekran w NLP. Spójrzcie na widżet poniżej.
Pokazuje on tę arytmetykę geometrycznie: każda strona obrazka jest własnym małym układem współrzędnych,
ale kierunek „królewskości” — policzony w układzie A jako v(king) − v(man) — wskazuje tak samo wszędzie w przestrzeni osadzeń.
Przenieś ten wektor do układu B, przyłóż go przy woman, a jego koniec wyląduje na queen.
Zatem king − man wyodrębnia kierunek, a + woman stosuje go do innej kotwicy.
Klikaj przycisk step, żeby obejrzeć tę konstrukcję krok po kroku.
To, co pokazuje widżet, to gęsta wektorowa reprezentacja słów: każde słowo jest punktem w wielowymiarowej przestrzeni, a geometria między punktami koduje relacje semantyczne. Nikt nie ustawiał tych wartości ręcznie i nikt nie powiedział modelowi, że „królewskość” czy „rodzaj” to w ogóle pojęcia — nie ma tu opisanych osi, tagów kategorii, żadnego człowieka oznaczającego king jako „królewski” czy queen jako „żeński”. Geometria wypada w całości z celu treningowego, a rozpracowanie, jak do tego dochodzi, to większość tego artykułu.
Przez większość historii NLP słowa nie były jednak żadnymi wektorami — były samodzielnymi symbolami, całkowitymi identyfikatorami albo wskaźnikami one-hot bez wbudowanej relacji między sobą. Cokolwiek semantycznego trzeba było doczepić dalej w potoku: ręcznie zaprojektowane cechy, statystyki oparte na zliczeniach, leksykony jak WordNet. Modele działały, ale kiepsko uogólniały między słowami — to, czego system nauczył się o cat, w ogóle nie przenosiło się na kitten.
Ten martwy punkt był wpisany w dominujący przepis epoki. Dokument zamieniano w TF-IDF macierz dokument-term — jeden rzadki wiersz na dokument, jedna kolumna na termin ze słownika, każda komórka ważąca, jak charakterystyczny jest dany termin dla tego dokumentu — po łańcuchu wstępnego przetwarzania: tokenizacji, stemmingu i usunięciu słów stopu. Ta macierz szła jako cechy do klasycznego klasyfikatora ML — SVM, regresji logistycznej, naiwnego Bayesa, lasów losowych — do zadań takich jak analiza sentymentu, klastrowanie dokumentów czy rankingowanie trafności. Potok działał, ale nosił tę samą wadę w kościach: każdy termin to własna niezależna kolumna bez pojęcia o tym, które terminy znaczą podobne rzeczy, a słownik z ponad 30 000 terminów zostawia wektor każdego dokumentu ogromny i niemal w całości zerowy.
Gęste osadzenia to to, co je zastąpiło. Każdy token staje się wektorem kilkuset liczb rzeczywistych, wszystkich wyuczonych z danych, a geometria tej przestrzeni układa się według znaczenia: podobne słowa lądują blisko siebie, a pary powiązanych słów siedzą przy podobnych przesunięciach — dokładnie to sprawia, że powyższa arytmetyka działa.
Tu wkracza word2vec. Przedstawiony w 2013 roku, był modelem, który uczynił gęste osadzenia praktycznymi: prosta, płytka architektura ucząca się ciągłych reprezentacji słów wprost z surowego tekstu, a mimo to bijąca cięższe modele w całej gamie zadań składniowych i semantycznych — analizie sentymentu, wykrywaniu parafraz, ekstrakcji relacji. Częścią atrakcyjności była czysta wydajność — trening na miliardach słów był na tyle tani, że mieścił się na zwykłym sprzęcie — a to połączenie mocnych wyników z niskim kosztem zamieniło osadzenia słów z badawczej ciekawostki w domyślny klocek współczesnego NLP.
To proces treningu nadaje tym wektorom znaczenie. Na starcie każde słowo to tylko krótka tablica losowych liczb — nic semantycznego jeszcze tam nie ma. Znaczenie nabierają wyłącznie jako efekt uboczny zmuszania ich do dobrego radzenia sobie w samonadzorowanym zadaniu predykcyjnym — przewidzieć kontekst słowa (skip-gram) albo słowo z jego kontekstu (CBOW). Nikt nigdy nie mówi modelowi „king i queen są powiązane”. Ale żeby trafnie przewidywać konteksty king i trafnie konteksty queen, gdy te dwa rozkłady kontekstów mocno się pokrywają, jedynym sposobem, w jaki ta maleńka sieć może odnieść sukces, jest nadanie king i queen podobnych wektorów. Podobieństwo w przestrzeni osadzeń jest mechanizmem, którym model dzieli predykcyjne przesłanki między słowami zachowującymi się podobnie. Geometria wypada z optymalizacji.
Od hipotezy do geometrii
Jak hipoteza o znaczeniu słów kończy jako realna geometria wektorowa — w której podobne słowa lądują blisko siebie, a spójne relacje stają się spójnymi przesunięciami wektorowymi? Rozumowanie ma cztery kroki, z których każdy wymusza następny: przesłanka (sama hipoteza dystrybucyjna), samonadzorowane zadanie zbudowane wprost na tej przesłance, reprezentacyjne wąskie gardło ograniczające sposoby rozwiązania zadania oraz geometria, która wyłania się na wyjściu. Przejdźcie je po kolei, a skok od „słowa mają znaczenie” do „słowa stają się wektorami ze strukturą” przestaje być tajemniczy.
Zaczynamy od hipotezy dystrybucyjnej: znaczenie słowa jest dobrze przybliżane przez rozkład kontekstów, w których się pojawia. Słowa występujące w tych samych rodzajach zdań — couch i sofa, huge i enormous — znaczą podobne rzeczy, co po cichu przeformułowuje pytanie o to, co słowo znaczy, w pytanie o to, obok czego się pojawia — a na to da się odpowiedzieć z samego surowego tekstu, bez słownika i bez ludzkich adnotacji.
Z tej przesłanki wynika samonadzorowane zadanie: weź słowo i przewidź słowa, które je otaczają — albo weź otaczające słowa i przewidź to w środku. Skoro hipoteza mówi, że to sąsiedzi niosą znaczenie, to bycie dobrym w tym zadaniu zmusza model do kodowania znaczenia — a przygotowanie nic nie kosztuje, bo poprawne odpowiedzi już leżą w tekście.
Potem przychodzi ograniczenie na to zadanie — wąskie gardło. Cała reprezentacja słowa musi zmieścić się w jednym krótkim wektorze — powiedzmy trzystu liczbach — i to jedyne miejsce, w którym może żyć jego informacja. Trzysta liczb to niewiele miejsca: ani trochę za dużo, by dać każdemu słowu jego własny niezależny opis. Więc żeby dobrze przewidywać dla wszystkich naraz, model musi oszczędzać — reprezentować słowa wspólnymi, wielokrotnie używanymi cechami i umieszczać słowa pełniące tę samą rolę niemal w tych samych współrzędnych. Podobne słowa lądują blisko siebie nie dlatego, że ktoś o to poprosił, ale dlatego, że stłoczenie ich to jedyny sposób, by wszystko się zmieściło. Zabierz wąskie gardło — pozwól wektorowi urosnąć do szerokości samego słownika, tak by każde słowo mogło zająć własny dedykowany wymiar i nigdy się nim nie dzielić — a ta presja znika: model po prostu zapamiętuje, które słowa współwystępują z którymi, jedno po drugim, i nie uczy się niczego, co przeniosłoby się na następne.
Na wyjściu wychodzi geometria: słowa o podobnych rozkładach kontekstów dostają podobne wektory, a spójne wzorce relacyjne — stolica-czegoś, liczba mnoga, czas przeszły — stają się spójnymi przesunięciami wektorowymi. Znaczenie kończy zakodowane jako pozycja i kierunek w przestrzeni — a to właśnie pokazuje obrazek na początku.
To, przez co właśnie przeszliście — przewidź kontekst, przeciśnij przez wąskie gardło, odczytaj geometrię — nie jest specyficzne dla word2vec. Każdy model osadzeń po word2vec rozgrywa tę samą akcję. Samonadzorowana predykcja przez reprezentacyjne wąskie gardło to sposób, w jaki BERT uczy się swoich reprezentacji, w jaki enkodery zdań stojące za RAG uczą się swoich i w jaki powstaje warstwa osadzeń wewnątrz każdego współczesnego LLM-a.
W dzisiejszych architekturach NLP „osadzenie” może znaczyć trzy dość różne rzeczy — wszystkie zbudowane tak samo, przez samonadzorowaną predykcję przez wąskie gardło, ale każda zastosowana do większej jednostki tekstu niż poprzednia:
- Statyczne osadzenia słów — word2vec, GloVe, fastText. Jeden stały wektor na słowo, pobierany z tablicy, zamrożony po treningu. Kolejność słów jest wyrzucana, a
bankdostaje jeden wektor uśredniający wszystkie swoje znaczenia. - Osadzenia kontekstowe — ELMo, potem BERT i jego potomkowie. Jeden wektor na słowo w postaci, w jakiej występuje w zdaniu:
bankw „river bank” ibankw „bank account” wychodzą różne, bo otaczające tokeny przeformowują wektor. Zadanie treningowe przesuwa się w stronę maskowanego modelowania języka — przewidzieć ukryty token z jego dwukierunkowego kontekstu, przepuszczony przez głęboki enkoder zamiast płytkiego uśrednienia — ale zasada leżąca u podstaw, samonadzorowana predykcja przez wąskie gardło, pozostaje bez zmian. - Osadzenia zdań / fragmentów — Sentence-BERT i współczesne modele wyszukiwania. Sprowadzają model kontekstowy do jednego wektora na zdanie albo dokument. To silnik retrieval-augmented generation: osadź korpus raz, osadzaj każde zapytanie i zwracaj najbliższe fragmenty według podobieństwa kosinusowego — dokładnie ten geometryczny zakład, którego demonstracją jest obrazek powyżej.
Możecie się zastanawiać, gdzie w tym wszystkim mieszczą się generatywne LLM-y — ChatGPT, Claude, Llama. Nie na własnym poziomie; one zawierają wszystkie trzy. Najniższa warstwa każdego z nich to statyczna tablica osadzeń słów, dokładnie taka jak w word2vec — wchodzi identyfikator tokenu, wychodzi wyuczony wektor. Każda warstwa transformera powyżej produkuje osadzenia kontekstowe, w stylu BERT-a. To, co czyni LLM LLM-em, nie jest nowym rodzajem osadzenia; to fakt, że odczytujesz jego rozkład następnego tokenu i próbkujesz z niego, zamieniając maszynę reprezentacji w maszynę generowania.
Ten artykuł dotyczy pierwszego poziomu — statycznych osadzeń słów. Zaczniemy od idei leżącej u podstaw wszystkiego (hipotezy dystrybucyjnej), przejdziemy przez wnętrze skip-gramu i CBOW, zbadamy słynną analogię king–queen na prawdziwym, wstępnie wytrenowanym słowniku i skończymy w miejscu, w którym statyczne osadzenia przestają działać — dokładnie tam, gdzie w następnym artykule podejmują pałeczkę BERT i poziom drugi. Poziom trzeci — osadzenia zdań i fragmentów stojące za retrieval-augmented generation — przyjdzie później w serii.
Hipoteza dystrybucyjna
Już w 1957 roku językoznawca J.R. Firth napisał słynne: „Poznasz słowo po towarzystwie, w jakim przebywa”. To cała idea. Słowa pojawiające się w podobnych kontekstach mają podobne znaczenia. Żeby zobaczyć dlaczego, rozważcie trzy zdania z jednym brakującym słowem:
- ___ zaszczekał na listonosza.
- ___ mruczał mi na kolanach.
- ___ odleciał na zimę na południe.
Nie musicie znać brakujących słów, żeby wiedzieć, że odnoszą się do różnych rodzajów zwierząt. Kontekst — słowa wokół luki — zawęża to, co pasuje. To jedna połowa idei: kontekst przewiduje słowo.
Druga połowa odwraca to. Zbierzcie każde słowo, które konsekwentnie wypełnia lukę w „___ zaszczekał” — pies, szczeniak, kundel, retriever. Wszystkie pasują, bo wszystkie znaczą coś psiego. Każde słowo konsekwentnie wypełniające „___ mruczał” — kot, kotek, bury — znaczy coś kociego. Zatem słowa wypełniające te same luki w tych samych rodzajach zdań muszą znaczyć podobne rzeczy. To kierunek, który opłaca się dla reprezentacji: model trenowany do przewidywania kontekstu musi nadać słowom o podobnych kontekstach podobne wektory, bo tylko tak może dobrze punktować je wszystkie naraz.
Hipoteza dystrybucyjna mówi: definiuj słowo przez jego konteksty. Zbuduj reprezentację, która stawia słowa blisko siebie, jeśli ich rozkłady kontekstów są podobne. Taka reprezentacja zakoduje podobieństwo semantyczne, rolę składniową i zaskakująco dużo wiedzy o świecie — a wszystko bez jakiegokolwiek ludzkiego etykietowania, wyłącznie z surowego tekstu.
Pytanie brzmi, jak policzyć to efektywnie. Klasyczną odpowiedzią, sięgającą wczesnych lat 90., były bardzo duże rzadkie macierze współwystępowania — analiza semantyki ukrytej i jej krewni. Dla zdania „the cat sat on the mat” z oknem 2 słów (patrzymy do 2 tokenów w lewo i w prawo od każdego słowa centralnego) macierz wygląda tak:
the cat sat on mat
the [ 0, 1, 2, 1, 1 ]
cat [ 1, 0, 1, 1, 0 ]
sat [ 2, 1, 0, 1, 0 ]
on [ 1, 1, 1, 0, 1 ]
mat [ 1, 0, 0, 1, 0 ]Jeden wiersz na słowo, jedna kolumna na słowo kontekstowe, a każdy wpis M[i][j] zlicza, jak często słowo j pojawiło się w kontekście słowa i — w obrębie pewnego okna. Przekątna jest zawsze zerowa, bo słowo nie współwystępuje samo ze sobą.
Macierz buduje się przesuwając okno po korpusie i zliczając.
W każdej pozycji ze słowem centralnym i patrzymy na słowa w jego oknie ±W i dla każdego sąsiada j w tym oknie zwiększamy M[i][j] o jeden. Przewińcie widżet od początku do końca, a macierz wypełni się komórka po komórce — jedno przejście przez korpus i wszystkie zliczenia współwystępowania są zapisane.
To pierwszy krok potoku „zlicz, potem faktoryzuj”, a nad nim są jeszcze dwa. Kształt wektora słowa istotnie zmienia się na każdym etapie:
Etap 1 — surowe zliczenia. Macierz powyżej, taka jaka jest. Każdy wiersz ma długość V liczb, po jednym slocie na każde słowo w słowniku — to już jest wektor słowa, tylko szalenie przewymiarowany. Każdy wpis to dosłownie „ile razy to konkretne słowo ze słownika pojawiło się w oknie ±2 tego słowa w całym korpusie”. Wiersz dla cat to [1, 0, 1, 1, 0]. Zabawkowy korpus ma tylko sześć tokenów, więc liczby są maleńkie, ale przy skali liczy się struktura.
Etap 2 — korekta o przypadek. W surowych zliczeniach dominuje częstość: the w prawdziwym korpusie współwystępuje z cat często, ale tylko dlatego, że the współwystępuje ze wszystkim. Żeby wydobyć realny sygnał, każde współwystąpienie trzeba zdyskontować tym, co przewidziałby sam przypadek — częstym słowom nie powinno się zaliczać zasługi za współwystępowanie ze wszystkim. Standardową poprawką jest PMI (punktowa informacja wzajemna): logarytm ilorazu, wysoki, gdy dwa słowa współwystępują częściej, niż przewidziałby przypadek, niski, gdy rzadziej, i zerowy, gdy ich łączna częstość odpowiada temu, co dałyby dwa niezależne słowa o takich częstościach. Kształt macierzy pozostaje (V, V); zmieniają się tylko wartości, z surowych zliczeń na wyniki skorygowane o przypadek.
Etap 3 — kompresja. Nawet po korekcie o przypadek macierz wciąż jest (V, V) — szalenie za szeroka, by być użyteczną tablicą wektorów słów. Standardową poprawką jest SVD (rozkład według wartości osobliwych): znajdź d kierunków największej wariancji w macierzy i zrzutuj każdy wiersz z V wymiarów do d — ten sam ruch „zachowaj najważniejsze kierunki zmienności i rzutuj na nie”, który PCA wykonuje na dwuwymiarowej chmurze, tu zastosowany do macierzy współwystępowania V × V:
raw cat vector (V long): [1, 0, 1, 1, 0]
PMI-weighted (V long): [−0.4, 0, 0.7, 0.3, 0] ← log-ratios per vocab word (illustrative)
SVD-compressed (d long): [0.21, -0.34, 0.18, …] ← d abstract latent dimensionsSkompresowany wektor to d liczb rzeczywistych (kilkaset), a wymiary nie oznaczają już „współwystępowania z jednym konkretnym słowem”. Każdy wymiar jest kombinacją liniową wszystkich V osi słownych — wyuczonym ukrytym kierunkiem, który chwyta jeden dominujący wzorzec zmienności współwystępowania w korpusie. Wymiar 47 nie oznacza już, jak często cat pojawiał się obok snowboard; teraz mierzy, jak bardzo ogólny wzorzec współwystępowania cat zgadza się z 47. najistotniejszym kierunkiem w danych.
Ta skompresowana tablica (V, d) — gęste liczby rzeczywiste, jeden wiersz na słowo — jest strukturalnie identyczna z wektorami word2vec omawianymi w reszcie artykułu. Różni się droga: zlicz-i-faktoryzuj kontra trenuj-sieć-wprost. Obie trasy lądują w zasadzie w tym samym miejscu — co Levy i Goldberg (2014) formalizują matematycznie.
Oznacza to, że trasa zlicz-i-faktoryzuj produkuje ten sam rodzaj geometrii, która podpiera king − man + woman ≈ queen — słynna arytmetyka analogii wypada na obu ścieżkach.
Dobrze dostrojone modele PPMI-SVD dorównują word2vec na standardowych benchmarkach analogii z dokładnością do paru punktów; ten sam cel, inna inżynieria.
Przez dwie dekady ten przepis zlicz-i-faktoryzuj podpierał LSA (analizę semantyki ukrytej, 1990 — macierz słowo-dokument, używaną w wyszukiwaniu informacji) i HAL (hyperspace analog to language, 1996 — macierz słowo-słowo, używaną w kognitywistyce do modelowania ludzkiej pamięci semantycznej). Oba produkowały wektory słów jako produkt uboczny innych celów; word2vec (2013) był pierwszą metodą, która uczyniła tablicę wektorów słów jawnym celem.
Wspólnym kosztem wszystkich była czysta skala, a rosła szybciej niż sam rozmiar słownika. Przy realnym V macierz to potwór V × V: milion słów z każdej strony to bilion komórek, prawie wszystkie zerowe, bo większość par słów nigdy nie współwystępuje. Przechowywanie jej jest ciężkie; faktoryzacja klasycznym SVD jeszcze cięższa. Poza samą mocą obliczeniową potok to łańcuch jawnych, ręcznie strojonych kroków — zlicz, zważ, faktoryzuj, obetnij, przeskaluj — a każdy ma pokrętła, które nieoczywiście oddziałują z pozostałymi. Na początku lat 2010. był to przepis, którego wszyscy używali i którego nikt nie kochał.
W 2013 roku przełomowa metoda o nazwie word2vec wyprodukowała gęste wektory kilkuset wymiarów, o tych samych własnościach semantycznych — i to nigdy nie budując tej macierzy. Zamiast liczyć współwystąpienia i ściskać wynik, uczy się wektorów wprost, trenując maleńką sieć neuronową na zadaniu predykcyjnym. (Rok później GloVe wrócił do budowania macierzy i faktoryzowania jej wprost; fastText rozszerzył word2vec o n-gramy znakowe. Artykuł o rodzinie statycznych osadzeń omawia te alternatywy i to, jak się mają do siebie.
word2vec
Artykuł o word2vec zaproponował dwa podejścia, które całkowicie pomijają macierz współwystępowania — produkują tę samą tablicę osadzeń (V, d), nigdy w ogóle nie budując siatki V × V. Zamiast liczyć współwystąpienia, a potem ściskać macierz przez SVD, wytrenuj maleńką sieć neuronową, żeby przewidywała słowa wokół każdego słowa, w dowolnym kierunku, a gdy trening się skończy, odczytaj wektory słów z wag sieci. Predykcja jest prawdziwa, ale nie o nią ci chodzi: jest tam wyłącznie po to, by wymusić ustrukturyzowane wektory z sieci. Zatrzymujesz tablicę wyuczonych osadzeń słów, a resztę wyrzucasz. Ten sam cel co LSA/HAL, bardzo inna inżynieria: strumieniuj pary słów jedna po drugiej i popychaj wektory po drodze, zamiast budować macierz o bilionie komórek i ją faktoryzować.
Ten sposób treningu daje też word2vec twarde ograniczenie zasięgu — skupia się na pojedynczych słowach, nie na zdaniach. Model produkuje jeden stały wektor na słowo i nic więcej — żadnej reprezentacji zdania, żadnej świadomości kolejności słów, żadnej kompozycyjności między słowami frazy. Przez dekadę między premierą word2vec a przejęciem władzy przez transformery standardowy potok NLP wypełniał tę lukę, stawiając LSTM (albo podobną sieć rekurencyjną) na osadzeniach word2vec: osadzenia niosły znaczenie każdego słowa, a LSTM zajmowała się tym, jak te znaczenia składają się w zdaniu. Dlaczego ten podział ostatecznie się zawalił, jest tematem końcowych sekcji.
Dwa algorytmy wprowadzone przez word2vec to skip-gram i CBOW — alternatywne przepisy, które prowadzą predykcję w przeciwnych kierunkach. Używasz jednego albo drugiego, nigdy obu:
- Skip-gram. Mając słowo centralne, przewidź słowa w małym oknie wokół niego. Podajemy słowo centralne na wejściu i przewidujemy po kolei każde z otaczających słów kontekstowych — każda para
(centrum, kontekst)jest osobnym przykładem treningowym, więc to samo słowo centralne jest wykorzystywane raz na sąsiada. - CBOW (continuous bag of words). Odwrotnie: mając słowa w oknie, przewidź to centralne. Podajemy zbiór słów kontekstowych i przewidujemy jedno słowo w środku. Ten kształt „wypełnij lukę” jest tym, który później dziedziczy i skaluje maskowane modelowanie języka w BERT.
Oba robią to samo, tylko prowadzą predykcję w przeciwnych kierunkach. Przejdziemy skip-gram od początku do końca, a potem wrócimy do CBOW.
Dane treningowe i wejścia one-hot
Cały potok skip-gramu dzieli się czysto na dwa etapy z ostrą granicą: wstępne przetwarzanie zamienia surowy tekst w listę przykładów treningowych, a potem trening przepuszcza te przykłady przez sieć neuronową.
Najpierw spójrzmy na wstępne przetwarzanie — zamianę surowego tekstu w długą listę par liczb całkowitych. Żadna sieć neuronowa nie jest tu jeszcze zaangażowana:
- Tokenizuj korpus — podziel tekst na listę tokenów słownych (oddzielonych spacjami, zwykle małymi literami; word2vec używa tokenów na poziomie słów, nie podsłów).
- Zbuduj słownik — przypisz każdemu odrębnemu tokenowi liczbę całkowitą; ten identyfikator posłuży później jako indeks tokenu w wektorach one-hot i jako numer wiersza w macierzy osadzeń. Łączna liczba to
V(rozmiar słownika). - Wyodrębnij pary
(centrum, kontekst)— przesuwaj okno po strumieniu identyfikatorów i emituj po jednym przykładzie treningowym na sąsiada.
Krok 3 to serce wstępnego przetwarzania. Koncepcyjnie wybierasz rozmiar okna — konwencja to dwa słowa z każdej strony, co daje okno szerokie na pięć słów — i przesuwasz je przez tekst po jednym słowie. W każdej pozycji słowo w środku jest centrum, otaczające słowa tworzą jego kontekst, a zadaniem modelu jest przewidzieć słowa kontekstowe z centrum. Każde sparowanie (centrum, kontekst) wyemitowane w tej pozycji to jeden przykład treningowy.
Biorąc zdanie „the cat sat on the mat” jako przykład, ustawiamy okno na ±2 i przesuwamy je słowo po słowie. Gdy okno jest wyśrodkowane na sat, sąsiadami są the, cat, on, the — ta jedna pozycja emituje cztery pary: (sat, the), (sat, cat), (sat, on), (sat, the). Krok naprzód do on, a okno znajduje cat, sat, the, mat — cztery kolejne pary. Kolejny krok, kolejne cztery, i tak dalej, aż cały korpus zapada się w długą listę par (centrum, kontekst), wygenerowaną w całości z samego tekstu, bez ani jednej ludzkiej etykiety.
Na końcu wstępnego przetwarzania masz sekwencję całkowitych par (c, t) — c to identyfikator słowa centralnego, t to identyfikator celu (kontekstu) — gotową do podania. Cały etap mieści się w kilkunastu liniach NumPy na naszym przykładzie przewodnim:
# Step 1: tokenize.
corpus = "the cat sat on the mat"
tokens = corpus.split()
# ['the', 'cat', 'sat', 'on', 'the', 'mat']
# Step 2: build vocabulary and convert tokens to integer IDs.
vocab = sorted(set(tokens)) # ['cat', 'mat', 'on', 'sat', 'the']
word2id = {w: i for i, w in enumerate(vocab)} # {'cat': 0, 'mat': 1, 'on': 2, 'sat': 3, 'the': 4}
V = len(vocab) # 5
ids = [word2id[w] for w in tokens] # [4, 0, 3, 2, 4, 1]
# Step 3: slide a ±2 window over the ID stream, emit (center, context) pairs.
window = 2
pairs = []
for i, c in enumerate(ids):
for j in range(max(0, i - window), min(len(ids), i + window + 1)):
if i != j:
pairs.append((c, ids[j]))
len(pairs) # 18 — exactly the (center_id, context_id) pairs the widget above emits.Wstępne przetwarzanie jest w zasadzie takie samo niezależnie od tego, który wariant word2vec trenujesz dalej — tokenizacja, słownik i okienkowanie są identyczne. Różni się tylko format emitowanych przykładów: skip-gram pakuje je jako pary, a CBOW emituje jeden worek kontekstu plus jego centrum na okno. Trening jest tym, gdzie faktycznie mieszka algorytm, i reszta tej sekcji omawia go szczegółowo.
Każda para to przykład treningowy
Gdy wstępne przetwarzanie jest gotowe, spójrzmy, jak algorytm treningowy wykorzystuje te pary.
Każda para to jeden przykład treningowy, reprezentujący wejście i cel predykcji.
Cztery pary wyemitowane z jednej pozycji okna wokół sat to cztery osobne przykłady treningowe z tym samym wejściem sat i czterema różnymi celami predykcji (the, cat, on, the). Przez przebieg w przód idzie tylko wejście; cel w ogóle nie bierze udziału w obliczeniach sieci — sięga się po niego dopiero na etapie straty, by ocenić, jak dobrze przewidziany rozkład do niego pasował.
To podobne do MNIST: każdy przykład MNIST paruje obraz z etykietą cyfry, a tutaj każda para skip-gramu (c, t) paruje słowo centralne c (wejście) z jednym słowem kontekstowym t (cel). Dla (sat, cat): podaj sat, dostań przewidziany rozkład po słowniku, porównaj go z cat, zrób krok SGD. Potem kolejna para.
Zanim cokolwiek podamy do sieci, musimy przedstawić słowo jako wektor liczb.
W MNIST ten krok jest w zasadzie darmowy — obraz już jest siatką intensywności pikseli, więc po prostu spłaszczamy go w wektor 784 liczb.
Słowo nie ma wrodzonej treści liczbowej, więc wymyśliliśmy ją w kroku budowania słownika powyżej — każde słowo ma już liczbę całkowitą od 0 do V−1. Żeby podać ją do sieci, rozwijamy ten indeks w one-hot wektor długości V — wektor V liczb, same zera poza jedną 1 na indeksie tego słowa.
Dla naszego przewodniego słownika 5 słów (V=5) kodowanie wygląda tak:
"cat" → [1, 0, 0, 0, 0]
"mat" → [0, 1, 0, 0, 0]
"on" → [0, 0, 1, 0, 0]
"sat" → [0, 0, 0, 1, 0]
"the" → [0, 0, 0, 0, 1]One-hot ma długość V — jego długość to rozmiar słownika, około miliona dla prawdziwego word2vec. Więc każde słowo dosłownie staje się wektorem miliona liczb, w którym 999 999 wpisów to 0, a tylko jeden to 1. Od razu widać, że to potwornie rozrzutna reprezentacja: niemal cała pamięć i niemal wszystkie mnożenia operują na zerach, które niczego nie wnoszą.
Co sieć robi z tym one-hotem? Podaje go wprost pierwszej warstwie jako wejście, która mnoży go skalarnie przez macierz wag warstwy — wpis po wpisie, z sumowaniem. Przy V rzędu miliona to mnożenie macierzy „milion na d” na każdy przykład treningowy, w niemal całości mnożące zera. Zrobione dosłownie, to ogromna ilość zmarnowanej arytmetyki.
Na szczęście jest sprytny kawałek algebry liniowej, który pozwala w ogóle nie budować wektora one-hot — pomnożenie one-hota przez macierz jest tym samym, co wybranie jednego wiersza tej macierzy (odczyt po całkowitym identyfikatorze słowa). Dla naszego 5-wyrazowego słownika, z sat na indeksie 3 i pewną macierzą M o kształcie (5, d):
one-hot for "sat" M (5 rows × 3 cols) result
[ 0 0 0 1 0 ] · [ row 0: 0.21 -0.43 0.15 ] = [ 0.33 -0.27 0.84 ]
[ row 1: 0.07 0.62 -0.31 ] (just row 3)
[ row 2: -0.55 0.18 0.40 ]
[ row 3: 0.33 -0.27 0.84 ]
[ row 4: -0.12 0.49 -0.06 ]Każdy składnik dotykający 0 z one-hota znika, zostawiając tylko wkład wiersza 3 — więc odpowiedzią jest po prostu wiersz 3 macierzy M.
# dot product, column by column:
col 0: 0·0.21 + 0·0.07 + 0·(-0.55) + 1·0.33 + 0·(-0.12) = 0.33
col 1: 0·(-0.43) + 0·0.62 + 0·0.18 + 1·(-0.27) + 0·0.49 = -0.27
col 2: 0·0.15 + 0·(-0.31) + 0·0.40 + 1·0.84 + 0·(-0.06) = 0.84Więc w kodzie po prostu przechowujemy liczbę 3 (indeks w słowniku) i używamy jej jako bezpośredniego odczytu wiersza — zobaczymy to konkretnie w sekcji o przebiegu w przód. Matematycznie wektor one-hot długości V i całkowity indeks niosą tę samą informację; one-hota używamy do matematyki, bo dzięki niemu algebra liniowa jest czysta, a liczby całkowitej do kodu, bo jest V razy tańsza.
Model myślowy — co próbujemy zrobić
Zanim przyjrzymy się bliżej architekturze sieci, warto ustalić, co sieć właściwie próbuje osiągnąć.
Sedno w jednym zdaniu: mając listę par (centrum, cel), dla każdej pary policz podobieństwo w poprzek wymiarów między słowem centralnym a każdym słowem w słowniku — a potem popychaj wagi, para po parze, tak by cele stopniowo wypływały na szczyt rankingu podobieństwa.
Podobieństwo mierzy się podobieństwem kosinusowym między wektorami słów (osadzeniami), a softmax ustawia w rankingu każde słowo słownika według przewidzianego prawdopodobieństwa bycia celem. Reszta tej sekcji rozpakowuje, czym są te wagi, jak sieć to realizuje i dlaczego pętla przez korpus produkuje sensowną geometrię.
Będziemy mieć dwie trenowalne macierze wag — każda między parą warstw sieci — razem dające każdemu słowu w słowniku dwa d-wymiarowe wektory (po jednym osadzeniu na rolę):
Eo kształcie(V, d)— każdy wiersz to osadzenie wejściowe jednego słowa, używane, gdy słowo występuje jako centrum pary treningowej (słowo, na które warunkujemy).E'o kształcie(d, V)— każda kolumna to osadzenie wyjściowe jednego słowa, używane, gdy słowo występuje jako cel/kontekst, który jest przewidywany (słowo oceniane jako kandydat).
Zwróćcie uwagę na różnicę kształtów: E' to w istocie transponowane E — tyle samo V × d liczb na macierz, ale ułożonych jako (d, V), więc słowa biegną jako kolumny zamiast wierszy.
Zatem pojedyncze słowo w ma zarówno wiersz w E (gdy jest centrum), jak i kolumnę w E' (gdy jest celem) — dwa niezależne trenowalne wektory d-wymiarowe, po jednym na rolę.
Dostają niezależne aktualizacje gradientowe i kończą z różnymi wartościami.
Asymetria jest zamierzona: centrum pełni w zadaniu predykcyjnym inną rolę niż cel (jedno jest warunkującym kontekstem, drugie jest oceniane pod kątem prawdopodobności), a model jest bardziej ekspresyjny, gdy może wyuczyć się odrębnych wektorów dla tych dwóch ról, niż gdy jest zmuszony je dzielić. Po treningu E jest tym, co trafia do wydania jako finalna tablica osadzeń słów, a E' jest wyrzucane.
Widżet poniżej czyni ten krok konkretnym — liczenie podobieństwa w poprzek wymiarów między słowem centralnym a każdym słowem słownika, interakcja E i E'. Skupiamy się na sat jako centrum (podświetlone na zielono w E). Przejdź krokami, by zobaczyć, jak mnożenie macierzy punktuje je względem każdego słowa słownika — po jednym iloczynie skalarnym na słowo, wypełniając wektor wyników wpis po wpisie. (Tylko etapy 1 i 2 — softmax i strata przyjdą parę sekcji później.)
Wynik dla pary (c, t) to po prostu v_c · v'_t — iloczyn skalarny osadzenia wejściowego c z osadzeniem wyjściowym t, czyli nieznormalizowane podobieństwo kosinusowe.
To także powód, dla którego E' jest przechowywane transponowane (kolumny = osadzenia wyjściowe), a nie jako kolejna tablica (V, d): przechowywanie ich jako kolumn oznacza, że scores = v_c @ E' liczy iloczyn skalarny v_c względem osadzenia wyjściowego każdego słowa w jednym mnożeniu macierz-wektor. Gdyby E' było (V, d) jak E, trzeba by pętli albo transpozycji, żeby dostać te same V iloczynów. Kształt (d, V) to dokładnie ten układ, który czyni „policz podobieństwo do wszystkich słów naraz” operacją jednolinijkową.
To ta sama operacja, której MNIST używał do punktowania cyfr — każda klasa wyjściowa miała własny szablon cech, mnożony skalarnie przez wektor ukryty, by ocenić, jak dobrze obraz pasuje do tej klasy. word2vec rozgrywa tę samą akcję z kolumnami E' jako szablonami dla poszczególnych słów i v_c jako wektorem cech słowa centralnego; jedyna różnica jest taka, że tutaj cechy są wyuczonymi wymiarami semantycznymi (królewskość, mnogość, rodzaj), a nie interpretowalnymi ręcznie łatkami (krawędzie, kreski, pętle).
Mając to na uwadze, architektura z następnej sekcji jest po prostu najprostszą możliwą siecią neuronową, która liczy podobieństwo (v_c · v'_t) dla każdej pary kandydującej (c, t) i aktualizuje wagi w dwóch macierzach przez spadek gradientu.
Powtórzone miliardy razy w korpusie, produkuje to geometrię, w której osadzenie wejściowe każdego słowa leży blisko osadzeń wyjściowych słów, z którymi współwystępuje — i przechodnio, w której słowa dzielące tych samych sąsiadów kończą blisko siebie. Nikt nigdy nie mówi king i queen, żeby były podobne; kończą podobne, bo oba są ciągnięte ku royal, throne, crown, monarch. Tak samo cat i dog: dzielą sąsiadów w rodzaju pet, fur, tail.
Architektura
Gdy mamy już zebrane pary, w istocie prowadzimy nadzorowane zadanie predykcji z etykietą — mając słowo centralne, przewidzieć, które słowo ze słownika pojawi się obok — więc układ jest podobny do MNIST w kluczowych aspektach: jedna warstwa ukryta, softmax po klasach wyjściowych, entropia krzyżowa względem etykiety. Różni się skala (tu rozmiar słownika V kontra 10 klas cyfr w MNIST), format wejścia (one-hot kontra gęste rzeczywiste piksele) i cel (chcemy wytrenowanych osadzeń, nie samej predykcji).
Architektonicznie word2vec (2013) to dwuwarstwowa sieć neuronowa jednokierunkowa — sieć feed-forward z dwóch w pełni połączonych warstw (dwie macierze wag E i E'), z danymi płynącymi wejście → ukryta → wyjście i bez pętli. To ta sama rodzina co klasyfikator MNIST, z jednym twistem: warstwa ukryta jest czysto liniowa, więc uczy się kodowań słów, zamiast działać jako nieliniowy ekstraktor cech.
Konkretnie: wejście to V-wymiarowy one-hot słowa centralnego; warstwa ukryta ma d neuronów (np. 300) i jest czysto liniowa (bez obciążenia, bez nieliniowości); wyjście ma V neuronów z softmaxem po wszystkich V, produkującym P(w | c) — prawdopodobieństwo każdego słowa słownika przy danym centrum. Dwie macierze E i E', które wprowadziliśmy w poprzedniej sekcji, żyją między tymi warstwami: E to macierz wag wejście → ukryta (kształt (V, d)), E' to ukryta → wyjście (kształt (d, V)). To jedyne wyuczone parametry sieci; obie startują losowo, a po treningu tylko E wychodzi jako finalna tablica osadzeń słów — E' jest wyrzucane.
Żeby uczynić strukturę warstw konkretną, cała bazowa sieć to sześć linii Kerasa:
from tensorflow import keras
from tensorflow.keras import layers
V = vocab_size # e.g. 1_000_000
d = embedding_dim # e.g. 300
model = keras.Sequential([
keras.Input(shape=(1,), dtype='int32'), # integer ID of the center word
layers.Embedding(input_dim=V, output_dim=d), # E: shape (V, d), the lookup
layers.Reshape((d,)), # (1, d) → (d,)
layers.Dense(V, use_bias=False), # E': shape (d, V), the linear layer
layers.Softmax(), # softmax over V vocab scores
])
model.compile(optimizer='sgd', loss='sparse_categorical_crossentropy')Dwie trenowalne warstwy — Embedding i Dense — obie liniowe, bez aktywacji pomiędzy.
Embedding(V, d) to E — kerasowa nazwa na odczyt wiersza z tablicy (V, d) po całkowitym indeksie. Dense(V, use_bias=False) to E' — zwykła liniowa projekcja (d, V).
Reshape pomiędzy usuwa zbędny wymiar długości 1, który dodaje kerasowy Embedding. To dziwactwo Kerasa — Embedding jest zbudowany pod sekwencje tokenów i dodaje dodatkową oś, nawet gdy podajemy po jednym tokenie.
Softmax zamienia V surowych wyników w rozkład prawdopodobieństwa po słowniku, a sparse_categorical_crossentropy to standardowa strata klasyfikacyjna na wierzchu — ten sam softmax-plus-entropia-krzyżowa, którego używa MNIST, tylko z V klasami słownika zamiast 10 cyfr. Stratę i jej gradient omówimy szczegółowo w kolejnych sekcjach.
Liczba wymiarów osadzenia słowa d to hiperparametr bez wzoru na dobór — większość roboty robi konwencja.
Wydeptaną wartością domyślną jest d = 300, to, czego artykuł word2vec z 2013 użył na Google News i co GloVe udostępnia jako największą wstępnie wytrenowaną opcję.
W praktyce zobaczysz d = 50–100 do lekkich/brzegowych zastosowań, d = 200–300 jako słodki punkt i rzadko cokolwiek większego.
Powodem, dla którego d musi być znacznie mniejsze od V, jest argument o wąskim gardle sprzed chwili: gdyby każde słowo mogło rozłożyć się we własnym dedykowanym wymiarze, model zapamiętywałby współwystąpienia zamiast budować wspólne cechy i żadna geometria by nie powstała.
To, co właśnie opisaliśmy, to bazowy przebieg w przód — softmax po całym słowniku po stronie wyjścia.
Przebieg w przód — odczyt, punktowanie, softmax
Skoro omówiliśmy architekturę na wysokim poziomie — wejściowy one-hot, ukryty odczyt, wyjściowe wyniki — przybliżmy i prześledźmy dokładnie, co się dzieje, gdy jeden przykład treningowy płynie przez sieć. Dla słowa centralnego c przebieg w przód idzie od lewej do prawej w trzech etapach: odczyt, punktowanie i softmax.
Widżet z sekcji o modelu myślowym pokazał już pierwsze dwa etapy w izolacji — one-hot razy macierz zapadający się w odczyt wiersza, a potem wiersz razy macierz produkujący V wyników. Tutaj je nazwiemy, dołożymy softmax na wierzch i prześledzimy liczby od początku do końca.
Etap 1: wejście × E → ukryta (odczyt). Matematycznie to mnożenie macierzy one_hot(c) @ E, produkujące d-wymiarowy wektor ukryty. Wejście jest rzadkie — V−1 wpisów to zera — więc niemal każde mnożenie daje zero, a całe mnożenie (V, d) zapada się w odczyt jednego wiersza: v_c = E[c].
Etap 2: ukryta × E’ → V wyników. Przebieg w przód drugiej warstwy: scores = v_c @ E', produkujący V liczb rzeczywistych (po jednej na słowo słownika). To autentyczne mnożenie macierz-wektor (d, V) — bez skrótów, bez rzadkości do wykorzystania. Każdy wynik w to v_c · E'[:, w] — iloczyn skalarny v_c z w-tą kolumną E' — po jednym takim iloczynie na słowo słownika. Tu dzieje się większość pracy mnożenia: każda kolumna E' wnosi wkład do przebiegu w przód i dostaje gradient w przebiegu wstecznym, więc niemal cała obliczeniowa praca kroku — i niemal cała nauka — żyje po stronie ukryta → wyjście.
Mnożenie nigdy nie patrzy na słowo docelowe. Używa wyłącznie v_c = E[sat] i punktuje sat względem całego słownika, produkując wszystkie V wyników naraz. Zatem te konkretne pięć wyników — cat -0.19, mat 0.26, on 0.39, sat -0.07, the -0.45 — są identyczne dla każdej pary treningowej dzielącej centrum sat: (sat, cat), (sat, on) i (sat, the) wykonują to samo mnożenie i lądują na tych samych pięciu liczbach. Słowo docelowe wchodzi dopiero później, przy stracie; przebieg w przód nigdy go nie widzi.
Etap 3: softmax → prawdopodobieństwa. V wyników — zwanych logitami — to dowolne liczby rzeczywiste: mogą być ujemne, nieograniczone, nie sumujące się do niczego szczególnego. Softmax zamienia je w P(w | c) — V nieujemnych liczb sumujących się do 1, przewidziane przez model prawdopodobieństwo, że słowo w jest w kontekście c.
Na tym samym 5-wyrazowym słowniku widżet poniżej przechodzi wszystkie trzy etapy. Wybierz parę treningową (center, context), potem naciskaj step, by wypełniać wektor wyników jeden iloczyn skalarny na raz; gdy wszystkie V wyników są na miejscu, softmax zamienia je w prawdopodobieństwa.
Pięć iloczynów skalarnych, pięć surowych wyników, potem softmax. Na etapie wyników najwyżej wychodzi on (+0.39), co ma sens — on faktycznie stoi obok sat w „the cat sat on the mat” — i po softmaxie także niesie największą część masy prawdopodobieństwa.
W skip-gramie te wyniki to iloczyny skalarne v_c · E'[:, w] dla każdego słowa słownika w, a wynikające prawdopodobieństwa to P(w | c). Wyciągając sam krok softmaxu z widżetu powyżej, pięć wyników [-0.19, 0.26, 0.39, -0.07, -0.45] zamienia się w porządny rozkład prawdopodobieństwa sumujący się do 1:
Mechanika jest prosta: podnieś każdy wynik do potęgi e, zsumuj je, by policzyć normalizator (tu ~5.17), potem podziel każde exp(score) przez tę sumę. Wynikiem jest rozkład prawdopodobieństwa sumujący się z konstrukcji dokładnie do 1, niezależnie od tego, jakie były wejścia. Po subtelności softmaxu — zachowanie soft-argmax, niezmienniczość na przesunięcie, temperatura — zajrzyjcie do artykułu o MNIST, który omawia je dogłębnie.
Funkcja straty
Strata dla jednego przykładu treningowego ze słowem docelowym t to ujemny logarytm prawdopodobieństwa, jakie model przypisał temu celowi:
loss = −log P(t | c)To daje jedną liczbę na parę treningową (c, t) — małą, gdy softmax nasypał prawdopodobieństwa na prawdziwy cel, dużą, gdy nie. To entropia krzyżowa z etykietą one-hot — ta sama strata, której używa MNIST, a gradient przez softmax jest tam wyprowadzony.
Konkretnie, dla pary (sat, cat) przy wynikach i prawdopodobieństwach z góry:
cat mat on sat the
scores = [-0.19, 0.26, 0.39, -0.07, -0.45]
probabilities = [ 0.16, 0.25, 0.29, 0.18, 0.12]
target = cat
P(cat | sat) = 0.16
loss = −log(0.16) ≈ 1.83
# what-ifs — how the loss responds to different P(cat):
P(cat) = 0.90 → loss = −log(0.90) ≈ 0.11 (good prediction)
P(cat) = 0.01 → loss = −log(0.01) ≈ 4.6 (bad prediction)Widżet poniżej przenosi pięć prawdopodobieństw softmaxu z góry i nanosi je na krzywą −log. Kliknij inne słowo, by wyznaczyć je jako cel — znacznik przesuwa się po krzywej i widać wprost, jak cel, który model już faworyzuje, kosztuje prawie nic, podczas gdy cel niedoważony płaci ostrą cenę.
Wykres pokazuje, że gdy model poprawnie przypisuje celowi duże prawdopodobieństwo, strata jest maleńka, a gdy go niedoważa — dając poprawnej odpowiedzi tylko małe prawdopodobieństwo — strata jest ogromna. Ze względu na logarytmiczny kształt −log(P) przejście między tymi skrajnościami jest ostre, nie stopniowe — im bardziej stroma krzywa, tym agresywniej gradient popycha prawdopodobieństwo celu w górę.
Porównajcie faktyczny cel cat z odniesieniem jednostajnym. cat siedzi w (0.16, 1.83) — cel w (sat, cat) — podczas gdy punkt odniesienia jednostajnego siedzi w (0.20, 1.61). Ta jednostajna linia bazowa to miejsce startu każdego niewytrenowanego modelu — na inicjalizacji softmax rozkłada prawdopodobieństwo mniej więcej równo po wszystkich słowach słownika.
Prawdopodobieństwo cat jest poniżej jednostajnego, więc jego strata jest powyżej linii bazowej — na tej parze model radzi sobie odrobinę gorzej niż losowo, dokładnie tę sytuację trening ma naprawiać.
Gradient
Gradient jest podobny do MNIST: gradient straty względem każdego logitu to P(w) − 𝟙[w == t] — przewidziane prawdopodobieństwo minus cel one-hot. Gradient słowa docelowego to P(t) − 1 (ujemny — popchnij jego wynik w górę); każdego innego słowa to P(w) (dodatni — popchnij jego wynik w dół, proporcjonalnie do tego, ile prawdopodobieństwa aktualnie ma). Słowa, które model już poprawnie uważa za mało prawdopodobne, ledwie drgną; słowa, co do których się myli, dostają najwięcej sygnału.
Widżet poniżej czyni to odejmowanie konkretnym na tych samych pięciu prawdopodobieństwach. Kliknij inny cel, by zobaczyć, jak wiersz gradientu się przerysowuje — jeden wysoki niebieski słupek ciągnący wynik celu w górę i cztery krótkie czerwone popychające pozostałe w dół.
Każde słowo poza celem dostaje mały dodatni gradient — SGD popycha jego wynik (a więc i jego kolumnę w E') w dół, proporcjonalnie do tego, ile prawdopodobieństwa aktualnie podkrada. Cel dostaje jeden duży ujemny gradient o wielkości P(target) − 1 (około -0.84 dla cat) — SGD ciągnie jego wynik w górę. Im dalej model jest od P(target) = 1, tym bliżej to ciągnięcie jest −1 (najmocniejsze możliwe jednokrokowe ciągnięcie dla pojedynczego przykładu). Zsumuj wiersz i wyjdzie zero: masa prawdopodobieństwa jest przenoszona, a nie tworzona.
Propagacja wsteczna przenosi te gradienty wyników do E' i E przez regułę łańcuchową:
scores = v_c @ E' (the forward step we're differentiating)
∂loss / ∂E'[:, w] = ( P(w) − 𝟙[w == t] ) · v_c ← gradient on column w of E'
∂loss / ∂v_c = E' @ ( P − one_hot_t ) ← gradient into the hidden vector
∂loss / ∂E[c] = ∂loss / ∂v_c ← because v_c = E[c]Dwie konsekwencje warte zapamiętania, obie z wejścia one-hot:
Edostaje gradient na dokładnie jednym wierszu na przykład treningowy — wierszu słowa centralnego. Pozostałe V−1 wierszy ma gradient zerowy, bov_c = E[c]czytało tylko z tego jednego wiersza. (Porównajcie z MNIST, gdzieW₁aktualizuje każdą wagę na każdym przykładzie, bo wejściem są gęste piksele.)E'dostaje gradient na każdej kolumnie. Kolumna prawdziwego celu jest ciągnięta kuv_c; kolumna każdego innego słowa jest odpychana, przeskalowana przezP(w) − 𝟙[w == t].
Te gradienty są następnie użyte do wykonania kroku spadku gradientu: zastosuj je do wag jako E -= lr × ∂L/∂E i E' -= lr × ∂L/∂E'. To zadanie optymalizatora, a wybór optymalizatora (zwykły SGD, momentum, Adam, RMSprop, AdaGrad…) ma znaczenie tylko na tym kroku — wszystkie konsumują te same gradienty, ale używają ich inaczej.
Mini-partie i epoki
Powyższe omówienie przetwarzało pary po jednej. Większość treningów sieci uogólnia to w SGD na mini-partiach — grupuj przykłady w partie po B i przetwarzaj całą partię w jednym przebiegu w przód + wstecz, dokładnie ten sam SGD na mini-partiach co w MNIST, tylko uśredniony po B przykładach na krok. To przepis, którego używają BERT, GPT i praktycznie każdy współczesny model, i to opisują punkty poniżej.
Word2vec jest wyjątkiem. Oryginalne wydanie z 2013 używało rozmiaru partii 1 — czystego SGD, jedna para na krok, z wielowątkową równoległością Hogwild! na CPU (każdy wątek strumieniuje własne pary i pisze do współdzielonych E / E' bez blokad; okazjonalne kolizje aktualizacji są po cichu wchłaniane). Tak wciąż działa Gensim w 2026 i dla word2vec akurat jest to właściwy wybór — podsekcja o Gensimie omawia dalej dlaczego. Punkty poniżej opisują bardziej ogólny przepływ na mini-partiach, bo to on przenosi się na BERT-a i dalej; pamiętajcie tylko, że dla samego word2vec wygrywają B=1 i Hogwild!.
Epoka to jedno pełne przejście przez wszystkie pary treningowe, niezależnie od rozmiaru partii. word2vec trenuje się zwykle 5–15 epok; domyślnie Gensim ma 5. Każda para jest oglądana wielokrotnie, bo pojedynczy krok SGD na jednej parze nie kształtuje w pełni odpowiednich wierszy — powtarzane przejścia zbiegają E i E' ku geometrii, którą strata preferuje.
Na partię pętla wygląda tak:
- Przebieg w przód na partii B przykładów — każda para
(c, t)płynie przez sieć. Zwektoryzowany odczyt układa B wierszy zEw macierz(B, d), krok punktowania staje się jednym mnożeniem(B, d) @ (d, V) → (B, V), a softmax działa wierszami. - Strata — policz entropię krzyżową na przykład (ten sam wzór co przy B=1), potem uśrednij po partii do jednego skalara.
- Przebieg wsteczny (backprop). Policz gradienty przez regułę łańcuchową — czysta analiza od straty wstecz do każdej wagi, jeszcze bez aktualizacji wag. Wyjście: tensory gradientów
∂L/∂Ei∂L/∂E', tego samego kształtu co tensory wag. - Krok spadku gradientu — zastosuj gradienty do wag przez optymalizator, dokładnie jak w przypadku pojedynczej pary powyżej. Powtórz dla kolejnej partii.
Uruchom tę pętlę przez wiele partii na epokę i garść epok po korpusie, a wiersze E i E' osiadają w geometrii, którą strata preferuje.
CBOW — i most do BERT-a
CBOW (continuous bag of words) to drugi algorytm word2vec, prowadzony jako lustrzane odbicie skip-gramu. Tam, gdzie skip-gram bierze słowo centralne i przewiduje słowo docelowe jako jego kontekst, CBOW bierze kilka słów jako kontekst i przewiduje centrum — brakujące słowo.
To ujęcie „wypełnij lukę” to dokładnie test Cloze’a z badań nad rozumieniem tekstu z lat 50. — ukryj część słów, poproś model o ich uzupełnienie z tego, co zostało — co działa tylko, gdy ma się działający model otaczającego języka. To także dokładnie cel stojący za maskowanym modelowaniem języka w BERT, zadaniem pretreningu, które napędzało każdy enkoder kontekstowy od 2018 roku. CBOW można czytać jako maleńkiego, liniowego BERT-a, a BERT-a jako dorosłego CBOW z uwagą: to samo zadanie treningowe, płytkie uśrednianie zastąpione głębokim stosem transformera, symetryczne okno zastąpione całym zdaniem, pojedyncza maska zastąpiona 15% tokenów naraz, statyczne wyjście zastąpione kontekstowymi wektorami na token. Artykuł o BERT omawia to wszystko szczegółowo.
Mechanicznie CBOW różni się od skip-gramu dokładnie jednym krokiem uśredniania na wejściu; reszta pętli treningowej jest identyczna. Więc większość powyższych sekcji o skip-gramie przenosi się bez zmian — skupiamy się tu tylko na tym, gdzie CBOW się różni.
Wstępne przetwarzanie — worki kontekstu zamiast par
Potok wstępnego przetwarzania jest ten sam co w skip-gramie: tokenizuj, zbuduj słownik, przesuwaj okno po strumieniu tokenów. Zmienia się kształt tego, co jest emitowane na pozycję okna:
Position centered on: Skip-gram emits (per position): CBOW emits (per position):
───────────────────── ────────────────────────────── ──────────────────────────
the (pos 0) (the, cat), (the, sat) ({cat, sat}, the)
cat (pos 1) (cat, the), (cat, sat), (cat, on) ({the, sat, on}, cat)
sat (pos 2) (sat, the), (sat, cat), ({the, cat, on, the}, sat)
(sat, on), (sat, the)
on (pos 3) (on, cat), (on, sat), ({cat, sat, the, mat}, on)
(on, the), (on, mat)
the (pos 4) (the, sat), (the, on), (the, mat) ({sat, on, mat}, the)
mat (pos 5) (mat, on), (mat, the) ({on, the}, mat)
───────────────────── ────────────────
total: 18 pairs total: 6 examplesTen sam korpus, to samo okno — skip-gram produkuje 18 osobnych par treningowych (centrum, sąsiad), CBOW produkuje 6 przykładów (worek_kontekstu, centrum). Przejdźcie to krokami w widżecie:
Architektura
Architektura CBOW to architektura skip-gramu z odwróconymi końcami — wejściami są słowa kontekstowe, wyjściem słowo centralne. Te same dwie macierze wag E i E', ta sama wymiarowość d, ten sam softmax po V. Jedyna różnica architektoniczna jest po stronie wejścia: skip-gram odczytuje jeden wiersz E (centrum), CBOW odczytuje C wierszy (po jednym na słowo kontekstowe) i uśrednia je w jeden d-wymiarowy wektor ukryty h:
Skip-gram: h = E[c] (one row read)
CBOW: h = mean(E[c_1], E[c_2], ..., E[c_C]) (C rows read, then averaged)Zwizualizowane poniżej — C wejść one-hot, C odpowiadających im odczytów wierszy w E oraz krok uśredniania, który produkuje h:
shape: C × V
shape: V × d
shape: d
Uśrednianie jest tym, co nadaje „workowi słów” nazwę — kolejność słów wewnątrz okna jest odrzucana, kontekst staje się multizbiorem. Na każdym kroku treningowym odczyty odbywają się na bieżących, „w locie” wartościach E — wiersze, które czytasz w przebiegu w przód, są tymi samymi, które aktualizujesz w przebiegu wstecznym, tak jak w każdym treningu SGD sieci neuronowej. Gdy h jest policzone, wszystko dalej — scores = h @ E', softmax, strata entropii krzyżowej, backprop, SGD na E i E' oraz skrót z negatywnym próbkowaniem — jest identyczne ze skip-gramem.
Trening — trzy drobne różnice względem skip-gramu
Przebieg w przód, strata, gradient, mini-partie i skrót z negatywnym próbkowaniem przenoszą się ze skip-gramu bez zmian. Trzy różnice warte zapamiętania:
- Jeden przebieg w przód na pozycję okna. Skip-gram emituje C osobnych par na okno i wykonuje C przebiegów w przód. CBOW emituje jeden przykład na okno — cały worek kontekstu przewiduje centrum. Zatem CBOW trenuje się mniej więcej C× szybciej, co jest jego główną praktyczną przewagą.
- Aktualizacje gradientowe dotykają więcej wierszy
Ena przykład. Skip-gram popycha jeden wierszEna parę (wiersz centrum). CBOW popycha C wierszy na przykład (wiersz każdego słowa kontekstowego, z gradientem przeskalowanym przez 1/C z uśredniania). Dlatego CBOW radzi sobie lepiej ze słowami częstymi, a gorzej z rzadkimi — pospolite słowa stopu są dotykane w większości okien; rzadkie słowa rzadko pojawiają się jako kontekst. - Aktualizacje
E'mają ten sam kształt, tylko inaczej kluczowane. W skip-gramie każda para(c, t)aktualizuje kolumnęE'słowa docelowego. W CBOW każdy przykład aktualizuje kolumnęE'słowa centralnego. Ten sam wzorzec aktualizacji, inne słowo go wskazuje.
Jak word2vec trenuje się w praktyce — Gensim
Może to zaskakiwać w 2026 roku — epoce, w której enkodery transformerowe dominują w NLP, a każdy nagłówkowy model to jakaś odmiana uwagi — ale word2vec wciąż jest trenowany, wdrażany i dostarczany na produkcję każdego dnia. Powodem jest po części dopasowanie architektoniczne (word2vec to obciążenie o kształcie CPU, do czego dojdziemy), po części to, że dla wielu zadań tanie i statyczne wektory są po prostu wystarczająco dobre (i o rzędy wielkości szybsze w odpytywaniu niż przebieg w przód BERT-a), a po części to, że niektóre potoki — systemy rekomendacyjne, ranking wyszukiwania, bootstrapping baz wektorowych, lekkie cechy semantyczne — chcą właśnie zachowania „odczyt osadzenia”, a nie głębokiego modelu kontekstowego. Pytanie, jak faktycznie taki wytrenować, nie jest więc historyczne — to bieżące pytanie inżynierskie dla wielu zespołów.
De facto standardową biblioteką do trenowania word2vec jest Gensim — skrót od „Generate Similar” — otwartoźródłowy Python, rozwijany od 2009 roku, i mimo istnienia Kerasa, PyTorcha i JAX-a wciąż to, na czym w 2026 roku działa większość produkcyjnych potoków word2vec. Z opisu samej biblioteki:
Gensim to darmowa, otwartoźródłowa biblioteka Pythona do reprezentowania dokumentów jako wektorów semantycznych, tak wydajnie (dla komputera) i bezboleśnie (dla człowieka), jak to możliwe. Gensim jest zaprojektowany do przetwarzania surowych, nieustrukturyzowanych tekstów cyfrowych („czystego tekstu”) za pomocą algorytmów uczenia nienadzorowanego. Algorytmy w Gensimie — Word2Vec, FastText, Latent Semantic Indexing (LSI/LSA), Latent Dirichlet Allocation (LDA) itd. — automatycznie odkrywają strukturę semantyczną dokumentów, badając statystyczne wzorce współwystępowania w korpusie dokumentów treningowych. Algorytmy te są nienadzorowane, co oznacza, że nie jest konieczny żaden ludzki wkład — potrzebujesz jedynie korpusu dokumentów tekstowych.
Dwie rzeczy do zauważenia w tym opisie.
Obejmuje rodzinę algorytmów, nie tylko word2vec. Word2Vec, FastText, LSA, LDA — każda metoda statystycznego współwystępowania omówiona w tym artykule (i kilka więcej) mieszka w tej samej bibliotece. Artykuł omówił konkretnie word2vec; Gensim to biblioteka, w której faktycznie uruchomiłbyś którykolwiek z nich.
Wydajnie (dla komputera) i bezboleśnie (dla człowieka) to dokładnie ten kompromis, pod który Gensim optymalizował się przez piętnaście lat: ekstremalnie szybki na sprzęcie, którego te algorytmy faktycznie potrzebują (CPU, jak zaraz zobaczymy), z jednolinijkowym API ukrywającym każdy drobiazg budowania słownika, podpróbkowania, tablic próbkowania i pętli treningowej. Obie te własności są powodem, dla którego ludzie wciąż po niego sięgają.
Jedna linia daje wytrenowane wektory:
from gensim.models import Word2Vec
model = Word2Vec(sentences, vector_size=300, window=5, min_count=5, sg=1, workers=8)
v_king = model.wv["king"]Budowanie słownika, podpróbkowanie częstych słów, tablica alias do negatywnego próbkowania, pętla treningowa, zapis/odczyt, zapytania o podobieństwo, arytmetyka analogii — Gensim ogarnia to wszystko. Żeby dorównać temu w surowym Kerasie czy PyTorchu, napisałbyś dziesięć razy więcej kodu, dostał gorszą przepustowość i skończył z wektorami, które mogą nie odtwarzać dokładnie kanonicznych wyników word2vec.
Gensim trzyma się po części z inercji, po części z ergonomii — ale najgłębszym powodem jest to, że word2vec to obciążenie o kształcie CPU, a Gensim to najlepsza istniejąca implementacja CPU.
Dlaczego word2vec obciąża CPU, a nie GPU
Standardowa intuicja „głębokie uczenie = GPU” bierze się z modeli, których wąskim gardłem jest gęste mnożenie macierzy na dużych tensorach: sieci konwolucyjnych, transformerów, wielkich MLP. GPU są pod to zaprojektowane — tysiące rdzeni wykonujących tę samą operację na różnych elementach równolegle, zasilanych pamięcią o wysokiej przepustowości ułożoną pod dostęp wektorowy. Daj GPU mnożenie (4096, 4096), a przemieli je w mikrosekundach.
Word2vec strukturalnie nigdy nie robi dużego mnożenia macierzy. Spójrzcie, co faktycznie liczy jedna para treningowa:
1. one lookup in E: read row E[center] ← 1 row out of V
2. lookup target + k negatives in E': read k+1 rows of E' ← k+1 rows out of V
3. dot products against v_c: k+1 dot products of d-dim vectors ← ~k+1 × d multiply-adds
4. gradient updates: update 1 row of E, k+1 rows of E' ← k+2 rows touchedPrzy k = 5–20 i d = 300 każdy krok treningowy wykonuje rzędu 10⁴ mnożeń z dodawaniem — jakieś tysiąc razy mniej obliczeń na przykład niż pojedynczy przebieg w przód przez mały klasyfikator obrazów. „Obliczeń” prawie tu nie ma.
To ten sam kształt problemu, który szczegółowo bada artykuł o tym, że MNIST trenuje się szybciej na CPU: gdy obliczenia na krok są dostatecznie małe, narzut uruchamiania kerneli i koszt transferu po PCIe dominują nad jakąkolwiek przewagą GPU we FLOP-ach, a CPU wygrywa na czasie zegarowym.
Pokazuje to, że wąskim gardłem jest dostęp do pamięci, nie arytmetyka — i właśnie tam CPU jest właściwym wyborem. E i E' razem zajmują 2 × V × d × 4 bajty — około 2,4 GB dla V = 10⁶, d = 300 — a każdy krok robi garstkę szeroko rozproszonych odczytów wierszy w nich, wzorzec, któremu prefetching pamięci podręcznej nie pomoże. Pamięci L1/L2 wchłaniają te losowe odczyty, RAM systemowy mieści model 2,4 GB z zapasem, a wątki OS mogą nadpisywać wspólną pamięć — sztuczka Hogwild! — znacznie taniej niż odpowiednik na GPU (atomiki przez tysiące rdzeni).
Gensim opiera się na tym wszystkim.
Jego wewnętrzna pętla jest w Cythonie, przód-i-tył na pojedynczej parze, z bezblokadową równoległością Hogwild! przez workers wątków CPU.
Na porządnym laptopie to 8 wątków, kilka milionów par na sekundę, miliardy par treningowych na godzinę, wszystko bez opuszczania CPU. To wystarcza, by wytrenować word2vec na pełnym zrzucie Wikipedii w kilka godzin na sprzęcie, który nie wymaga drogich GPU.
Wąskie gardło softmaxu — i droga, którą nie poszliśmy
Wszystko powyżej używało pełnego softmaxu: każdy krok treningowy normalizuje po wszystkich V słowach, sumując exp(score) po całym słowniku, by utworzyć mianownik. To koszt O(V) na każdym pojedynczym przykładzie, a przy V idącym w setki tysięcy staje się dominującym wydatkiem treningu — powodem, dla którego naiwna wersja jest zbyt wolna, by uruchomić ją na prawdziwym korpusie.
Mikolov i in. zaproponowali dwa obejścia. Jedno to negatywne próbkowanie — którego praktyczne implementacje powyżej już używają i które ma własny artykuł: nie normalizuj w ogóle, po prostu zamień każdy krok w garstkę binarnych klasyfikacji prawdziwe-kontra-szum kosztujących O(k). Drugie, zaproponowane w tych samych artykułach, to hierarchiczny softmax — wart poznania właśnie dlatego, że przegrał.
Hierarchiczny softmax układa V słów jako liście drzewa binarnego (word2vec używa drzewa Huffmana, więc częste słowa siedzą bliżej korzenia). Każdy z V − 1 węzłów wewnętrznych niesie własny uczony wektor, zastępując wektory wyjściowe poszczególnych słów w E'. Prawdopodobieństwo słowa to iloczyn decyzji lewo/prawo wzdłuż ścieżki od korzenia do liścia, każda będąca sigmoidą σ(v_node · v_c). Ponieważ ścieżka ma długość około log₂V, krok dotyka tylko ~log₂V wektorów węzłów zamiast wszystkich V — a w przeciwieństwie do negatywnego próbkowania pozostaje właściwym znormalizowanym rozkładem: zsumuj P(w | c) po wszystkich liściach, a dostaniesz dokładnie 1.
Dlaczego więc nie używa się go dziś? Trzy powody, a uogólniają się poza word2vec:
- Negatywne próbkowanie po prostu trenowało lepsze osadzenia na dużych korpusach, i jest prostsze — żadnego drzewa do budowania i utrzymywania — więc wygrało bezpośredni pojedynek nawet wewnątrz word2vec.
- Jest wrogie GPU. Każde słowo idzie inną ścieżką, dotykając innych wektorów węzłów — nieregularna, rozgałęziona, rozproszona praca, przeciwieństwo gęstego, partiowanego mnożenia macierzy, pod które GPU są zbudowane. Na współczesnym sprzęcie „więcej FLOP-ów, ale regularnie” bije „mniej FLOP-ów, ale nieregularnie”.
- Wąskie gardło, które rozwiązywał, zniknęło. Tokenizacja podsłowna skurczyła słowniki do ~30–100 tys. kawałków, co sprawiło, że zwykły softmax
O(V)stał się na tyle tani, że żadne przybliżenie nie jest w ogóle potrzebne. To, bardziej niż jakakolwiek konkurencyjna sztuczka, wysłało hierarchiczny softmax na emeryturę w modelach językowych.
Przetrwał głównie jako opcja historyczna w fastText (który odziedziczył bazę kodu word2vec) i jako przodek softmaxów drzewiastych wciąż używanych w klasyfikacji ekstremalnej i systemach rekomendacyjnych z milionami etykiet. Dla osadzeń słów historia kończy się jednak na negatywnym próbkowaniu.
Gdzie statyczne osadzenia się psują
word2vec produkuje statyczne osadzenia: jeden stały wektor na słowo, niezależnie od kontekstu. To dokładnie właściwy kształt dla hipotezy dystrybucyjnej w pierwotnym sformułowaniu, ale ma trzy tryby awarii, które stawały się coraz bardziej widoczne, gdy NLP przechodziło do trudniejszych zadań.
Polisemia
Rozważcie te dwa zdania:
- I deposited the cheque at the bank. (Wpłaciłem czek w banku.)
- We had a picnic on the river bank. (Urządziliśmy piknik na brzegu rzeki.)
W angielskim oba miejsca zajmuje to samo słowo bank. Statyczne osadzenie daje bank jeden wektor. Ten wektor jest jakąś średnią po obu znaczeniach, co oznacza, że nie jest dobrą reprezentacją żadnego z nich.
bank with two sense clustersglove-wiki-gigaword-300Widżet powyżej pokazuje podobieństwo kosinusowe między bank a dwoma klastrami słów kontekstowych: money, loan, account, deposit, interest po jednej stronie; river, shore, water, creek, flood po drugiej. Oba klastry ciągną powyżej zera — pojedynczy wektor pokrywa oba znaczenia — ale klaster finansowy wygrywa. To nie jest fakt o słowie; to skrzywienie korpusu.
Nie ma sposobu, by kod dalej w potoku odzyskał, które znaczenie było zamierzone w konkretnym zdaniu, bo ma dostęp do jednego wektora i otaczającej sekwencji innych pojedynczych wektorów, wszystkich ślepych na znaczenia w ten sam sposób.
Brak wrażliwości na składnię
Reprezentacja typu worek wektorów wyrzuca kolejność słów. Zdania:
- Pies gryzie człowieka.
- Człowiek gryzie psa.
zawierają identyczne zbiory słów, a więc identyczne reprezentacje typu worek wektorów, mimo przeciwnych znaczeń. Cokolwiek zbudowanego na statycznych osadzeniach musi odzyskać kolejność skądinąd — zwykle z warstwy rekurencyjnej albo konwolucyjnej przetwarzającej sekwencję wprost. To działa (tak zbudowano każdy model NLP sprzed 2018), ale oznacza, że same osadzenia wykonują tylko część roboty.
Zamrożone w chwili treningu
Statyczne osadzenia są ustalone po wytrenowaniu. Nowe znaczenia, nowe złożenia, nowe słownictwo dziedzinowe — wektory się nie aktualizują. Co gorsza, słowa, które nie wystąpiły w korpusie treningowym, po prostu nie mają wektorów w ogóle.
Te trzy awarie wyglądają na powierzchni różnie, ale mają jedną przyczynę: statyczne osadzenie jest funkcją słowa, nie zdania. Cokolwiek zależnego od zdania — znaczenie, rola, pozycja w dyskursie — musi być obsłużone poza osadzeniem. To wystarczająco duża robota, by ograniczała architektury całej epoki sprzed 2018.
Te same trzy awarie dotykają też każdego innego statycznego osadzenia słów — GloVe i fastText produkują tę samą tablicę (V, d) innymi procedurami treningowymi, więc dziedziczą te same martwe punkty. Rozwiązanie ich wymaga całkowitej rezygnacji z „jednego wektora na słowo”, co robią BERT i enkodery kontekstowe.
Co dalej
Poprawka jest łatwa do sformułowania i była trudna do zbudowania: spraw, by wektor zależał od otaczającego zdania, a nie tylko od słowa. bank nie powinien mieć jednego wektora — powinien mieć ten, na który zapracował w „river bank”, i inny w „bank account”. To dokładnie ta luka, którą zamknęły architektury w stylu BERT.
BERT dziedziczy po word2vec więcej, niż wynikałoby z architektonicznej przepaści. Hipoteza dystrybucyjna przenosi się (przewiduj brakujące tokeny z kontekstu); przenosi się też sztuczka z fikcyjnym zadaniem predykcyjnym, które istnieje tylko po to, by wymusić dobre wektory z sieci; przenosi się także zakład, że geometria koduje znaczenie. Samo zadanie treningowe — wypełnij lukę — jest tym samym, które CBOW prowadził w maleńkiej skali jeszcze w 2013.
Zmieniły się architektura i skala. ELMo (2018) uruchomił pierwszą poważną wersję z dwukierunkową LSTM i stanami ukrytymi na pozycję. BERT (również 2018) zamienił LSTM na enkoder transformera i wprowadził maskowane modelowanie języka — strukturalnie CBOW z większością ograniczeń CBOW usuniętych: głęboki stos dwukierunkowych warstw transformera po całym zdaniu zamiast płytkiego uśrednienia po pięciowyrazowym oknie, wiele masek na przykład zamiast jednej i kontekstowe wektory wyjściowe na token zamiast pojedynczego zamrożonego wiersza tablicy. Artykuł o BERT omawia tę maszynerię szczegółowo.