diff --git a/wyk/11_Transformer.org b/wyk/11_Transformer.org index efaf0e4..3a41ea9 100644 --- a/wyk/11_Transformer.org +++ b/wyk/11_Transformer.org @@ -61,4 +61,221 @@ $(j-1) \times d_v$), powyższy wzór będziemy mogli zapisać jako iloczyn wekto $$A(w_1,\dots,j-1) = \vec{\alpha}_{*,j}^T V = \operatorname{softmax}(\vec{q_j}^T K)^T V.$$ Sposób patrzenia na atencję przez pryzmat trójki -zapytania-klucze-wartości okaże się niezwykle ważny w wypadku modelu Transformer (zob. kolejny wykład). +zapytania-klucze-wartości okaże się niezwykle ważny w wypadku modelu Transformer. + +** Model Transformer — historia + +Architekturę Transformer opracowano, pierwotnie, na potrzeby +tłumaczenia automatycznego (rok 2017, artykuł [Attention Is All You +Need](https://arxiv.org/abs/1706.03762)). Szybko okazało się, że +podobnie jak w wypadku modelu ELMo dla sieci LSTM, można *pretrenować* +duże modele Transformer (po prostu na dużych korpusach tekstowych, w +sposób nienadzorowany), a potem dostrajać pod konkretne zadanie +przetwarzania języka naturalnego. Jednym z pierwszych przykładów +takiego podejścia był model BERT (rok 2018, artykuł [BERT: +Pre-training of Deep Bidirectional Transformers for Language +Understanding](https://arxiv.org/abs/1810.04805)). To podejście było +później rozwinięte w postaci różnych modeli Transformer, również dla innych +języków niż angielski (RoBERTa, XLM, Polish RoBERTa itd.). + +Na tym wykładzie my skupimy się na innej odnodze modeli Transformer — +modelach generatywnych, takich jak na przykład GPT-2 czy GPT-3. To +podejście jest bliższe duchowi czystego modelowania języka — model +języka jest używany wprost jako generator. + +** GPT-2 — przykład działania + +Dokonajmy najpierw tokenizacji: + +#+BEGIN_SRC python :session mysession :exports both :results raw drawer + from transformers import AutoTokenizer + tokenizer = AutoTokenizer.from_pretrained("gpt2") + text = "The World War III will begin in" + encoded_input = tokenizer(text, return_tensors='pt') + encoded_input +#+END_SRC + +#+RESULTS: +:results: +{'input_ids': tensor([[ 464, 2159, 1810, 6711, 481, 2221, 287]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1]])} +:end: + +Możemy podejrzeć uzyskane tokeny: + +#+BEGIN_SRC python :session mysession :exports both :results raw drawer + [tokenizer.decode(i) for i in encoded_input.input_ids[0]] +#+END_SRC + +#+RESULTS: +:results: +['The', ' World', ' War', ' III', ' will', ' begin', ' in'] +:end: + +Zwróćmy uwagę, że w GPT-2 tokeny obejmują spacje! + +Teraz uruchommy zasadniczy model: + +#+BEGIN_SRC python :session mysession :exports both :results raw drawer + from transformers import AutoModelForCausalLM + model = AutoModelForCausalLM.from_pretrained("gpt2") + outputs = model(**encoded_input) +#+END_SRC + +#+RESULTS: +:results: +:end: + +Z modelu GPT-2 otrzymamy rozkład prawdopodobieństwa kolejnego wyrazu, najpierw w postaci +nieznormalizowanych *logitów*: + +#+BEGIN_SRC python :session mysession :exports both :results raw drawer + logits = outputs[0][0][-1] + logits +#+END_SRC + +#+RESULTS: +:results: +tensor([-130.2947, -129.5677, -136.4030, ..., -138.3791, -138.8967, + -131.6319], grad_fn=) +:end: + +#+BEGIN_SRC python :session mysession :exports both :results raw drawer + from torch import softmax, topk + + k = 20 + + t = topk(softmax(logits, -1), k) + + tb = [[tokenizer.decode(t.indices[ix]), t.values[ix].item()] for ix in range(k)] + tb +#+END_SRC + +#+RESULTS: +:results: +[[' earnest', 0.07378227263689041], [' the', 0.06698606163263321], [' 1945', 0.043497972190380096], [' September', 0.024068640545010567], [' March', 0.0228887926787138], [' October', 0.02232857048511505], [' Europe', 0.02032744698226452], [' 2020', 0.018564637750387192], [' Japan', 0.018423961475491524], [' December', 0.016560807824134827], [' January', 0.015074416995048523], [' July', 0.014139187522232533], [' April', 0.013183596543967724], [' November', 0.012901309877634048], [' 20', 0.012770282104611397], [' Afghanistan', 0.012765118852257729], [' 1944', 0.01266297698020935], [' June', 0.012072316370904446], [' 1914', 0.011765970848500729], [' May', 0.011659453622996807]] +:end: + +*** Generowanie tekstu za pomocą GPT-2 + +#+BEGIN_SRC python :session mysession :exports both :results raw drawer + from transformers import pipeline + generator = pipeline('text-generation', model='gpt2') + generator('Hello, I\'m a language model,', max_length=30, num_return_sequences=1) +#+END_SRC + +#+RESULTS: +:results: +:end: + +** Model Transformer — podstawowa idea + +Model Transformer sprowadza się właściwie do atencji; nie posiada +żadnego komponentu rekurencyjnego, ani nawet nie stosujemy czegoś w +rodzaju połączenia modelu worka słów i modelu n-gramowego. + +W pierwszym przybliżeniu przy obliczaniu rozkładu prawdopodobieństwa +dla kolejnego wyrazu, to jest: + +$$P(w_j|w_1\dots w_{j-1})$$ + +na $j$-tym miejscu (w miejscu przewidywanego wyrazu) doklejamy +specjalny token, powiedzmy ~~. Token ten będzie „atendował” do +innych wszystkich wcześniejszych tokenów w zdaniu: + +$$\vec{\alpha}_{*,j}^T V = \operatorname{softmax}(\vec{q_j}^T K)^T V.$$ + +Samo to byłoby oczywiście zbyt proste: + +1. Otrzymalibyśmy model (ważonego) worka słów, w dodatku każde słowo + miałoby zawsze taką samą wagę! — Token $w_j$, który atenduje jest + zawsze ten sam (~~). Musimy wzbogacić reprezentację wektorową + słów i specjalnego tokenu (~~). + +2. Model Transformer w swojej podstawowej postaci w ogóle nie jest + wyposażony w pojęcie sekwencji — w przeciwieństwie do sieci + rekurencyjnych, które w sposób inherentny operują krok po kroku, w + sekwencji (w czasie). Musimy pozycję tokenów wprowadzić do sieci + Transformer nie przez modyfikację jej architektury, lecz przez dołączenie + informacji pozycyjnej do początkowych zanurzeń. + +3. Model Transformer nie powinien mieć żadnych tokenów OOV/UNK. Musimy + wrócić do kwestii tokenizacji tekstu i wprowadzić podział rzadszych + tokenów na mniejsze, *podwyrazowe* jednostki. + +** Atencja wsobna + +Jeśli chodzi problem (1), rozwiążemy go przez wprowadzenie +**skontekstualizowanych reprezentacji** tokenów. + +Na przykład słowo /mysz/ ma jedno wejściowe (/statyczne/) zanurzenie +(embedding) — bez względu na to, czy chodzi o zwierzę czy urządzenie +peryferyjne, tymczasem dość łatwo ustalić na podstawie kontekstu, o +które znaczenie chodzi. + +Rozwiązanie polega na tym, że tokenom będziemy przypisywać kolejne +zanurzenia skontekstualizowane — zależne od innych wyrazów w zdaniu. W +tym celu zastosujemy atencję wsobną (samo-atencję, /self-attention/). +Każdy token będzie atendował potencjalnie do każdego innego tokenu, +również do samego siebie (!). + +*** Wzory + +Rozpatrywać zatem będziemy nie tylko pojedynczy wektor znormalizowanych atencji + +$$\vec{\alpha}_{*,j}^T V = \operatorname{softmax}(\vec{q_j}^T K)^T V$$, + +lecz całą serię wektorów: + +$$\vec{\alpha}_{*,1},\dots,\vec{\alpha}_{*,i},\dots,\vec{\alpha}_{*,j}$$ + +gdzie: + +$$\vec{\alpha}_{*,i} = \operatorname{softmax}(\vec{q_i}^T K)$$ + +i $K$ jest macierzą kluczy o rozmiarze $d_k \times j$ (tym razem obejmuje również sam $i$-ty token). + +Nowa, skontekstualizowana reprezentacja $i$-tego tokenu będzie po prostu średnią wszystkich +wektorów ważoną atencją: + +$$E_1(w_i) = \operatorname{softmax}(\vec{q_i}^T K)^T V,$$ + +gdzie: + +- $E_1(w_i)$ — skontekstualizowane zanurzenie $i$-tego tokenu; używając indeksu $_1$ + zaznaczamy, że to jest pierwszy skonstekstualizowany embedding, rekurencyjnie będziemy budowali + kolejne $E_2(w_i)$, $E_3(w_i)$ itd. (zaś wejściowy statyczny embedding możemy oznaczyć przez $E_0(w_i)$); +- $V$ to macierz wartości o rozmiarze $j \times d_v$. + +**** Zwarta postać macierzowa atencji wsobnej + +Z praktycznych powodów (szybkość obliczeń na kartach graficznych) dużą +zaletą atencji wsobnej jest to, że wyliczenie skonstekstualizowanych zanurzeń dla wszystkich tokenów +w tekście da się zapisać w postaci zwartego wzoru macierzowego: + +$$E_1 = \operatorname{Attention}(Q, K, V) = \operatorname{softmax}(QK)^T V,$$ + +gdzie $Q$ to macierz wszystkich zapytań o rozmiarze $j \times d_k$ (wektory ułożone poziomo). + +**** Skalowanie atencji + +Twórcy modelu Transformer odkryli, że lepsze wyniki daje skalowanie atencji +przez stałą zależną od rozmiaru wektora klucza/zapytania $d_k$: + +$\operatorname{Attention}(Q, K, V) = \operatorname{softmax}(\frac{QK}{d_k})^T V,$$ + +** Wielogłowicowa atencja + +Od samego początku w Transformerze zamiast jednej atencji zaproponowano wiele **głowic atencji** +$(\operatorname{head}_1,\dots,\operatorname{head}_h$, każda głowica atencji działa w następujący sposób: + +$$\operatorname{head_i} = \operatorname{Attention}(QW_i^Q, KW_i^K,VW_i^V),$$ + +to znaczy każda głowica atencji działa tak samo, tylko przed jej zastosowaniem mnożymy +wektory zapytań, kluczy i wartości przez różne wyuczalne macierze, odpowiednio, +W_i^Q, W_i^K,W_i^V. Otrzymamy w ten sposób $h$ wektorów, konkatenujemy po prostu i mnożymy +przez dodatkową wyuczalną macierz $W^O$: + +$$\operatorname{MultiHead}(Q, K, V) = [\operatorname{head}_1,...,\operatorname{head}_n]W^O.$$ + +Przyjmujemy, że $d_k = d_v = m/h$, wtedy rozmiary macierzy $W_i^Q$ i $W_i^K$ będą wynosiły +$m \times d_k$, macierzy $W_i^V$ — $m \times d_v$, $W^O$ — $hd_v \times m$.