Up
This commit is contained in:
parent
d008173ce2
commit
7c270a9360
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
|
||||
}
|
Loading…
Reference in New Issue
Block a user