From 7c270a9360ecfc0b6d0725cb5e47b7c5a6d96967 Mon Sep 17 00:00:00 2001 From: Filip Gralinski Date: Tue, 5 Jul 2022 10:40:46 +0200 Subject: [PATCH] Up --- wyk/15_Pozycyjne_zanurzenia.ipynb | 481 ++++++++++++++++++ ...rzenia.org => 15_Pozycyjne_zanurzenia.org} | 0 2 files changed, 481 insertions(+) create mode 100644 wyk/15_Pozycyjne_zanurzenia.ipynb rename wyk/{12_Pozycyjne_zanurzenia.org => 15_Pozycyjne_zanurzenia.org} (100%) 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/12_Pozycyjne_zanurzenia.org b/wyk/15_Pozycyjne_zanurzenia.org similarity index 100% rename from wyk/12_Pozycyjne_zanurzenia.org rename to wyk/15_Pozycyjne_zanurzenia.org