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 ops

MNIST 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”
~00.5niepewny
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.

scoring one pair · k+1 dot products, not V
training pair
negatives — sampled
all 3 dot products done — loss is defined
E (V=5 × d=3)
row sat = v_c
cat
0.21
-0.43
0.15
mat
0.07
0.62
-0.31
on
-0.55
0.18
0.40
sat
0.33
-0.27
0.84
the
-0.12
0.49
-0.06
E' (V=5 × d=3)
target + k negatives — the rest unread
cat
0.45
0.62
-0.20
mat
-0.31
0.15
0.48
on
0.18
-0.40
0.27
sat
-0.22
0.33
0.11
the
0.07
0.55
-0.39
scores → σ target + k negatives
Stage 2 — score · k+1 dot products, not V
cat
mat
on
sat
the
-0.19
0.26
0.39
·
skip
Stage 3 — sigmoid · independent σ, no softmax
cat
mat
on
sat
the
0.45
0.56
0.60
·
each score through its own σ — no shared denominator; target → 1, negatives → 0.
loss −Σ log σ
k+1 terms
1.95
wordrolescoreσ(score)direction
onpositive+0.3940.597→ pushed toward 1
catnegative-0.1870.453→ pushed toward 0
matnegative+0.2600.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)=count(w)0.75wcount(w)0.75P(w) = \frac{\text{count}(w)^{0.75}}{\sum_{w'} \text{count}(w')^{0.75}}

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 partiiPj 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 negatives

Gdzie 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łoworolaσskładnikwartość
onpozytyw0.60−log(0.60)0.51
catnegatyw0.45−log(1 − 0.45)0.60
matnegatyw0.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ą.

softmaxnegatywne próbkowanie
co jest modelowaneP(target | center) po całym słownikuP(prawdziwa para | center, w) dla każdego w
typ rozkładujeden wielomianowy po V klasachk+1 niezależnych rozkładów Bernoulliego
wyjściaV liczb sumujących się do 1k+1 liczb z (0,1), bez ograniczenia na sumę
stratajedna entropia krzyżowa po V klasachsuma 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 środka

gdzie σ_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ą — σ_t jest poniżej 1). Gdy odejmujemy gradient, v'_t zostaje pociągnięty w kierunku v_c — ku v_c.
  • Gradient każdego negatywu też wskazuje wzdłuż v_c, przeskalowany przez σ_n (dodatnie). Odjęcie go pcha każdy v'_n w kierunku −v_c — z dala od v_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'_n

Zwróćcie uwagę na rozmiary kroków:

  • v'_t przesuwa się o η(1 − σ_t) — największy, gdy σ_t jest małe (pozytyw zawodzi).
  • v'_n przesuwa się o η · σ_n — największy, gdy σ_n jest 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łowoprzedpokierunek
v_c · v'_on0.390.41w górę — pozytyw lepiej dopasowany ✓
v_c · v'_cat−0.19−0.22w dół — negatyw odepchnięty ✓
v_c · v'_mat0.260.25w 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.

one SGD step on a (center, context) pair · simplified to 2D
v_sat (center)v_cat (true)v_bananav_zebrav_rocket
σ(v_sat · v_w)
cat0.576→ 1
banana0.582→ 0
zebra0.511→ 0
rocket0.516→ 0
loss = 2.866
steps taken: 0

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:

step-by-step skip-gram training · 21-word vocab, 2-D embeddings, k=5 negatives
thekingsatinpalacequeenruledworkedplayedgardenamanchairwomanboyrangirlonmatcatdog
step
0
epoch ≈ 0 · 532 pairs/epoch
current pair
legend
royalty
adult
youth
animal
verb
place
function

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_c

I 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+1 niezależ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 k losowymi 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 N kandydatów) plus entropia krzyżowa z softmaksem po każdej kolumnie (podpis wybiera swój obraz spośród N kandydató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.