diff --git a/wyk/11_Transformer.ipynb b/wyk/11_Transformer.ipynb
new file mode 100644
index 0000000..b5e0f8a
--- /dev/null
+++ b/wyk/11_Transformer.ipynb
@@ -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",
+ "
\n",
+ "
Modelowanie języka
\n",
+ " 11. Transformer [wykład]
\n",
+ " Filip Graliński (2022)
\n",
+ "\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=)"
+ ]
+ }
+ ],
+ "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 ``. 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 (``). Musimy wzbogacić reprezentację wektorową\n",
+ " słów i specjalnego tokenu (``).\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
+}
diff --git a/wyk/11_Transformer.org b/wyk/11_Transformer.org
index 3a41ea9..bd9be02 100644
--- a/wyk/11_Transformer.org
+++ b/wyk/11_Transformer.org
@@ -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=)
: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 (~~). Musimy wzbogacić reprezentację wektorową
słów i specjalnego tokenu (~~).
@@ -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.$$
diff --git a/wyk/15_Pozycyjne_zanurzenia.ipynb b/wyk/15_Pozycyjne_zanurzenia.ipynb
new file mode 100644
index 0000000..50dda59
--- /dev/null
+++ b/wyk/15_Pozycyjne_zanurzenia.ipynb
@@ -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",
+ " $Epp{\\operatorname{max}}+i = Ep(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",
+ "\\\\{-Δ\\operatorname{max},…,-1,0,1,…,Δ\\operatorname{max}\\\\}.\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
+}
diff --git a/wyk/15_Pozycyjne_zanurzenia.org b/wyk/15_Pozycyjne_zanurzenia.org
new file mode 100644
index 0000000..43bda8d
--- /dev/null
+++ b/wyk/15_Pozycyjne_zanurzenia.org
@@ -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.
diff --git a/wyk/helpers/intro b/wyk/helpers/intro
index 6da9943..7d82176 100644
--- a/wyk/helpers/intro
+++ b/wyk/helpers/intro
@@ -8,7 +8,7 @@
"![Logo 1](https://git.wmi.amu.edu.pl/AITech/Szablon/raw/branch/master/Logotyp_AITech1.jpg)\n",
"\n",
"
Modelowanie języka
\n",
- " 10. Atencja [wykład]
\n",
+ " 11. Transformer [wykład]
\n",
" Filip Graliński (2022)
\n",
"\n",
"\n",