diff --git a/wyk/09_neurozoo.org b/wyk/09_neurozoo.org index d95c66c..cc72bb5 100644 --- a/wyk/09_neurozoo.org +++ b/wyk/09_neurozoo.org @@ -375,13 +375,13 @@ Definicja w PyTorchu: z_plus = torch.exp(z) return z_plus / torch.sum(z_plus) - softmax(torch.tensor([3., -1., 0., 5.])) + softmax(torch.tensor([3., 1., -1., 1.])) #+END_SRC #+RESULTS: :results: -# Out[75]: -: tensor([0.1182, 0.0022, 0.0059, 0.8737]) +# Out[3]: +: tensor([7.8678e-01, 1.0648e-01, 2.6393e-04, 1.0648e-01]) :end: #+CAPTION: Softmax diff --git a/wyk/11_rnn.ipynb b/wyk/11_rnn.ipynb index aedf48a..89ec808 100644 --- a/wyk/11_rnn.ipynb +++ b/wyk/11_rnn.ipynb @@ -1 +1,522 @@ -{"cells":[{"cell_type":"markdown","metadata":{},"source":["## Rekurencyjne sieci neuronowe\n\n"]},{"cell_type":"markdown","metadata":{},"source":["### Inne spojrzenie na sieci przedstawione do tej pory\n\n"]},{"cell_type":"markdown","metadata":{},"source":["#### Regresja liniowa/logistyczna lub klasyfikacja wieloklasowa na całym tekście\n\n"]},{"cell_type":"markdown","metadata":{},"source":["W regresji liniowej czy logistycznej bądź w klasyfikacji wieloklasowej\n(z funkcją Softmax) stosowaliśmy następujący schemat:\n\nDo tej pory patrzyliśmy na to tak, że po prostu cały tekst jest od\nrazu przetwarzany przez (prostą) sieć neuronową, popatrzmy na ten\nprzypadek, jak na sytuację przetwarzania sekwencyjnego. Będzie to\ntrochę sztuczne, ale uogólnimy to potem w sensowny sposób.\n\n"]},{"cell_type":"markdown","metadata":{},"source":["##### Wektoryzacja\n\n"]},{"cell_type":"markdown","metadata":{},"source":["Po pierwsze, zauważmy, że w wielu schematach wektoryzacji (np. tf), wektor\ndokumentów jest po prostu sumą wektorów poszczególnych składowych:\n\n$$\\vec{v}(d) = \\vec{v}(t^1,\\ldots,t^K) = \\vec{v}(t^1) + \\ldots + \\vec{v}(t^K) = \\sum_{k=1}^K \\vec{v}(t^i),$$\n\ngdzie w schemacie tf \\vec{v}(ti) to po prostu wektor *one-hot* dla słowa.\n\n**Pytanie** Jak postać przyjmie w \\vec{v}(ti) dla wektoryzacji tf-idf?\n\nWektory $\\vec{v}(t^k)$ mogą być również gęstymi wektorami\n($\\vec{v}(t^k) \\in \\mathcal{R}^n$, gdzie $n$ jest rzędu 10-1000), np.\nw modelu Word2vec albo mogą to być **wyuczalne** wektory (zanurzenia\nsłów, *embeddings*), tzn. wektory, które są parametrami uczonej sieci!\n\n**Pytanie** Ile wag (parametrów) wnoszą wyuczalne wektory do sieci?\n\n"]},{"cell_type":"markdown","metadata":{},"source":["##### Prosta wektoryzacja wyrażona w modelu sekwencyjnym\n\n"]},{"cell_type":"markdown","metadata":{},"source":["Jak zapisać równoważnie powyższą wektoryzację w modelu **sekwencyjnym**, tj. przy założeniu, że\nprzetwarzamy wejście token po tokenie (a nie „naraz”)? Ogólnie wprowadzimy bardzo\nogólny model sieci **rekurencyjnej**.\n\nPo pierwsze zakładamy, że sieć ma pewien stan $\\vec{s^k} \\in\n\\mathcal{R}^m$ (stan jest wektorem o długości $m$), który może\nzmieniać się z każdym krokiem (przetwarzanym tokenem). Zmiana stanu\njest określona przez pewną funkcję $R : \\mathcal{R}^m \\times\n\\mathcal{R}^n \\rightarrow \\mathcal{R}^m$ ($n$ to rozmiar wektorów\n$\\vec{v}(t^k)$):\n\n$$\\vec{s^k} = R(\\vec{s^{k-1}}, \\vec{v}(t^k)).$$\n\nW przypadku wektoryzacji tf-idf mamy do czynienia z prostym\nsumowaniem, więc $R$ przyjmuje bardzo prostą postać:\n\n$$\\vec{s^0} = [0,\\dots,0],$$\n\n$$R(\\vec{s}, \\vec{x}) = \\vec{s} + \\vec{x}.$$\n\n"]},{"cell_type":"markdown","metadata":{},"source":["##### Wyjście z modelu\n\n"]},{"cell_type":"markdown","metadata":{},"source":["Dla regresji liniowej/logistycznej, oprócz funkcji $R$, która określa\nzmianę stanu, potrzebujemy funkcji $O$, która określa wyjście systemu w każdym kroku.\n\n$$y^k = O(\\vec{s^k})$$\n\nW zadaniach klasyfikacji czy regresji, kiedy patrzymy na cały tekst w\nzasadzie wystarczy wziąć *ostatnią* wartość (tj. $y^K$). Można sobie\nwyobrazić sytuację, kiedy wartości $y^k$ dla $k < k$ również mogą być jakoś przydatne\n(np. klasyfikujemy na bieżąco tekst wpisywany przez użytkownika).\n\nW każdym razie dla regresji liniowej funkcja $O$ przyjmie postać:\n\n$$O(\\vec{s}) = \\vec{w}\\vec{s}$$,\n\ngdzie $\\vec{w}$ jest wektorem wyuczylnych wag, dla regresji zaś logistycznej:\n\n$$O(\\vec{s}) = \\operatorname{softmax}(\\vec{w}\\vec{s})$$\n\n**Pytanie**: jaką postać przyjmie $O$ dla klasyfikacji wieloklasowej\n\n"]},{"cell_type":"markdown","metadata":{},"source":["### Prosta sieć rekurencyjna\n\n"]},{"cell_type":"markdown","metadata":{},"source":["W najprostszej sieci rekurencyjnej (*Vanilla RNN*, sieć Elmana,\nczasami po prostu RNN) w każdym kroku oprócz właściwego wejścia\n($\\vec{v}(t^k)$) będziemy również podawać na wejściu poprzedni stan\nsieci ($\\vec{s^{k-1}}$).\n\nInnymi słowy, funkcje $R$ przyjmie następującą postać:\n\n$$s^k = \\sigma(W\\langle\\vec{v}(t^k), \\vec{s^{k-1}}\\rangle + \\vec{b}),$$\n\ngdzie:\n\n- $\\langle\\vec{x},\\vec{y}\\rangle$ to konkatenacja dwóch wektorów,\n- $W ∈ \\mathcal{R}m × \\mathcal{R}n+m $ — macierz wag,\n- $b \\in \\mathcal{R}^m$ — wektor obciążeń (*biases*).\n\nTaką sieć RNN można przedstawić schematycznie w następujący sposób:\n\n![img](./img-rnn.png)\n\nZauważmy, że zamiast macierzy $W$ działającej na konkatenacji wektorów można wprowadzić dwie\nmacierze $U$ i $V$ i tak zapisać wzór:\n\n$$s^k = \\sigma(U\\vec{v}(t^k) + V\\vec{s^{k-1}} + \\vec{b}).$$\n\nJeszcze inne spojrzenie na sieć RNN:\n\n![img](./rnn.png)\n\nPowyższy rysunek przedstawia pojedynczy krok sieci RNN. Dla całego\nwejścia (powiedzmy, 3-wyrazowego) możemy sieć rozwinąć (*unroll*):\n\n![img](./rnn-seq.png)\n\n"]},{"cell_type":"markdown","metadata":{},"source":["#### Zastosowanie sieci RNN do etykietowania sekwencji\n\n"]},{"cell_type":"markdown","metadata":{},"source":["#### Problemy z prostymi sieciami RNN\n\n"]},{"cell_type":"markdown","metadata":{},"source":["W praktyce proste sieci RNN są bardzo trudne w uczenia, zazwyczaj\npojawia się problem **zanikających** (rzadziej: **eksplodujących**)\ngradientów: w propagacji wstecznej błąd szybko zanika i nie jest w\nstanie dotrzeć do początkowych wejść.\n\n"]},{"cell_type":"markdown","metadata":{},"source":["### Sieci RNN z bramkami\n\n"]},{"cell_type":"markdown","metadata":{},"source":["W prostych sieciach RNN podstawowa trudność polega na tym, że mamy\nniewielką kontrolę nad tym jak pamięć (stan) jest aktualizowana. Aby\nzwiększyć tę kontrolę, potrzebujemy **bramek**.\n\n"]},{"cell_type":"markdown","metadata":{},"source":["#### Bramki\n\n"]},{"cell_type":"markdown","metadata":{},"source":["Zazwyczaj do tej pory rozpatrywaliśmy iloczyn skalarny wektorów, w\nwyniku którego otrzymujemy liczbę (w PyTorchu wyrażany za pomocą operatora `@`), np.\n\n"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":"# Out[2]:\ntensor(-5)"}],"source":["import torch\n\na = torch.tensor([-1, 0, 3])\nb = torch.tensor([2, 5, -1])\na @ b"]},{"cell_type":"markdown","metadata":{},"source":["Czasami przydatny jest **iloczyn Hadamarda**, czyli przemnożenie\nwektorów (albo macierzy) po współrzędnych. W PyTorchu taki iloczyn\nwyrażany jest za pomocą operatora `*`, w notacji matematycznej będziemy używali\nznaku $\\odot$.\n\n"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":"# Out[3]:\ntensor([-2, 0, -3])"}],"source":["import torch\n\na = torch.tensor([-1, 0, 3])\nb = torch.tensor([2, 5, -1])\na * b"]},{"cell_type":"markdown","metadata":{},"source":["Zauważmy, że iloczyn Hadamarda przez wektor złożony z zer i jedynek daje nam *filtr*, możemy\nselektywnie wygaszać pozycje wektora, np. tutaj wyzerowaliśmy 2. i 5. pozycję wektora:\n\n"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":"# Out[4]:\ntensor([1., 0., 3., 4., 0.])"}],"source":["import torch\n\na = torch.tensor([1., 2., 3., 4., 5.])\nb = torch.tensor([1., 0., 1., 1., 0.])\na * b"]},{"cell_type":"markdown","metadata":{},"source":["Co więcej, za pomocą bramki możemy selektywnie kontrolować, co\nzapamiętujemy, a co zapominamy. Rozpatrzmy mianowicie wektor zer i\njedynek $\\vec{g} \\in \\{0,1}^m$, dla stanu (pamięci) $\\vec{s}$ i nowej informacji\n$\\vec{x}$ możemy dokonywać aktualizacji w następujący sposób:\n\n$$\\vec{s} \\leftarrow \\vec{g} \\odot \\vec{x} + (1 - \\vec{g}) \\odot \\vec{s}$$\n\nNa przykład, za pomocą bramki można wpisać nową wartość na 2. i 5. pozycję wektora.\n\n"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":"# Out[8]:\ntensor([ 1., 7., 3., 4., -8.])"}],"source":["import torch\n\ns = torch.tensor([1., 2., 3., 4., 5.])\nx = torch.tensor([8., 7., 15., -3., -8.])\n\ng = torch.tensor([0., 1., 0., 0., 1.])\n\ns = g * x + (1 - g) * s\ns"]},{"cell_type":"markdown","metadata":{},"source":["Wektor bramki nie musi być z góry określony, może być wyuczalny. Wtedy\njednak lepiej założyć, że bramka jest „miękka”, np. jej wartości\npochodzi z sigmoidy zastosowanej do jakiejś wcześniejszej warstwy.\n\n"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":"# Out[14]:\ntensor([ 1.5310, 6.9998, 5.7777, 4.0000, -5.2159])"}],"source":["import torch\n\ns = torch.tensor([1., 2., 3., 4., 5.])\nx = torch.tensor([8., 7., 15., -3., -8.])\n\npre_g = torch.tensor([-2.5, 10.0, -1.2, -101., 1.3])\ng = torch.sigmoid(pre_g)\n\ns = g * x + (1 - g) * s\ns"]},{"cell_type":"markdown","metadata":{},"source":["**Pytanie:** dlaczego sigmoida zamiast tanh?\n\n"]},{"cell_type":"markdown","metadata":{},"source":["#### Sieć LSTM\n\n"]},{"cell_type":"markdown","metadata":{},"source":["Architektura LSTM (*Long Short-Term Memory*) pozwala rozwiązać problem\nznikających gradientów — za cenę komplikacji obliczeń.\n\nW sieci LSTM stan $\\vec{s^k}$ ma dwie połówki, tj. $\\vec{s^k} =\n\\langle\\vec{c^k},\\vec{h^k}\\rangle$, gdzie\n\n- $\\vec{c^k}$ to **komórka pamięci**, która nie zmienia swojej, chyba że celowo zmodyfikujemy jej wartość\n za pomocą bramek,\n- $\\vec{h^k}$ to ukryty stan (przypominający $\\vec{s^k}$ ze zwykłej sieci RNN).\n\nSieć LSTM zawiera 3 bramki:\n\n- bramkę zapominania (*forget gate*), która steruje wymazywaniem informacji z komórki\n pamięci $\\vec{c^k}$,\n- bramkę wejścia (*input gate*), która steruje tym, na ile nowe informacje aktualizują\n komórkę pamięci $\\vec{c^k}$,\n- bramkę wyjścia (*output gate*), która steruje tym, co z komórki\n pamięci przekazywane jest na wyjście.\n\nWszystkie trzy bramki definiowane są za pomocą bardzo podobnego wzoru — warstwy liniowej na\npoprzedniej wartości warstwy ukrytej i bieżącego wejścia.\n\n$$\\vec{i} = \\sigma(W_i\\langle\\vec{v}(t^k),\\vec{h^{k-1}}\\rangle)$$\n\n$$\\vec{f} = \\sigma(W_f\\langle\\vec{v}(t^k),\\vec{h^{k-1}}\\rangle)$$\n\n$$\\vec{o} = \\sigma(W_f\\langle\\vec{v}(t^k),\\vec{h^{k-1}}\\rangle)$$\n\nJak widać, wzory różnią się tylko macierzami wag $W_*$.\n\nZmiana komórki pamięci jest zdefiniowana jak następuje:\n\n$$\\vec{c^k} = \\vec{f} \\odot \\vec{c^{k-1}} + \\vec{i} \\vec{z^k}$$,\n\ngdzie\n\n$$\\vec{z^k} = \\operatorname{tanh}(W_z\\langle\\vec{v}(t^k),\\vec{h^{k-1}}\\rangle)$$\n\nStan ukryty zmienia się w następujący sposób:\n\n$$\\vec{h^K} = \\vec{o} \\odot \\operatorname{tanh}(\\vec{c^k})$$.\n\nOstateczne wyjście może być wyliczane na podstawie wektora $\\vec{h^k}$:\n\n$$O(\\vec{s}) = O(\\langle\\vec{c},\\vec{h}\\rangle) = \\vec{h}$$\n\n**Pytanie**: Ile wag/parametrów ma sieć RNN o rozmiarze wejścia $n$ i\n\n"]},{"cell_type":"markdown","metadata":{},"source":["### Literatura\n\n"]},{"cell_type":"markdown","metadata":{},"source":["Yoav Goldberg, *Neural Network Methods for Natural Language\nProcessing*, Morgan & Claypool Publishers, 2017\n\n"]}],"metadata":{"org":null,"kernelspec":{"display_name":"Python 3","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.5.2"}},"nbformat":4,"nbformat_minor":0} \ No newline at end of file +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Rekurencyjne sieci neuronowe\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Inne spojrzenie na sieci przedstawione do tej pory\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Regresja liniowa/logistyczna lub klasyfikacja wieloklasowa na całym tekście\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "W regresji liniowej czy logistycznej bądź w klasyfikacji wieloklasowej\n", + "(z funkcją Softmax) stosowaliśmy następujący schemat:\n", + "\n", + "Do tej pory patrzyliśmy na to tak, że po prostu cały tekst jest od\n", + "razu przetwarzany przez (prostą) sieć neuronową, popatrzmy na ten\n", + "przypadek, jak na sytuację przetwarzania sekwencyjnego. Będzie to\n", + "trochę sztuczne, ale uogólnimy to potem w sensowny sposób.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Wektoryzacja\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Po pierwsze, zauważmy, że w wielu schematach wektoryzacji (np. tf), wektor\n", + "dokumentów jest po prostu sumą wektorów poszczególnych składowych:\n", + "\n", + "$$\\vec{v}(d) = \\vec{v}(t^1,\\ldots,t^K) = \\vec{v}(t^1) + \\ldots + \\vec{v}(t^K) = \\sum_{k=1}^K \\vec{v}(t^i),$$\n", + "\n", + "gdzie w schemacie tf \\vec{v}(ti) to po prostu wektor *one-hot* dla słowa.\n", + "\n", + "**Pytanie** Jak postać przyjmie w \\vec{v}(ti) dla wektoryzacji tf-idf?\n", + "\n", + "Wektory $\\vec{v}(t^k)$ mogą być również gęstymi wektorami\n", + "($\\vec{v}(t^k) \\in \\mathcal{R}^n$, gdzie $n$ jest rzędu 10-1000), np.\n", + "w modelu Word2vec albo mogą to być **wyuczalne** wektory (zanurzenia\n", + "słów, *embeddings*), tzn. wektory, które są parametrami uczonej sieci!\n", + "\n", + "**Pytanie** Ile wag (parametrów) wnoszą wyuczalne wektory do sieci?\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Prosta wektoryzacja wyrażona w modelu sekwencyjnym\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Jak zapisać równoważnie powyższą wektoryzację w modelu **sekwencyjnym**, tj. przy założeniu, że\n", + "przetwarzamy wejście token po tokenie (a nie „naraz”)? Ogólnie wprowadzimy bardzo\n", + "ogólny model sieci **rekurencyjnej**.\n", + "\n", + "Po pierwsze zakładamy, że sieć ma pewien stan $\\vec{s^k} \\in\n", + "\\mathcal{R}^m$ (stan jest wektorem o długości $m$), który może\n", + "zmieniać się z każdym krokiem (przetwarzanym tokenem). Zmiana stanu\n", + "jest określona przez pewną funkcję $R : \\mathcal{R}^m \\times\n", + "\\mathcal{R}^n \\rightarrow \\mathcal{R}^m$ ($n$ to rozmiar wektorów\n", + "$\\vec{v}(t^k)$):\n", + "\n", + "$$\\vec{s^k} = R(\\vec{s^{k-1}}, \\vec{v}(t^k)).$$\n", + "\n", + "W przypadku wektoryzacji tf-idf mamy do czynienia z prostym\n", + "sumowaniem, więc $R$ przyjmuje bardzo prostą postać:\n", + "\n", + "$$\\vec{s^0} = [0,\\dots,0],$$\n", + "\n", + "$$R(\\vec{s}, \\vec{x}) = \\vec{s} + \\vec{x}.$$\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Wyjście z modelu\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dla regresji liniowej/logistycznej, oprócz funkcji $R$, która określa\n", + "zmianę stanu, potrzebujemy funkcji $O$, która określa wyjście systemu w każdym kroku.\n", + "\n", + "$$y^k = O(\\vec{s^k})$$\n", + "\n", + "W zadaniach klasyfikacji czy regresji, kiedy patrzymy na cały tekst w\n", + "zasadzie wystarczy wziąć *ostatnią* wartość (tj. $y^K$). Można sobie\n", + "wyobrazić sytuację, kiedy wartości $y^k$ dla $k < k$ również mogą być jakoś przydatne\n", + "(np. klasyfikujemy na bieżąco tekst wpisywany przez użytkownika).\n", + "\n", + "W każdym razie dla regresji liniowej funkcja $O$ przyjmie postać:\n", + "\n", + "$$O(\\vec{s}) = \\vec{w}\\vec{s}$$,\n", + "\n", + "gdzie $\\vec{w}$ jest wektorem wyuczylnych wag, dla regresji zaś logistycznej:\n", + "\n", + "$$O(\\vec{s}) = \\operatorname{softmax}(\\vec{w}\\vec{s})$$\n", + "\n", + "**Pytanie**: jaką postać przyjmie $O$ dla klasyfikacji wieloklasowej\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Prosta sieć rekurencyjna\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "W najprostszej sieci rekurencyjnej (*Vanilla RNN*, sieć Elmana,\n", + "czasami po prostu RNN) w każdym kroku oprócz właściwego wejścia\n", + "($\\vec{v}(t^k)$) będziemy również podawać na wejściu poprzedni stan\n", + "sieci ($\\vec{s^{k-1}}$).\n", + "\n", + "Innymi słowy, funkcje $R$ przyjmie następującą postać:\n", + "\n", + "$$s^k = \\sigma(W\\langle\\vec{v}(t^k), \\vec{s^{k-1}}\\rangle + \\vec{b}),$$\n", + "\n", + "gdzie:\n", + "\n", + "- $\\langle\\vec{x},\\vec{y}\\rangle$ to konkatenacja dwóch wektorów,\n", + "- $W \\in \\mathcal{R}^m \\times \\mathcal{R}^{n+m}$ — macierz wag,\n", + "- $b \\in \\mathcal{R}^m$ — wektor obciążeń (*biases*).\n", + "\n", + "Taką sieć RNN można przedstawić schematycznie w następujący sposób:\n", + "\n", + "![img](./img-rnn.png)\n", + "\n", + "Zauważmy, że zamiast macierzy $W$ działającej na konkatenacji wektorów można wprowadzić dwie\n", + "macierze $U$ i $V$ i tak zapisać wzór:\n", + "\n", + "$$s^k = \\sigma(U\\vec{v}(t^k) + V\\vec{s^{k-1}} + \\vec{b}).$$\n", + "\n", + "Jeszcze inne spojrzenie na sieć RNN:\n", + "\n", + "![img](./rnn.png)\n", + "\n", + "Powyższy rysunek przedstawia pojedynczy krok sieci RNN. Dla całego\n", + "wejścia (powiedzmy, 3-wyrazowego) możemy sieć rozwinąć (*unroll*):\n", + "\n", + "![img](./rnn-seq.png)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Zastosowanie sieci RNN do etykietowania sekwencji\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Problemy z prostymi sieciami RNN\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "W praktyce proste sieci RNN są bardzo trudne w uczenia, zazwyczaj\n", + "pojawia się problem **zanikających** (rzadziej: **eksplodujących**)\n", + "gradientów: w propagacji wstecznej błąd szybko zanika i nie jest w\n", + "stanie dotrzeć do początkowych wejść.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Sieci RNN z bramkami\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "W prostych sieciach RNN podstawowa trudność polega na tym, że mamy\n", + "niewielką kontrolę nad tym jak pamięć (stan) jest aktualizowana. Aby\n", + "zwiększyć tę kontrolę, potrzebujemy **bramek**.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Bramki\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Zazwyczaj do tej pory rozpatrywaliśmy iloczyn skalarny wektorów, w\n", + "wyniku którego otrzymujemy liczbę (w PyTorchu wyrażany za pomocą operatora `@`), np.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# Out[2]:\n", + "tensor(-5)" + ] + } + ], + "source": [ + "import torch\n", + "\n", + "a = torch.tensor([-1, 0, 3])\n", + "b = torch.tensor([2, 5, -1])\n", + "a @ b" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Czasami przydatny jest **iloczyn Hadamarda**, czyli przemnożenie\n", + "wektorów (albo macierzy) po współrzędnych. W PyTorchu taki iloczyn\n", + "wyrażany jest za pomocą operatora `*`, w notacji matematycznej będziemy używali\n", + "znaku $\\odot$.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# Out[3]:\n", + "tensor([-2, 0, -3])" + ] + } + ], + "source": [ + "import torch\n", + "\n", + "a = torch.tensor([-1, 0, 3])\n", + "b = torch.tensor([2, 5, -1])\n", + "a * b" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Zauważmy, że iloczyn Hadamarda przez wektor złożony z zer i jedynek daje nam *filtr*, możemy\n", + "selektywnie wygaszać pozycje wektora, np. tutaj wyzerowaliśmy 2. i 5. pozycję wektora:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# Out[4]:\n", + "tensor([1., 0., 3., 4., 0.])" + ] + } + ], + "source": [ + "import torch\n", + "\n", + "a = torch.tensor([1., 2., 3., 4., 5.])\n", + "b = torch.tensor([1., 0., 1., 1., 0.])\n", + "a * b" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Co więcej, za pomocą bramki możemy selektywnie kontrolować, co\n", + "zapamiętujemy, a co zapominamy. Rozpatrzmy mianowicie wektor zer i\n", + "jedynek $\\vec{g} \\in \\{0,1\\}^m$, dla stanu (pamięci) $\\vec{s}$ i nowej informacji\n", + "$\\vec{x}$ możemy dokonywać aktualizacji w następujący sposób:\n", + "\n", + "$$\\vec{s} \\leftarrow \\vec{g} \\odot \\vec{x} + (1 - \\vec{g}) \\odot \\vec{s}$$\n", + "\n", + "Na przykład, za pomocą bramki można wpisać nową wartość na 2. i 5. pozycję wektora.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# Out[8]:\n", + "tensor([ 1., 7., 3., 4., -8.])" + ] + } + ], + "source": [ + "import torch\n", + "\n", + "s = torch.tensor([1., 2., 3., 4., 5.])\n", + "x = torch.tensor([8., 7., 15., -3., -8.])\n", + "\n", + "g = torch.tensor([0., 1., 0., 0., 1.])\n", + "\n", + "s = g * x + (1 - g) * s\n", + "s" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Wektor bramki nie musi być z góry określony, może być wyuczalny. Wtedy\n", + "jednak lepiej założyć, że bramka jest „miękka”, np. jej wartości\n", + "pochodzi z sigmoidy zastosowanej do jakiejś wcześniejszej warstwy.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# Out[14]:\n", + "tensor([ 1.5310, 6.9998, 5.7777, 4.0000, -5.2159])" + ] + } + ], + "source": [ + "import torch\n", + "\n", + "s = torch.tensor([1., 2., 3., 4., 5.])\n", + "x = torch.tensor([8., 7., 15., -3., -8.])\n", + "\n", + "pre_g = torch.tensor([-2.5, 10.0, -1.2, -101., 1.3])\n", + "g = torch.sigmoid(pre_g)\n", + "\n", + "s = g * x + (1 - g) * s\n", + "s" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Pytanie:** dlaczego sigmoida zamiast tanh?\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Sieć LSTM\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Architektura LSTM (*Long Short-Term Memory*) pozwala rozwiązać problem\n", + "znikających gradientów — za cenę komplikacji obliczeń.\n", + "\n", + "W sieci LSTM stan $\\vec{s^k}$ ma dwie połówki, tj. $\\vec{s^k} =\n", + "\\langle\\vec{c^k},\\vec{h^k}\\rangle$, gdzie\n", + "\n", + "- $\\vec{c^k}$ to **komórka pamięci**, która nie zmienia swojej, chyba że celowo zmodyfikujemy jej wartość\n", + " za pomocą bramek,\n", + "- $\\vec{h^k}$ to ukryty stan (przypominający $\\vec{s^k}$ ze zwykłej sieci RNN).\n", + "\n", + "Sieć LSTM zawiera 3 bramki:\n", + "\n", + "- bramkę zapominania (*forget gate*), która steruje wymazywaniem informacji z komórki\n", + " pamięci $\\vec{c^k}$,\n", + "- bramkę wejścia (*input gate*), która steruje tym, na ile nowe informacje aktualizują\n", + " komórkę pamięci $\\vec{c^k}$,\n", + "- bramkę wyjścia (*output gate*), która steruje tym, co z komórki\n", + " pamięci przekazywane jest na wyjście.\n", + "\n", + "Wszystkie trzy bramki definiowane są za pomocą bardzo podobnego wzoru — warstwy liniowej na\n", + "poprzedniej wartości warstwy ukrytej i bieżącego wejścia.\n", + "\n", + "$$\\vec{i} = \\sigma(W_i\\langle\\vec{v}(t^k),\\vec{h^{k-1}}\\rangle)$$\n", + "\n", + "$$\\vec{f} = \\sigma(W_f\\langle\\vec{v}(t^k),\\vec{h^{k-1}}\\rangle)$$\n", + "\n", + "$$\\vec{o} = \\sigma(W_o\\langle\\vec{v}(t^k),\\vec{h^{k-1}}\\rangle)$$\n", + "\n", + "Jak widać, wzory różnią się tylko macierzami wag $W_*$.\n", + "\n", + "Zmiana komórki pamięci jest zdefiniowana jak następuje:\n", + "\n", + "$$\\vec{c^k} = \\vec{f} \\odot \\vec{c^{k-1}} + \\vec{i} \\vec{z^k}$$,\n", + "\n", + "gdzie\n", + "\n", + "$$\\vec{z^k} = \\operatorname{tanh}(W_z\\langle\\vec{v}(t^k),\\vec{h^{k-1}}\\rangle)$$\n", + "\n", + "Stan ukryty zmienia się w następujący sposób:\n", + "\n", + "$$\\vec{h^K} = \\vec{o} \\odot \\operatorname{tanh}(\\vec{c^k})$$.\n", + "\n", + "Ostateczne wyjście może być wyliczane na podstawie wektora $\\vec{h^k}$:\n", + "\n", + "$$O(\\vec{s}) = O(\\langle\\vec{c},\\vec{h}\\rangle) = \\vec{h}$$\n", + "\n", + "**Pytanie**: Ile wag/parametrów ma sieć RNN o rozmiarze wejścia $n$ i rozmiarze warstwy ukrytej $m$?\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Literatura\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Yoav Goldberg, *Neural Network Methods for Natural Language Processing*,\n", + "Morgan & Claypool Publishers, 2017\n", + "\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.9.2" + }, + "org": null + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/wyk/11_rnn.org b/wyk/11_rnn.org index 1efba6a..606a6eb 100644 --- a/wyk/11_rnn.org +++ b/wyk/11_rnn.org @@ -87,7 +87,7 @@ $$s^k = \sigma(W\langle\vec{v}(t^k), \vec{s^{k-1}}\rangle + \vec{b}),$$ gdzie: - $\langle\vec{x},\vec{y}\rangle$ to konkatenacja dwóch wektorów, -- $W \in \mathcal{R}^m \times \mathcal{R}^{n+m} $ — macierz wag, +- $W \in \mathcal{R}^m \times \mathcal{R}^{n+m}$ — macierz wag, - $b \in \mathcal{R}^m$ — wektor obciążeń (/biases/). Taką sieć RNN można przedstawić schematycznie w następujący sposób: @@ -181,7 +181,7 @@ selektywnie wygaszać pozycje wektora, np. tutaj wyzerowaliśmy 2. i 5. pozycję Co więcej, za pomocą bramki możemy selektywnie kontrolować, co zapamiętujemy, a co zapominamy. Rozpatrzmy mianowicie wektor zer i -jedynek $\vec{g} \in \{0,1}^m$, dla stanu (pamięci) $\vec{s}$ i nowej informacji +jedynek $\vec{g} \in \{0,1\}^m$, dla stanu (pamięci) $\vec{s}$ i nowej informacji $\vec{x}$ możemy dokonywać aktualizacji w następujący sposób: $$\vec{s} \leftarrow \vec{g} \odot \vec{x} + (1 - \vec{g}) \odot \vec{s}$$ @@ -259,7 +259,7 @@ $$\vec{i} = \sigma(W_i\langle\vec{v}(t^k),\vec{h^{k-1}}\rangle)$$ $$\vec{f} = \sigma(W_f\langle\vec{v}(t^k),\vec{h^{k-1}}\rangle)$$ -$$\vec{o} = \sigma(W_f\langle\vec{v}(t^k),\vec{h^{k-1}}\rangle)$$ +$$\vec{o} = \sigma(W_o\langle\vec{v}(t^k),\vec{h^{k-1}}\rangle)$$ Jak widać, wzory różnią się tylko macierzami wag $W_*$. @@ -279,10 +279,10 @@ Ostateczne wyjście może być wyliczane na podstawie wektora $\vec{h^k}$: $$O(\vec{s}) = O(\langle\vec{c},\vec{h}\rangle) = \vec{h}$$ -*Pytanie*: Ile wag/parametrów ma sieć RNN o rozmiarze wejścia $n$ i +*Pytanie*: Ile wag/parametrów ma sieć RNN o rozmiarze wejścia $n$ i rozmiarze warstwy ukrytej $m$? ** Literatura -Yoav Goldberg, /Neural Network Methods for Natural Language -Processing/, Morgan & Claypool Publishers, 2017 +Yoav Goldberg, /Neural Network Methods for Natural Language Processing/, +Morgan & Claypool Publishers, 2017 diff --git a/wyk/12_bpe.ipynb b/wyk/12_bpe.ipynb new file mode 100644 index 0000000..0220f04 --- /dev/null +++ b/wyk/12_bpe.ipynb @@ -0,0 +1,839 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Podział na jednostki podwyrazowe\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Słownik nie może być za duży…\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Jeśli używamy wyuczalnych zanurzeń słów (embeddingów), wówczas musimy\n", + "je dopisać do listy parametrów całego modelu — jest to $|V|n$ wag,\n", + "gdzie $n$ to rozmiar embeddingów; w wypadku uczenia dodatkowo musimy\n", + "jeszcze pamiętać związane z embeddingami gradienty. Pamięć RAM karty\n", + "graficznej jest rzecz jasna ograniczona, słownik więc nie może być\n", + "dowolnie duży. Dla danego modelu karty graficznej dość łatwo ustalić\n", + "maksymalny rozmiar słownika — jest „twarde” ograniczenie, które musimy\n", + "spełnić.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Czy rzeczywiście słownik może być taki duży?\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ile jest różnych form fleksyjnych w języku polskim? Zobaczmy w słowniku PoliMorf…\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a\n", + "aa\n", + "AA\n", + "Aachen\n", + "Aalborg\n", + "Aalborgiem\n", + "Aalborgowi\n", + "Aalborgu\n", + "AAP\n", + "Aar\n", + "Aarem\n", + "Aarowi\n", + "Aaru\n", + "Aarze\n", + "Aara\n", + "Aarą\n", + "Aarę\n", + "Aaro\n", + "Aary\n", + "Aarze\n", + "uniq: błąd zapisu: Przerwany potok\n" + ] + } + ], + "source": [ + "! wget -q 'http://zil.ipipan.waw.pl/PoliMorf?action=AttachFile&do=get&target=PoliMorf-0.6.7.tab.gz' -O - | zcat | cut -f 1 | uniq | head -n 20" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3844535\n" + ] + } + ], + "source": [ + "! wget -q 'http://zil.ipipan.waw.pl/PoliMorf?action=AttachFile&do=get&target=PoliMorf-0.6.7.tab.gz' -O - | zcat | cut -f 1 | sort -u | wc -l" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Pytanie** W którym języku europejskim wyrazów będzie jeszcze więcej niż języku polskim?\n", + "\n", + "Tak naprawdę form jest jeszcze więcej, oczywiście PoliMorf nie wyczerpuje zbioru…\n", + "\n", + "**Pytanie** Podaj przykłady „oczywistych” wyrazów, których nie ma w PoliMorfie. Jak w sposób systematyczny szukać takich wyrazów?\n", + "\n", + "Z drugiej strony, w PoliMorfie jest dużo dziwnych, „sztucznych” wyrazów.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "niebiałościenną\n", + "nieponadosobowości\n", + "nieknerający\n", + "inspektoratów\n", + "Korytkowskich\n", + "elektrostatyczności\n", + "Okola\n", + "bezsłowny\n", + "indygowcu\n", + "gadany\n", + "nieładowarkowościach\n", + "niepawężnicowate\n", + "Thom\n", + "poradlmy\n", + "olejący\n", + "Ziemianinów\n", + "stenotropizmami\n", + "wigiliowości\n", + "pognanej\n", + "niekinezyterapeutycznym\n" + ] + } + ], + "source": [ + "! wget -q 'http://zil.ipipan.waw.pl/PoliMorf?action=AttachFile&do=get&target=PoliMorf-0.6.7.tab.gz' -O - | zcat | cut -f 1 | shuf -n 20" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Inaczej, zobaczmy, ile różnych wyrazów jest w jakimś rzeczywistym zbiorze tekstów, rozpatrzmy\n", + "teksty zebrane na potrzeby identyfikacji płci autora tekstu:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# Out[7]:" + ] + } + ], + "source": [ + "! git clone --single-branch --depth 1 git://gonito.net/petite-difference-challenge2" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "! xzcat petite-difference-challenge2/train/in.tsv.xz | perl -C -ne 'print \"$&\\n\" while/\\p{L}+/g;' | sort -u > vocab.txt" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ˆ\n", + "ˇ\n", + "゚\n", + "a\n", + "A\n", + "á\n", + "Á\n", + "à\n", + "À\n", + "ă\n", + "Ă\n", + "â\n", + "Â\n", + "å\n", + "Å\n", + "ä\n", + "Ä\n", + "Ã\n", + "ā\n", + "aa\n", + "aA\n", + "Aa\n", + "AA\n", + "aĂ\n", + "AĂ\n", + "aâ\n", + "aÂ\n", + "Aâ\n", + "aÅ\n", + "aÄ\n", + "ª\n", + "aaa\n", + "aAa\n", + "Aaa\n", + "AaA\n", + "AAa\n", + "AAA\n", + "aaaa\n", + "aAaa\n", + "Aaaa\n", + "AaAa\n", + "AAaa\n", + "AAAa\n", + "AAAA\n", + "aaaaa\n", + "Aaaaa\n", + "AaaaA\n", + "AAaaa\n", + "AAAAA\n", + "aaaaaa\n" + ] + } + ], + "source": [ + "! head -n 50 vocab.txt" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2974556 vocab.txt\n" + ] + } + ], + "source": [ + "! wc -l vocab.txt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Co gorsza, nawet jak weźmiemy cały taki słownik bez ograniczeń i tak\n", + "nie pokryje on sporej części tekstów przetwarzanych w czasie inferencji.\n", + "Zobaczmy, ilu wyrazów ze zbioru deweloperskiego nie będzie w słowniku.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "81380\n" + ] + } + ], + "source": [ + "! cat petite-difference-challenge2/dev-0/in.tsv | perl -C -ne 'print \"$&\\n\" while/\\p{L}+/g;' | sort -u | comm vocab.txt - -13 | wc -l" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Takie wyrazy nazywamy wyrazami **OOV** (*out-of-vocabulary*).\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Obcięcie słownika\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Najprostszy sposób ograniczenia słownika to po prostu obcięcie do $N$ najczęstszych słów.\n", + "\n", + "Spróbujmy zastosować do korpusu „płci”:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sort: błąd zapisu: 'standardowe wyjście': Przerwany potok\n", + "sort: błąd zapisu\n" + ] + } + ], + "source": [ + "! xzcat petite-difference-challenge2/train/in.tsv.xz | perl -C -ne 'print \"$&\\n\" while/\\p{L}+/g;' | sort | uniq -c | sort -k 1rn | head -n 50000 | sort -k 2 > vocab50000.txt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Daje to lepszy efekt niż można się spodziewać. Odrzucamy w ten sposób\n", + "tylko bardzo rzadkie słowa (albo takie, które wystąpiły tylko raz w\n", + "korpusie — tzw. *hapax legomena*), choć tych słów jest bardzo dużo.\n", + "\n", + "**Zagadka**: 50000 najczęstszych słów (1,9% **typów**) pokrywa jaki odsetek **wystąpień**?\n", + "\n", + "Rozkład normalny w języku nie jest… normalny — nie spotkamy się z nim\n", + "badając języki. W tekstach dominują „skrzywione” rozkłady z długimi,\n", + "„chudymi” ogonami.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "! xzcat petite-difference-challenge2/train/in.tsv.xz | perl -C -ne 'print \"$&\\n\" while/\\p{L}+/g;' | sort | uniq -c | sort -k 1rn | cut -f 1 > freqs.txt" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'word-distribution.png'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "import re\n", + "from math import log\n", + "\n", + "freqs = []\n", + "\n", + "with open('freqs.txt', 'r') as fh:\n", + " for line in fh:\n", + " m = re.match(r'\\s*(\\d+)', line)\n", + " if m:\n", + " freqs.append(log(float(m.group(1))))\n", + "\n", + "plt.plot(range(len(freqs)), freqs)\n", + "fname = 'word-distribution.png'\n", + "plt.savefig(fname)\n", + "fname" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[[file:# Out[25]:\n", + "\n", + " 'word-distribution.png'\n", + "\n", + "![img](./obipy-resources/c0TrCn.png)]]\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Lematyzacja\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lematyzacja wydaje się dobrym pomysłem, zwłaszcza dla języków dla bogatej fleksji:\n", + "\n", + "- znacznie redukujemy słownik,\n", + "- formy fleksyjne tego samego wyrazu są traktowane tak samo (co wydaje się słuszne).\n", + "\n", + "W praktyce współcześnie **nie** stosuje się lematyzacji (w połączeniu z\n", + "metodami opartymi na sieciach neuronowych):\n", + "\n", + "- lematyzacja wymaga wiedzy językowej (reguł lub słownika),\n", + " wytworzenie takiej wiedzy może być kosztowne, obecnie preferowane\n", + " są metody niezależne od języka;\n", + "- tracimy pewną informację niesioną przez formę fleksyjną (co w szczególnych\n", + " przypadkach może być niefortunne, np. *aspiracja* i *aspiracje*);\n", + "- lematyzacja nie jest trywialnym problemem ze względu na niejednoznaczności\n", + " (*Lekarzu, lecz się sam*);\n", + "- niektóre niejednoznaczności są seryjne, wybór lematu może być arbitralny,\n", + " np. czy *posiadanie*, *gotowanie*, *skakanie* to rzeczowniki czy czasowniki?\n", + " a *urządzenie*, *mieszkanie*?\n", + "- zazwyczaj sieci neuronowe (czy nawet prostsze modele typu Word2vec)\n", + " są w stanie nauczyć się rekonstruowania zależności między formami fleksyjnymi\n", + " (i więcej: błędnych form, błędów ortograficznych, form archaicznych itd.)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Zejście na poziom znaków\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Skoro słownik wyrazów jest zbyt duży, to może zejść na poziom znaków?\n", + "\n", + "- pojedynczy znak alfabetu wprawdzie nic nie znaczy (co znaczy *h*?)\n", + "\n", + "- … ale rozmiar wejścia przy kodowaniu gorącą jedynką\n", + " dramatycznie się zmniejsza\n", + "\n", + "- może działać, jeśli dodać wielowarstwową sieć\n", + " neuronową\n", + "\n", + "- … ale może być bardzo kosztowne obliczeniowo\n", + "\n", + "A może coś pośredniego między znakami a wyrazami?\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### BPE\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ani znaki, ani wyrazy — coś pomiędzy: jednostki podwyrazowe (*subword\n", + "units*). Moglibyśmy np. dzielić wyraz *superkomputera* na dwie\n", + "jednostki *super/+/komputera*, a może nawet trzy: *super/+/komputer/+/a*?\n", + "\n", + "Najpopularniejszy algorytm podziału na jednostki podwyrazowe to BPE\n", + "(*byte-pair encoding*), zainspirowany algorytmami kompresji danych.\n", + "Lista jednostek jest automatycznie indukowana na podstawie tekstu (nie\n", + "potrzeba żadnej wiedzy o języku!). Ich liczba musi być natomiast z góry\n", + "określona.\n", + "\n", + "W kroku początkowym zaznaczamy końce wyrazów (tokenów), robimy to po\n", + "to, żeby jednostki podwyrazowe nie przekraczały granic wyrazów.\n", + "\n", + "Następnie wykonujemy tyle kroków iteracji, ile wynosi rozmiar zadanego\n", + "słownika. W każdym kroku szukamy najczęstszego bigramu, od tego\n", + "momentu traktujemy go jako całostkę (wkładamy go do „pudełka”).\n", + "\n", + "![img](./bpe.png)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Implementacja w Pythonie\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['e$', 'to', 'to$', 'be$', 't$', 'th', 'or', 'or$', 'no', 'not$']" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from collections import Counter\n", + "\n", + "def replace_bigram(l, b, r):\n", + " i = 0\n", + " while i < len(l) - 1:\n", + " if (l[i], l[i+1]) == b:\n", + " l[i:i+2] = [r]\n", + " i += 1\n", + " return l\n", + "\n", + "def learn_bpe_vocab(d, max_vocab_size):\n", + " d = list(d.replace(' ', '$') + '$')\n", + "\n", + " vocab = []\n", + "\n", + " for ix in range(0, max_vocab_size):\n", + " bigrams = [(d[i], d[i+1]) for i in range(0, len(d) - 1) if d[i][-1] != '$']\n", + " selected_bigram = Counter(bigrams).most_common(1)[0][0]\n", + "\n", + " new_subword = selected_bigram[0] + selected_bigram[1]\n", + " d = replace_bigram(d, selected_bigram, new_subword)\n", + "\n", + " vocab.append(new_subword)\n", + "\n", + " return vocab\n", + "\n", + "vocab1 = learn_bpe_vocab('to be or not to be that is the question', 10)\n", + "vocab1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Słownik jednostek podwyrazowych możemy zastosować do dowolnego tekstu, np. do tekstu,\n", + "na którym słownik był wyuczony:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'to$ be$ or$ not$ to$ be$ th a t$ i s $ th e$ q u e s t i o n $'" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def apply_bpe_vocab(vocab, d):\n", + " d = list(d.replace(' ', '$') + '$')\n", + " vocab_set = set(vocab)\n", + "\n", + " modified = True\n", + " while modified:\n", + " ix = 0\n", + " modified = False\n", + " while ix < len(d) - 1:\n", + " bigram = d[ix] + d[ix+1]\n", + " if bigram in vocab_set:\n", + " d[ix:ix+2] = [bigram]\n", + " modified = True\n", + " else:\n", + " ix += 1\n", + "\n", + " return d\n", + "\n", + "' '.join(apply_bpe_vocab(vocab1, 'to be or not to be that is the question'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Zauważmy, że oprócz jednostek podwyrazowych zostały izolowane litery,\n", + "zazwyczaj dodajemy je do słownika. (I zazwyczaj, słownik jest trochę\n", + "większy niż wartość podana jako parametr przy uczeniu BPE — jest\n", + "większy o znaki i specjalne tokeny typu `UNK`, `BOS`, `EOS`, `PAD`.)\n", + "\n", + "**Pytanie**: Jaki problem może pojawić przy zastosowaniu BPE dla tekstu,\n", + "gdzie pojawiają się chińskie znaki? Jak można sobie z nim poradzić?\n", + "\n", + "Słownik jednostek podwyrazowych można stosować dla dowolnego tekstu:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'to m $ w i l l $ be$ th e$ b e s t$'" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "' '.join(apply_bpe_vocab(vocab1, 'tom will be the best'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Jak można zauważyć algorytm BPE daje dwa rodzaje jednostek podwyrazowych:\n", + "\n", + "- jednostki, które mogą doklejane na początku wyrazu;\n", + "- jednostki, które stanowią koniec wyrazu, w szczególności są całym wyrazem.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Gotowa implementacja\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Po raz pierwszy BPE użyto do neuronowego tłumaczenia maszynowego.\n", + "Użyjmy modułu autorstwa Rica Sennricha ([https://github.com/rsennrich/subword-nmt](https://github.com/rsennrich/subword-nmt)).\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "! pip install subword-nmt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Wyindukujmy słownik dla zbioru uczącego zadania identyfikacji płci\n", + "autora tekstu:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "! xzcat petite-difference-challenge2/train/in.tsv.xz | perl -C -ne 'print \"$&\\n\" while/\\p{L}+/g;' | python -m subword_nmt.learn_bpe -s 50000 -v > bpe_vocab.txt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Procedura trwa kilka minut, trzeba uzbroić się w cierpliwość (ale wypisywanie bigramów przyspieszy!).\n", + "\n", + " pair 0: n i -> ni (frequency 17625075)\n", + " pair 1: i e -> ie (frequency 11471590)\n", + " pair 2: c z -> cz (frequency 9143490)\n", + " pair 3: ni e -> nie (frequency 7901783)\n", + " pair 4: p o -> po (frequency 7790826)\n", + " pair 5: r z -> rz (frequency 7542046)\n", + " pair 6: s t -> st (frequency 7269069)\n", + " pair 7: e m -> em (frequency 7207280)\n", + " pair 8: d z -> dz (frequency 6860931)\n", + " pair 9: s z -> sz (frequency 6609907)\n", + " pair 10: r a -> ra (frequency 6601618)\n", + " pair 11: o w -> ow (frequency 6395963)\n", + " pair 12: i e -> ie (frequency 5906869)\n", + " pair 13: n a -> na (frequency 5300380)\n", + " pair 14: r o -> ro (frequency 5181363)\n", + " pair 15: n a -> na (frequency 5125807)\n", + " pair 16: a ł -> ał (frequency 4786696)\n", + " pair 17: j e -> je (frequency 4599579)\n", + " pair 18: s i -> si (frequency 4300984)\n", + " pair 19: a l -> al (frequency 4276823)\n", + " pair 20: t e -> te (frequency 4033344)\n", + " pair 21: w i -> wi (frequency 3939063)\n", + " pair 22: c h -> ch (frequency 3919410)\n", + " pair 23: c h -> ch (frequency 3661410)\n", + " pair 24: k o -> ko (frequency 3629840)\n", + " pair 25: z a -> za (frequency 3625424)\n", + " pair 26: t a -> ta (frequency 3570094)\n", + " pair 27: p rz -> prz (frequency 3494551)\n", + " pair 28: g o -> go (frequency 3279997)\n", + " pair 29: a r -> ar (frequency 3081492)\n", + " pair 30: si ę -> się (frequency 2973681)\n", + " ...\n", + " pair 49970: brz mieniu -> brzmieniu (frequency 483)\n", + " pair 49971: bieżą cych -> bieżących (frequency 483)\n", + " pair 49972: biegu nkę -> biegunkę (frequency 483)\n", + " pair 49973: ban kowości -> bankowości (frequency 483)\n", + " pair 49974: ba ku -> baku (frequency 483)\n", + " pair 49975: ba cznie -> bacznie (frequency 483)\n", + " pair 49976: Przypad kowo -> Przypadkowo (frequency 483)\n", + " pair 49977: MA Ł -> MAŁ (frequency 483)\n", + " pair 49978: Lep pera -> Leppera (frequency 483)\n", + " pair 49979: Ko za -> Koza (frequency 483)\n", + " pair 49980: Jak byś -> Jakbyś (frequency 483)\n", + " pair 49981: Geni alne -> Genialne (frequency 483)\n", + " pair 49982: Że nada -> Żenada (frequency 482)\n", + " pair 49983: ń czykiem -> ńczykiem (frequency 482)\n", + " pair 49984: zwie ń -> zwień (frequency 482)\n", + " pair 49985: zost ałaś -> zostałaś (frequency 482)\n", + " pair 49986: zni szczona -> zniszczona (frequency 482)\n", + " pair 49987: ze stawi -> zestawi (frequency 482)\n", + " pair 49988: za sób -> zasób (frequency 482)\n", + " pair 49989: węd rówkę -> wędrówkę (frequency 482)\n", + " pair 49990: wysko czyła -> wyskoczyła (frequency 482)\n", + " pair 49991: wyle czenia -> wyleczenia (frequency 482)\n", + " pair 49992: wychowaw cze -> wychowawcze (frequency 482)\n", + " pair 49993: w t -> wt (frequency 482)\n", + " pair 49994: un da -> unda (frequency 482)\n", + " pair 49995: udzie lałem -> udzielałem (frequency 482)\n", + " pair 49996: tę czy -> tęczy (frequency 482)\n", + " pair 49997: tro sce -> trosce (frequency 482)\n", + " pair 49998: słusz ności -> słuszności (frequency 482)\n", + " pair 49999: su me -> sume (frequency 482\n", + "\n", + "Zastosujmy teraz wyindukowany słownik BPE dla jakiegoś rzeczywistego tekstu.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cier@@ piałem na straszne la@@ gi kilkanaście sekund lub dłużej czarnego ekranu przy próbie przełą@@ czenia się uruchomienia prawie każdej aplikacji Dodatkowo telefon mi się wyłą@@ czał czasem bez powodu sam z siebie albo rese@@ tował Ostatnio nawet przeglądarka zaczęła się często zawie@@ szać i Android proponował wymu@@ szone zamknięcie Do tego te problemy z połączeniem do komputera przez USB " + ] + } + ], + "source": [ + "! echo 'Cierpiałem na straszne lagi – kilkanaście sekund lub dłużej czarnego ekranu przy próbie przełączenia się / uruchomienia prawie każdej aplikacji. Dodatkowo telefon mi się wyłączał czasem bez powodu – sam z siebie, albo resetował. Ostatnio nawet przeglądarka zaczęła się często zawieszać i Android proponował wymuszone zamknięcie. Do tego te problemy z połączeniem do komputera przez USB.' | perl -C -ne 'print \"$& \" while/\\p{L}+/g;' | python -m subword_nmt.apply_bpe -c bpe_vocab.txt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ta konkretna implementacja zaznacza za pomocą sekwencji ~@@ ~ koniec jednostki podwyrazowej.\n", + "\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.9.2" + }, + "org": null + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/wyk/12_bpe.org b/wyk/12_bpe.org index 9829c85..486ef45 100644 --- a/wyk/12_bpe.org +++ b/wyk/12_bpe.org @@ -36,7 +36,7 @@ Ile jest różnych form fleksyjnych w języku polskim? Zobaczmy w słowniku Poli Tak naprawdę form jest jeszcze więcej, oczywiście PoliMorf nie wyczerpuje zbioru… -*Pytanie* Podaj przykłady „oczywistych” wyrazów, których w PoliMorfie. Jak w sposób systematyczny szukać takich wyrazów? +*Pytanie* Podaj przykłady „oczywistych” wyrazów, których nie ma w PoliMorfie. Jak w sposób systematyczny szukać takich wyrazów? Z drugiej strony, w PoliMorfie jest dużo dziwnych, „sztucznych” wyrazów. @@ -88,7 +88,7 @@ nie pokryje on sporej części tekstów przetwarzanych w czasie inferencji. Zobaczmy, ilu wyrazów ze zbioru deweloperskiego nie będzie w słowniku. #+BEGIN_SRC ipython :session mysession :exports both :results raw drawer -! cat petite-difference-challenge2/dev-0/in.tsv.xz | perl -C -ne 'print "$&\n" while/\p{L}+/g;' | sort -u | comm vocab.txt - -13 | wc -l +! cat petite-difference-challenge2/dev-0/in.tsv | perl -C -ne 'print "$&\n" while/\p{L}+/g;' | sort -u | comm vocab.txt - -13 | wc -l #+END_SRC Takie wyrazy nazywamy wyrazami *OOV* (/out-of-vocabulary/). @@ -112,7 +112,7 @@ Daje to lepszy efekt niż można się spodziewać. Odrzucamy w ten sposób tylko bardzo rzadkie słowa (albo takie, które wystąpiły tylko raz w korpusie — tzw. /hapax legomena/), choć tych słów jest bardzo dużo. -*Zagadka*: 50000 najczęstszych słów (1,9\% *typów*) pokrywa jaki odsetek *wystąpień*? +*Zagadka*: 50000 najczęstszych słów (1,9% *typów*) pokrywa jaki odsetek *wystąpień*? Rozkład normalny w języku nie jest… normalny — nie spotkamy się z nim badając języki. W tekstach dominują „skrzywione” rozkłady z długimi, @@ -244,7 +244,7 @@ momentu traktujemy go jako całostkę (wkładamy go do „pudełka”). #+RESULTS: :results: -# Out[4]: +# Out[1]: : ['e$', 'to', 'to$', 'be$', 't$', 'th', 'or', 'or$', 'no', 'not$'] :end: @@ -256,13 +256,17 @@ na którym słownik był wyuczony: d = list(d.replace(' ', '$') + '$') vocab_set = set(vocab) - ix = 0 - while ix < len(d) - 1: - bigram = d[ix] + d[ix+1] - if bigram in vocab_set: - d[ix:ix+2] = [bigram] - else: - ix += 1 + modified = True + while modified: + ix = 0 + modified = False + while ix < len(d) - 1: + bigram = d[ix] + d[ix+1] + if bigram in vocab_set: + d[ix:ix+2] = [bigram] + modified = True + else: + ix += 1 return d @@ -272,7 +276,7 @@ na którym słownik był wyuczony: #+RESULTS: :results: # Out[5]: -: 'to$ b e$ or$ no t$ to$ b e$ th a t$ i s $ th e$ q u e s t i o n $' +: 'to$ be$ or$ not$ to$ be$ th a t$ i s $ th e$ q u e s t i o n $' :end: Zauważmy, że oprócz jednostek podwyrazowych zostały izolowane litery, @@ -289,6 +293,12 @@ Słownik jednostek podwyrazowych można stosować dla dowolnego tekstu: ' '.join(apply_bpe_vocab(vocab1, 'tom will be the best')) #+END_SRC +#+RESULTS: +:results: +# Out[6]: +: 'to m $ w i l l $ be$ th e$ b e s t$' +:end: + Jak można zauważyć algorytm BPE daje dwa rodzaje jednostek podwyrazowych: - jednostki, które mogą doklejane na początku wyrazu; diff --git a/wyk/13_generative_approach.ipynb b/wyk/13_generative_approach.ipynb new file mode 100644 index 0000000..8234d7a --- /dev/null +++ b/wyk/13_generative_approach.ipynb @@ -0,0 +1,113 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Ekstrakcja informacji a podejście generatywne\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Podejście generatywne\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Do tej pory zadanie ekstrakcji informacji traktowaliśmy jako zadanie etykietowania sekwencji, tzn. uczyliśmy system zaznaczać tokeny składające się na ekstrahowane informacje.\n", + "\n", + "![img](./ie-seqlab.png)\n", + "\n", + "Możliwe jest inne podeście, **generatywne**, w którym podchodzimy do problemu ekstrakcji informacji jak do swego rodzaju **tłumaczenia maszynowego** — „tłumaczymy” tekst (wraz z pytaniem lub etykietą) na informację.\n", + "\n", + "![img](./ie-gener.png)\n", + "\n", + "To podejście może się wydawać trudniejsze niż etykietowanie sekwencji, ale wystarczająco zaawansowanej architekturze sieci, jest wykonalne.\n", + "\n", + "Zalety:\n", + "\n", + "- informacja nie musi być dosłownie zapisana w tekście, ekstraktor może nauczyć się również normalizacji czy parafrazowania,\n", + "- nie wprowadzamy wielu kroków przetwarzania (gdzie błędy mogą się\n", + " namnażać), system działa na zasadzie *end-to-end*.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Atencja\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Pierwsze systemu neuronowego tłumaczenia maszynowego używały siecie LSTM. Dopiero jednak dodanie tzw. atencji (*attention*) umożliwiło duży przeskok jakościowy. Najpierw atencję dodano do sieci rekurencyjnych, później powstały sieci oparte *wyłącznie* na atencji — modele Transformer.\n", + "\n", + "Idea atencji polega na tym, że sieć może kierować selektywnie „snop” uwagi na wyrazy na wejściu lub do tej pory wygenerowane wyrazy.\n", + "\n", + "Mechanizm atencji korzysta z:\n", + "\n", + "- z poprzedniego stanu sieci $\\vec{s^{k-1}}$ (to jest „miejsce”, z którego „kierujemy” atencję),\n", + "- z wektora reprezentującego słowo $\\vec{v}(t_i)$ (to jest „miejsce”, na które kierujemy atencję), gdzie\n", + " $\\vec{v}(t_i)$ to reprezentacja wektorowa wyrazu $t_i$ (statyczny embedding lub reprezentacja wektorowa\n", + " z poprzedniej warstwy dla sieci wielowarstwowej),\n", + "\n", + "aby wytworzyć wektor kontekstu $\\vec{\\xi^k}$ (który z kolei będzie w jakiś sposób wnosił wkład do wyliczenia nowej wartości stanu $\\vec{s^k}$ lub wyjścia $y^k$.\n", + "\n", + "Najpierw wyliczymy skalarne wartości atencji, tzn. liczby, które będą sygnalizowały, jak bardzo wektor $\\vec{v}(t_i)$ „pasuje” do $\\vec{s^{k-1}}$, w najprostszej wersji można po prostu skorzystać z iloczynu skalarnego (o ile $n=m$),\n", + "\n", + "$$a(\\vec{s^{k-1}}, \\vec{v}(t_i)) = \\vec{s^{k-1}}\\vec{v}(t_i).$$\n", + "\n", + "**Pytanie**: co jeśli $n$ nie jest równe $m$, tzn. rozmiar embeddingu nie jest równy rozmiarowi wektora stanu?\n", + "\n", + "W przypadku sieci LSTM korzysta się częściej z bardziej skomplikowanego wzoru zawierającego dodatkowe wyuczalne wagi:\n", + "\n", + "$$a(\\vec{s^{k-1}}, \\vec{v}(t_i)) = \\vec{w_a}\\operatorname{tanh}(W_a\\vec{s^{k-1}} + U_a\\vec{v}(t_i))$$\n", + "\n", + "**Pytanie**: jakie rozmiary mają macierze $W_a$, $U_a$ i wektor $w_a$?\n", + "\n", + "Powtórzmy, że wartości $a$ są wartościami skalarnymi, natomiast nie są one znormalizowane (nie sumują się do jedynki), normalizujemy je używając schematu podobnego do softmaxa:\n", + "\n", + "$$\\alpha_{i} = \\frac{e^{a(\\vec{s^{k-1}}, \\vec{v}(t_i))}}{\\sum_j e^{a(\\vec{s^{k-1}}, \\vec{v}(t_j))}}$$\n", + "\n", + "Wektor kontekstu $\\vec{\\xi^k}$ będzie po prostu średnią ważoną wektorowych reprezentacji słów:\n", + "\n", + "$$\\vec{\\xi^k} = \\sum_i \\alpha_i\\vec{v}(t_i)$$\n", + "\n", + "**Pytanie**: zasadniczo atencja jest środkiem do celu (żeby sieć się sprawniej uczyła), czy można atencja sama w sobie może być do czegoś przydatna?\n", + "\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.9.2" + }, + "org": null + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/wyk/13_generative_approach.org b/wyk/13_generative_approach.org new file mode 100644 index 0000000..3d0885b --- /dev/null +++ b/wyk/13_generative_approach.org @@ -0,0 +1,55 @@ +* Ekstrakcja informacji a podejście generatywne +** Podejście generatywne + +Do tej pory zadanie ekstrakcji informacji traktowaliśmy jako zadanie etykietowania sekwencji, tzn. uczyliśmy system zaznaczać tokeny składające się na ekstrahowane informacje. + +[[./ie-seqlab.png]] + +Możliwe jest inne podeście, *generatywne*, w którym podchodzimy do problemu ekstrakcji informacji jak do swego rodzaju *tłumaczenia maszynowego* — „tłumaczymy” tekst (wraz z pytaniem lub etykietą) na informację. + +[[./ie-gener.png]] + +To podejście może się wydawać trudniejsze niż etykietowanie sekwencji, ale wystarczająco zaawansowanej architekturze sieci, jest wykonalne. + +Zalety: + +- informacja nie musi być dosłownie zapisana w tekście, ekstraktor może nauczyć się również normalizacji czy parafrazowania, +- nie wprowadzamy wielu kroków przetwarzania (gdzie błędy mogą się + namnażać), system działa na zasadzie /end-to-end/. + +** Atencja + +Pierwsze systemu neuronowego tłumaczenia maszynowego używały siecie LSTM. Dopiero jednak dodanie tzw. atencji (/attention/) umożliwiło duży przeskok jakościowy. Najpierw atencję dodano do sieci rekurencyjnych, później powstały sieci oparte /wyłącznie/ na atencji — modele Transformer. + +Idea atencji polega na tym, że sieć może kierować selektywnie „snop” uwagi na wyrazy na wejściu lub do tej pory wygenerowane wyrazy. + +Mechanizm atencji korzysta z: + +- z poprzedniego stanu sieci $\vec{s^{k-1}}$ (to jest „miejsce”, z którego „kierujemy” atencję), +- z wektora reprezentującego słowo $\vec{v}(t_i)$ (to jest „miejsce”, na które kierujemy atencję), gdzie + $\vec{v}(t_i)$ to reprezentacja wektorowa wyrazu $t_i$ (statyczny embedding lub reprezentacja wektorowa + z poprzedniej warstwy dla sieci wielowarstwowej), + +aby wytworzyć wektor kontekstu $\vec{\xi^k}$ (który z kolei będzie w jakiś sposób wnosił wkład do wyliczenia nowej wartości stanu $\vec{s^k}$ lub wyjścia $y^k$. + +Najpierw wyliczymy skalarne wartości atencji, tzn. liczby, które będą sygnalizowały, jak bardzo wektor $\vec{v}(t_i)$ „pasuje” do $\vec{s^{k-1}}$, w najprostszej wersji można po prostu skorzystać z iloczynu skalarnego (o ile $n=m$), + +$$a(\vec{s^{k-1}}, \vec{v}(t_i)) = \vec{s^{k-1}}\vec{v}(t_i).$$ + +*Pytanie*: co jeśli $n$ nie jest równe $m$, tzn. rozmiar embeddingu nie jest równy rozmiarowi wektora stanu? + +W przypadku sieci LSTM korzysta się częściej z bardziej skomplikowanego wzoru zawierającego dodatkowe wyuczalne wagi: + +$$a(\vec{s^{k-1}}, \vec{v}(t_i)) = \vec{w_a}\operatorname{tanh}(W_a\vec{s^{k-1}} + U_a\vec{v}(t_i))$$ + +*Pytanie*: jakie rozmiary mają macierze $W_a$, $U_a$ i wektor $w_a$? + +Powtórzmy, że wartości $a$ są wartościami skalarnymi, natomiast nie są one znormalizowane (nie sumują się do jedynki), normalizujemy je używając schematu podobnego do softmaxa: + +$$\alpha_{i} = \frac{e^{a(\vec{s^{k-1}}, \vec{v}(t_i))}}{\sum_j e^{a(\vec{s^{k-1}}, \vec{v}(t_j))}}$$ + +Wektor kontekstu $\vec{\xi^k}$ będzie po prostu średnią ważoną wektorowych reprezentacji słów: + +$$\vec{\xi^k} = \sum_i \alpha_i\vec{v}(t_i)$$ + +*Pytanie*: zasadniczo atencja jest środkiem do celu (żeby sieć się sprawniej uczyła), czy można atencja sama w sobie może być do czegoś przydatna? diff --git a/wyk/14_pretrenowanie.ipynb b/wyk/14_pretrenowanie.ipynb new file mode 100644 index 0000000..ae7721a --- /dev/null +++ b/wyk/14_pretrenowanie.ipynb @@ -0,0 +1,338 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Pretrenowanie modeli\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "System AlphaZero uczy się grając sam ze sobą — wystarczy 24 godziny,\n", + "by system nauczył się grać w szachy lub go na nadludzkim poziomie.\n", + "\n", + "**Pytanie**: Dlaczego granie samemu ze sobą nie jest dobrym sposobem\n", + " nauczenia się grania w szachy dla człowieka, a dla maszyny jest?\n", + "\n", + "Co jest odpowiednikiem grania samemu ze sobą w świecie przetwarzania tekstu?\n", + "Tzn. **pretrenowanie** (*pretraining*) na dużym korpusie tekstu. (Tekst jest tani!)\n", + "\n", + "Jest kilka sposobów na pretrenowanie modelu, w każdym razie sprowadza\n", + "się do odgadywania następnego bądź zamaskowanego słowa.\n", + "W każdym razie zawsze stosujemy softmax (być może ze „sztuczkami” takimi jak\n", + "negatywne próbkowanie albo hierarchiczny softamx) na pewnej **representecji kontekstowej**:\n", + "\n", + "$$\\vec{p} = \\operatorname{softmax}(f(\\vec{c})).$$\n", + "\n", + "Model jest karany używając funkcji log loss:\n", + "\n", + "$$-\\log(p_j),$$\n", + "\n", + "gdzie $w_j$ jest wyrazem, który pojawił się rzeczywiście w korpusie.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Przewidywanie słowa (GPT-2)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Jeden ze sposobów pretrenowania modelu to po prostu przewidywanie\n", + "następnego słowa.\n", + "\n", + "Zainstalujmy najpierw bibliotekę transformers.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "! pip install transformers" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "50257\n" + ] + }, + { + "data": { + "text/plain": [ + "[('Ġon', 0.6786560416221619),\n", + " ('Ġupon', 0.04339785501360893),\n", + " ('Ġheavily', 0.02208443358540535),\n", + " ('Ġin', 0.021049050614237785),\n", + " (',', 0.020188499242067337),\n", + " ('Ġa', 0.01833895780146122),\n", + " ('Ġvery', 0.017935041338205338),\n", + " ('Ġentirely', 0.017528969794511795),\n", + " ('Ġlargely', 0.016769640147686005),\n", + " ('Ġto', 0.01009418722242117),\n", + " ('Ġgreatly', 0.010009866207838058),\n", + " ('Ġnot', 0.009016563184559345),\n", + " ('Ġmore', 0.005853226874023676),\n", + " ('Ġprimarily', 0.005203146021813154),\n", + " ('Ġstrongly', 0.0034501152113080025),\n", + " ('Ġpartly', 0.0033184229396283627),\n", + " ('Ġmuch', 0.0033095215912908316),\n", + " ('Ġmostly', 0.0032150144688785076),\n", + " ('Ġmainly', 0.0030899408739060163),\n", + " ('Ġfor', 0.003034428460523486),\n", + " ('.', 0.0028878094162791967),\n", + " ('Ġboth', 0.0028405177872627974),\n", + " ('Ġsomewhat', 0.0028194624464958906),\n", + " ('Ġcru', 0.002263976726680994),\n", + " ('Ġas', 0.00221616611815989),\n", + " ('Ġof', 0.0022000609897077084),\n", + " ('Ġalmost', 0.001968063646927476),\n", + " ('Ġat', 0.0018015997484326363),\n", + " ('Ġhighly', 0.0017461496172472835),\n", + " ('Ġcompletely', 0.001692073536105454)]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import torch\n", + "from transformers import GPT2Tokenizer, GPT2LMHeadModel\n", + "tokenizer = GPT2Tokenizer.from_pretrained('gpt2-large')\n", + "model = GPT2LMHeadModel.from_pretrained('gpt2-large')\n", + "text = \"This issue depends\"\n", + "encoded_input = tokenizer(text, return_tensors='pt')\n", + "output = model(**encoded_input)\n", + "next_token_probs = torch.softmax(output[0][:, -1, :][0], dim=0)\n", + "\n", + "next_token_probs\n", + "nb_of_tokens = next_token_probs.size()[0]\n", + "print(nb_of_tokens)\n", + "\n", + "_, top_k_indices = torch.topk(next_token_probs, 30, sorted=True)\n", + "\n", + "words = tokenizer.convert_ids_to_tokens(top_k_indices)\n", + "\n", + "top_probs = []\n", + "\n", + "for ix in range(len(top_k_indices)):\n", + " top_probs.append((words[ix], next_token_probs[top_k_indices[ix]].item()))\n", + "\n", + "top_probs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Zalety tego podejścia:\n", + "\n", + "- prostota,\n", + "- dobra podstawa do strojenia systemów generowania tekstu zwłaszcza\n", + " „otwartego” (systemy dialogowe, generowanie (fake) newsów, streszczanie tekstu),\n", + " ale niekoniecznie tłumaczenia maszynowego,\n", + "- zaskakująca skuteczność przy uczeniu *few-shot* i *zero-shot*.\n", + "\n", + "Wady:\n", + "\n", + "- asymetryczność, przetwarzanie tylko z lewej do prawej, preferencja\n", + " dla lewego kontekstu,\n", + "- mniejsza skuteczność przy dostrajaniu do zadań klasyfikacji i innych zadań\n", + " niepolegających na prostym generowaniu.\n", + "\n", + "Przykłady modeli: GPT, GPT-2, GPT-3, DialoGPT.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Maskowanie słów (BERT)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Inną metodą jest maskowanie słów (*Masked Language Modeling*, *MLM*).\n", + "\n", + "W tym podejściu losowe wybrane zastępujemy losowe słowa specjalnym\n", + "tokenem (`[MASK]`) i każemy modelowi odgadywać w ten sposób\n", + "zamaskowane słowa (z uwzględnieniem również prawego kontekstu!).\n", + "\n", + "Móciąc ściśle, w jednym z pierwszych modeli tego typu (BERT)\n", + "zastosowano schemat, w którym również niezamaskowane słowa są odgadywane (!):\n", + "\n", + "- wybieramy losowe 15% wyrazów do odgadnięcia\n", + "- 80% z nich zastępujemy tokenem `[MASK]`,\n", + "- 10% zastępujemy innym losowym wyrazem,\n", + "- 10% pozostawiamy bez zmian.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# Out[3]:" + ] + } + ], + "source": [ + "from transformers import AutoModelWithLMHead, AutoTokenizer\n", + "import torch\n", + "\n", + "tokenizer = AutoTokenizer.from_pretrained(\"xlm-roberta-large\")\n", + "model = AutoModelWithLMHead.from_pretrained(\"xlm-roberta-large\")\n", + "\n", + "sequence = f'II wojna światowa zakończyła się w {tokenizer.mask_token} roku.'\n", + "\n", + "input_ids = tokenizer.encode(sequence, return_tensors=\"pt\")\n", + "mask_token_index = torch.where(input_ids == tokenizer.mask_token_id)[1]\n", + "\n", + "token_logits = model(input_ids)[0]\n", + "mask_token_logits = token_logits[0, mask_token_index, :]\n", + "mask_token_logits = torch.softmax(mask_token_logits, dim=1)\n", + "\n", + "top_10 = torch.topk(mask_token_logits, 10, dim=1)\n", + "top_10_tokens = zip(top_10.indices[0].tolist(), top_10.values[0].tolist())\n", + "\n", + "for token, score in top_10_tokens:\n", + " print(sequence.replace(tokenizer.mask_token, tokenizer.decode([token])), f\"(score: {score})\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Przykłady: BERT, RoBERTa (również Polish RoBERTa).\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Podejście generatywne (koder-dekoder).\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "System ma wygenerować odpowiedź na różne pytania (również\n", + "odpowiadające zadaniu MLM), np.:\n", + "\n", + "- \"translate English to German: That is good.\" => \"Das ist gut.\"\n", + "- \"cola sentence: The course is jumping well.\" => \"not acceptable\"\n", + "- \"summarize: state authorities dispatched emergency crews tuesday to survey the damage after an onslaught of severe weather in mississippi…\"\n", + " => \"six people hospitalized after a storm in attala county\"\n", + "- \"Thank you for me to your party week.\" => for inviting last \n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from transformers import T5Tokenizer, T5Config, T5ForConditionalGeneration\n", + "\n", + "T5_PATH = 't5-base'\n", + "\n", + "t5_tokenizer = T5Tokenizer.from_pretrained(T5_PATH)\n", + "t5_config = T5Config.from_pretrained(T5_PATH)\n", + "t5_mlm = T5ForConditionalGeneration.from_pretrained(T5_PATH, config=t5_config)\n", + "\n", + "slot = ''\n", + "\n", + "text = f'Warsaw is the {slot} of Poland.'\n", + "\n", + "encoded = t5_tokenizer.encode_plus(text, add_special_tokens=True, return_tensors='pt')\n", + "input_ids = encoded['input_ids']\n", + "\n", + "outputs = t5_mlm.generate(input_ids=input_ids,\n", + " num_beams=200, num_return_sequences=5,\n", + " max_length=5)\n", + "\n", + "_0_index = text.index(slot)\n", + "_result_prefix = text[:_0_index]\n", + "_result_suffix = text[_0_index+len(slot):]\n", + "\n", + "def _filter(output, end_token=''):\n", + " _txt = t5_tokenizer.decode(output[2:], skip_special_tokens=False, clean_up_tokenization_spaces=False)\n", + " if end_token in _txt:\n", + " _end_token_index = _txt.index(end_token)\n", + " return _result_prefix + _txt[:_end_token_index] + _result_suffix\n", + " else:\n", + " return _result_prefix + _txt + _result_suffix\n", + "\n", + "\n", + "results = [_filter(out) for out in outputs]\n", + "results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "(Zob. [https://arxiv.org/pdf/1910.10683.pdf](https://arxiv.org/pdf/1910.10683.pdf))\n", + "\n", + "Przykład: T5, mT5\n", + "\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.9.2" + }, + "org": null + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/wyk/14_pretrenowanie.org b/wyk/14_pretrenowanie.org new file mode 100644 index 0000000..cc46039 --- /dev/null +++ b/wyk/14_pretrenowanie.org @@ -0,0 +1,212 @@ +* Pretrenowanie modeli + +System AlphaZero uczy się grając sam ze sobą — wystarczy 24 godziny, +by system nauczył się grać w szachy lub go na nadludzkim poziomie. + +*Pytanie*: Dlaczego granie samemu ze sobą nie jest dobrym sposobem + nauczenia się grania w szachy dla człowieka, a dla maszyny jest? + +Co jest odpowiednikiem grania samemu ze sobą w świecie przetwarzania tekstu? +Tzn. *pretrenowanie* (/pretraining/) na dużym korpusie tekstu. (Tekst jest tani!) + +Jest kilka sposobów na pretrenowanie modelu, w każdym razie sprowadza +się do odgadywania następnego bądź zamaskowanego słowa. +W każdym razie zawsze stosujemy softmax (być może ze „sztuczkami” takimi jak +negatywne próbkowanie albo hierarchiczny softamx) na pewnej *representecji kontekstowej*: + +$$\vec{p} = \operatorname{softmax}(f(\vec{c})).$$ + +Model jest karany używając funkcji log loss: + +$$-\log(p_j),$$ + +gdzie $w_j$ jest wyrazem, który pojawił się rzeczywiście w korpusie. + +** Przewidywanie słowa (GPT-2) + +Jeden ze sposobów pretrenowania modelu to po prostu przewidywanie +następnego słowa. + +Zainstalujmy najpierw bibliotekę transformers. + +#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer +! pip install transformers +#+END_SRC + +#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer + import torch + from transformers import GPT2Tokenizer, GPT2LMHeadModel + tokenizer = GPT2Tokenizer.from_pretrained('gpt2-large') + model = GPT2LMHeadModel.from_pretrained('gpt2-large') + text = "Warsaw is the capital city of" + encoded_input = tokenizer(text, return_tensors='pt') + output = model(**encoded_input) + next_token_probs = torch.softmax(output[0][:, -1, :][0], dim=0) + + nb_of_tokens = next_token_probs.size()[0] + + _, top_k_indices = torch.topk(next_token_probs, 30, sorted=True) + top_k_indices + # words = tokenizer.convert_ids_to_tokens(top) + + # top_probs = [] + + # for ix in range(len(top)): + # top_probs.append((words[ix], next_token_probs[top[ix]].item())) + + # top_probs +#+END_SRC + +#+RESULTS: +:results: +# Out[8]: +#+BEGIN_EXAMPLE + [('Ġthe', 0.4415026307106018), + ('ĠPoland', 0.236798495054245), + ('ĠBelarus', 0.10114768147468567), + ('ĠUkraine', 0.058283545076847076), + ('Ġeastern', 0.020564062520861626), + ('ĠEastern', 0.011137397028505802), + ('ĠPolish', 0.010205904021859169), + ('ĠWestern', 0.00833223108202219), + ('Ġwestern', 0.006872199941426516), + ('Ġa', 0.004939113277941942), + ('ĠSlovakia', 0.003553805174306035), + ('ĠLithuania', 0.003335304092615843), + ('ĠRussia', 0.002872465644031763), + ('Ġcentral', 0.002493523992598057), + ('Ġmodern', 0.0022767107002437115), + ('ĠCzech', 0.0022264323197305202), + ('ĠPr', 0.002146221464499831), + ('Ġformer', 0.0021054286044090986), + ('Ġwhat', 0.0017435317859053612), + ('ĠSlov', 0.0014634730760008097), + ('ĠUkrainian', 0.0014347084797918797), + ('ĠCentral', 0.0013676199596375227), + ('ĠSouth', 0.0013484350638464093), + ('Ġone', 0.001204205909743905), + ('ĠNorthern', 0.0011802552035078406), + ('ĠWest', 0.001175572513602674), + ('ĠEast', 0.0011596156982704997), + ('Ġsouthern', 0.0011580033460631967), + ('Ġnorthern', 0.001110077602788806), + ('Ġ"', 0.0010494199814274907)] +#+END_EXAMPLE +:end: + +Zalety tego podejścia: + +- prostota, +- dobra podstawa do strojenia systemów generowania tekstu zwłaszcza + „otwartego” (systemy dialogowe, generowanie (fake) newsów, streszczanie tekstu), + ale niekoniecznie tłumaczenia maszynowego, +- zaskakująca skuteczność przy uczeniu /few-shot/ i /zero-shot/. + +Wady: + +- asymetryczność, przetwarzanie tylko z lewej do prawej, preferencja + dla lewego kontekstu, +- mniejsza skuteczność przy dostrajaniu do zadań klasyfikacji i innych zadań + niepolegających na prostym generowaniu. + +Przykłady modeli: GPT, GPT-2, GPT-3, DialoGPT. + +** Maskowanie słów (BERT) + +Inną metodą jest maskowanie słów (/Masked Language Modeling/, /MLM/). + +W tym podejściu losowe wybrane zastępujemy losowe słowa specjalnym +tokenem (~[MASK]~) i każemy modelowi odgadywać w ten sposób +zamaskowane słowa (z uwzględnieniem również prawego kontekstu!). + +Móciąc ściśle, w jednym z pierwszych modeli tego typu (BERT) +zastosowano schemat, w którym również niezamaskowane słowa są odgadywane (!): + +- wybieramy losowe 15% wyrazów do odgadnięcia +- 80% z nich zastępujemy tokenem ~[MASK]~, +- 10% zastępujemy innym losowym wyrazem, +- 10% pozostawiamy bez zmian. + + +#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer +from transformers import AutoModelWithLMHead, AutoTokenizer +import torch + +tokenizer = AutoTokenizer.from_pretrained("xlm-roberta-large") +model = AutoModelWithLMHead.from_pretrained("xlm-roberta-large") + +sequence = f'II wojna światowa zakończyła się w {tokenizer.mask_token} roku.' + +input_ids = tokenizer.encode(sequence, return_tensors="pt") +mask_token_index = torch.where(input_ids == tokenizer.mask_token_id)[1] + +token_logits = model(input_ids)[0] +mask_token_logits = token_logits[0, mask_token_index, :] +mask_token_logits = torch.softmax(mask_token_logits, dim=1) + +top_10 = torch.topk(mask_token_logits, 10, dim=1) +top_10_tokens = zip(top_10.indices[0].tolist(), top_10.values[0].tolist()) + +for token, score in top_10_tokens: + print(sequence.replace(tokenizer.mask_token, tokenizer.decode([token])), f"(score: {score})") +#+END_SRC + +#+RESULTS: +:results: +# Out[3]: +:end: + + +Przykłady: BERT, RoBERTa (również Polish RoBERTa). + +** Podejście generatywne (koder-dekoder). + +System ma wygenerować odpowiedź na różne pytania (również +odpowiadające zadaniu MLM), np.: + +- "translate English to German: That is good." => "Das ist gut." +- "cola sentence: The course is jumping well." => "not acceptable" +- "summarize: state authorities dispatched emergency crews tuesday to survey the damage after an onslaught of severe weather in mississippi..." + => "six people hospitalized after a storm in attala county" +- "Thank you for me to your party week." => for inviting last + +#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer +from transformers import T5Tokenizer, T5Config, T5ForConditionalGeneration + +T5_PATH = 't5-base' + +t5_tokenizer = T5Tokenizer.from_pretrained(T5_PATH) +t5_config = T5Config.from_pretrained(T5_PATH) +t5_mlm = T5ForConditionalGeneration.from_pretrained(T5_PATH, config=t5_config) + +slot = '' + +text = f'Warsaw is the {slot} of Poland.' + +encoded = t5_tokenizer.encode_plus(text, add_special_tokens=True, return_tensors='pt') +input_ids = encoded['input_ids'] + +outputs = t5_mlm.generate(input_ids=input_ids, + num_beams=200, num_return_sequences=5, + max_length=5) + +_0_index = text.index(slot) +_result_prefix = text[:_0_index] +_result_suffix = text[_0_index+len(slot):] + +def _filter(output, end_token=''): + _txt = t5_tokenizer.decode(output[2:], skip_special_tokens=False, clean_up_tokenization_spaces=False) + if end_token in _txt: + _end_token_index = _txt.index(end_token) + return _result_prefix + _txt[:_end_token_index] + _result_suffix + else: + return _result_prefix + _txt + _result_suffix + + +results = [_filter(out) for out in outputs] +results +#+END_SRC + +(Zob. https://arxiv.org/pdf/1910.10683.pdf) + +Przykład: T5, mT5 diff --git a/wyk/bpe.png b/wyk/bpe.png new file mode 100644 index 0000000..5e0e835 Binary files /dev/null and b/wyk/bpe.png differ diff --git a/wyk/ie-gener.drawio b/wyk/ie-gener.drawio new file mode 100644 index 0000000..7e34f99 --- /dev/null +++ b/wyk/ie-gener.drawio @@ -0,0 +1 @@ +3Vjbbts4EP0aAe2DF9bNiR9jO9lisVekQLF9Y8SxRJsSVYqKrHz9Di+SJctF0m6TtvGDRB2RM+QcnhnKXrjOD79KUmZ/CArcC+b04IUbLwiWkY9XDbQWiMPQAqlk1EL+EbhlD+DAuUNrRqEadVRCcMXKMZiIooBEjTAipWjG3baCj72WJIUJcJsQPkU/MKoyi14GF0f8HbA06zz7i6V9k5Ous1tJlREqmgEUXnvhWgqhbCs/rIHr2HVxseNuPvO2n5iEQj1lgPgrjt7/+du/G76THw8f5Wyxv545K/eE127BXrDgaG+V4atFqlsdUpWk0OtQrQvO4lOtJ7+6I8k+laIu6CwRXEgvvMJ+Mr17E8Sxp6e1xutJ++1xfOfoHRDKirTzh0uxLsfTQHg6t/LFJ/u7kJDr8JZVre/UGEOPTOmtl4Oyo3FjVrgxQdX6LS6wZBVLzDLnwJnrVQE1NjTI6ioX+lFBXhqbrEgYZbQutOlaXzi5Q/+6t+p8g9l0aUG0G84+1eSXxyM5iFswCleg4GC2gco5Aj42t4zztQ3ZphAF2CibpYSb+Pj0XpQIzDB84arJmIJbxLXVBjMEYuIe5JYbKWSMUigQM4SA3ql6VKWk2EPnywvCufn100QDOL3P6sDv1YVZCQRSIVvs0g24dIJ0GclfuufmqO8gclg20rYDicspaW/7KDtsOOV9gQqDiQoV7CvNbAM7bx17y1XCRNM+gaRx6BxNZ5jDHZJi4DcJRg0QX+mYMsx7V+5FjtRoN2cpHNP1LUjxHyflHCfBc1ESnkmMJ8HHhF7q5pbD4UqXGgwFFNQ1NwknFSp9TI81AnRSdB4N1CAQ8ZlAdJgEThS7H5s/Fx3n4W/BTFZxPERRPOIBpTc2UYlaJuBGDavNiaHYf8SQIjIFNTFkyOqX/fX8RRP+tKIk2Sc7nSBNlnwgFaEPDEymtymRdfkQiZwpMcPbIFuyL8iWP6kQo8WJEC++sxCXEyK7SquLXV19XeH6llF/1moVh2M++kAP+Fi+JB9doh4Q0rR9kYJOLXey08qbprXSy0RDCjDHma2QOUl28Pb1Cik+PWYspsT1Ve9lmDtX0yxblN13dP1Tm7MnVP3B0gtvpqwOsuJg8P8TI4et+oGlODk4Rk88OD4fo9MqV7aKFFqGN6DaPQNFXq/E/PkJIeF3rlXdmWeUGwfJrzUfdfZkb0ia53XF9A0f1pF3eWE84PLb5KHAoz9GQ1QKv6rsV+KO7PXqrJVByn2tBMfLk+J3+Xw5FB+P/8XYU+jxD63w+j8= \ No newline at end of file diff --git a/wyk/ie-gener.png b/wyk/ie-gener.png new file mode 100644 index 0000000..8b2dc9f Binary files /dev/null and b/wyk/ie-gener.png differ diff --git a/wyk/ie-seqlab.drawio b/wyk/ie-seqlab.drawio new file mode 100644 index 0000000..68b1b48 --- /dev/null +++ b/wyk/ie-seqlab.drawio @@ -0,0 +1 @@ +7Vhtk5MwEP41zOiHOhQKd/14bc+3Uet4zjj6LSVbyDUQDOEo/no3EEp5uempd+qc9kMbHpLd7PNkl6WWu4z3LyRJo7eCArccm+4td2U5znw2xW8NlDXguW4NhJLRGpq2wBX7Bga0DZozCllnohKCK5Z2wUAkCQSqgxEpRdGdthW86zUlIQyAq4DwIfqJURXV6Llz1uIvgYVR43nqz+s7MWkmm0iyiFBRHEHupeUupRCqHsX7JXDNXcNLve75LXcPG5OQqLssEGtv9vHd688rfi2/7L/Iib+7nBgrN4TnJmDL8TnaW0R4yw/1qEGylCQ6DlUacvyvud78YkOCXShFntBJILiQlnuB82S4eeJ4nqW3tcTv3vhpu75x9BIIZUnY+MNQapfdbSA83Fv62zf7RkiINb1plutfWhlDj0zpoxeDqlfjwczwYILK9V0MMGUZC6owbeDMzMqAVjY0yPIsFvpSQZxWNlkSMMponmjTuf7iZIP+9WzV+Ibq0IUJ0W44+5qTZ6eZPOLN6dDlKNhXx0DFHIEpDreM82VN2SoRCdQsV6G4K6+9+ihSBCZIn7soIqbgCnFttcAKgZi4AbnlVSpEjFJIEKsEAX1S9apMSbGDxpfluHb1OWwTDeD2bs2D6SG7sCqBQClkiVOaBecmIU1Fmvrmumjz25kZLOrktgGJqSnhwXabdjgwmfcDWegMslDBLtPKFnBtLT1rvgiYKMo7iNSlzsg0ohyekBCJXwXIGiC+0JwyrHsX5kaM0mg3oxJ25boPUaY9Uc6Gooxp4jyUJO5IYeyRjwU91cMth/2FftQgFZBQM1wFnGSY6V15aiNABw+dk0QdEeGNENFgEjhR7KZrfowd4+G9YFVVMTrMvK4OA4IzkcsAzKrjp03PkOecMKSIDEENDFViHcL+ef1mA/1AlTsGShQkYfB4M2nm/2WZ5P1vMR5Bi/EAQXvTkZj7rcwtOvU7nI3sq7MVVSjbOttaR0ssYAx0yO+gGPrHXxLrxEw2WTp6ba9Pz7nLmtswezFZrd+sP+Do1WG0PmKijqwb7TD+/z3fUVH0vDv0fP5Yzzd7qLLo/5sNxrl7Tw3G3P6zDcbZQL9Uikzp0lWUVGzKQHcZdtXI54+33Tif/WXtxnygS/+R8jNF7z5Zf9BKN+/nl+0N9Jj/Tj2aF7sjQYry8FILwwfYk6LUOSNJpFt1qHqTrZAxCa7h6eNNpPnZ6UfU4S35F5XDy/Z/x7ogtn/eupffAQ== \ No newline at end of file diff --git a/wyk/ie-seqlab.png b/wyk/ie-seqlab.png new file mode 100644 index 0000000..79bd0fe Binary files /dev/null and b/wyk/ie-seqlab.png differ diff --git a/wyk/rnn-seq.drawio b/wyk/rnn-seq.drawio new file mode 100644 index 0000000..431f885 --- /dev/null +++ b/wyk/rnn-seq.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/wyk/rnn-seq.png b/wyk/rnn-seq.png new file mode 100644 index 0000000..1d2c06d Binary files /dev/null and b/wyk/rnn-seq.png differ diff --git a/wyk/rnn.drawio b/wyk/rnn.drawio new file mode 100644 index 0000000..826eb1a --- /dev/null +++ b/wyk/rnn.drawio @@ -0,0 +1 @@ +5Vltj5s4EP41SO2HXcUYwuZjQ3J3H1pdpb3TtffNBRfcAI6M88L++hsH8w45qk2W7FbarDyPX7Bn5pkZjIHd+Pi7INvwE/dpZJgz/2jglWGaCwvBfwVkOYDmjpkjgWC+xirgkT1RDc40umM+TRsDJeeRZNsm6PEkoZ5sYEQIfmgO+86j5lO3JKAd4NEjURf9h/kyzNEH06nwPygLwuLJaL7Ie2JSDNZLpCHx+SGHTofDawO7gnOZt+KjSyOlvEIvuQZ+G+gtNyZoIsdM+Fu6u39ttrGyB2edrZxPf338cadX2ZNopw+sNyuzQgOC7xKfqkVmBl4eQibp45Z4qvcANgcslHEEEoKmXo4KSY+D+0Tl6cFtKI+pFBkM0ROwpU1fuAzW8qHSP9JnnYV13TsaJNrmQbl2pRZoaM38hJbmHaVQH7xEi1zIkAc8IdG6QpdNtVVjPnK+1cr6QaXMtMuTneRNVdIjk1/U9HtbS1/1Yqq9OtaFrBASOG5tkhK/FuspoZp2kop5+fnUoc4bDXTAd8KjZ3SlHUgSEVB5ZhzudwJBIyLZvrmPixvU7HF7C/6kYa83ebNjcvBo2bRQKgXfUJdHXACS8ETZ/TuLohZEIhYkIHqgTAr4UvGDQYz5oDti5vsnp+ljV9ORLkGwhybB8KJLMKuHX/ha9LJfC70uSBM8kiZoPiVPcIcnNP5GfZ8lAcCG7b6D3556hrPcG87q3Yk/7wF7fyaNoJdJIwjdm3bT0a2eTOI49/YL+nqRuKZxdlR39dLx/y+X1DNJLbFcP5c4r4IkzkAySYEMQIwNFFnO6q0nlaL6LLhmWvaopGJei2jIPmOWN5/jbXxj5ig5f/NJ/gZqaDQfG/icKQNfsc0axQ5EpPJADHMeKSJ9E9AKVCtiCeOqY+qywG7WBGZPTbB4yeIXLV4fMSYrCNDoisCekhgmfn02nS7YjbVpfjc1WbAbKvPgbSdlQUwGSorJA9xsXIC7Xubv6uRGyXBBp362r+qpnzmDR5fGtWatu9FZy2o5ifSsluHKbTzDlkMXZ3/eqPejken9et4/9BqS/QqvIe2rRmfcTePVrIFHfO/wdmJfeixN/A/qK5JSaUTSlHnPDzM+ScPyAUr4TCQYKzkh5gxdOL0OVEI1E9g9JiiwZwYsu/Uxp2PafP+dgNVZyGwvZI2LfGA9ktWGbdWA9MyGF/0bHtqX3YrId/b58e1z3KHGeGjkO75o2MbdsJ1uxZPhWsZiYbi2sXRpwiiMeIJfQlKZd22TLAZgA7GHUdXynkjKDwp8sxEL99uncWd8oZgFYvX5N7d19REdr/8D \ No newline at end of file diff --git a/wyk/rnn.png b/wyk/rnn.png new file mode 100644 index 0000000..c082f76 Binary files /dev/null and b/wyk/rnn.png differ diff --git a/wyk/word-distribution.png b/wyk/word-distribution.png new file mode 100644 index 0000000..6622d70 Binary files /dev/null and b/wyk/word-distribution.png differ