ML Atlas

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

Sekwencje: od pamięci RNN do uwagi

W skrócie

RNN czyta po jednym symbolu i niesie pamięć w stanie. Sygnał błędu słabnie z każdym krokiem wstecz, GRU go przepuszcza, a uwaga patrzy wprost na każde słowo.

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 Na jakim zadaniu trenuje się duży model językowy?

Pokaż odpowiedź

Poprawna odpowiedź: Na przewidywaniu następnego tokenu na podstawie wcześniejszych

LLM uczy się dawać wysokie prawdopodobieństwo faktycznemu następnemu tokenowi, a cała reszta wyrasta z tego zadania.

Z kursu ML, lekcja 7.1: Sieci neuronowe: wiele prostych elementów razem Co liczy propagacja wsteczna?

Pokaż odpowiedź

Poprawna odpowiedź: Ile każda waga dołożyła do błędu, żeby gradient mógł ją poprawić

Propagacja wsteczna stosuje regułę łańcuchową od wyjścia wstecz i daje gradient dla każdej wagi.

Zdanie, nagranie i kurs akcji to sekwencje: liczy się w nich kolejność. Sieć rekurencyjna (RNN) czyta sekwencję po jednym elemencie. Po każdym kroku zapisuje to, co przeczytała, w stanie h: wektorze kilku liczb. Następny krok dostaje nowy symbol i poprzedni stan. W każdym kroku działają te same wagi. To współdzielenie wag z lekcji 10, tylko w czasie zamiast w przestrzeni.

Przewidź, potem sprawdź Zadanie: zapamiętaj pierwszy symbol ciągu, reszta to szum. Zwykła RNN z 8 neuronami uczy się przez 200 kroków treningu na ciągach po 20 symboli. Jaką trafność ma na teście?
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ć. W eksperymencie: Zadanie syntetyczne „zapamiętaj pierwszy symbol”: zwykła RNN z 8 neuronami przy 10 krokach już tylko zgaduje (46,8%), a GRU pamięta bezbłędnie do 30 kroków; do pierwszego kroku dociera w RNN 2,5·10⁻³ sygnału błędu, w GRU 0,189.

Pokaż odpowiedź

Poprawna odpowiedź: Blisko 50%, czyli zgadywanie

W tym labie 49,0% ze 400 ciągów testowych, czyli zgadywanie; na każdym z pięciu startów wag poniżej 55%. GRU z tyloma samymi neuronami trafia w 100% na każdym. Krótsze ciągi zwykła RNN pamięta: na pięciu startach od 5 do 15 kroków.

Spróbuj

  • Lab startuje przy 10 symbolach. Na tym starcie wag zwykła RNN zgaduje już tutaj (46,8%), choć na 4 z 5 startów pamięta 10 symboli. GRU pamięta. Górny wykres zbiera każdą sprawdzoną długość.
  • Cel: znajdź najdłuższy ciąg, który zwykła RNN jeszcze pamięta. Potem idź w drugą stronę i znajdź długość, przy której zawodzi także GRU.
  • Dolny wykres pokazuje, ile sygnału błędu z końca ciągu dociera do pierwszego symbolu, zanim sieć zacznie się uczyć. Oś jest logarytmiczna.

Pomysł w prostych słowach

Żeby nauczyć się zadania, sieć musi powiązać odpowiedź na końcu z symbolem na początku. Propagacja wsteczna z lekcji 5 niesie błąd od końca do początku, krok po kroku. W zwykłej RNN każdy krok mnoży ten sygnał przez wagi i przez pochodną tanh, która nie przekracza 1. Po 9 takich mnożeniach do pierwszego symbolu dociera w tym labie 2,5·10⁻³ sygnału. Sieć dostaje słaby sygnał, że liczył się początek, więc uczy się tego dużo wolniej. Sama mała liczba nie przesądza o wyniku: na startach 4 i 5 dociera 1,4·10⁻³ i 2,5·10⁻³, a w 200 krokach RNN uczy się tam 10 symboli bezbłędnie.

GRU (gated recurrent unit) ma bramkę aktualizacji z: liczbę od 0 do 1 dla każdego neuronu. Nowy stan to mieszanka: (1 − z) starego stanu i z nowej propozycji. Gdy z jest blisko 0, stan przechodzi dalej prawie bez zmian, a sygnał błędu wraca tą samą drogą. W labie bramka startuje z przesunięciem −2, więc bez innych sygnałów z ≈ 0,12: „domyślnie zachowuj”. Do pierwszego symbolu dociera wtedy 0,189 sygnału, ok. 76 razy więcej niż w RNN. LSTM robi to podobnie: stan jej komórki pamięci zmienia się przez dodawanie, a bramka zapominania decyduje, ile z niego zostaje.

Dlaczego to działa

W zwykłej RNN sygnał w kroku 1 to iloczyn 9 czynników, gdy ciąg ma 10 symboli. Zwykle każdy jest mniejszy od 1, więc iloczyn maleje wykładniczo z długością ciągu. To to samo zanikanie gradientu co w głębokiej sieci z lekcji 7: ciąg 10 symboli działa jak 10 warstw z tymi samymi wagami. GRU daje gradientowi drogę z czynnikiem 1 − z, bliskim 1, jak skrót w sieci rezydualnej.

Granica jest też w GRU, a jej miejsce zależy od długości treningu. Lab uczy każdą sieć przez 200 kroków Adama. W tym labie GRU pamięta bezbłędnie do 30 kroków, a przy 50 i 80 zgaduje (47,8% i 52,8%). Na pięciu startach wag, przy 200 krokach treningu, GRU pamięta bezbłędnie do 20 kroków, a przy 30 trafia co najmniej w 90%. Przy 80 krokach nie pamięta na 4 z 5 startów. Zwykła RNN od 20 kroków nie pamięta na żadnym.

Przy 1000 krokach treningu granice się przesuwają: zwykła RNN pamięta 10 symboli na 5 z 5 startów, a 20 na 2 z 5; GRU pamięta 50 symboli na 5 z 5 startów, a 80 na 3 z 5. Słaby sygnał nie zamyka drogi, tylko ją wydłuża. GRU wciąż sięga dalej, bo jej sygnał słabnie wolniej.

Uwaga: każde słowo patrzy wprost

RNN musi przenieść początek zdania przez stan, krok po kroku. Uwaga (attention) działa inaczej: przy każdym słowie liczy, jak bardzo pasuje do niego każde inne słowo, i bierze ich ważoną średnią. Droga od pierwszego słowa do dziesiątego to jeden krok, a nie 9.

Najpierw słowa muszą stać się wektorami. Osadzenia (embeddings) dają każdemu słowu kilka liczb tak, że słowa z podobnych otoczeń mają podobne wektory. W zabawce poniżej wektory powstały z 48 zdań. Król − mężczyzna + kobieta daje wektor najbliższy słowu „królowa” (cos 0,913). To efekt zdań napisanych równolegle, a nie wiedza o świecie.

Ten interaktywny eksperyment działa w przeglądarce z włączonym JavaScriptem. Tekst wokół opisuje, co w nim widać. zabawkowe wektory słów z 48 ręcznie napisanych polskich zdań (współwystąpienia w oknie ±2, PPMI, PCA do 8 wymiarów): mapa, najbliżsi sąsiedzi według cosinusa i analogia król − mężczyzna + kobieta ≈ królowa (cos 0,913).

Uwaga porównuje wektory iloczynem skalarnym. Każde słowo ma zapytanie (czego szuka) i klucz (co oferuje). Wyniki zapytania z kluczami po softmaksie z lekcji 4 to wagi uwagi. W zabawce poniżej wagi ustawiono ręcznie, bez treningu. W zdaniu „Kot zjadł rybę, bo był głodny” słowo „był” daje 51% uwagi słowu „Kot” i tylko 17% słowu „rybę”, bo szuka rzeczownika męskiego.

Ten interaktywny eksperyment działa w przeglądarce z włączonym JavaScriptem. Tekst wokół opisuje, co w nim widać. samouwaga w zdaniu „Kot zjadł rybę, bo był głodny” na ręcznie ustawionych wagach: „był” kieruje najwięcej uwagi na „Kot”.

Częsty błąd

Prawda czy fałsz: „RNN pamięta całe zdanie, bo czyta je w całości.” Fałsz. RNN czyta po jednym symbolu, a o początku wie tylko to, co przetrwało w stanie. Do pierwszego z 10 symboli dociera w RNN 2,5·10⁻³ sygnału błędu, a w GRU 0,189. Na pięciu startach wag GRU dostaje przy 10 symbolach co najmniej 15 razy więcej sygnału niż RNN. Uwaga omija ten problem, bo każde słowo patrzy bezpośrednio na każde inne.

Quiz

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

1. Czemu zwykła RNN tak wolno uczy się zapamiętać pierwszy symbol długiego ciągu?
Pokaż odpowiedź

Poprawna odpowiedź: Bo sygnał błędu słabnie przy każdym kroku wstecz i do początku dociera go mało

Do pierwszego z 10 symboli dociera w labie 2,5·10⁻³ sygnału. GRU z tyloma samymi neuronami pamięta w tych samych 200 krokach bezbłędnie, więc nie chodzi o liczbę neuronów.

2. Co robi bramka aktualizacji GRU, gdy jest blisko 0?
Pokaż odpowiedź

Poprawna odpowiedź: Przepisuje stary stan prawie bez zmian, więc pamięć i sygnał błędu przechodzą przez wiele kroków

Nowy stan to (1 − z) starego i z nowej propozycji. Przy z blisko 0 zostaje stary stan, a gradient wraca drogą z czynnikiem 1 − z, bliskim 1.

3. Czym uwaga różni się od RNN w drodze od pierwszego słowa do dziesiątego?
Pokaż odpowiedź

Poprawna odpowiedź: Uwaga łączy je w jednym kroku, a RNN przez 9 kroków stanu

Każde słowo liczy wagę dla każdego innego słowa wprost, więc sygnał nie musi przechodzić przez 9 mnożeń.

Najważniejsze wnioski

  • RNN czyta po jednym symbolu i niesie pamięć w stanie, z tymi samymi wagami w każdym kroku.
  • Sygnał błędu słabnie z każdym krokiem wstecz: przy 10 symbolach do początku dociera 2,5·10⁻³ w RNN i 0,189 w GRU.
  • Bramka GRU przepuszcza stan i gradient prawie bez zmian, ale i GRU ma granicę długości.
  • Granice z labu to granice 200 kroków treningu: przy 1000 krokach obie sieci sięgają dalej, a GRU wciąż dalej niż RNN.
  • Uwaga łączy każde słowo z każdym innym w jednym kroku: wagi to softmax iloczynów zapytań z kluczami.

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

Matematyka

RNN: h_t = tanh(W x_t + U h_(t−1) + b), wyjście o = v · h_T + c.

  • Sygnał wstecz: ∂h_t/∂h_(t−1) = diag(1 − h_t²) · U, więc ∂h_T/∂h_1 to iloczyn T − 1 takich macierzy. Gdy ich normy są mniejsze od 1, iloczyn maleje wykładniczo z T.
  • GRU: z = σ(W_z x + U_z h + b_z), r = σ(W_r x + U_r h + b_r), n = tanh(W_n x + U_n (r ⊙ h) + b_n), h_t = (1 − z) ⊙ h_(t−1) + z ⊙ n. Pochodna po starym stanie ma składnik diag(1 − z), przy z blisko 0 bliski macierzy jednostkowej. Przesunięcie −2 daje σ(−2) ≈ 0,12.
  • Uwaga: A = softmax(Q Kᵀ / √d), wynik A V, gdzie Q = E W_Q, K = E W_K, V = E W_V. Każdy wiersz A sumuje się do 1. W zabawce d = 4, więc dzielnik to 2.
  • Analogia: cos(a − b + c, w) = (a − b + c) · w / (‖a − b + c‖ · ‖w‖). Wynik to słowo o największym cosinusie spośród innych niż a, b, c.
W kodzie (Python)

Dwie rzeczy z lekcji w numpy: sygnał błędu przez 10 kroków jednego neuronu i jedna głowa uwagi z macierzami zabawki:

import numpy as np

# 1) the error signal from the last of 10 symbols back to the first: 9 multiplications
x = np.array([1.0, -0.5, 0.3, 0.8, -1.0, 0.2, 0.6, -0.3, 0.9, -0.7])
w, h, rnn = 0.9, 0.0, []
for xt in x:
    h = np.tanh(w * h + xt)       # a one-neuron RNN: the same w at every step
    rnn.append(w * (1 - h ** 2))  # dh_t / dh_(t-1) = w * tanh'
z = 0.12                          # the GRU update gate near 0 = keep the state
print(f"RNN, signal at step 1: {np.prod(rnn[1:]):.1e}")
print(f"GRU, copy path (1 - z)^9: {(1 - z) ** 9:.3f}")

# 2) self-attention: each word looks at every word in one step (the attention-toy matrices)
words = ["Kot", "zjadł", "rybę", "bo", "był", "głodny"]
E = np.array([[1, 0, 0, 0, 1, 0], [0, 1, .3, 0, 0, 1], [.6, 0, 1, 0, 0, 0],
              [0, 0, 0, 1, 0, 0], [0, .7, 0, .2, 0, 1], [0, .8, .3, 0, 0, 1]])
WK = np.zeros((6, 4)); WK[0, 0], WK[1, 2], WK[2, 1], WK[4, 3] = 1.5, 2, 2, 1.5
WQ = np.zeros((6, 4)); WQ[0, 2], WQ[1, 0], WQ[1, 1], WQ[2, 2], WQ[3, 0], WQ[3, 2], WQ[5, 3] = 1.5, 1.5, .5, 1, .5, .5, 1.5
S = (E @ WQ) @ (E @ WK).T / np.sqrt(4)
A = np.exp(S - S.max(axis=1, keepdims=True))
A /= A.sum(axis=1, keepdims=True)
print("'był' looks at:", " ".join(f"{word} {a:.2f}" for word, a in zip(words, A[4])))
print("each row sums to 1:", np.allclose(A.sum(axis=1), 1))

Wynik

RNN, signal at step 1: 2.4e-02
GRU, copy path (1 - z)^9: 0.316
'był' looks at: Kot 0.51 zjadł 0.09 rybę 0.17 bo 0.07 był 0.08 głodny 0.08
each row sums to 1: True

Jeden neuron z wagą 0,9 przepuszcza do pierwszego kroku 2,4·10⁻² sygnału, a sama droga kopiowania GRU 0,316. To inne liczby niż w labie, bo tam jest 8 neuronów i wylosowane wagi, ale kierunek jest ten sam. Wiersz „był” to wagi z zabawki: 0,51 dla „Kot”. Uruchomione w numpy 2.1.3.

Dla praktyka
  • Do tekstu sięga się dziś zwykle po transformer, czyli uwagę (lekcja 12). RNN i GRU mają sens przy strumieniach danych na żywo: koszt jednego kroku jest stały, a uwaga porównuje każdą parę słów.
  • Gdy uczysz sieć rekurencyjną, zacznij od GRU albo LSTM, nie od zwykłej RNN. Ustaw obcinanie gradientu (gradient clipping), bo w RNN gradient może też wybuchać.
  • Zanim uwierzysz, że model korzysta z dalekiego kontekstu, sprawdź go na sztucznym zadaniu pamięci, jak w labie.
  • Osadzenia słów bierz z gotowego modelu wytrenowanego na dużym korpusie. Kilkadziesiąt zdań wystarcza tylko na zabawkę.

Więcej w Atlasie

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