Merge branch 'master' of git.wmi.amu.edu.pl:filipg/aitech-moj
This commit is contained in:
commit
0b5a7c18c7
495
wyk/11_Transformer.ipynb
Normal file
495
wyk/11_Transformer.ipynb
Normal file
@ -0,0 +1,495 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"![Logo 1](https://git.wmi.amu.edu.pl/AITech/Szablon/raw/branch/master/Logotyp_AITech1.jpg)\n",
|
||||
"<div class=\"alert alert-block alert-info\">\n",
|
||||
"<h1> Modelowanie języka</h1>\n",
|
||||
"<h2> 11. <i>Transformer</i> [wykład]</h2> \n",
|
||||
"<h3> Filip Graliński (2022)</h3>\n",
|
||||
"</div>\n",
|
||||
"\n",
|
||||
"![Logo 2](https://git.wmi.amu.edu.pl/AITech/Szablon/raw/branch/master/Logotyp_AITech2.jpg)\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Transformer\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Atencja jako „miękka” baza danych\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"O atencji można myśleć metaforycznie jako o odpytywaniu „miękkiej”, wektorowej\n",
|
||||
"bazy danych. Możemy sobie wyobrazić, że słowa $w_1,\\dots,w_{j-1}$ są\n",
|
||||
"naszą bazą danych, a słowo $w_j$ (z którego kierujemy „snop” uwagi)\n",
|
||||
"jest **zapytaniem** (*query*). To zapytanie dopasowujemy do **kluczy**\n",
|
||||
"(*keys*), w najprostszym ujęciu po prostu słów $w_1,\\dots,w_{j-1}$ (a\n",
|
||||
"właściwie ich zanurzeń). Jeśli klucz pasuje do zapytania, odpowiednia\n",
|
||||
"wartość (*value*) jest wydobywana z bazy. Nasza baza jest jednak\n",
|
||||
"„miękka”, nie — zerojedynkowa, zapytanie pasuje do klucza w pewnym\n",
|
||||
"stopniu, mniej lub bardziej.\n",
|
||||
"\n",
|
||||
"W najprostszym ujęciu wartości są tym samym co klucze, czyli z naszej\n",
|
||||
"bazy wydobywamy te same zanurzenia słów, których używamy jako kluczy.\n",
|
||||
"Można jednak skomplikować schemat, rozróżniając klucze i wartości —\n",
|
||||
"mogą one powstawać przez rzutowanie podstawowych zanurzeń różnymi\n",
|
||||
"macierzami:\n",
|
||||
"\n",
|
||||
"$$\\vec{k_i} = W_k E(w_i),$$\n",
|
||||
"\n",
|
||||
"$$\\vec{v_i} = W_v E(w_i).$$\n",
|
||||
"\n",
|
||||
"Również samo zapytanie może powstać przez rzutowanie:\n",
|
||||
"\n",
|
||||
"$$\\vec{q_i} = W_q E(w_i).$$\n",
|
||||
"\n",
|
||||
"Jeśli zanurzenie $E(w_i)$ o rozmiarze $m$ przedstawimy w postaci\n",
|
||||
"kolumnowej, wówczas macierze będą $W_k$ i $W_q$ będą miały rozmiar\n",
|
||||
"$d_k \\times m$, gdzie $d_k$ jest rozmiarem kluczy i zapytań (dlaczego\n",
|
||||
"wektory kluczy i zapytań powinny mieć raczej ten sam rozmiar?), zaś macierz\n",
|
||||
"$W_v$ — $d_v \\times m$, gdzie $d_v$ to rozmiar zanurzenia wektora wartości.\n",
|
||||
"Zazwyczaj $d_k = d_v = m$, ale nie jest to obligatoryjne.\n",
|
||||
"\n",
|
||||
"Teraz nieznormalizowane wagi atencji przyjmą postać:\n",
|
||||
"\n",
|
||||
"$$\\hat{\\alpha}_{i,j} = \\vec{q_i}^T\\vec{k_j} = (W_q E(w_i))(W_k E(k_j)).$$\n",
|
||||
"\n",
|
||||
"Zauważmy, że ciąg $\\hat{\\alpha}_{1,j},\\dots,\\hat{\\alpha}_{j-1,j}$ można potraktować jako wektor\n",
|
||||
"$\\hat{\\vec{\\alpha}_{*,j}}$ i wyliczać w postaci zwartej:\n",
|
||||
"\n",
|
||||
"$$\\hat{\\vec{\\alpha}_{*,j}} = \\vec{q_j}^T K$$\n",
|
||||
"\n",
|
||||
"gdzie $K$ to macierz kluczy złożona z wektorów\n",
|
||||
"$\\vec{k_1},\\dots,\\vec{k_{j-1}}$, tj. macierz o rozmiarze $d_k \\times (j-1)$.\n",
|
||||
"\n",
|
||||
"Wektor znormalizowanych wag atencji będzie miał wówczas postać:\n",
|
||||
"\n",
|
||||
"$$\\vec{\\alpha}_{*,j} = \\operatorname{softmax}(\\vec{q_j}^T K).$$\n",
|
||||
"\n",
|
||||
"Dokonajmy teraz agregacji wartości — obliczamy średnią wektorów\n",
|
||||
"wartości ($\\vec{v_i}$) ważoną atencją:\n",
|
||||
"\n",
|
||||
"$$A(w_1,\\dots,j-1) = \\alpha_{1,j} \\vec{v_1} + \\dots + \\alpha_{j-1,j} \\vec{v_{j-1}} = \\sum_{i=1}^{j-1} \\alpha_{i,j} v_i.$$\n",
|
||||
"\n",
|
||||
"Jeśli $j-1$ wektorów wartości ułożymy w macierz $V$ (o rozmiarze\n",
|
||||
"$(j-1) \\times d_v$), powyższy wzór będziemy mogli zapisać jako iloczyn wektora wag atencji i macierzy $V$:\n",
|
||||
"\n",
|
||||
"$$A(w_1,\\dots,j-1) = \\vec{\\alpha}_{*,j}^T V = \\operatorname{softmax}(\\vec{q_j}^T K)^T V.$$\n",
|
||||
"\n",
|
||||
"Sposób patrzenia na atencję przez pryzmat trójki\n",
|
||||
"zapytania-klucze-wartości okaże się niezwykle ważny w wypadku modelu Transformer.\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Model Transformer — historia\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Architekturę Transformer opracowano, pierwotnie, na potrzeby\n",
|
||||
"tłumaczenia automatycznego (rok 2017, artykuł [Attention Is All You\n",
|
||||
"Need]([https://arxiv.org/abs/1706.03762](https://arxiv.org/abs/1706.03762))). Szybko okazało się, że\n",
|
||||
"podobnie jak w wypadku modelu ELMo dla sieci LSTM, można **pretrenować**\n",
|
||||
"duże modele Transformer (po prostu na dużych korpusach tekstowych, w\n",
|
||||
"sposób nienadzorowany), a potem dostrajać pod konkretne zadanie\n",
|
||||
"przetwarzania języka naturalnego. Jednym z pierwszych przykładów\n",
|
||||
"takiego podejścia był model BERT (rok 2018, artykuł [BERT:\n",
|
||||
"Pre-training of Deep Bidirectional Transformers for Language\n",
|
||||
"Understanding]([https://arxiv.org/abs/1810.04805](https://arxiv.org/abs/1810.04805))). To podejście było\n",
|
||||
"później rozwinięte w postaci różnych modeli Transformer, również dla innych\n",
|
||||
"języków niż angielski (RoBERTa, XLM, Polish RoBERTa itd.).\n",
|
||||
"\n",
|
||||
"Na tym wykładzie my skupimy się na innej odnodze modeli Transformer —\n",
|
||||
"modelach generatywnych, takich jak na przykład GPT-2 czy GPT-3. To\n",
|
||||
"podejście jest bliższe duchowi czystego modelowania języka — model\n",
|
||||
"języka jest używany wprost jako generator.\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### GPT-2 — przykład działania\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Dokonajmy najpierw tokenizacji:\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'input_ids': tensor([[ 464, 2159, 1810, 6711, 481, 2221, 287, 1160, 2078, 287]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])}"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from transformers import AutoTokenizer\n",
|
||||
"tokenizer = AutoTokenizer.from_pretrained(\"gpt2\")\n",
|
||||
"text = \"The World War III will begin in 2028 in\"\n",
|
||||
"encoded_input = tokenizer(text, return_tensors='pt')\n",
|
||||
"encoded_input"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"['The', ' World', ' War', ' III', ' will', ' begin', ' in', ' 20', '28', ' in']"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"[tokenizer.decode(i) for i in encoded_input.input_ids[0]]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Zwróćmy uwagę, że w GPT-2 tokeny obejmują spacje!\n",
|
||||
"\n",
|
||||
"Teraz uruchommy zasadniczy model:\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from transformers import AutoModelForCausalLM\n",
|
||||
"model = AutoModelForCausalLM.from_pretrained(\"gpt2\")\n",
|
||||
"outputs = model(**encoded_input)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"softmax(outputs[0][0][-1])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Z modelu GPT-2 otrzymamy rozkład prawdopodobieństwa kolejnego wyrazu, najpierw w postaci\n",
|
||||
"nieznormalizowanych **logitów**:\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"tensor([-130.2947, -129.5677, -136.4030, ..., -138.3791, -138.8967,\n",
|
||||
" -131.6319], grad_fn=<SelectBackward0>)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"logits = outputs[0][0][-1]\n",
|
||||
"logits"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[[' 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]]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from torch import softmax, topk\n",
|
||||
"\n",
|
||||
"k = 20\n",
|
||||
"\n",
|
||||
"t = topk(softmax(logits, -1), k)\n",
|
||||
"\n",
|
||||
"tb = [[tokenizer.decode(t.indices[ix]), t.values[ix].item()] for ix in range(k)]\n",
|
||||
"tb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Generowanie tekstu za pomocą GPT-2\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from transformers import pipeline\n",
|
||||
"generator = pipeline('text-generation', model='gpt2')\n",
|
||||
"generator('Hello, I\\'m a language model,', max_length=30, num_return_sequences=1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Model Transformer — podstawowa idea\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Model Transformer sprowadza się właściwie do atencji; nie posiada\n",
|
||||
"żadnego komponentu rekurencyjnego, ani nawet nie stosujemy czegoś w\n",
|
||||
"rodzaju połączenia modelu worka słów i modelu n-gramowego.\n",
|
||||
"\n",
|
||||
"W pierwszym przybliżeniu przy obliczaniu rozkładu prawdopodobieństwa\n",
|
||||
"dla kolejnego wyrazu, to jest:\n",
|
||||
"\n",
|
||||
"$$P(w_j|w_1\\dots w_{j-1})$$\n",
|
||||
"\n",
|
||||
"na $j$-tym miejscu (w miejscu przewidywanego wyrazu) doklejamy\n",
|
||||
"specjalny token, powiedzmy `<mask>`. Token ten będzie „atendował” do\n",
|
||||
"innych wszystkich wcześniejszych tokenów w zdaniu:\n",
|
||||
"\n",
|
||||
"$$\\vec{\\alpha}_{*,j}^T V = \\operatorname{softmax}(\\vec{q_j}^T K)^T V.$$\n",
|
||||
"\n",
|
||||
"Samo to byłoby oczywiście zbyt proste:\n",
|
||||
"\n",
|
||||
"1. Otrzymalibyśmy model (ważonego) worka słów, w dodatku każde słowo\n",
|
||||
" miałoby zawsze taką samą wagę! — token $w_j$, który atenduje jest\n",
|
||||
" zawsze ten sam (`<mask>`). Musimy wzbogacić reprezentację wektorową\n",
|
||||
" słów i specjalnego tokenu (`<mask>`).\n",
|
||||
"\n",
|
||||
"2. Model Transformer w swojej podstawowej postaci w ogóle nie jest\n",
|
||||
" wyposażony w pojęcie sekwencji — w przeciwieństwie do sieci\n",
|
||||
" rekurencyjnych, które w sposób inherentny operują krok po kroku, w\n",
|
||||
" sekwencji (w czasie). Musimy pozycję tokenów wprowadzić do sieci\n",
|
||||
" Transformer nie przez modyfikację jej architektury, lecz przez dołączenie\n",
|
||||
" informacji pozycyjnej do początkowych zanurzeń.\n",
|
||||
"\n",
|
||||
"3. Model Transformer nie powinien mieć żadnych tokenów OOV/UNK. Musimy\n",
|
||||
" wrócić do kwestii tokenizacji tekstu i wprowadzić podział rzadszych\n",
|
||||
" tokenów na mniejsze, **podwyrazowe** jednostki.\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Atencja wsobna\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Jeśli chodzi problem (1), rozwiążemy go przez wprowadzenie\n",
|
||||
"****skontekstualizowanych reprezentacji**** tokenów.\n",
|
||||
"\n",
|
||||
"Na przykład słowo *mysz* ma jedno wejściowe (*statyczne*) zanurzenie\n",
|
||||
"(embedding) — bez względu na to, czy chodzi o zwierzę czy urządzenie\n",
|
||||
"peryferyjne, tymczasem dość łatwo ustalić na podstawie kontekstu, o\n",
|
||||
"które znaczenie chodzi.\n",
|
||||
"\n",
|
||||
"Rozwiązanie polega na tym, że wszystkim tokenom będziemy przypisywać kolejne\n",
|
||||
"zanurzenia skontekstualizowane — zależne od innych tokenów w zdaniu. W\n",
|
||||
"tym celu zastosujemy atencję wsobną (samo-atencję, *self-attention*).\n",
|
||||
"Każdy token będzie atendował potencjalnie do każdego innego tokenu,\n",
|
||||
"również do samego siebie (!).\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Wzory\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Rozpatrywać zatem będziemy nie tylko pojedynczy wektor znormalizowanych atencji\n",
|
||||
"\n",
|
||||
"$$\\vec{\\alpha}_{*,j}^T V = \\operatorname{softmax}(\\vec{q_j}^T K)^T V,$$\n",
|
||||
"\n",
|
||||
"lecz całą serię wektorów:\n",
|
||||
"\n",
|
||||
"$$\\vec{\\alpha}_{*,1},\\dots,\\vec{\\alpha}_{*,i},\\dots,\\vec{\\alpha}_{*,j},$$\n",
|
||||
"\n",
|
||||
"gdzie:\n",
|
||||
"\n",
|
||||
"$$\\vec{\\alpha}_{*,i} = \\operatorname{softmax}(\\vec{q_i}^T K)$$\n",
|
||||
"\n",
|
||||
"i $K$ jest macierzą kluczy o rozmiarze $d_k \\times j$ (tym razem obejmuje również sam $i$-ty token).\n",
|
||||
"\n",
|
||||
"Nowa, skontekstualizowana reprezentacja $i$-tego tokenu będzie po prostu średnią wszystkich\n",
|
||||
"wektorów ważoną atencją:\n",
|
||||
"\n",
|
||||
"$$E_1(w_i) = \\operatorname{softmax}(\\vec{q_i}^T K)^T V,$$\n",
|
||||
"\n",
|
||||
"gdzie:\n",
|
||||
"\n",
|
||||
"- $E_1(w_i)$ — skontekstualizowane zanurzenie $i$-tego tokenu; używając indeksu $_1$\n",
|
||||
" zaznaczamy, że to jest pierwszy skonstekstualizowany embedding, rekurencyjnie będziemy budowali\n",
|
||||
" kolejne $E_2(w_i)$, $E_3(w_i)$ itd. (zaś wejściowy statyczny embedding możemy oznaczyć przez $E_0(w_i)$);\n",
|
||||
"- $V$ — macierz wartości o rozmiarze $j \\times d_v$.\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"##### Zwarta postać macierzowa atencji wsobnej\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Z praktycznych powodów (szybkość obliczeń na kartach graficznych) dużą\n",
|
||||
"zaletą atencji wsobnej jest to, że wyliczenie skonstekstualizowanych zanurzeń dla wszystkich tokenów\n",
|
||||
"w tekście da się zapisać w postaci zwartego wzoru macierzowego:\n",
|
||||
"\n",
|
||||
"$$E_1 = \\operatorname{Attention}(Q, K, V) = \\operatorname{softmax}(QK)^T V,$$\n",
|
||||
"\n",
|
||||
"gdzie $Q$ to macierz wszystkich zapytań o rozmiarze $j \\times d_k$ (wektory ułożone poziomo).\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"##### Skalowanie atencji\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Twórcy modelu Transformer odkryli, że lepsze wyniki daje skalowanie atencji\n",
|
||||
"przez stałą zależną od rozmiaru wektora klucza/zapytania $d_k$:\n",
|
||||
"\n",
|
||||
"$$\\operatorname{Attention}(Q, K, V) = \\operatorname{softmax}(\\frac{QK}{d_k})^T V,$$\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Wielogłowicowa atencja\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Od samego początku w Transformerze zamiast jednej atencji zaproponowano wiele ****głowic atencji****\n",
|
||||
"$(\\operatorname{head}_1,\\dots,\\operatorname{head}_h)$, każda głowica atencji działa w następujący sposób:\n",
|
||||
"\n",
|
||||
"$$\\operatorname{head_i} = \\operatorname{Attention}(QW_i^Q, KW_i^K,VW_i^V),$$\n",
|
||||
"\n",
|
||||
"to znaczy każda głowica atencji działa tak samo, tylko przed jej zastosowaniem mnożymy\n",
|
||||
"wektory zapytań, kluczy i wartości przez różne wyuczalne macierze, odpowiednio,\n",
|
||||
"$W_i^Q$, $W_i^K$, $W_i^V$. Otrzymamy w ten sposób $h$ wektorów, konkatenujemy je po prostu i mnożymy\n",
|
||||
"przez dodatkową wyuczalną macierz $W^O$:\n",
|
||||
"\n",
|
||||
"$$\\operatorname{MultiHead}(Q, K, V) = [\\operatorname{head}_1,...,\\operatorname{head}_n]W^O.$$\n",
|
||||
"\n",
|
||||
"Przyjmujemy, że $d_k = d_v = m/h$, wtedy rozmiary macierzy $W_i^Q$ i $W_i^K$ będą wynosiły\n",
|
||||
"$m \\times d_k$, macierzy $W_i^V$ — $m \\times d_v$, $W^O$ — $hd_v \\times m$.\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.4"
|
||||
},
|
||||
"org": null
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 1
|
||||
}
|
@ -15,7 +15,7 @@ stopniu, mniej lub bardziej.
|
||||
|
||||
W najprostszym ujęciu wartości są tym samym co klucze, czyli z naszej
|
||||
bazy wydobywamy te same zanurzenia słów, których używamy jako kluczy.
|
||||
Można jednak skomplikować schemat rozróżniając klucze i wartości —
|
||||
Można jednak skomplikować schemat, rozróżniając klucze i wartości —
|
||||
mogą one powstawać przez rzutowanie podstawowych zanurzeń różnymi
|
||||
macierzami:
|
||||
|
||||
@ -55,7 +55,7 @@ wartości ($\vec{v_i}$) ważoną atencją:
|
||||
|
||||
$$A(w_1,\dots,j-1) = \alpha_{1,j} \vec{v_1} + \dots + \alpha_{j-1,j} \vec{v_{j-1}} = \sum_{i=1}^{j-1} \alpha_{i,j} v_i.$$
|
||||
|
||||
Jeśli $j-1$ wektorów wartości ułożyłem w macierz $V$ (o rozmiarze
|
||||
Jeśli $j-1$ wektorów wartości ułożymy w macierz $V$ (o rozmiarze
|
||||
$(j-1) \times d_v$), powyższy wzór będziemy mogli zapisać jako iloczyn wektora wag atencji i macierzy $V$:
|
||||
|
||||
$$A(w_1,\dots,j-1) = \vec{\alpha}_{*,j}^T V = \operatorname{softmax}(\vec{q_j}^T K)^T V.$$
|
||||
@ -87,35 +87,33 @@ języka jest używany wprost jako generator.
|
||||
|
||||
Dokonajmy najpierw tokenizacji:
|
||||
|
||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained("gpt2")
|
||||
text = "The World War III will begin in"
|
||||
text = "The World War III will begin in 2028 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]])}
|
||||
{'input_ids': tensor([[ 464, 2159, 1810, 6711, 481, 2221, 287, 1160, 2078, 287]]), 'attention_mask': tensor([[1, 1, 1, 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]]
|
||||
#+BEGIN_SRC ipython :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']
|
||||
['The', ' World', ' War', ' III', ' will', ' begin', ' in', ' 20', '28', ' 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
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
from transformers import AutoModelForCausalLM
|
||||
model = AutoModelForCausalLM.from_pretrained("gpt2")
|
||||
outputs = model(**encoded_input)
|
||||
@ -125,10 +123,19 @@ Teraz uruchommy zasadniczy model:
|
||||
:results:
|
||||
:end:
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
softmax(outputs[0][0][-1])
|
||||
#+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
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
logits = outputs[0][0][-1]
|
||||
logits
|
||||
#+END_SRC
|
||||
@ -139,15 +146,15 @@ tensor([-130.2947, -129.5677, -136.4030, ..., -138.3791, -138.8967,
|
||||
-131.6319], grad_fn=<SelectBackward0>)
|
||||
:end:
|
||||
|
||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
||||
from torch import softmax, topk
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
from torch import softmax, topk
|
||||
|
||||
k = 20
|
||||
k = 20
|
||||
|
||||
t = topk(softmax(logits, -1), k)
|
||||
t = topk(softmax(logits, -1), k)
|
||||
|
||||
tb = [[tokenizer.decode(t.indices[ix]), t.values[ix].item()] for ix in range(k)]
|
||||
tb
|
||||
tb = [[tokenizer.decode(t.indices[ix]), t.values[ix].item()] for ix in range(k)]
|
||||
tb
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
@ -157,7 +164,7 @@ tensor([-130.2947, -129.5677, -136.4030, ..., -138.3791, -138.8967,
|
||||
|
||||
*** Generowanie tekstu za pomocą GPT-2
|
||||
|
||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
||||
#+BEGIN_SRC ipython :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)
|
||||
@ -187,7 +194,7 @@ $$\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
|
||||
miałoby zawsze taką samą wagę! — token $w_j$, który atenduje jest
|
||||
zawsze ten sam (~<mask>~). Musimy wzbogacić reprezentację wektorową
|
||||
słów i specjalnego tokenu (~<mask>~).
|
||||
|
||||
@ -212,8 +219,8 @@ Na przykład słowo /mysz/ ma jedno wejściowe (/statyczne/) zanurzenie
|
||||
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
|
||||
Rozwiązanie polega na tym, że wszystkim tokenom będziemy przypisywać kolejne
|
||||
zanurzenia skontekstualizowane — zależne od innych tokenó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 (!).
|
||||
@ -222,11 +229,11 @@ również do samego siebie (!).
|
||||
|
||||
Rozpatrywać zatem będziemy nie tylko pojedynczy wektor znormalizowanych atencji
|
||||
|
||||
$$\vec{\alpha}_{*,j}^T V = \operatorname{softmax}(\vec{q_j}^T K)^T V$$,
|
||||
$$\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}$$
|
||||
$$\vec{\alpha}_{*,1},\dots,\vec{\alpha}_{*,i},\dots,\vec{\alpha}_{*,j},$$
|
||||
|
||||
gdzie:
|
||||
|
||||
@ -244,7 +251,7 @@ 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$.
|
||||
- $V$ — macierz wartości o rozmiarze $j \times d_v$.
|
||||
|
||||
**** Zwarta postać macierzowa atencji wsobnej
|
||||
|
||||
@ -261,18 +268,18 @@ gdzie $Q$ to macierz wszystkich zapytań o rozmiarze $j \times d_k$ (wektory uł
|
||||
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,$$
|
||||
$$\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}_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
|
||||
$W_i^Q$, $W_i^K$, $W_i^V$. Otrzymamy w ten sposób $h$ wektorów, konkatenujemy je po prostu i mnożymy
|
||||
przez dodatkową wyuczalną macierz $W^O$:
|
||||
|
||||
$$\operatorname{MultiHead}(Q, K, V) = [\operatorname{head}_1,...,\operatorname{head}_n]W^O.$$
|
||||
|
481
wyk/15_Pozycyjne_zanurzenia.ipynb
Normal file
481
wyk/15_Pozycyjne_zanurzenia.ipynb
Normal file
@ -0,0 +1,481 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Pozycyjne zanurzenia\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Atencja nie uwzględnia kolejności wyrazów\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"W przeciwieństwie do sieci rekurencyjnych sama atencja nie ma\n",
|
||||
"naturalnego, wbudowanego pojęcia kolejności, porządku czy „strzałki\n",
|
||||
"czasu”. Oznacza to, że sieci Transformer w postaci przedstawionej do\n",
|
||||
"tej pory właściwie operowałyby na tekście jak na worku słów (dowolna\n",
|
||||
"permutacja tekstu dawałaby identyczny wynik.\n",
|
||||
"\n",
|
||||
"Oznacza to, że pozycje wyrazów (tokenów) muszą być w jakiś sposób,\n",
|
||||
"celowo „wstrzyknięte” do sieci Transformer. Standardowa procedura\n",
|
||||
"polega na uzupełnieniu zanurzeń do element pozycyjny. Taki element\n",
|
||||
"sieci neuronowej nazywamy **zanurzeniami (embeddingami) pozycyjnymi**\n",
|
||||
"(*position(al) embeddings, encodings*).\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Rodzaje zanurzeń pozycyjnych\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Opracowano kilka różnych typów embeddingów pozycyjnych, najczęściej stosowane:\n",
|
||||
"\n",
|
||||
"- właściwe zanurzenia pozycyjne zależne wprost od bezwzględnej pozycji tokena\n",
|
||||
" w zdaniu\n",
|
||||
" - wyuczalne embeddingi pozycyjne\n",
|
||||
" - embeddingi sinusoidalne\n",
|
||||
"- zanurzenia względne,\n",
|
||||
"- zanurzenia obrotowe.\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Zanurzenia zależne od bezwzględnej pozycji tokena\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Najprostszy sposób uwzględnienia pozycji tokena polega na\n",
|
||||
"zdefiniowaniu pewnej funkcji $E_p(i) \\colon \\mathcal{N} \\rightarrow\n",
|
||||
"\\mathcal{R}^m$, to jest zanurzenia zależnego tylko od pozycji tokenu\n",
|
||||
"$i$.\n",
|
||||
"\n",
|
||||
"Następnie takie zanurzenie $E^p$ jest po prostu dodawane do standardowego\n",
|
||||
"embeddingu „semantycznego” $E^s$, by ostatecznie otrzymać embeddingu\n",
|
||||
"tokenu na konkretnej pozycji:\n",
|
||||
"\n",
|
||||
"$$E(w_i) = E^p(i) + E^s(w_i).$$\n",
|
||||
"\n",
|
||||
"Rzecz jasna rozmiar embeddingu pozycyjnego musi być identyczny jak\n",
|
||||
"rozmiar zwykłego embeddingu ($m$).\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Wyuczalne embeddingi pozycyjne\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Najprostszy sposób definiowania funkcji $E_p$ to po prostu przypisanie\n",
|
||||
"każdej pozycji (siłą rzeczy ze skończonego zbioru\n",
|
||||
"$\\{1,\\dots,p_{\\operatorname{max}}\\}$) osobnego wyuczalnego wektora.\n",
|
||||
"Przypomina to zwykły embedding wyuczalny dla poszczególnych słów:\n",
|
||||
"podobnie jak słowa *a*, *aby*, *abrakadabra*,…,/żyzny/ mają swoje\n",
|
||||
"embeddingi, tak i pozycje będą miały swoje embeddingi (pozycja 1 ma\n",
|
||||
"swój embedding, pozycja 2 — inny, itd., oczywiście nie należy tego\n",
|
||||
"mylić z embeddingami tokenów złożonych z cyfr *1*, *2*, itd.).\n",
|
||||
"\n",
|
||||
"Zaletą tego podejścia jest prostota, wady to:\n",
|
||||
"\n",
|
||||
"- nie generalizuje się na sekwencje dowolnej długości\n",
|
||||
" - jeśli zdarzy się zdanie dłuższe niż $p_{\\operatorname{max}}\\}$,\n",
|
||||
" embeddingu po prostu… nie ma\n",
|
||||
" - … wprawdzie można po prostu zdefiniować cyklicznie embeddingi\n",
|
||||
" $E<sup>p</sup><sub>p{\\operatorname{max}}+i</sub> = E<sup>p</sup>(i)\n",
|
||||
" - … ma to jednak swoje wady (na pozycji $p{\\operatorname{max}}+1$ sztucznie\n",
|
||||
" „wracamy” na początek tekstu,\n",
|
||||
"- sieć musi uczyć się osobno dla sąsiadujących pozycji, na przykład\n",
|
||||
" embedding pozycji 38 musi zostać wyuczony zupełnie niezależnie od pozycji 39,\n",
|
||||
"- … co jest szczególnie problematyczne dla pozycji o wyższych numerach pojawiających\n",
|
||||
" się rzadziej,\n",
|
||||
"- tego rodzaju embeddingów nie da się stosować relatywnie, a chcielibyśmy, żeby\n",
|
||||
" atencja z wyrazu na pozycji 14 kierowana na wyraz 12 była w pewnym stopniu podobna\n",
|
||||
" do atencji z wyrazu 27 kierowanej na wyraz 25.\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Embeddingi sinusoidalne\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Byłoby pożądane, gdyby za pomocą embeddingów pozycyjnych możliwe było wyrażenie pozycji wyrazów\n",
|
||||
"w różnych **skalach** — zarówno bezpośrednio poprzedzanie/następowanie, jak również\n",
|
||||
"relację typu „słowo X występuje około 8 słów wcześniej od słowa Y” (i kontrastowania z\n",
|
||||
"relacją typu „słowo X występuje około 15 słów wcześniej od słowa Y”).\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"##### Analogie z reprezentacją czasu\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Co ciekawe, istnieją analogie między sposobami, w jakie można reprezentować pozycji\n",
|
||||
"i technikami reprezentacji czasu, wypracowywanymi przez ludzkość (od setek lat!).\n",
|
||||
"\n",
|
||||
"Przede wszystkim, jesteśmy przyzwyczajeni do reprezentacji czasu, z\n",
|
||||
"natury ciągłego, za pomocą serii coraz dłuższych cykli (dotyczy to nie\n",
|
||||
"tylko kultury zachodniej, lecz także na przykład cywilizacji Majów!).\n",
|
||||
"\n",
|
||||
"Na przykład znacznik czasowy dla konkretnej chwili w czasie może mieć postać: `2022-06-13 09:11:23.12`,\n",
|
||||
"co można by reprezentować za pomocą 7-elementowego wektora:\n",
|
||||
"\n",
|
||||
"$$[2022,06,13,09,11,23,12].$$\n",
|
||||
"\n",
|
||||
"Dla spójności elementy wektora można by znormalizować względem czasu\n",
|
||||
"trwania danego cyklu, otrzymamy wówczas (załóżmy, że dla lat nadrzędnym cyklem są stulecia):\n",
|
||||
"\n",
|
||||
"$$[0.220,0.500,0.433,0.375,0.183,0.383,0.120]$$\n",
|
||||
"\n",
|
||||
"(np. dla godzin $9/24=0,375$).\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"##### Analogowa reprezentacja czasu\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Zauważmy, że powyższa reprezentacja jest „cyfrowa”, nieciągła. Na\n",
|
||||
"przykład w sylwestra 2022 roku pierwsza pozycja nagle „przeskoczy”\n",
|
||||
"na 2023. Matematycznie oznacza to nieciągłość, która nie jest pożądana\n",
|
||||
"z punktu widzenia procesu propagacji wstecznej. Lepiej gdyby wszystkie\n",
|
||||
"pozycje wektora zmieniają się w sposób ciągły, analogowy, jak\n",
|
||||
"wskazówki zegara! W zwykłym bowiem zegarze analogowym wszystkie\n",
|
||||
"wskazówki się cały czas obracają, jedne szybciej, drugie wolniej. Na\n",
|
||||
"przykład 30 czerwca 2022 roku nie oznaczałby tak naprawdę roku 2022,\n",
|
||||
"tylko 2022,5.\n",
|
||||
"\n",
|
||||
"A zatem właściwa reprezentacja wektorowa przykładowego momentu w czasie powinna mieć raczej postać:\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[0.22537174740226337, 0.5371747402263375, 0.4460968827160494, 0.3829064814814815, 0.18975555555555554, 0.38533333333333336, 0.12]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def to_analog(tv):\n",
|
||||
" cycles = [100,12,30,24,60,60,100]\n",
|
||||
" analog_tv = []\n",
|
||||
"\n",
|
||||
" prev = 0.0\n",
|
||||
"\n",
|
||||
" for ix in range(len(tv)-1, -1, -1):\n",
|
||||
" v = tv[ix]/cycles[ix] + prev * (1 / cycles[ix])\n",
|
||||
" analog_tv.append(v)\n",
|
||||
" prev = v\n",
|
||||
"\n",
|
||||
" analog_tv = analog_tv[-1::-1]\n",
|
||||
" return analog_tv\n",
|
||||
"\n",
|
||||
"tv = to_analog([22,6,13,9,11,23,12])\n",
|
||||
"tv"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"##### Dodawanie czasu\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Podstawowa operacja dotycząca czasu to przesunięcie momentu czasu o\n",
|
||||
"pewien okres (wprzód lub w tył), właściwie przypomina to zwykłe dodawanie czy odejmowanie,\n",
|
||||
"pojawia się jednak pewne trudność.\n",
|
||||
"Proste dodawanie wektorów nie zawsze da dobry wyniku, np.\n",
|
||||
"jeśli dodamy do naszego znacznika 20 godzin:\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[2.3148148148148147e-05, 0.0023148148148148147, 0.02777777777777778, 0.8333333333333334, 0.0, 0.0, 0.0]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"delta_v = to_analog([0,0,0,20,0,0,0])\n",
|
||||
"delta_v"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[0.22539489555041153, 0.5394895550411523, 0.4738746604938272, 1.2162398148148148, 0.18975555555555554, 0.38533333333333336, 0.12]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def vsum(a, b):\n",
|
||||
" return [ai+bi for ai, bi in zip(a, b)]\n",
|
||||
"\n",
|
||||
"vsum(tv, delta_v)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Problem polega na tym, że na czwartej pozycji pojawiła się wartość\n",
|
||||
"większa od 1. Powinniśmy zostawić tylko część ułamkową\n",
|
||||
"(0.2162398148148148). Możemy tak uczynić, ale wtedy oznacza to\n",
|
||||
"nieciągłość między 0.99 a 1.00 (czyli 0.00 bo obcięciu do części\n",
|
||||
"ułamkowej). Znowu powracamy do problemu nieciągłości po wypełnieniu\n",
|
||||
"cyklu (tak naprawdę „rozwiązaliśmy” go tylko dla najdłuższego cyklu).\n",
|
||||
"Gdybyśmy mogli tylko utożsamić wartość 1 z 0… podobnie jak $2\\pi$ z 0\n",
|
||||
"dla funkcji trygonometrycznych. Albo gdyby reprezentować czas\n",
|
||||
"dosłownie za pomocą wskazówek, które po wypełnieniu cyklu w ciągły\n",
|
||||
"sposób powracają do pozycji początkowej…\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"##### Czas reprezentowany przez wskazówki zegara\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Pozycja wskazówki jest reprezentowana w sposób jednoznaczny przez\n",
|
||||
"współrzędne końca, dla każdego cyklu możemy wprowadzić dwie wartości\n",
|
||||
"(znormalizowane) względem długości wskazówki. To znaczy, gdyby\n",
|
||||
"przyjąć, że długość wskazówki wynosi 1, wówczas współrzędne jej końca mają wartość:\n",
|
||||
"\n",
|
||||
"$$x = \\cos\\phi, y = \\sin\\phi,$$\n",
|
||||
"\n",
|
||||
"gdzie $phi$ jest kątem wskazówki.\n",
|
||||
"\n",
|
||||
"W ten sposób otrzymamy dwa razy dłuższy wektor:\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[0.9379890645739346, 0.34666484497236694, 0.6646342658032391, 0.7471688515457462, 0.7643734166510766, 0.6447738207442666, 0.8245057772081804, 0.5658535352459454, 0.9559058477167625, 0.2936733053937619, 0.8223427069797843, 0.5689925063453478, 0.9822872507286887, 0.1873813145857246]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import math\n",
|
||||
"\n",
|
||||
"def to_hand_coord(atv):\n",
|
||||
" out = []\n",
|
||||
" for v in atv:\n",
|
||||
" phi = v / 2*math.pi\n",
|
||||
" out.append(math.cos(phi))\n",
|
||||
" out.append(math.sin(phi))\n",
|
||||
"\n",
|
||||
" return out\n",
|
||||
"\n",
|
||||
"to_hand_coord(tv)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"##### Obrót jako mnożenie macierzy\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Okazuje się, że obrót w przestrzeni euklidesowej można wyrazić za pomocą mnożenia macierzy,\n",
|
||||
"na przykład, aby obliczyć współrzędne na płaszczyźnie po obrocie o 90 stopnia, wystarczy przemnożyć\n",
|
||||
"wektor współrzędnych przez macierz:\n",
|
||||
"\n",
|
||||
"\\begin{matrix}\n",
|
||||
"0 & -1 \\\\\n",
|
||||
"1 & 0 \\\\\n",
|
||||
"\\end{matrix}\n",
|
||||
"\n",
|
||||
"W ogólnym przypadku obrót o kąt $\\phi$ oznacza przemnożenie przez macierz:\n",
|
||||
"\n",
|
||||
"\\begin{matrix}\n",
|
||||
"\\cos\\phi & -\\sin\\phi \\\\\n",
|
||||
"\\sin\\phi & \\cos\\phi \\\\\n",
|
||||
"\\end{matrix}\n",
|
||||
"\n",
|
||||
"Jest to bardzo dobra wiadomość! Okazuje się, że „przemieszczanie” się\n",
|
||||
"w czasie można zrealizować za pomocą mnożenia macierzy — operacji, do\n",
|
||||
"której stworzono karty graficzne!\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"##### Reprezentacja pozycji tokenów\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Analogicznie do czasu, można reprezentować pozycję wyrazów. Zamiast naturalnych cykli\n",
|
||||
"lat, miesięcy, dni, itd. musimy tylko narzucić z góry cykle. Jeśli rozmiar\n",
|
||||
"embeddingu wynosi $m$, musimy wprowadzić $m/2$ cykli (dlaczego?).\n",
|
||||
"\n",
|
||||
"Można na przykład wprowadzić narastające geometrycznie cykle o\n",
|
||||
"długości od 1 do 10000 (10000 to arbitralnie wybrana liczba), tj.\n",
|
||||
"$k$-ty cykl będzie miał długość $10000^{2k/m}$, wtedy dla parzystej pozycji wektora zanurzeń:\n",
|
||||
"\n",
|
||||
"$$E_p(i)_2k = \\sin(\\frac{i}{10000^{2k/m}),$$\n",
|
||||
"\n",
|
||||
"dla nieparzystej zaś:\n",
|
||||
"\n",
|
||||
"$$E_p(i)_{2k+1} = \\sin(\\frac{i}{10000^{2k/m}),$$\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Zanurzenia względne\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Inne podejście polega na skupieniu się na tym, by Transformer był w\n",
|
||||
"stanie operować na **względnych** pozycjach. W tym podejściu informacja\n",
|
||||
"o pozycji nie jest dodawana do zanurzeń, lecz jest „wstrzykiwana” do\n",
|
||||
"atencji jako pozycyjne obciążenie (*positional bias*), tj. np. w modelu T5 model uczy\n",
|
||||
"się serii skalarnych obciążeń $b_{\\Delta}$, gdzie $Δ ∈\n",
|
||||
"\\\\{-Δ<sub>\\operatorname{max}</sub>,…,-1,0,1,…,Δ<sub>\\operatorname{max}</sub>\\\\}.\n",
|
||||
"\n",
|
||||
"Obciążenie te po prostu są dodawane do atencji:\n",
|
||||
"\n",
|
||||
"$$\\hat{\\alpha}_{i,j} = \\vec{q_i}^T\\vec{k_j} + b_{j-i}.$$\n",
|
||||
"\n",
|
||||
"Zalety takiego podejścia:\n",
|
||||
"\n",
|
||||
"- stosunkowo niski koszt obliczeniowy,\n",
|
||||
"- względne pozycje są uwzględniane nie tylko na początku obliczeń, lecz w każdej warstwie.\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.5"
|
||||
},
|
||||
"org": null
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 1
|
||||
}
|
266
wyk/15_Pozycyjne_zanurzenia.org
Normal file
266
wyk/15_Pozycyjne_zanurzenia.org
Normal file
@ -0,0 +1,266 @@
|
||||
* Pozycyjne zanurzenia
|
||||
|
||||
** Atencja nie uwzględnia kolejności wyrazów
|
||||
|
||||
W przeciwieństwie do sieci rekurencyjnych sama atencja nie ma
|
||||
naturalnego, wbudowanego pojęcia kolejności, porządku czy „strzałki
|
||||
czasu”. Oznacza to, że sieci Transformer w postaci przedstawionej do
|
||||
tej pory właściwie operowałyby na tekście jak na worku słów (dowolna
|
||||
permutacja tekstu dawałaby identyczny wynik.
|
||||
|
||||
Oznacza to, że pozycje wyrazów (tokenów) muszą być w jakiś sposób,
|
||||
celowo „wstrzyknięte” do sieci Transformer. Standardowa procedura
|
||||
polega na uzupełnieniu zanurzeń do element pozycyjny. Taki element
|
||||
sieci neuronowej nazywamy *zanurzeniami (embeddingami) pozycyjnymi*
|
||||
(/position(al) embeddings, encodings/).
|
||||
|
||||
** Rodzaje zanurzeń pozycyjnych
|
||||
|
||||
Opracowano kilka różnych typów embeddingów pozycyjnych, najczęściej stosowane:
|
||||
|
||||
- właściwe zanurzenia pozycyjne zależne wprost od bezwzględnej pozycji tokena
|
||||
w zdaniu
|
||||
- wyuczalne embeddingi pozycyjne
|
||||
- embeddingi sinusoidalne
|
||||
- zanurzenia względne,
|
||||
- zanurzenia obrotowe.
|
||||
|
||||
** Zanurzenia zależne od bezwzględnej pozycji tokena
|
||||
|
||||
Najprostszy sposób uwzględnienia pozycji tokena polega na
|
||||
zdefiniowaniu pewnej funkcji $E_p(i) \colon \mathcal{N} \rightarrow
|
||||
\mathcal{R}^m$, to jest zanurzenia zależnego tylko od pozycji tokenu
|
||||
$i$.
|
||||
|
||||
Następnie takie zanurzenie $E^p$ jest po prostu dodawane do standardowego
|
||||
embeddingu „semantycznego” $E^s$, by ostatecznie otrzymać embeddingu
|
||||
tokenu na konkretnej pozycji:
|
||||
|
||||
$$E(w_i) = E^p(i) + E^s(w_i).$$
|
||||
|
||||
Rzecz jasna rozmiar embeddingu pozycyjnego musi być identyczny jak
|
||||
rozmiar zwykłego embeddingu ($m$).
|
||||
|
||||
*** Wyuczalne embeddingi pozycyjne
|
||||
|
||||
Najprostszy sposób definiowania funkcji $E_p$ to po prostu przypisanie
|
||||
każdej pozycji (siłą rzeczy ze skończonego zbioru
|
||||
$\{1,\dots,p_{\operatorname{max}}\}$) osobnego wyuczalnego wektora.
|
||||
Przypomina to zwykły embedding wyuczalny dla poszczególnych słów:
|
||||
podobnie jak słowa /a/, /aby/, /abrakadabra/,…,/żyzny/ mają swoje
|
||||
embeddingi, tak i pozycje będą miały swoje embeddingi (pozycja 1 ma
|
||||
swój embedding, pozycja 2 — inny, itd., oczywiście nie należy tego
|
||||
mylić z embeddingami tokenów złożonych z cyfr /1/, /2/, itd.).
|
||||
|
||||
Zaletą tego podejścia jest prostota, wady to:
|
||||
|
||||
- nie generalizuje się na sekwencje dowolnej długości
|
||||
- jeśli zdarzy się zdanie dłuższe niż $p_{\operatorname{max}}\}$,
|
||||
embeddingu po prostu… nie ma
|
||||
- … wprawdzie można po prostu zdefiniować cyklicznie embeddingi
|
||||
$E^p_{p{\operatorname{max}}+i} = E^p(i)
|
||||
- … ma to jednak swoje wady (na pozycji $p{\operatorname{max}}+1$ sztucznie
|
||||
„wracamy” na początek tekstu,
|
||||
- sieć musi uczyć się osobno dla sąsiadujących pozycji, na przykład
|
||||
embedding pozycji 38 musi zostać wyuczony zupełnie niezależnie od pozycji 39,
|
||||
- … co jest szczególnie problematyczne dla pozycji o wyższych numerach pojawiających
|
||||
się rzadziej,
|
||||
- tego rodzaju embeddingów nie da się stosować relatywnie, a chcielibyśmy, żeby
|
||||
atencja z wyrazu na pozycji 14 kierowana na wyraz 12 była w pewnym stopniu podobna
|
||||
do atencji z wyrazu 27 kierowanej na wyraz 25.
|
||||
|
||||
*** Embeddingi sinusoidalne
|
||||
|
||||
Byłoby pożądane, gdyby za pomocą embeddingów pozycyjnych możliwe było wyrażenie pozycji wyrazów
|
||||
w różnych *skalach* — zarówno bezpośrednio poprzedzanie/następowanie, jak również
|
||||
relację typu „słowo X występuje około 8 słów wcześniej od słowa Y” (i kontrastowania z
|
||||
relacją typu „słowo X występuje około 15 słów wcześniej od słowa Y”).
|
||||
|
||||
**** Analogie z reprezentacją czasu
|
||||
|
||||
Co ciekawe, istnieją analogie między sposobami, w jakie można reprezentować pozycji
|
||||
i technikami reprezentacji czasu, wypracowywanymi przez ludzkość (od setek lat!).
|
||||
|
||||
Przede wszystkim, jesteśmy przyzwyczajeni do reprezentacji czasu, z
|
||||
natury ciągłego, za pomocą serii coraz dłuższych cykli (dotyczy to nie
|
||||
tylko kultury zachodniej, lecz także na przykład cywilizacji Majów!).
|
||||
|
||||
Na przykład znacznik czasowy dla konkretnej chwili w czasie może mieć postać: ~2022-06-13 09:11:23.12~,
|
||||
co można by reprezentować za pomocą 7-elementowego wektora:
|
||||
|
||||
$$[2022,06,13,09,11,23,12].$$
|
||||
|
||||
Dla spójności elementy wektora można by znormalizować względem czasu
|
||||
trwania danego cyklu, otrzymamy wówczas (załóżmy, że dla lat nadrzędnym cyklem są stulecia):
|
||||
|
||||
$$[0.220,0.500,0.433,0.375,0.183,0.383,0.120]$$
|
||||
|
||||
(np. dla godzin $9/24=0,375$).
|
||||
|
||||
**** Analogowa reprezentacja czasu
|
||||
|
||||
Zauważmy, że powyższa reprezentacja jest „cyfrowa”, nieciągła. Na
|
||||
przykład w sylwestra 2022 roku pierwsza pozycja nagle „przeskoczy”
|
||||
na 2023. Matematycznie oznacza to nieciągłość, która nie jest pożądana
|
||||
z punktu widzenia procesu propagacji wstecznej. Lepiej gdyby wszystkie
|
||||
pozycje wektora zmieniają się w sposób ciągły, analogowy, jak
|
||||
wskazówki zegara! W zwykłym bowiem zegarze analogowym wszystkie
|
||||
wskazówki się cały czas obracają, jedne szybciej, drugie wolniej. Na
|
||||
przykład 30 czerwca 2022 roku nie oznaczałby tak naprawdę roku 2022,
|
||||
tylko 2022,5.
|
||||
|
||||
A zatem właściwa reprezentacja wektorowa przykładowego momentu w czasie powinna mieć raczej postać:
|
||||
|
||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
||||
def to_analog(tv):
|
||||
cycles = [100,12,30,24,60,60,100]
|
||||
analog_tv = []
|
||||
|
||||
prev = 0.0
|
||||
|
||||
for ix in range(len(tv)-1, -1, -1):
|
||||
v = tv[ix]/cycles[ix] + prev * (1 / cycles[ix])
|
||||
analog_tv.append(v)
|
||||
prev = v
|
||||
|
||||
analog_tv = analog_tv[-1::-1]
|
||||
return analog_tv
|
||||
|
||||
tv = to_analog([22,6,13,9,11,23,12])
|
||||
tv
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
[0.22537174740226337, 0.5371747402263375, 0.4460968827160494, 0.3829064814814815, 0.18975555555555554, 0.38533333333333336, 0.12]
|
||||
:end:
|
||||
|
||||
**** Dodawanie czasu
|
||||
|
||||
Podstawowa operacja dotycząca czasu to przesunięcie momentu czasu o
|
||||
pewien okres (wprzód lub w tył), właściwie przypomina to zwykłe dodawanie czy odejmowanie,
|
||||
pojawia się jednak pewne trudność.
|
||||
Proste dodawanie wektorów nie zawsze da dobry wyniku, np.
|
||||
jeśli dodamy do naszego znacznika 20 godzin:
|
||||
|
||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
||||
delta_v = to_analog([0,0,0,20,0,0,0])
|
||||
delta_v
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
[2.3148148148148147e-05, 0.0023148148148148147, 0.02777777777777778, 0.8333333333333334, 0.0, 0.0, 0.0]
|
||||
:end:
|
||||
|
||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
||||
def vsum(a, b):
|
||||
return [ai+bi for ai, bi in zip(a, b)]
|
||||
|
||||
vsum(tv, delta_v)
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
[0.22539489555041153, 0.5394895550411523, 0.4738746604938272, 1.2162398148148148, 0.18975555555555554, 0.38533333333333336, 0.12]
|
||||
:end:
|
||||
|
||||
Problem polega na tym, że na czwartej pozycji pojawiła się wartość
|
||||
większa od 1. Powinniśmy zostawić tylko część ułamkową
|
||||
(0.2162398148148148). Możemy tak uczynić, ale wtedy oznacza to
|
||||
nieciągłość między 0.99 a 1.00 (czyli 0.00 bo obcięciu do części
|
||||
ułamkowej). Znowu powracamy do problemu nieciągłości po wypełnieniu
|
||||
cyklu (tak naprawdę „rozwiązaliśmy” go tylko dla najdłuższego cyklu).
|
||||
Gdybyśmy mogli tylko utożsamić wartość 1 z 0… podobnie jak $2\pi$ z 0
|
||||
dla funkcji trygonometrycznych. Albo gdyby reprezentować czas
|
||||
dosłownie za pomocą wskazówek, które po wypełnieniu cyklu w ciągły
|
||||
sposób powracają do pozycji początkowej…
|
||||
|
||||
**** Czas reprezentowany przez wskazówki zegara
|
||||
|
||||
Pozycja wskazówki jest reprezentowana w sposób jednoznaczny przez
|
||||
współrzędne końca, dla każdego cyklu możemy wprowadzić dwie wartości
|
||||
(znormalizowane) względem długości wskazówki. To znaczy, gdyby
|
||||
przyjąć, że długość wskazówki wynosi 1, wówczas współrzędne jej końca mają wartość:
|
||||
|
||||
$$x = \cos\phi, y = \sin\phi,$$
|
||||
|
||||
gdzie $phi$ jest kątem wskazówki.
|
||||
|
||||
W ten sposób otrzymamy dwa razy dłuższy wektor:
|
||||
|
||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
||||
import math
|
||||
|
||||
def to_hand_coord(atv):
|
||||
out = []
|
||||
for v in atv:
|
||||
phi = v / 2*math.pi
|
||||
out.append(math.cos(phi))
|
||||
out.append(math.sin(phi))
|
||||
|
||||
return out
|
||||
|
||||
to_hand_coord(tv)
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
[0.9379890645739346, 0.34666484497236694, 0.6646342658032391, 0.7471688515457462, 0.7643734166510766, 0.6447738207442666, 0.8245057772081804, 0.5658535352459454, 0.9559058477167625, 0.2936733053937619, 0.8223427069797843, 0.5689925063453478, 0.9822872507286887, 0.1873813145857246]
|
||||
:end:
|
||||
|
||||
**** Obrót jako mnożenie macierzy
|
||||
|
||||
Okazuje się, że obrót w przestrzeni euklidesowej można wyrazić za pomocą mnożenia macierzy,
|
||||
na przykład, aby obliczyć współrzędne na płaszczyźnie po obrocie o 90 stopnia, wystarczy przemnożyć
|
||||
wektor współrzędnych przez macierz:
|
||||
|
||||
\begin{matrix}
|
||||
0 & -1 \\
|
||||
1 & 0 \\
|
||||
\end{matrix}
|
||||
|
||||
W ogólnym przypadku obrót o kąt $\phi$ oznacza przemnożenie przez macierz:
|
||||
|
||||
\begin{matrix}
|
||||
\cos\phi & -\sin\phi \\
|
||||
\sin\phi & \cos\phi \\
|
||||
\end{matrix}
|
||||
|
||||
Jest to bardzo dobra wiadomość! Okazuje się, że „przemieszczanie” się
|
||||
w czasie można zrealizować za pomocą mnożenia macierzy — operacji, do
|
||||
której stworzono karty graficzne!
|
||||
|
||||
**** Reprezentacja pozycji tokenów
|
||||
|
||||
Analogicznie do czasu, można reprezentować pozycję wyrazów. Zamiast naturalnych cykli
|
||||
lat, miesięcy, dni, itd. musimy tylko narzucić z góry cykle. Jeśli rozmiar
|
||||
embeddingu wynosi $m$, musimy wprowadzić $m/2$ cykli (dlaczego?).
|
||||
|
||||
Można na przykład wprowadzić narastające geometrycznie cykle o
|
||||
długości od 1 do 10000 (10000 to arbitralnie wybrana liczba), tj.
|
||||
$k$-ty cykl będzie miał długość $10000^{2k/m}$, wtedy dla parzystej pozycji wektora zanurzeń:
|
||||
|
||||
$$E_p(i)_2k = \sin(\frac{i}{10000^{2k/m}),$$
|
||||
|
||||
dla nieparzystej zaś:
|
||||
|
||||
$$E_p(i)_{2k+1} = \sin(\frac{i}{10000^{2k/m}),$$
|
||||
|
||||
*** Zanurzenia względne
|
||||
|
||||
Inne podejście polega na skupieniu się na tym, by Transformer był w
|
||||
stanie operować na *względnych* pozycjach. W tym podejściu informacja
|
||||
o pozycji nie jest dodawana do zanurzeń, lecz jest „wstrzykiwana” do
|
||||
atencji jako pozycyjne obciążenie (/positional bias/), tj. np. w modelu T5 model uczy
|
||||
się serii skalarnych obciążeń $b_{\Delta}$, gdzie $\Delta \in
|
||||
\{-\Delta_{\operatorname{max}},\dots,-1,0,1,\dots,\Delta_{\operatorname{max}}\}.
|
||||
|
||||
Obciążenie te po prostu są dodawane do atencji:
|
||||
|
||||
$$\hat{\alpha}_{i,j} = \vec{q_i}^T\vec{k_j} + b_{j-i}.$$
|
||||
|
||||
Zalety takiego podejścia:
|
||||
|
||||
- stosunkowo niski koszt obliczeniowy,
|
||||
- względne pozycje są uwzględniane nie tylko na początku obliczeń, lecz w każdej warstwie.
|
@ -8,7 +8,7 @@
|
||||
"![Logo 1](https://git.wmi.amu.edu.pl/AITech/Szablon/raw/branch/master/Logotyp_AITech1.jpg)\n",
|
||||
"<div class=\"alert alert-block alert-info\">\n",
|
||||
"<h1> Modelowanie języka</h1>\n",
|
||||
"<h2> 10. <i>Atencja</i> [wykład]</h2> \n",
|
||||
"<h2> 11. <i>Transformer</i> [wykład]</h2> \n",
|
||||
"<h3> Filip Graliński (2022)</h3>\n",
|
||||
"</div>\n",
|
||||
"\n",
|
||||
|
Loading…
Reference in New Issue
Block a user