Negatywne próbkowanie i uczenie kontrastywne — od word2vec do CLIP
Podręcznikowa wersja word2vec opiera się na warstwie wyjściowej softmax, która zamienia surowe oceny modelu w prawdopodobieństwo dla każdego słowa w słowniku — i stąd właśnie bierze się problem skalowania. Podczas treningu funkcja straty korzysta zawsze tylko z jednego prawdopodobieństwa na parę, P(target | center). Ale softmax definiuje je jako udział w sumie po całym słowniku:
P(target) = exp(score_target) / Σ_w exp(score_w)Mianownik sumuje po wszystkich V słowach, więc żeby dostać tę jedną liczbę, o którą naprawdę chodzi, trzeba policzyć wszystkie V ocen — nie dlatego, że są potrzebne, tylko dlatego, że potrzebuje ich normalizator. Wyrzuć dowolną pojedynczą ocenę, a normalizacja się rozsypie.
Przy słowniku V = 10⁶ słów i wymiarze osadzenia d = 300 ocenianie słowa centralnego względem każdego słowa to mnożenie macierzy V × d — 300 milionów mnożeń z dodawaniem — plus milion eksponent na mianownik, i to na jedną parę treningową.
Żeby poczuć skalę, porównajmy koszt na przykład z MNIST:
INPUT HIDDEN OUTPUT OUTPUT-LAYER MATMUL
MNIST 784 128 10 128 × 10 = 1,280 ops
word2vec (V = 10⁶) V → 1 300 V 300 × 10⁶ = 300,000,000 opsMNIST to zadanie klasyfikacji na 10 klas; word2vec to zadanie na milion klas — po jednym neuronie wyjściowym na słowo słownika. Każdy koszt w warstwie wyjściowej — wagi, mnożenie macierzy, softmax — rośnie liniowo z tą liczbą klas.
W rzeczywistości word2vec nigdy nie liczył tego softmaksu po V klasach. Artykuł od początku wprowadzał negatywne próbkowanie — rezygnujące z normalizatora i skupiające się zamiast tego na pojedynczych ocenach. Każda para (center, w) przestaje być jedną klasyfikacją na V klas — oceną tego, jak prawdopodobne jest w względem wszystkich pozostałych słów słownika — i staje się samodzielną oceną tego, jak prawdopodobne jest, że akurat ta para jest prawdziwa.
Dla każdej wybranej pary model produkuje jedną ocenę sigmoidalną σ(v_c · v'_w) — pchaną w górę dla par prawdziwych i w dół dla losowych.
Ponieważ każdy osąd stoi samodzielnie, strata jest zwykłą sumą składników na parę — i to właśnie ta rozdzielność jest całym powodem, dla którego można ocenić tylko k+1 wierszy E' i zignorować resztę: pozostałe słowa w ogóle nie wchodzą do straty na tym kroku.
Mechanicznie skraca to krok oceniania w drastyczny sposób. Tam gdzie softmax wykonuje jedno mnożenie przez całą macierz wyjściową, żeby dostać wszystkie oceny naraz, negatywne próbkowanie wykonuje kilka pojedynczych iloczynów skalarnych — po jednym na każdy z k losowo wybranych negatywów plus jeden na parę prawdziwą, razem k+1. Pozostałe V − (k+1) wierszy E' nie są czytane, nie biorą udziału w mnożeniu ani nie są aktualizowane na tym kroku. Każdy z k+1 iloczynów przechodzi przez własną sigmoidę, niezależnie — żaden mianownik ich nie sprzęga. Strata dla pary rozkłada się na sumę k+1 niezależnych składników — ta sama maszyneria największej wiarygodności co w softmaksie, tylko na k+1 niezależnych rozkładach Bernoulliego zamiast jednym wielomianowym rozkładzie po całym słowniku.
Ten mechanizm nazywa się uczeniem kontrastywnym — przyciągaj to, co sparowane, odpychaj to, co losowe — i można go zastosować do wszystkiego, co da się osadzić w przestrzeni wektorowej. Dwa warianty, na które mogliście się natknąć: gęste retrievery w rodzaju DPR, silnika RAG, trenowane na parach zapytania z odpowiadającym mu fragmentem jako pozytywem i losowych fragmentach jako negatywach; oraz modele nagrody w RLHF, gdzie rolę pozytywu grają odpowiedzi preferowane, a negatywu — odrzucone.
Od par do negatywów
Dane treningowe word2vec to pary (center, context) z okna przesuwanego po korpusie: the cat sat on the mat daje (sat, cat), (sat, on), (sat, the) i tak dalej — miliardy autentycznych współwystąpień.
Negatywne próbkowanie zostawia pary bez zmian, ale zamiast po prostu maksymalizować wiarygodność prawdziwego dopasowania (center, context), dokłada k losowych par na krok jako przeciwwagę — dostrajając wagi tak, żeby ich wiarygodność była z kolei minimalizowana.
Oba rodzaje par dzielą ten sam środek, ale inaczej dobierają słowo towarzyszące (partnera):
- Pary prawdziwe — idziemy po korpusie i bierzemy słowa, które faktycznie pojawiają się w oknie kontekstowym każdego środka. Partner jest kształtowany przez to, co naprawdę współwystępuje w korpusie (rozkład łączny) —
(sat, cat)pojawia się, bo tak było naprawdę. - Pary losowe — zostawiamy ten sam środek, ale losujemy partnera z tego, jak często każde słowo występuje w korpusie (rozkład unigramowy), całkowicie ignorując współwystępowanie. Wychodzą pary w rodzaju
(sat, banana)— oba słowa prawdziwe, ale nigdy nie stały obok siebie.
Negatywne próbkowanie zachowuje szkielet pełnego softmaksu — trzy etapy przejścia w przód, strata, gradient — i zmienia tylko to, co dzieje się w środku każdego z nich. Jedyną naprawdę nową operacją jest próbkowanie: wylosuj k negatywnych słów dla każdej pary z rozkładu unigramowego (to właśnie losowanie pary losowej powyżej). Cała reszta to pełny softmax ograniczony w zasięgu: odczyt nadal pobiera pojedynczy wiersz z E, a krok oceniania nadal mnoży skalarnie środek przez wiersze E' — tylko k+1 z nich (cel plus k negatywów) zamiast wszystkich V.
k+1 iloczynów skalarnych zamiast V — ta sama operacja v_c · v'_w, którą wykonuje pełny softmax, tylko policzona dla słów obecnych w stracie, a nie dla całego słownika. Pełne mnożenie v_c @ E', wymagane przy softmaksie, nie zachodzi w ogóle.
Surowy iloczyn skalarny v_c · v'_w może być dowolną liczbą rzeczywistą — dodatnią, ujemną, dużą, małą — ale strata potrzebuje prawdopodobieństwa między 0 a 1: „jak prawdopodobne jest, że ta para jest prawdziwa?”. Doprowadza nas tam funkcja sigmoidalna σ.
Mając oceny, możemy zamienić każdą z nich na prawdopodobieństwo za pomocą sigmoidy. σ ściska dowolną rzeczywistą ocenę do (0, 1):
iloczyn skalarny v_c · v'_w | σ | model mówi |
|---|---|---|
| duży dodatni | ~1 | „para prawdziwa” |
| ~0 | 0.5 | niepewny |
| duży ujemny | ~0 | „para losowa” |
Dla każdej pary prawdziwej chcemy, żeby σ wspinała się ku 1; dla każdej losowej — ku 0; trening pcha iloczyny skalarne w te strony.
Na tej samej pięciowyrazowej zabawce z artykułu o word2vec widżet poniżej odtwarza ten krok oceniania. Wybierz parę treningową (center, target), przełącz, które słowa są próbkowane jako negatywy, a potem przejdź krokami przez k+1 iloczynów skalarnych — score[w] = v_c · v'_w, rozpisane składnik po składniku, a następnie przez sigmoidę. Oceniane są tylko cel i negatywy; pozostałe wiersze E' zostają wyszarzone i nigdy nie są czytane.
| word | role | score | σ(score) | direction |
|---|---|---|---|---|
| on | positive | +0.394 | 0.597 | → pushed toward 1 |
| cat | negative | -0.187 | 0.453 | → pushed toward 0 |
| mat | negative | +0.260 | 0.565 | → pushed toward 0 |
Tam gdzie pełny softmax przepuszcza V ocen przez jeden znormalizowany rozkład, negatywne próbkowanie przesyła każdą z k+1 ocen przez własną σ, niezależnie — σ(v_c · v'_w) stoi samodzielnie jako oszacowanie modelu, że para (center, w) jest prawdziwa, bez wspólnego mianownika. k+1 niezależnych prawdopodobieństw zamiast jednego rozkładu po całym słowniku.
Jak próbkuje się negatywy?
Word2vec pierwotnie proponował próbkowanie negatywów z rozkładu częstości unigramów — losuj słowa ze słownika z wagą według tego, jak często występują. Współczesne systemy kontrastywne — CLIP, DPR, Sentence-BERT — całkowicie to pomijają i traktują pozostałe przykłady z tej samej partii treningowej jako negatywy. To podejście nazywa się negatywami wewnątrz partii (in-batch negatives). Przyjrzyjmy się szybko obu.
W podejściu z próbkowaniem według częstości unigramów faworyzujemy słowa częste: kontrastowanie ze słowami, które model faktycznie napotyka, znaczy więcej niż kontrastowanie z rzadkimi. Jeśli zebra pojawia się w korpusie ledwie kilka razy, model rzadko musi się uczyć, że inne słowa nie powinny mieć podobnych skojarzeń z zebra — to zmarnowany sygnał.
Ale czyste ważenie częstością oznaczałoby, że ~98% negatywów to słowa współwystępujące ze wszystkim i nienoszące żadnej informacji różnicującej — tak zwane słowa stopu, takie jak the i and. Złoty środek osiągamy przez wygładzenie częstości: nadal z przechyłem ku słowom częstym (żeby większość negatywów była realistyczna), ale na tyle stłumionym, żeby słowa treściowe w rodzaju cat, mat, king trafiały się dostatecznie często i uczyły realnych rozróżnień semantycznych.
Każde słowo jest próbkowane z prawdopodobieństwem proporcjonalnym do tego, jak często dane słowo występuje w korpusie, liczone samodzielnie (jego częstość unigramowa). Oryginalny artykuł ustalił empirycznie, że najlepiej działa podniesienie tych zliczeń do potęgi 3/4 przed normalizacją — wartości od 0.5 do 1.0 dają przyzwoite wyniki, a 0.75 trafia w sam środek:
P(w) to tutaj prawdopodobieństwo, że słowo w zostanie wylosowane jako próbka negatywna. Kształt — zliczenia znormalizowane przez ich sumę — to ta sama receptura, z której korzysta softmax; podniesienie zliczeń do 0.75 przed normalizacją to zabieg skalowania temperaturą, który spłaszcza rozkład. W liczniku jest count(w)^0.75 każdego słowa; mianownik sumuje je po słowniku, żeby prawdopodobieństwa dawały w sumie 1.
Mając rozkład, próbkowanie jest proste: policz P(w) dla każdego słowa w słowniku według wzoru powyżej, a potem dla każdej pary pozytywnej (center, context) wylosuj k słów z tego rozkładu — ważony rzut kostką po słowniku, powtórzony k razy, w którym słowa o wyższym P(w) wypadają częściej.
Współczesne systemy natomiast w ogóle nie próbkują z rozkładu słownikowego. Wykorzystują ponownie partię, którą i tak już przetwarzają — to podejście nazywa się negatywami wewnątrz partii.
Trening odbywa się w partiach po N prawdziwych par (query, positive) — (Q1, P1), …, (QN, PN). Każde Qi naprawdę pasuje do własnego Pi (zapytanie i odpowiadający mu fragment, obraz i jego podpis itd.). Jako negatywy dla Qi wystarczy wziąć pozostałe pozytywy z partii — Pj dla j ≠ i — które względem Qi są losowe:
batch: (Q1, P1) (Q2, P2) (Q3, P3) (Q4, P4)
for Q1: positive = P1, negatives = {P2, P3, P4}
for Q2: positive = P2, negatives = {P1, P3, P4}
for Q3: positive = P3, negatives = {P1, P2, P4}
for Q4: positive = P4, negatives = {P1, P2, P3}Na czym polega spryt: przejście w przód po P1 do PN i tak byś wykonał, żeby policzyć każde Qi · Pi. Osadzenia wszystkich P są już w pamięci. Ocenienie Qi względem Pj dla j ≠ i to tylko jeden dodatkowy iloczyn skalarny — żadnego dodatkowego przejścia przez enkoder. Partia o rozmiarze 256 daje 255 negatywów na zapytanie „za darmo”, co odpowiada k = 255 zamiast typowego dla word2vec k = 5–20.
Takie negatywy są zwykle też wyższej jakości niż losowania unigramowe: to prawdziwe teksty albo obrazy, które model musi naprawdę odróżnić, a nie przypadkowe słowa stopu. Ostrzejsze gradienty, szybsza zbieżność. Kompromis: obejmują tylko bieżącą partię, a nie cały korpus — dlatego systemy takie jak MoCo dokładają na wierzchu kolejkę pamięci, a inne stosują wydobywanie trudnych negatywów dla jeszcze ciaśniejszych kontrastów.
Funkcja straty
Strata. Strata na parę to suma k+1 składników log-sigmoidalnych — jeden dla pozytywu, po jednym na każdy negatyw — w miejsce −log P(target | center) z pełnego softmaksu:
loss = − log σ(v_c · v'_t) − Σ log σ(−v_c · v'_n)
───────────────── ───────────────────────
true (positive) pair k sampled negativesGdzie v_c to wejściowe osadzenie słowa centralnego, v'_t to wyjściowe osadzenie prawdziwego celu, v'_n to wyjściowe osadzenie wylosowanego słowa negatywnego, a σ to funkcja sigmoidalna. Pierwszy składnik pcha iloczyn skalarny prawdziwej pary w górę (ku σ(·) = 1); drugi pcha iloczyn każdego negatywu w dół (ku σ(·) = 0).
Składnik negatywny używa σ(−v_c · v'_n) — minus iloczyn skalarny — co działa dzięki tożsamości σ(−x) = 1 − σ(x). Zatem −log σ(−v_c · v'_n) to po prostu −log(1 − σ(v_c · v'_n)): standardowa połówka „zła klasa” z entropii krzyżowej, zastosowana w kierunku „to nie jest prawdziwa para”. Każdy składnik straty to binarna entropia krzyżowa (BCE) zastosowana do jednej pary (center, w) — etykieta 1 dla pozytywu, etykieta 0 dla każdego negatywu. Całkowita strata to k+1 zsumowanych BCE.
Jak wygląda −log σ(z) przy zmieniającej się σ? Gdy σ → 1, składnik dąży do 0 — poprawna predykcja, brak straty. Gdy σ → 0, składnik eksploduje ku nieskończoności — błędna predykcja, wielka kara. Dla negatywów zachodzi lustrzane odbicie: −log(1 − σ) → 0 przy σ → 0 (poprawnie), → ∞ przy σ → 1 (model błędnie uznaje losową parę za prawdziwą). Ta asymetryczna krzywa sprawia, że strata jest samoregulująca się — predykcje prawie trafione ledwie ruszają stratę, bardzo błędne ją zdominowują. Spadek gradientu automatycznie kieruje wtedy uwagę na najgorsze predykcje.
Każde ocenione słowo wnosi jeden składnik — −log σ dla pozytywu, −log(1 − σ) dla każdego negatywu:
| słowo | rola | σ | składnik | wartość |
|---|---|---|---|---|
on | pozytyw | 0.60 | −log(0.60) | 0.51 |
cat | negatyw | 0.45 | −log(1 − 0.45) | 0.60 |
mat | negatyw | 0.56 | −log(1 − 0.56) | 0.83 |
Razem: L ≈ 1.95. Najwięcej wnosi mat — jego σ (0.56) jest najdalej od tego, gdzie negatyw powinien być (0).
A ta suma nie jest tylko wygodna — to łączna logarytmiczna strata k+1 niezależnych decyzji binarnych. Ponieważ każda para (center, w) jest oceniana niezależnie, prawdopodobieństwo, że wszystkie k+1 decyzji są poprawne, to iloczyn ich pojedynczych prawdopodobieństw:
P(all right) = P(positive right) × P(neg₁ right) × … × P(neg_k right)−log zamienia iloczyn w sumę:
−log P(all right) = −log P(positive) + −log P(neg₁) + … + −log P(neg_k)To dokładnie ten rząd składników co powyżej — dodawanie −log-ów z poszczególnych par nie jest dodatkową decyzją modelową; tak właśnie wygląda łączna logarytmiczna wiarygodność niezależnych decyzji.
Krok wstecz: i softmax, i negatywne próbkowanie robią estymację największej wiarygodności — różnią się tylko tym, jaką rodzinę prawdopodobieństw modelują.
| softmax | negatywne próbkowanie | |
|---|---|---|
| co jest modelowane | P(target | center) po całym słowniku | P(prawdziwa para | center, w) dla każdego w |
| typ rozkładu | jeden wielomianowy po V klasach | k+1 niezależnych rozkładów Bernoulliego |
| wyjścia | V liczb sumujących się do 1 | k+1 liczb z (0,1), bez ograniczenia na sumę |
| strata | jedna entropia krzyżowa po V klasach | suma k+1 binarnych entropii krzyżowych |
Każda σ w powyższym rzędzie jest pełnoprawnym prawdopodobieństwem — oszacowaniem modelu, że akurat ta para (c, w) jest prawdziwa — a całkowita strata to autentyczna łączna log-wiarygodność. Negatywne próbkowanie jest w pełni probabilistyczne — tylko lokalnie (jeden rozkład Bernoulliego na parę), a nie globalnie (jeden znormalizowany rozkład po słowniku).
Oddajesz skalibrowane P(target | center), które sumuje się do 1 po V — globalny, znormalizowany widok. Zatrzymujesz maszynerię największej wiarygodności: każda σ jest prawdziwym prawdopodobieństwem, a gradient prawdziwym gradientem ML. Mikolov i współautorzy wprost pisali o tym kompromisie we wcześniejszym akapicie o głębszej poincie — chcieli tylko dobrych wektorów, a nie skalibrowanego modelu prawdopodobieństwa. Strata jest zadowolona, gdy σ pozytywu → 1, każda σ negatywu → 0, a suma → 0 — i każdy krok gradientu pcha w tę stronę.
Gradient
Propagacja wsteczna dotyka tylko tego, co przeczytało przejście w przód. Aktualizuje się k+1 ocenionych wierszy E' — wiersz celu przyciągany ku v_c, wiersz każdego negatywu odpychany — podczas gdy pozostałe V − (k+1) wierszy, nigdy nieprzeczytanych, nie zbiera żadnego gradientu. Po stronie wejścia aktualizuje się pojedynczy wiersz E[center], dokładnie jak w pełnym softmaksie.
Żeby zminimalizować L, potrzebujemy jej gradientu względem każdego parametru, który dotknął przejścia w przód: v_c (wiersz środka w E), v'_t (wiersz celu w E') i każdego v'_n (po jednym wierszu na negatyw). Z jednym faktem z analizy —
d/dz [ −log σ(z) ] = σ(z) − 1— reguła łańcuchowa daje wszystkie trzy:
∂L / ∂v'_t = (σ_t − 1) · v_c ← wiersz wyjściowy celu
∂L / ∂v'_n = σ_n · v_c ← wiersz wyjściowy każdego negatywu
∂L / ∂v_c = (σ_t − 1) · v'_t + Σ_n σ_n · v'_n ← wiersz wejściowy środkagdzie σ_t = σ(v_c · v'_t) oraz σ_n = σ(v_c · v'_n) — dokładnie te liczby, co w sekcji ### Funkcja straty powyżej.
Zwróćcie uwagę na symetrię: każdy gradient wiersza wyjściowego (∂L/∂v'_t, ∂L/∂v'_n) to skalar razy v_c, a gradient środka to ważona suma wierszy wyjściowych, względem których był oceniany. To bezpośrednia konsekwencja symetryczności iloczynu skalarnego względem jego argumentów — różniczkowanie dowolnego f(v_c · v'_w) po v'_w zawsze daje coś proporcjonalnego do v_c, i odwrotnie.
Trzy obserwacje wypadają z tego wprost:
- Gradient pozytywu wskazuje wzdłuż
v_c, przeskalowany przezσ_t − 1(liczbę ujemną —σ_tjest poniżej 1). Gdy odejmujemy gradient,v'_tzostaje pociągnięty w kierunkuv_c— kuv_c. - Gradient każdego negatywu też wskazuje wzdłuż
v_c, przeskalowany przezσ_n(dodatnie). Odjęcie go pcha każdyv'_nw kierunku−v_c— z dala odv_c. - Gradient środka łączy je wszystkie — jedno przyciągnięcie od celu, po jednym odepchnięciu na negatyw — każde ważone tym, jak bardzo myli się w tej chwili jego sigmoida.
Podstawiając zabawkowe wartości z góry (σ_on = 0.60, σ_cat = 0.45, σ_mat = 0.56, z tabelami osadzeń z widżetu oceniania):
∂L/∂v_c = (0.60 − 1) · v'_on + 0.45 · v'_cat + 0.56 · v'_mat
≈ [ −0.04, 0.53, 0.07 ]Ujemny wkład pozytywu (σ_on − 1 = −0.40) i dodatnie wkłady negatywów dają wypadkowy kierunek, wzdłuż którego zaraz przesuniemy środek.
Jeszcze jedna elegancka własność: ten sam skalar (1 − σ_t) pojawia się i w aktualizacji celu (v'_t przesuwa się ku v_c o (1 − σ_t)·v_c), i w przyciąganiu środka (v_c przesuwa się ku v'_t o (1 − σ_t)·v'_t). Oba wektory ruszają ku sobie dokładnie o tyle samo — to symetria koadaptacji straty opartej na iloczynie skalarnym. To samo zachodzi dla każdego negatywu, tylko z σ_n. Sekcja o aktualizacji formalizuje to za chwilę.
Aktualizacja
Spadek gradientu z krokiem uczenia η:
v'_t ← v'_t + η · (1 − σ_t) · v_c ← krok ku v_c
v'_n ← v'_n − η · σ_n · v_c ← krok z dala od v_c
v_c ← v_c + η · (1 − σ_t) · v'_t − η · Σ_n σ_n · v'_n
← ku v'_t, z dala od każdego v'_nZwróćcie uwagę na rozmiary kroków:
v'_tprzesuwa się oη(1 − σ_t)— największy, gdyσ_tjest małe (pozytyw zawodzi).v'_nprzesuwa się oη · σ_n— największy, gdyσ_njest duże (negatyw fałszywie wygląda na prawdziwy).
Więc w naszym stanie mat (σ = 0.56) dostaje mocniejsze pchnięcie niż cat (σ = 0.45); a im bardziej para jest błędna, tym większą korektę otrzymuje. Ta samoregulacja — małe korekty, gdy model już ma rację, duże, gdy jej nie ma — jest tym, co monotonicznie spycha stratę w dół w trakcie treningu.
Podstawienie gradientu z góry przy η = 0.1 przesuwa środek:
v_c ≈ [ 0.33, −0.27, 0.84 ]
v_c_new = v_c − η · ∂L/∂v_c ≈ [ 0.33, −0.32, 0.83 ]Mały krok, ale w kierunku, którego żąda strata. v'_on, v'_cat i v'_mat dostają w tym samym momencie własne aktualizacje według wzorów powyżej; skupiamy się tutaj na v_c, żeby prześledzenie było krótkie.
Sprawdzamy krok
Czy aktualizacja rzeczywiście pomogła? Przeliczmy trzy iloczyny skalarne z nowym v_c (dla jasności używając tych samych v'_w — w praktyce aktualizują się jednocześnie, wzmacniając efekt):
| słowo | przed | po | kierunek |
|---|---|---|---|
v_c · v'_on | 0.39 | 0.41 | w górę — pozytyw lepiej dopasowany ✓ |
v_c · v'_cat | −0.19 | −0.22 | w dół — negatyw odepchnięty ✓ |
v_c · v'_mat | 0.26 | 0.25 | w dół — negatyw odepchnięty ✓ |
Każda σ rusza się we właściwą stronę, a strata na parę spada z 1.95 do ~1.92. Jedna para, jeden malutki krok — pomnożone przez miliardy par i wiele epok, to właśnie to wyrzeźbia geometrię osadzeń, w której rzeczy dopasowane lądują blisko, a losowe daleko.
Porównajcie z sześciolinijkową pętlą w NumPy powyżej — te trzy linie aktualizacji to dokładnie te trzy aktualizacje: Ep[t] -= lr * (s_t - 1) * v_c to krok v'_t, Ep[negs] -= lr * s_n[:, None] * v_c to krok negatywów, a E[c] -= lr * ((s_t - 1) * v_t + s_n @ v_n) to krok v_c.
Dlaczego rezygnacja z softmaksu nie szkodzi jakości
Przejście od „klasyfikacji na V klas” do „klasyfikacji binarnej z losowymi negatywami” wygląda, jakby miało tracić informację — nie maksymalizujemy już bezpośrednio P(prawdziwy cel | środek). Dlaczego więc daje równie dobre osadzenia?
Dlaczego to działa: sygnał gradientowy jest w gruncie rzeczy ten sam. Gradient pełnego softmaksu ciągnie v_c ku v'_t i odpycha go od wyjściowego osadzenia każdego innego słowa, ważąc to przewidywanym prawdopodobieństwem tego słowa. Negatywne próbkowanie robi to samo, tylko na wylosowanym podzbiorze — przyciągnij ku v'_t, odepchnij od osadzeń k losowo wybranych słów. Na przestrzeni wielu przykładów oczekiwany gradient jest taki sam jak gradient pełnego softmaksu z dokładnością do skali. Model zbiega do podobnej geometrii; po prostu dociera tam, wykonując na krok pracę o stałym, niewielkim rozmiarze zamiast pracy skalującej się ze słownikiem.
Jedna para, jeden krok. Wejściowy wektor środka v_sat (zielony) jest przyciągany ku prawdziwemu celowi v_cat (niebieski) — co podnosi ich iloczyn skalarny, więc σ(v_sat · v_cat) wspina się ku 1 — i odpychany od każdego negatywu v_banana, v_zebra, v_rocket (czerwone), więc każde σ(v_sat · v_neg) opada ku 0. Naciskajcie step wielokrotnie i patrzcie, jak geometria sama się układa. Prawdziwy word2vec wykonuje tę samą aktualizację w kilkuset wymiarach, z 5–20 negatywami, dla każdej z miliardów par.
Uruchomcie tę samą pętlę na całym słowniku — wiele par, wiele aktualizacji — a tabela osadzeń sama posortuje się w klastry znaczeniowe:
Prawdziwy skip-gram z negatywnym próbkowaniem, uruchomiony na żywo w przeglądarce na malutkim syntetycznym korpusie. Każdy krok wybiera jedną parę (center, context), próbkuje 5 losowych negatywów i stosuje jedną aktualizację SGD: v_center jest przyciągany ku v_context i odpychany od każdego negatywu. W kroku 0 wektory są losowe — słowa z tego samego klastra nie stoją bliżej niż pary losowe. Naciśnijcie play albo +200 i patrzcie, jak się sortują: słowa królewskie zjeżdżają się razem, tak samo dorośli, młodzi, zwierzęta, czasowniki. Nikt nie powiedział modelowi, że te grupy istnieją; wypadają one same, bo słowa dzielące konteksty w korpusie kończą z wierszami, które muszą dzielić geometrię predykcyjną. Wektory są na siłę dwuwymiarowe, żeby dało się je narysować — sam algorytm przy 300 wymiarach jest identyczny.
Nikt żadnemu z modeli nie powiedział, że te grupy istnieją; wypadają one same, bo słowa dzielące konteksty w korpusie kończą z wierszami, które muszą dzielić geometrię predykcyjną. Wektory są na siłę dwuwymiarowe, żeby dało się je narysować — przy d=300 nie da się tego zwizualizować, ale algorytmy są identyczne.
W NumPy wewnętrzna pętla ma sześć linii:
for c, t in pairs:
negs = rng.choice(V, size=k, p=neg_dist)
v_c, v_t, v_n = E[c], Ep[t], Ep[negs]
s_t = sigmoid(v_c @ v_t) # want → 1
s_n = sigmoid(v_c @ v_n.T) # want → 0 (each)
Ep[t] -= lr * (s_t - 1) * v_c # pull v_t toward v_c
Ep[negs] -= lr * s_n[:, None] * v_c # push v_n's away from v_c
E[c] -= lr * ((s_t - 1) * v_t + s_n @ v_n) # both, into v_cI to cały krok treningowy. Każda iteracja to jedna klatka widżetów powyżej; przejście po miliardach par jest tym, co produkuje prawdziwą tabelę osadzeń word2vec.
To przeformułowanie tłumaczy też, dlaczego model uczy się czegokolwiek ponad surową częstość słów: pary prawdziwe niosą sygnał współwystępowania, pary losowe — jedynie częstość brzegową. Logarytm ilorazu tych dwóch rozkładów to punktowa informacja wzajemna — więc klasyfikator, który je rozdziela, niejawnie uczy się PMI między środkiem a kontekstem.
Mikolov i in. (2013) pokazali, że powstałe wektory wypadają w granicach paru procent od wersji z pełnym softmaksem na standardowych benchmarkach analogii. Levy i Goldberg (2014) udowodnili później, że skip-gram z negatywnym próbkowaniem niejawnie faktoryzuje przesuniętą macierz PMI (punktowej informacji wzajemnej) — tę samą macierz, którą starsze metody „najpierw policz, potem faktoryzuj”, jak SVD, próbowały rozłożyć wprost. Sztuczka nie była hakiem; była inną drogą do tego samego matematycznego celu.
Czego kiedy używać
Negatywne próbkowanie i pełny softmax to narzędzia do różnych zadań. Softmax zachowuje dwie autentyczne przewagi, które dla celu word2vec nie mają znaczenia, ale gdzie indziej mają:
- Skalibrowane prawdopodobieństwa — softmax daje prawdziwe warunkowe
P(target | center), sumujące się do 1 po słowniku.k+1niezależnych rozkładów Bernoulliego z negatywnego próbkowania nie tworzy spójnego rozkładu; każdy da się odczytać jako „czy ta para jest prawdziwa?”, ale nie jako „jak to słowo plasuje się wobec wszystkich V alternatyw?”. - Gęstszy gradient na krok — gradient softmaksu podnosi cel przeciwko każdej alternatywie jednocześnie. Negatywne próbkowanie kontrastuje tylko z
klosowymi próbkami na krok, więc aktualizacje na krok są bardziej zaszumione — w wartości oczekiwanej po wielu przykładach wynik jest ten sam, ale każdy pojedynczy krok to słabszy sygnał.
Jeśli więc twój słownik jest na tyle mały, że softmax po V klasach jest do udźwignięcia, softmax wygrywa dokładnością i niewiele za to płacisz. Dlatego większość współczesnych klasyfikatorów przy nim zostaje:
- Maskowane modelowanie języka w BERT używa pełnego softmaksu po swoim słowniku WordPiece liczącym ~30 000 jednostek.
- Przewidywanie następnego tokenu w stylu GPT używa pełnego softmaksu po swoim słowniku BPE liczącym ~50 000–128 000 jednostek.
- Standardowa klasyfikacja obrazów (od 10 do ~21 000 klas) używa pełnego softmaksu.
Negatywne próbkowanie staje się właściwym wyborem, gdy klasami jest coś w rodzaju każdego słowa słownika, każdego produktu w katalogu, każdego fragmentu w korpusie, każdego użytkownika platformy — zbiorów tak wielkich, że pełny softmax staje się nie do udźwignięcia.
Odejmij poziom słownikowy, a negatywne próbkowanie uogólnia się w jeden z najważniejszych paradygmatów treningu we współczesnym ML: uczenie kontrastywne. Mając pary rzeczy, które do siebie pasują, i pary, które nie pasują, naucz się osadzeń tak, żeby pary dopasowane lądowały blisko (wysoki iloczyn skalarny / podobieństwo kosinusowe), a niedopasowane daleko. „Rzeczami” może być cokolwiek osadzalnego: słowa, obrazy, zdania, fragmenty tekstu, klipy audio, węzły grafu, pary użytkownik-produkt. Strata ma ten sam kształt — zbliżaj pozytywy, odpychaj negatywy — ale dane i enkodery się zmieniają.
Metoda leży u podstaw sporej części współczesnych procedur treningowych. DPR (Dense Passage Retrieval), silnik RAG, trenuje na pozytywach (zapytanie, trafny_fragment) plus wylosowanych negatywach (zapytanie, nietrafny_fragment) — ten sam kształt co (c, t) kontra (c, losowe) u word2vec. Modele nagrody w RLHF trenują na parach (odpowiedź_preferowana, odpowiedź_odrzucona) — strukturalny odpowiednik (pozytyw, negatyw), ten sam kontrastywny charakter. CLIP przyciąga do siebie osadzenia dopasowanych par (obraz, podpis) i odpycha niedopasowane kombinacje z tej samej partii — wyrównanie obrazu i tekstu jako jedna gigantyczna strata kontrastywna wewnątrz partii.
CLIP — pomysł word2vec, przeskalowany do granic
Uczenie kontrastywne stosuje się do wszystkiego, co osadzalne, nie tylko do słów — a CLIP pokazuje to najdobitniej. Word2vec trenował 300-wymiarowe wektory słów na współwystępowaniu w zdaniach; CLIP trenował wspólne osadzenia obrazu i tekstu na 400 milionach par (obraz, podpis) zebranych z internetu — używając tej samej straty „przyciągaj sparowane, odpychaj losowe”, tylko z dwoma enkoderami zamiast jednej tabeli osadzeń i z negatywami wewnątrz partii zamiast losowań według częstości unigramów.
Co sprawia, że CLIP zasługuje w kontekście tego artykułu na bliższe przyjrzenie:
- Potwierdza główną tezę — metoda się uogólnia. Gdyby negatywne próbkowanie było specyficzne dla modelowania języka, CLIP nie mógłby działać. To, że działa, a powstała przestrzeń osadzeń jest dość bogata, żeby unieść klasyfikację obrazów zero-shot (o tym niżej), jest bezpośrednim dowodem, że geometria przyciągania i odpychania wykonuje prawdziwą pracę semantyczną — a nie tylko zapamiętuje częstości słów.
- To szablon architektoniczny, za którym idzie większość współczesnych systemów multimodalnych. DALL-E używa CLIP. Stable Diffusion używa enkodera tekstu z CLIP. Praktycznie każdy współczesny system wyrównujący obraz i tekst ma gdzieś w środku kontrastywny kręgosłup w stylu CLIP. Rozumieć negatywne próbkowanie i negatywy wewnątrz partii to więc rozumieć, jak to wszystko było trenowane.
Dwa enkodery — vision transformer dla obrazów i transformer dla tekstu — produkują osadzenia we wspólnej 512-wymiarowej przestrzeni. Dane treningowe: 400 milionów par (obraz, podpis) zebranych z internetu.
Dla każdej partii treningowej złożonej z N par (I_1, T_1), ..., (I_N, T_N):
- Zakoduj każdy obraz w osadzenie; zakoduj każdą podpis w osadzenie.
- Policz macierz podobieństw
N × N:S[i][j] = I_embed[i] · T_embed[j]— osadzenie każdego obrazu pomnożone skalarnie przez osadzenie każdego podpisu. - Elementy diagonalne
S[i][i]to pary dopasowane (pozytywy); wszystko poza diagonalą jest niedopasowane (negatywy wewnątrz partii, dostarczone za darmo przez resztę partii). - Strata: entropia krzyżowa z softmaksem po każdym wierszu (obraz wybiera swój podpis spośród
Nkandydatów) plus entropia krzyżowa z softmaksem po każdej kolumnie (podpis wybiera swój obraz spośródNkandydatów), zsumowane.
Przy rozmiarze partii CLIP wynoszącym 32K każdy obraz ma 32K − 1 negatywów za darmo — wszystkie pozostałe podpisy w partii. Porównajcie z k = 5–20 u word2vec. Skrót „wewnątrz partii” z wcześniejszej części artykułu jest w tej skali całą grą.
Co się dostaje: przestrzeń osadzeń, w której semantycznie powiązane obrazy i teksty lądują blisko, a niepowiązane daleko. Dlatego CLIP potrafi klasyfikować obrazy zero-shot — policz osadzenia tekstowe dla nazw klas („zdjęcie psa”, „zdjęcie kota” itd.), a potem sklasyfikuj obraz według tego, do którego osadzenia klasy jest najbliżej. Geometria wyrzeźbiona przez stratę kontrastywną koduje już znaczenie w obu modalnościach; żaden klasyfikator na etykietach nie jest potrzebny.
Ta sama metoda, którą wprowadził word2vec. Inne dane, inne enkodery, partie w skali internetu — ale kościec jest identyczny: przyciągaj to, co sparowane, odpychaj to, co losowe.
Jeden niuans, który ten artykuł pominął: jak daleko należy odpychać negatywy? Każdy składnik negatywny gna tu σ(v_c · v'_n) ku 0, co bez końca ciągnie iloczyn skalarny w dół — ku kosinusowi −1, pełnemu przeciwstawieniu. Ale gdy klas jest więcej niż dwie, taki cel jest geometrycznie niemożliwy: k wektorów jednostkowych nie może wskazywać przeciwnie każdy do każdego. Najlepsze, co mogą zrobić, to rozłożyć się w regularny sympleks, gdzie każda para leży pod kosinusem −1/(k−1) — co wraz ze wzrostem liczby klas dąży do ortogonalności (cos ≈ 0). Tekst Tahy Bouhsine’a Untangling the Moons śledzi to napięcie przez całą linię kontrastywną — pair-contrastive, triplet, InfoNCE/NT-Xent, SupCon, CLIP, SigLIP — i argumentuje, że straty pchające ku −1 celują dalej, niż pozwala geometria, marnując wysiłek optymalizacji i częściowo tłumacząc, dlaczego CLIP potrzebuje tak ogromnych partii, żeby przybliżyć właściwy układ. To geometryczny odpowiednik probabilistycznej historii opowiedzianej tutaj, z interaktywnymi wizualizacjami „dwóch księżyców” dla każdej straty; warto przeczytać.
Czysty sposób na odczytanie współczesnego krajobrazu: trening oparty na softmaksie (LLM-y, BERT, klasyfikatory obrazów) służy przewidywaniu właściwego tokenu z ustalonego małego zbioru; trening kontrastywny w stylu negatywnego próbkowania służy uczeniu osadzeń dwóch rzeczy tak, żeby pary dopasowane lądowały blisko, a losowe daleko. Współczesny ML używa obu, do różnych zadań. Word2vec nauczył tę dziedzinę, jak robić to drugie efektywnie, i ta lekcja zestarzała się wyjątkowo dobrze.