ML Atlas

Kurs: sieci neuronowe od środka · Lekcja 12 z 12 · 15 min

Transformer: klocki, które już znasz

W skrócie

Blok transformera to osadzenie z pozycją, uwaga, MLP, dwa skróty z normalizacją i softmax. Uwaga bez maski zna kolejność słów tylko z kodowania pozycji.

Rozgrzewka Dwa pytania z kursu ML, na których ta lekcja się opiera. Minuta, a lekcji nie blokuje.
Z kursu ML, lekcja 7.2: Od sieci neuronowych do dużych modeli językowych Obniżasz temperaturę losowania. Co dzieje się z generowanym tekstem?

Pokaż odpowiedź

Poprawna odpowiedź: Dominują najbardziej prawdopodobne tokeny, więc tekst staje się przewidywalny

Temperatura przekształca prawdopodobieństwa w chwili losowania, nie zmieniając tego, czego model się nauczył.

Z kursu ML, lekcja 7.1: Sieci neuronowe: wiele prostych elementów razem Dlaczego sieć neuronowa potrzebuje funkcji aktywacji?

Pokaż odpowiedź

Poprawna odpowiedź: Bo bez niej kilka warstw działa jak jedna ważona suma i rysuje prostą

Aktywacja zagina sygnał; bez zgięć kolejne warstwy nie dodają nic ponad jeden model liniowy.

Transformer to sieć, na której stoją dzisiejsze modele językowe. Nie ma w nim nowej magii. Składa się z klocków, które znasz z tego kursu: osadzeń i uwagi z lekcji 11, skrótu z lekcji 7, MLP z lekcji 2 i softmaksu z lekcji 4. Model to wiele takich samych bloków ułożonych jeden na drugim.

Przewidź, potem sprawdź Samouwaga bez kodowania pozycji i bez maski, jak w labie, dostaje dwa zdania: „auto uderzyło drzewo” i „drzewo uderzyło auto”. Czy widzi różnicę?
Jak bardzo jesteś pewny(-a)?

Eksperyment

Ten interaktywny eksperyment działa w przeglądarce z włączonym JavaScriptem. Tekst wokół opisuje, co w nim widać.

Pokaż odpowiedź

Poprawna odpowiedź: Nie, dla niej oba zdania to ten sam zbiór słów

Nie. Uwaga porównuje każde słowo z każdym, ale nie wie, które stoi gdzie. Drugie zdanie dostaje te same trzy wektory, tylko w innej kolejności. W labie słowo „uderzyło” wychodzi z bloku bez pozycji identyczne w obu zdaniach: różnica 0,00. Z pozycją różni się po uwadze o 0,38.

Spróbuj

  • Najpierw ułóż blok. Dwa klocki to pułapki z innych lekcji. Przy każdym klocku jest link do lekcji, w której go poznałeś.
  • Gdy blok jest ułożony, policzy oba zdania naraz. Wyłącz kodowanie pozycji i porównaj wiersze A i B przy każdym etapie.
  • Potem przełącz na zdania o psie. Sprawdź, czy blok rozróżnia je bez kodowania pozycji.

Pomysł w prostych słowach

Blok robi dwie rzeczy na zmianę. Uwaga zbiera informację od innych słów: każde słowo bierze ważoną średnią wszystkich. MLP przerabia potem każde słowo osobno, tymi samymi wagami dla każdego. Wokół obu jest skrót: wynik dodaje się do wejścia, a potem normalizacja warstwy sprowadza każdy wektor do średniej 0 i wariancji 1, a potem mnoży go i przesuwa wyuczonymi wagami γ i β (w labie ich nie ma). Tak ułożył blok pierwszy transformer z „Attention Is All You Need”: normalizacja po skrócie (post-LN). GPT-2, Llama i większość dzisiejszych modeli normalizują przed uwagą i MLP, x + uwaga(LN(x)), bo głęboki stos bloków uczy się wtedy stabilniej. Lab trzyma się pierwszego układu. Na końcu ostatniego bloku softmax zamienia wektor słowa na prawdopodobieństwa następnego słowa.

Uwaga wielogłowa to kilka uwag naraz, każda na swoim kawałku wektora. W labie wektor ma d = 8 liczb i 2 głowy po 4 wymiary, a MLP ma postać 8 → 16 → 8. Cały blok ma 576 wag. Wagi są losowe, bez treningu: lab pokazuje, co blok liczy, a nie, co umie.

Dlaczego to działa

Uwaga bez maski traktuje zdanie jak zbiór. Zamień dwa słowa miejscami, a jej wynik zamieni się tak samo i niczym więcej się nie zmieni. Dlatego do wektora każdego słowa dodaje się na wejściu kodowanie pozycji: wektor, który zależy tylko od miejsca w zdaniu. W labie „uderzyło” stoi w obu zdaniach w środku, więc na wejściu jest identyczne. Różnica pojawia się dopiero w uwadze, bo tam słowo patrzy na sąsiadów, a ci zamienili się miejscami.

Kodowanie sinusoidalne z pierwszego transformera to fale o różnych prędkościach. Iloczyn skalarny kodów dwóch pozycji zależy tylko od odległości między nimi. W widżecie poniżej przełącz na d = 512: sąsiad daje 249,1, pozycja o 5 dalej 189,6, a o 40 dalej 137,0, przy maksimum 256.

Ten interaktywny eksperyment działa w przeglądarce z włączonym JavaScriptem. Tekst wokół opisuje, co w nim widać. Mapa sinusoidalnego kodowania pozycji (pozycja × wymiar) i wykres iloczynu skalarnego: dla d = 512 sąsiad daje 249,1, odległość 5 – 189,6, odległość 40 – 137,0, niezależnie od pozycji.

Czy to tylko ten jeden start wag? Na pięciu startach bez pozycji różnica „uderzyło” między zdaniami wynosi poniżej 10⁻¹⁴, czyli zero z dokładnością zaokrągleń. Z pozycją różnica po uwadze waha się od 0,38 do 2,35.

Po polsku końcówki mówią więcej

Po angielsku „dog bites man” i „man bites dog” mają te same słowa. Po polsku „pies gryzie człowieka” i „człowiek gryzie psa” już nie: pies i psa, człowieka i człowiek to inne słowa. Blok rozróżnia je nawet bez pozycji. Na pięciu startach różnica „gryzie” po uwadze wynosi od 0,55 do 2,11. Kolejność sprawdza dopiero para z tymi samymi słowami, dlatego lab ma „auto” i „drzewo”: mianownik i biernik brzmią tu tak samo.

Częsty błąd

Prawda czy fałsz: „Transformer czyta zdanie od lewej do prawej, jak RNN.” Fałsz. Uwaga patrzy na wszystkie pozycje naraz. Bez maski, jak w labie, kolejność zna tylko z kodowania pozycji: bez niego „uderzyło” wychodzi z bloku identyczne w obu zdaniach. W modelu, który pisze tekst, maska pozwala słowu patrzeć tylko w lewo, więc już ona zdradza kolejność: bez kodowania pozycji „uderzyło” widzi wtedy w obu zdaniach innych sąsiadów i w kodzie lekcji przesuwa się o 0,847. Na pięciu startach wag labu z dodaną maską, bez pozycji, „uderzyło” różni się po uwadze o 1,18 do 2,73. Tekst, który już jest, model z maską też liczy dla wszystkich pozycji naraz: tak idzie trening i czytanie pytania. Odpowiedź dopisuje po jednym tokenie, bo każdy następny zależy od poprzedniego. RNN z lekcji 11 idzie krok po kroku zawsze.

Dalej: kurs LLM

To ostatnia lekcja kursu. Tokeny i trening na ogromnym tekście są już w osobnym kursie „Modele językowe od środka”, a dostrajanie i RAG dochodzą tam po kolei. Przycisk „Dalej” na dole strony prowadzi do tego kursu.

Quiz

Trzy pytania na sprawdzenie się. Wybierz odpowiedź albo rozwiń odpowiedź pod pytaniem. Z klawiatury: strzałki wybierają, Enter albo spacja zatwierdza.

1. Po co transformerowi kodowanie pozycji?
Pokaż odpowiedź

Poprawna odpowiedź: Bo bez niego uwaga bez maski widzi tylko zbiór słów, a nie ich kolejność

Zamiana słów miejscami zamienia wynik uwagi tak samo i nic więcej. W labie bez pozycji „uderzyło” ma w obu zdaniach różnicę 0,00, a z pozycją 0,38 po uwadze.

2. Po co w bloku skrót i normalizacja wokół uwagi i MLP?
Pokaż odpowiedź

Poprawna odpowiedź: Skrót daje gradientowi prostą drogę przez wiele bloków, a normalizacja trzyma liczby w stałej skali

To ten sam skrót co w sieci rezydualnej z lekcji 7: dodaje do pochodnej jedynkę. Normalizacja sprowadza każdy wektor do średniej 0 i wariancji 1, więc kolejne bloki dostają liczby w tej samej skali.

3. Co robi MLP w bloku transformera?
Pokaż odpowiedź

Poprawna odpowiedź: Przerabia każde słowo osobno, tymi samymi wagami, po tym jak uwaga zebrała informację od innych słów

Słowa wymieniają informację tylko w uwadze. MLP to sieć z lekcji 2 nałożona na każde słowo z osobna. Prawdopodobieństwa liczy softmax na samym końcu.

Najważniejsze wnioski

  • Blok transformera to osadzenie z pozycją, uwaga, skrót z normalizacją, MLP, drugi skrót z normalizacją, a na końcu modelu softmax. Tak ułożył go pierwszy transformer; dziś normalizacja zwykle stoi przed uwagą i MLP.
  • Uwaga bez maski traktuje zdanie jak zbiór słów: bez kodowania pozycji kolejność znika. Maska przyczynowa sama ją zdradza.
  • Kodowanie sinusoidalne daje iloczyn skalarny zależny tylko od odległości: 249,1 dla sąsiada, 137,0 o 40 dalej przy d = 512.
  • Uwaga liczy wszystkie pozycje danego tekstu naraz, a RNN krok po kroku. Model z maską dopisuje odpowiedź po jednym tokenie.

Głębiej: wzór, kod, praktyka

Matematyka

Wejście: X = E[słowa] + PE, gdzie PE(pos, 2i) = sin(pos / 10000^(2i/d)), PE(pos, 2i+1) = cos(pos / 10000^(2i/d)).

  • Uwaga jednej głowy: A = softmax(Q Kᵀ / √d_h), wynik A V, gdzie Q = X W_Q, K = X W_K, V = X W_V. Głowy skleja się i mnoży przez W_O.
  • Bez PE, dla macierzy permutacji Π: uwaga(Π X) = Π · uwaga(X). Zamiana słów zamienia wiersze wyniku i nic więcej.
  • Kody pozycji: PE(p) · PE(p + k) = Σ_i cos(k · w_i), w_i = 1 / 10000^(2i/d). Suma zależy tylko od k, a dla k = 0 wynosi d/2.
  • Blok: H = LN(X + uwaga(X)), potem Y = LN(H + W₂ · ReLU(W₁ H)), gdzie LN odejmuje średnią i dzieli przez odchylenie, osobno dla każdego słowa. To post-LN z „Attention Is All You Need”. Pre-LN (GPT-2, Llama): H = X + uwaga(LN(X)), Y = H + MLP(LN(H)). Pełna LN mnoży jeszcze przez wyuczone γ i dodaje β; w labie ich nie ma.
  • Maska przyczynowa: przed softmaksem S_ij = −∞ dla j > i, więc słowo i widzi tylko pozycje do i włącznie. Równość z permutacją przestaje wtedy działać, nawet bez PE.
  • Wagi labu: E 8 × 8, W_Q, W_K, W_V, W_O po 8 × 8, W₁ 8 × 16, W₂ 16 × 8, razem 576. Softmax na wyjściu używa tej samej macierzy E.
W kodzie (Python)

Dwie rzeczy z lekcji w numpy: iloczyny kodów pozycji przy d = 512 i jedna głowa uwagi, która bez pozycji nie widzi kolejności:

import numpy as np

def pe(pos, d):                       # sinusoidal position code of the original transformer
    w = 1 / 10000 ** (np.arange(d // 2) * 2 / d)
    return np.stack([np.sin(pos * w), np.cos(pos * w)], axis=1).ravel()

p = pe(10, 512)
print("d = 512, distance 1, 5, 40:", " ".join(f"{p @ pe(10 + k, 512):.1f}" for k in (1, 5, 40)))

def attend(X, causal=False):          # one head with W_Q = W_K = W_V = I: softmax(X Xᵀ / √d) X
    S = X @ X.T / np.sqrt(X.shape[1])
    if causal:                        # the mask of a model that writes text: word i sees only words 0…i
        S = S + np.triu(np.full(S.shape, -np.inf), 1)
    A = np.exp(S - S.max(axis=1, keepdims=True))
    return (A / A.sum(axis=1, keepdims=True)) @ X

E = {"auto": [1.0, 0.2, -0.5, 0.3], "uderzyło": [0.1, 1.0, 0.4, -0.2], "drzewo": [-0.6, 0.3, 1.0, 0.5]}
a = np.array([E[w] for w in ["auto", "uderzyło", "drzewo"]])
b = np.array([E[w] for w in ["drzewo", "uderzyło", "auto"]])
print("no positions, B = A in reverse:", np.allclose(attend(b), attend(a)[::-1]))
P = np.array([pe(i, 4) for i in range(3)])
gap = np.linalg.norm(attend(a + P)[1] - attend(b + P)[1])
print(f"with positions, 'uderzyło' moves by {gap:.3f}")
gap = np.linalg.norm(attend(a, causal=True)[1] - attend(b, causal=True)[1])
print(f"causal mask, no positions, 'uderzyło' moves by {gap:.3f}")

Wynik

d = 512, distance 1, 5, 40: 249.1 189.6 137.0
no positions, B = A in reverse: True
with positions, 'uderzyło' moves by 0.318
causal mask, no positions, 'uderzyło' moves by 0.847

Iloczyny to te same liczby co w widżecie. Bez pozycji wynik drugiego zdania to wynik pierwszego czytany od końca. Z pozycją „uderzyło” przesuwa się o 0,318, choć stoi w obu zdaniach w środku. Z samą maską, bez pozycji, o 0,847: w pierwszym zdaniu widzi „auto”, w drugim „drzewo”. Uruchomione w numpy 2.1.3.

Dla praktyka
  • Transformera nie pisze się od zera. Bierze się gotowy model z biblioteki, a blok z tej lekcji pomaga zrozumieć, co w nim jest.
  • Dzisiejsze modele zwykle zamiast sinusów kodują pozycję obrotem wektorów zapytań i kluczy (RoPE). Cel jest ten sam: uwaga ma wiedzieć, jak daleko są słowa.
  • Uwaga porównuje każdą parę słów, więc koszt rośnie z kwadratem długości tekstu. Stąd limit okna kontekstu.
  • Do tabel transformer zwykle nie pomaga: boosting na drzewach z kursu ML jest tam prostszy i często lepszy.

Więcej w Atlasie

Hasła encyklopedii o pojęciach z tej lekcji: