497 lines
16 KiB
Plaintext
497 lines
16 KiB
Plaintext
{
|
|
"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> 15. <i>Sieci Transformer — pozycyjne zanurzenia</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": [
|
|
"## 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ń o 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 tokena\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",
|
|
"tokena 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 z\n",
|
|
"rozmiarem 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^p_{p{\\operatorname{max}}+i} = E^p(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 pewna 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ążenia 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
|
|
}
|