From 89155edea0eccd5435b6c29839470c1c633c3519 Mon Sep 17 00:00:00 2001 From: Filip Gralinski Date: Sat, 12 Jun 2021 15:47:37 +0200 Subject: [PATCH] Up --- wyk/11_rnn.ipynb | 523 ++++++++++++++++++++++++++++++- wyk/11_rnn.org | 12 +- wyk/12_bpe.ipynb | 201 ++++++------ wyk/12_bpe.org | 30 +- wyk/13_generative_approach.ipynb | 114 ++++++- wyk/word-distribution.png | Bin 3983 -> 6402 bytes 6 files changed, 764 insertions(+), 116 deletions(-) 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 index 9a04687..0220f04 100644 --- a/wyk/12_bpe.ipynb +++ b/wyk/12_bpe.ipynb @@ -93,7 +93,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "3844535\r\n" + "3844535\n" ] } ], @@ -109,7 +109,7 @@ "\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 w PoliMorfie. Jak w sposób systematyczny szukać takich wyrazów?\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" @@ -124,26 +124,26 @@ "name": "stdout", "output_type": "stream", "text": [ - "niemałomieszczańskiej\r\n", - "kwatereczki\r\n", - "słowniejszej\r\n", - "oranżysta\r\n", - "myrmekofagów\r\n", - "hipokratesowego\r\n", - "rozdziałująca\r\n", - "wielosettysięczne\r\n", - "redempcyjno\r\n", - "łącznikowce\r\n", - "niesłowacyzowana\r\n", - "sosnowieckościach\r\n", - "niewschodoznawczy\r\n", - "niekłosokształtnego\r\n", - "niegenialności\r\n", - "Gierowskiego\r\n", - "nieumierzwiających\r\n", - "bezzakłóceniowości\r\n", - "niedziurkowatościach\r\n", - "Krzaklewskich\r\n" + "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" ] } ], @@ -195,56 +195,56 @@ "name": "stdout", "output_type": "stream", "text": [ - "ˆ\r\n", - "ˇ\r\n", - "゚\r\n", - "a\r\n", - "A\r\n", - "á\r\n", - "Á\r\n", - "à\r\n", - "À\r\n", - "ă\r\n", - "Ă\r\n", - "â\r\n", - "Â\r\n", - "å\r\n", - "Å\r\n", - "ä\r\n", - "Ä\r\n", - "Ã\r\n", - "ā\r\n", - "aa\r\n", - "aA\r\n", - "Aa\r\n", - "AA\r\n", - "aĂ\r\n", - "AĂ\r\n", - "aâ\r\n", - "aÂ\r\n", - "Aâ\r\n", - "aÅ\r\n", - "aÄ\r\n", - "ª\r\n", - "aaa\r\n", - "aAa\r\n", - "Aaa\r\n", - "AaA\r\n", - "AAa\r\n", - "AAA\r\n", - "aaaa\r\n", - "aAaa\r\n", - "Aaaa\r\n", - "AaAa\r\n", - "AAaa\r\n", - "AAAa\r\n", - "AAAA\r\n", - "aaaaa\r\n", - "Aaaaa\r\n", - "AaaaA\r\n", - "AAaaa\r\n", - "AAAAA\r\n", - "aaaaaa\r\n" + "ˆ\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" ] } ], @@ -261,7 +261,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "2974556 vocab.txt\r\n" + "2974556 vocab.txt\n" ] } ], @@ -281,14 +281,14 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "81380\r\n" + "81380\n" ] } ], @@ -324,14 +324,15 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "# Out[8]:" + "sort: błąd zapisu: 'standardowe wyjście': Przerwany potok\n", + "sort: błąd zapisu\n" ] } ], @@ -366,7 +367,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -375,13 +376,13 @@ "'word-distribution.png'" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -404,7 +405,7 @@ " for line in fh:\n", " m = re.match(r'\\s*(\\d+)', line)\n", " if m:\n", - " freqs.append(int(m.group(1)))\n", + " freqs.append(log(float(m.group(1))))\n", "\n", "plt.plot(range(len(freqs)), freqs)\n", "fname = 'word-distribution.png'\n", @@ -531,7 +532,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -540,7 +541,7 @@ "['e$', 'to', 'to$', 'be$', 't$', 'th', 'or', 'or$', 'no', 'not$']" ] }, - "execution_count": 9, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -587,16 +588,16 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'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 $'" ] }, - "execution_count": 10, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -606,13 +607,17 @@ " d = list(d.replace(' ', '$') + '$')\n", " vocab_set = set(vocab)\n", "\n", - " ix = 0\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", - " else:\n", - " ix += 1\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", @@ -637,16 +642,16 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'to m $ w i l l $ b e$ th e$ b e s t$'" + "'to m $ w i l l $ be$ th e$ b e s t$'" ] }, - "execution_count": 11, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -785,7 +790,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -830,5 +835,5 @@ "org": null }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 4 } diff --git a/wyk/12_bpe.org b/wyk/12_bpe.org index 17adf4e..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. @@ -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 index 11c1953..8234d7a 100644 --- a/wyk/13_generative_approach.ipynb +++ b/wyk/13_generative_approach.ipynb @@ -1 +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\nMoż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\nTo podejście może się wydawać trudniejsze niż etykietowanie sekwencji, ale wystarczająco zaawansowanej architekturze sieci, jest wykonalne.\n\nZalety:\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\nIdea atencji polega na tym, że sieć może kierować selektywnie „snop” uwagi na wyrazy na wejściu lub do tej pory wygenerowane wyrazy.\n\nMechanizm 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\naby 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\nNajpierw 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\nW 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\nPowtó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\nWektor 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":{"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": [ + "## 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/word-distribution.png b/wyk/word-distribution.png index 413a6461bd96655dd437bbc13111c4b97236a660..6622d701ef3e98e79f847feceab223865b92e96c 100644 GIT binary patch literal 6402 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 B&hP*L literal 3983 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 NAbR