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.
Praca z 2013 roku, rozwijająca wcześniejszy model, wprowadziła negative sampling jako alternatywę dla hierarchicznego softmaxu. Zastępuje on przewidywanie słowa klasyfikacją binarną: odróżnianiem zaobserwowanych par od par z rozkładu szumu. Zmienia to funkcję celu, a nie tylko przybliża mianownik softmaxu.
Jedna para dodatnia i k próbek ujemnych wymagają k+1 iloczynów skalarnych: koszt to O((k+1)d) zamiast O(Vd). Gradienty otrzymują tylko wybrane wektory wyjściowe; powtórzone próbki liczą się wielokrotnie. Tutaj zapisujemy je w wierszach Ep o kształcie (V, d), czyli transpozycji układu E' z artykułu o word2vec.
Porównywanie par obserwowanych i losowanych łączy negative sampling z uczeniem kontrastowym. CLIP i gęste retrievery wykorzystują pokrewne idee, lecz ich typowe straty oparte na softmaxie różnią się od niezależnych składników sigmoidalnych word2vec.
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 dodatnie pochodzą z okien kontekstu. Próbki ujemne losują partnera niezależnie od słowa centralnego, zwykle z prawdopodobieństwem proporcjonalnym do count(w)^0.75. Wylosowane słowo może też być rzeczywistym kontekstem: „ujemny” opisuje sposób losowania, a nie brak współwystępowania. Mogą więc powstawać fałszywe negatywy.
Wyszukiwanie wektora i iloczyny skalarne pozostają takie same. Zmienia się wybór ocenianych słów oraz sposób wykorzystania wyników w stracie.
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 Ep 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 |
Każdy sigmoid estymuje etykietę dane kontra szum w wybranym schemacie losowania. Nie jest to P(słowo kontekstu | słowo centralne); wynik zależy od rozkładu szumu i liczby negatywów. Wartości dla różnych słów nie muszą sumować się do jedności.
Jak próbkuje się negatywy?
Negatywy można uzyskać na dwa przydatne sposoby: losować słowa z rozkładu szumu, jak w word2vec, lub wykorzystywać inne przykłady z batcha, jak w CLIP i DPR.
Potęgowanie liczności wykładnikiem mniejszym od jedności ogranicza dominację częstych słów, zachowując ich kolejność. Udział stop words zależy od korpusu; nie ma uniwersalnej wartości procentowej.
Praca o negative sampling podaje, że w jej eksperymentach wykładnik 0.75 dawał lepsze wyniki niż losowanie według częstości unigramów lub jednostajne:
P(w) to prawdopodobieństwo losowania szumu. Mianownik normalizuje potęgowane liczności; pochodzi z częstości korpusu, a nie z bieżących wyników modelu.
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.
Przy negatywach wewnątrz batcha przykładami do porównania są pozostałe elementy bieżącej partii.
W batchu N oznaczonych par (Qi, Pi) traktujemy Pi jako pozytyw dla Qi, a pozostałych kandydatów jako negatywy. To nadanie etykiet treningowych, a nie dowód, że każda para poza przekątną jest semantycznie niezwiązana:
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}Enkodery obliczyły już wszystkie N wektorów kandydatów. Ich ponowne użycie oszczędza dodatkowe przejścia enkodera, ale macierz wyników N×N nadal kosztuje czas i pamięć. Batch liczący 256 przykładów daje 255 kandydatów ujemnych na zapytanie.
Inni kandydaci mogą również pasować do zapytania, tworząc fałszywe negatywy. Skład batcha, obsługa duplikatów i wybór trudnych negatywów wpływają na trening; negatywy wewnątrz batcha nie są automatycznie lepsze.
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.
Strata rośnie, gdy model przypisuje niskie prawdopodobieństwo obserwowanej etykiecie. Jej pochodna względem logitu to σ(z) − y, ograniczona do przedziału od −1 do 1. Duża strata nie oznacza więc nieograniczonego gradientu logitu.
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.5973 | −log(0.5973) | 0.5153 |
cat | negatyw | 0.4534 | −log(1 − 0.4534) | 0.6041 |
mat | negatyw | 0.5647 | −log(1 − 0.5647) | 0.8318 |
Razem: L ≈ 1.95. Najwięcej wnosi mat — jego σ (0.56) jest najdalej od tego, gdzie negatyw powinien być (0).
Etykiety próbek modelujemy jako warunkowo niezależne obserwacje Bernoulliego przy danych wynikach. Pomnożenie ich wiarygodności i wzięcie ujemnego logarytmu daje tę sumę. Składniki nadal współdzielą parametry przez embedding słowa centralnego.
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)Softmax modeluje kategorię słowa kontekstu, a negative sampling — etykietę dane kontra szum. Optymalizują różne wiarygodności, więc znormalizowane prawdopodobieństwa softmaxu i sigmoidy odpowiadają na różne pytania. Żadne nie są automatycznie skalibrowane na nowych danych.
Gradient
W zwykłym SGD bez regularyzacji aktualizowane są tylko wiersz wejściowy słowa centralnego i wybrane wiersze wyjściowe. Powtórzone próbki wymagają zsumowania wkładów. Wszystkie gradienty obliczaj z tych samych wartości parametrów sprzed aktualizacji.
Ż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 Ep) 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.
Aktualizacja dodatniego wektora wyjściowego dodaje wielokrotność v_c, a ujemnego ją odejmuje. Przy stałym v_c zwiększa to lub zmniejsza iloczyn skalarny. „Przyciąganie” i „odpychanie” opisują zmianę wyniku, nie gwarantowaną zmianę odległości euklidesowej.
Używając niezaokrąglonych wartości sigmoidu z widgetu wyników, otrzymujemy gradient słowa centralnego:
∂L/∂v_c = (σ_on − 1) · v'_on + σ_cat · v'_cat + σ_mat · v'_mat
≈ [−0.0435, 0.5269, 0.0717]Dodatnie aktualizacje mają wspólny czynnik η(1 − σ_t), ale ich długości wynoszą η(1 − σ_t)‖v_c‖ i η(1 − σ_t)‖v'_t‖. Są równe tylko przy równych normach wektorów. Słowo centralne otrzymuje też wszystkie wkłady ujemne.
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'_nWiększy błąd sigmoidu daje większy współczynnik, ale długość aktualizacji zależy też od normy wektora. Różne pary mogą dawać sprzeczne gradienty, a zbyt duży krok może przestrzelić minimum. Strata nie musi maleć w każdym kroku.
Podstawienie gradientu z góry przy η = 0.1 przesuwa środek:
v_c = [0.33, −0.27, 0.84]
v_c_new = v_c − 0.1 · ∂L/∂v_c ≈ [0.3344, −0.3227, 0.8328]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
Najpierw aktualizujemy tylko v_c, zachowując stałe wektory wyjściowe, aby wyodrębnić wkład słowa centralnego:
| 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 ✓ |
Przy tej aktualizacji tylko wektora centralnego strata spada z 1,9512 do 1,9229. Jednoczesna aktualizacja wektorów wyjściowych na podstawie tych samych starych wartości daje 1,8626. To wyniki konkretnego przykładu i kroku uczenia, a nie gwarancja dla każdego kroku treningu.
Dlaczego negative sampling uczy przydatnych embeddingów
Negative sampling nie jest nieobciążonym estymatorem gradientu pełnego softmaxu. Oba cele nagradzają obserwowane powiązania, lecz inaczej ważą konkurujące słowa. Rozróżnianie współwystępowania i szumu pozwala uczyć użytecznych reprezentacji bez odtwarzania rozwiązania softmaxu.
Ten przykład 2D wielokrotnie trenuje jedną parę dodatnią i trzy stałe negatywy. Obserwuj wyniki sigmoidu i stratę podczas zmian wektorów wejściowych oraz wyjściowych. To ilustracja funkcji celu, a nie wytrenowana mapa semantyczna pięciu słów.
Następny widget trenuje na wielu parach i pokazuje zmiany embeddingów wejściowych:
Demonstracja trenuje na celowo uporządkowanym korpusie syntetycznym. Każdy krok przetwarza jedną parę i losuje pięć negatywów ze zwracaniem z wygładzonego rozkładu częstości, także z możliwą kolizją z pozytywem. Wykres pokazuje tylko embeddingi wejściowe; wyjściowe są trenowane osobno. Kolory pomagają czytelnikowi i nie są danymi treningowymi. Dwa wymiary służą wizualizacji i nie gwarantują wyraźnych grup semantycznych.
Poniższy krok NumPy kopiuje potrzebne wektory przed aktualizacją obu tablic. np.add.at poprawnie sumuje wkłady powtórzonych indeksów wyjściowych, także słowa będącego jednocześnie pozytywem i negatywem. E i Ep to osobne tablice zmiennoprzecinkowe o kształcie (V, d).
import numpy as np
def sgns_step(E, Ep, c, t, negatives, lr=0.1):
rows = np.r_[t, np.asarray(negatives, dtype=int)]
labels = np.zeros(len(rows))
labels[0] = 1
center = E[c].copy()
outputs = Ep[rows].copy()
logits = outputs @ center
z = np.exp(-np.abs(logits))
probabilities = np.where(logits >= 0, 1 / (1 + z), z / (1 + z))
errors = probabilities - labels
grad_center = errors @ outputs
grad_outputs = errors[:, None] * center
loss = np.sum(np.logaddexp(0, logits) - labels * logits)
np.add.at(Ep, rows, -lr * grad_outputs)
E[c] -= lr * grad_center
return float(loss) # loss before the update
# rng is a NumPy Generator; neg_dist sums to 1 over V words.
for c, t in pairs:
negatives = rng.choice(len(E), size=k, replace=True, p=neg_dist)
loss = sgns_step(E, Ep, c, t, negatives, lr)Dla nieograniczonego wyniku i k niezależnych negatywów z rozkładu q optimum populacyjne wynosi s*(c,w) = log[P_data(w|c) / (k q(w))]. Gdy q(w) = P_data(w), daje to PMI(c,w) − log(k). Rozkład q(w) ∝ count(w)^0.75 zmienia poprawkę. Embeddingi o ograniczonym wymiarze jedynie przybliżają tę macierz idealnych wyników.
To powiązanie stanowi podstawę analizy przesuniętego PMI Levy’ego i Goldberga. Wyjaśnia, co opisują wyniki SGNS, ale nie czyni SGNS, SVD i pełnego softmaxu równoważnymi celami.
Czego kiedy używać
Pełny softmax sprawdza się, gdy zadanie wymaga znormalizowanego rozkładu po ustalonym zbiorze odpowiedzi, a koszt jest akceptowalny. Cel z próbkowaniem wybieraj, gdy uzasadniają to jakość reprezentacji i koszt treningu. Żaden cel nie gwarantuje wyższej dokładności we wszystkich zadaniach.
Przewidywanie zamaskowanych tokenów w BERT i autoregresyjne modelowanie języka zwykle używają softmaxu po słowniku. Systemy wyszukiwania często porównują trafny element z kandydatami losowanymi lub z batcha. Wybór kandydatów jest osobną decyzją od wyboru straty sigmoid lub softmax.
DPR uczy wyników zapytanie–fragment tekstu za pomocą softmaxu po kandydatach. Modele nagrody uczą się względnych ocen odpowiedzi, często przez −log σ(r_preferred − r_rejected); to cel rankingowy, a nie próbkowanie szumu word2vec. CLIP jest kolejnym przykładem kontrastowego celu opartego na softmaxie.
CLIP: porównywanie obrazów i podpisów
CLIP trenowano na 400 milionach par obraz–tekst. Używa niedopasowanych par z batcha jako negatywów, lecz jego strata to symetryczna entropia krzyżowa z softmaxem, a nie niezależne straty binarne negative samplingu word2vec.
Pierwotne eksperymenty CLIP łączyły transformer tekstowy z ResNetem lub transformerem wizyjnym. Warstwy projekcji przenoszą oba wyjścia do wspólnej przestrzeni embeddingów, której wymiar zależy od wariantu modelu.
Dla każdej partii treningowej złożonej z N par (I_1, T_1), ..., (I_N, T_N):
Normalizujemy oba zestawy wektorów normą L2 i obliczamy S[i,j] = exp(t) × dot(image[i], text[j]), gdzie t jest uczonym logarytmem skali. Iloczyn skalarny jest teraz podobieństwem cosinusowym. Liczymy entropię krzyżową dla każdego wiersza i kolumny z celem i, a następnie średnią obu średnich strat. Przekątna jest oznaczona jako dodatnia, choć poza nią mogą występować fałszywe negatywy. Zobacz implementację CLIP.
Pierwotny batch treningowy zawierał 32 768 par, dając każdemu obrazowi 32 767 potencjalnie ujemnych podpisów. Wyjścia enkoderów są wykorzystywane ponownie, ale obliczanie wyników par i synchronizacja wektorów między urządzeniami nadal kosztują.
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.
Surowy iloczyn skalarny nie jest cosinusem: SGNS może zmieniać normy wektorów i kąty. CLIP normalizuje wektory i uczy skali wyników. Te różne geometrie nie wyznaczają uniwersalnego kąta docelowego dla negatywów ani same nie wyjaśniają potrzebnej wielkości batcha.
Wspólną ideą jest uczenie przez porównania. Szczegóły mają znaczenie: SGNS klasyfikuje losowane pary niezależnie, a CLIP każe kandydatom konkurować przez softmax. Schemat losowania i stratę należy dobierać razem, zgodnie ze znaczeniem potrzebnych wyników.