Merge branch 'master' of git.wmi.amu.edu.pl:filipg/aitech-eks

This commit is contained in:
kubapok 2021-06-02 13:27:45 +02:00
commit af907e23af
6 changed files with 882 additions and 244 deletions

File diff suppressed because one or more lines are too long

View File

@ -840,7 +840,7 @@ Można spróbować uzyskać bogatszą reprezentację dla słowa biorąc pod uwag
- długość słowa - długość słowa
- kształt słowa (/word shape/), np. czy pisany wielkimi literami, czy składa się z cyfr itp. - kształt słowa (/word shape/), np. czy pisany wielkimi literami, czy składa się z cyfr itp.
- n-gramy znakowe wewnątrz słowa (np. słowo /Kowalski/ można zakodować jako sumę wektorów - n-gramy znakowe wewnątrz słowa (np. słowo /Kowalski/ można zakodować jako sumę wektorów
trigramów znakówych $\vec{v}(Kow) + \vec{v}(owa) + \vec{v}(wal) + \vec{v}(als) + \vec{v}(lsk) + + \vec{v}(ski)$ trigramów znakówych $\vec{v}(Kow) + \vec{v}(owa) + \vec{v}(wal) + \vec{v}(als) + \vec{v}(lsk) + \vec{v}(ski)$
Cały czas nie rozpatrujemy jednak w tej metodzie kontekstu wyrazu. Cały czas nie rozpatrujemy jednak w tej metodzie kontekstu wyrazu.
(/Renault/ w pewnym kontekście może być nazwą firmy, w innym — (/Renault/ w pewnym kontekście może być nazwą firmy, w innym —
@ -852,7 +852,7 @@ Za pomocą wektora można przedstawić nie pojedynczy token $t^k$, lecz
cały kontekst, dla /okna/ o długości $c$ będzie to kontekst $t^{k-c},\dots,t^k,\dots,t^{k+c}$. cały kontekst, dla /okna/ o długości $c$ będzie to kontekst $t^{k-c},\dots,t^k,\dots,t^{k+c}$.
Innymi słowy klasyfikujemy token na podstawie jego samego oraz jego kontekstu: Innymi słowy klasyfikujemy token na podstawie jego samego oraz jego kontekstu:
$$p(l^k=j) = \frac{e^{(W\vec{v}(t^{k-c},\dots,t^k,\dots,t^{k+c}))_j}}{Z_k}.$$ $$p(l^k=j) = \frac{e^{(W\vec{v}(t^{k-c},\dots,t^k,\dots,t^{k+c}))_j}}{Z}.$$
Zauważmy, że w tej metodzie w ogóle nie rozpatrujemy sensowności Zauważmy, że w tej metodzie w ogóle nie rozpatrujemy sensowności
sekwencji wyjściowej (etykiet), np. może być bardzo mało sekwencji wyjściowej (etykiet), np. może być bardzo mało
@ -861,12 +861,12 @@ prawdopodobne, że bezpośrednio po nazwisku występuje data.
Napiszmy wzór określający prawdopodobieństwo całej sekwencji, nie Napiszmy wzór określający prawdopodobieństwo całej sekwencji, nie
tylko pojedynczego tokenu. Na razie będzie to po prostu iloczyn poszczególnych wartości. tylko pojedynczego tokenu. Na razie będzie to po prostu iloczyn poszczególnych wartości.
$$p(l) = \prod_{k=1}^K \frac{e^{(W\vec{v}(t^{k-c},\dots,t^k,\dots,t^{k+c}))_{l^k}}}{Z_k} = \frac{e^{\sum_{k=1}^K (W\vec{v}(t^{k-c},\dots,t^k,\dots,t^{k+c}))_{l^k}}}{\prod_{k=1}^K Z_k}$$ $$l = (l^1,\\dots,l^k), p(l) = \prod_{k=1}^K \frac{e^{(W\vec{v}(t^{k-c},\dots,t^k,\dots,t^{k+c}))_{l^k}}}{Z_k} = \frac{e^{\sum_{k=1}^K (W\vec{v}(t^{k-c},\dots,t^k,\dots,t^{k+c}))_{l^k}}}{\prod_{k=1}^K Z_k}$$
Reprezentacja kontekstu może być funkcją embeddingów wyrazów Reprezentacja kontekstu może być funkcją embeddingów wyrazów
(zakładamy, że embedding nie zależy od pozycji słowa). (zakładamy, że embedding nie zależy od pozycji słowa).
$$\vec{v}(t^{k-c},\dots,t^k,\dots,t^{k+c}) = f(\vec{E}(t^{k-c}),\dots,\vec{E}(t^k),\dots,\vec{E}({t^{k+c}})$$ $$\vec{v}(t^{k-c},\dots,t^k,\dots,t^{k+c}) = f(\vec{E}(t^{k-c}),\dots,\vec{E}(t^k),\dots,\vec{E}({t^{k+c}}))$$
** Warunkowe pola losowe ** Warunkowe pola losowe
@ -934,6 +934,8 @@ Aby uzyskać cały ciąg, kierujemy się /wstecz/ używając wskaźników:
$$y^i = b[i, y^{i+1}]$$ $$y^i = b[i, y^{i+1}]$$
[[./crf-viterbi.png]]
*** Złożoność obliczeniowa *** Złożoność obliczeniowa
Zauważmy, że rozmiar tabel $s$ i $b$ wynosi $K \times |L|$, a koszt Zauważmy, że rozmiar tabel $s$ i $b$ wynosi $K \times |L|$, a koszt
@ -985,10 +987,10 @@ macierzy $W$ i $V$ (samego procesu uczenia nie pokazujemy tutaj):
import torch.nn as nn import torch.nn as nn
matrixW = torch.tensor( matrixW = torch.tensor(
[[-1., 3.0, 3.0], [[-1., 3.0, 3.0], # C
[0., 2.0, -2.0], [0., 2.0, -2.0], # P
[4., -2.0, 3.0]]) [4., -2.0, 3.0]]) # R
# Ala ma powieść
# rozkład prawdopodobieństwa, gdyby patrzeć tylko na słowo # rozkład prawdopodobieństwa, gdyby patrzeć tylko na słowo
nn.functional.softmax(matrixW @ onehot['powieść'], dim=0) nn.functional.softmax(matrixW @ onehot['powieść'], dim=0)
#+END_SRC #+END_SRC
@ -1005,9 +1007,10 @@ macierzy $W$ i $V$ (samego procesu uczenia nie pokazujemy tutaj):
import torch.nn as nn import torch.nn as nn
matrixV = torch.tensor( matrixV = torch.tensor(
[[-0.5, 1.5, 2.0], [[-0.5, 1.5, 2.0], # C
[0.5, 0.8, 2.5], [0.5, 0.8, 2.5], # P
[2.0, 0.8, 0.2]]) [2.0, 0.8, 0.2]]) # R
# C P R
# co występuje po przymiotniku? - rozkład prawdopodobieństwa # co występuje po przymiotniku? - rozkład prawdopodobieństwa
nn.functional.softmax(matrixV[1], dim=0) nn.functional.softmax(matrixV[1], dim=0)

1
wyk/11_rnn.ipynb Normal file

File diff suppressed because one or more lines are too long

288
wyk/11_rnn.org Normal file
View File

@ -0,0 +1,288 @@
* Rekurencyjne sieci neuronowe
** Inne spojrzenie na sieci przedstawione do tej pory
*** Regresja liniowa/logistyczna lub klasyfikacja wieloklasowa na całym tekście
W regresji liniowej czy logistycznej bądź w klasyfikacji wieloklasowej
(z funkcją Softmax) stosowaliśmy następujący schemat:
Do tej pory patrzyliśmy na to tak, że po prostu cały tekst jest od
razu przetwarzany przez (prostą) sieć neuronową, popatrzmy na ten
przypadek, jak na sytuację przetwarzania sekwencyjnego. Będzie to
trochę sztuczne, ale uogólnimy to potem w sensowny sposób.
**** Wektoryzacja
Po pierwsze, zauważmy, że w wielu schematach wektoryzacji (np. tf), wektor
dokumentów jest po prostu sumą wektorów poszczególnych składowych:
$$\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),$$
gdzie w schemacie tf \vec{v}(t^i) to po prostu wektor /one-hot/ dla słowa.
*Pytanie* Jak postać przyjmie w \vec{v}(t^i) dla wektoryzacji tf-idf?
Wektory $\vec{v}(t^k)$ mogą być również gęstymi wektorami
($\vec{v}(t^k) \in \mathcal{R}^n$, gdzie $n$ jest rzędu 10-1000), np.
w modelu Word2vec albo mogą to być *wyuczalne* wektory (zanurzenia
słów, /embeddings/), tzn. wektory, które są parametrami uczonej sieci!
*Pytanie* Ile wag (parametrów) wnoszą wyuczalne wektory do sieci?
**** Prosta wektoryzacja wyrażona w modelu sekwencyjnym
Jak zapisać równoważnie powyższą wektoryzację w modelu *sekwencyjnym*, tj. przy założeniu, że
przetwarzamy wejście token po tokenie (a nie „naraz”)? Ogólnie wprowadzimy bardzo
ogólny model sieci *rekurencyjnej*.
Po pierwsze zakładamy, że sieć ma pewien stan $\vec{s^k} \in
\mathcal{R}^m$ (stan jest wektorem o długości $m$), który może
zmieniać się z każdym krokiem (przetwarzanym tokenem). Zmiana stanu
jest określona przez pewną funkcję $R : \mathcal{R}^m \times
\mathcal{R}^n \rightarrow \mathcal{R}^m$ ($n$ to rozmiar wektorów
$\vec{v}(t^k)$):
$$\vec{s^k} = R(\vec{s^{k-1}}, \vec{v}(t^k)).$$
W przypadku wektoryzacji tf-idf mamy do czynienia z prostym
sumowaniem, więc $R$ przyjmuje bardzo prostą postać:
$$\vec{s^0} = [0,\dots,0],$$
$$R(\vec{s}, \vec{x}) = \vec{s} + \vec{x}.$$
**** Wyjście z modelu
Dla regresji liniowej/logistycznej, oprócz funkcji $R$, która określa
zmianę stanu, potrzebujemy funkcji $O$, która określa wyjście systemu w każdym kroku.
$$y^k = O(\vec{s^k})$$
W zadaniach klasyfikacji czy regresji, kiedy patrzymy na cały tekst w
zasadzie wystarczy wziąć /ostatnią/ wartość (tj. $y^K$). Można sobie
wyobrazić sytuację, kiedy wartości $y^k$ dla $k < k$ również mogą być jakoś przydatne
(np. klasyfikujemy na bieżąco tekst wpisywany przez użytkownika).
W każdym razie dla regresji liniowej funkcja $O$ przyjmie postać:
$$O(\vec{s}) = \vec{w}\vec{s}$$,
gdzie $\vec{w}$ jest wektorem wyuczylnych wag, dla regresji zaś logistycznej:
$$O(\vec{s}) = \operatorname{softmax}(\vec{w}\vec{s})$$
*Pytanie*: jaką postać przyjmie $O$ dla klasyfikacji wieloklasowej
** Prosta sieć rekurencyjna
W najprostszej sieci rekurencyjnej (/Vanilla RNN/, sieć Elmana,
czasami po prostu RNN) w każdym kroku oprócz właściwego wejścia
($\vec{v}(t^k)$) będziemy również podawać na wejściu poprzedni stan
sieci ($\vec{s^{k-1}}$).
Innymi słowy, funkcje $R$ przyjmie następującą postać:
$$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,
- $b \in \mathcal{R}^m$ — wektor obciążeń (/biases/).
Taką sieć RNN można przedstawić schematycznie w następujący sposób:
[[./img-rnn.png]]
Zauważmy, że zamiast macierzy $W$ działającej na konkatenacji wektorów można wprowadzić dwie
macierze $U$ i $V$ i tak zapisać wzór:
$$s^k = \sigma(U\vec{v}(t^k) + V\vec{s^{k-1}} + \vec{b}).$$
Jeszcze inne spojrzenie na sieć RNN:
[[./rnn.png]]
Powyższy rysunek przedstawia pojedynczy krok sieci RNN. Dla całego
wejścia (powiedzmy, 3-wyrazowego) możemy sieć rozwinąć (/unroll/):
[[./rnn-seq.png]]
*** Zastosowanie sieci RNN do etykietowania sekwencji
*** Problemy z prostymi sieciami RNN
W praktyce proste sieci RNN są bardzo trudne w uczenia, zazwyczaj
pojawia się problem *zanikających* (rzadziej: *eksplodujących*)
gradientów: w propagacji wstecznej błąd szybko zanika i nie jest w
stanie dotrzeć do początkowych wejść.
** Sieci RNN z bramkami
W prostych sieciach RNN podstawowa trudność polega na tym, że mamy
niewielką kontrolę nad tym jak pamięć (stan) jest aktualizowana. Aby
zwiększyć tę kontrolę, potrzebujemy *bramek*.
*** Bramki
Zazwyczaj do tej pory rozpatrywaliśmy iloczyn skalarny wektorów, w
wyniku którego otrzymujemy liczbę (w PyTorchu wyrażany za pomocą operatora ~@~), np.
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
import torch
a = torch.tensor([-1, 0, 3])
b = torch.tensor([2, 5, -1])
a @ b
#+END_SRC
#+RESULTS:
:results:
# Out[2]:
: tensor(-5)
:end:
Czasami przydatny jest *iloczyn Hadamarda*, czyli przemnożenie
wektorów (albo macierzy) po współrzędnych. W PyTorchu taki iloczyn
wyrażany jest za pomocą operatora ~*~, w notacji matematycznej będziemy używali
znaku $\odot$.
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
import torch
a = torch.tensor([-1, 0, 3])
b = torch.tensor([2, 5, -1])
a * b
#+END_SRC
#+RESULTS:
:results:
# Out[3]:
: tensor([-2, 0, -3])
:end:
Zauważmy, że iloczyn Hadamarda przez wektor złożony z zer i jedynek daje nam /filtr/, możemy
selektywnie wygaszać pozycje wektora, np. tutaj wyzerowaliśmy 2. i 5. pozycję wektora:
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
import torch
a = torch.tensor([1., 2., 3., 4., 5.])
b = torch.tensor([1., 0., 1., 1., 0.])
a * b
#+END_SRC
#+RESULTS:
:results:
# Out[4]:
: tensor([1., 0., 3., 4., 0.])
:end:
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
$\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}$$
Na przykład, za pomocą bramki można wpisać nową wartość na 2. i 5. pozycję wektora.
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
import torch
s = torch.tensor([1., 2., 3., 4., 5.])
x = torch.tensor([8., 7., 15., -3., -8.])
g = torch.tensor([0., 1., 0., 0., 1.])
s = g * x + (1 - g) * s
s
#+END_SRC
#+RESULTS:
:results:
# Out[8]:
: tensor([ 1., 7., 3., 4., -8.])
:end:
Wektor bramki nie musi być z góry określony, może być wyuczalny. Wtedy
jednak lepiej założyć, że bramka jest „miękka”, np. jej wartości
pochodzi z sigmoidy zastosowanej do jakiejś wcześniejszej warstwy.
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
import torch
s = torch.tensor([1., 2., 3., 4., 5.])
x = torch.tensor([8., 7., 15., -3., -8.])
pre_g = torch.tensor([-2.5, 10.0, -1.2, -101., 1.3])
g = torch.sigmoid(pre_g)
s = g * x + (1 - g) * s
s
#+END_SRC
#+RESULTS:
:results:
# Out[14]:
: tensor([ 1.5310, 6.9998, 5.7777, 4.0000, -5.2159])
:end:
*Pytanie:* dlaczego sigmoida zamiast tanh?
*** Sieć LSTM
Architektura LSTM (/Long Short-Term Memory/) pozwala rozwiązać problem
znikających gradientów — za cenę komplikacji obliczeń.
W sieci LSTM stan $\vec{s^k}$ ma dwie połówki, tj. $\vec{s^k} =
\langle\vec{c^k},\vec{h^k}\rangle$, gdzie
- $\vec{c^k}$ to *komórka pamięci*, która nie zmienia swojej, chyba że celowo zmodyfikujemy jej wartość
za pomocą bramek,
- $\vec{h^k}$ to ukryty stan (przypominający $\vec{s^k}$ ze zwykłej sieci RNN).
Sieć LSTM zawiera 3 bramki:
- bramkę zapominania (/forget gate/), która steruje wymazywaniem informacji z komórki
pamięci $\vec{c^k}$,
- bramkę wejścia (/input gate/), która steruje tym, na ile nowe informacje aktualizują
komórkę pamięci $\vec{c^k}$,
- bramkę wyjścia (/output gate/), która steruje tym, co z komórki
pamięci przekazywane jest na wyjście.
Wszystkie trzy bramki definiowane są za pomocą bardzo podobnego wzoru — warstwy liniowej na
poprzedniej wartości warstwy ukrytej i bieżącego wejścia.
$$\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)$$
Jak widać, wzory różnią się tylko macierzami wag $W_*$.
Zmiana komórki pamięci jest zdefiniowana jak następuje:
$$\vec{c^k} = \vec{f} \odot \vec{c^{k-1}} + \vec{i} \vec{z^k}$$,
gdzie
$$\vec{z^k} = \operatorname{tanh}(W_z\langle\vec{v}(t^k),\vec{h^{k-1}}\rangle)$$
Stan ukryty zmienia się w następujący sposób:
$$\vec{h^K} = \vec{o} \odot \operatorname{tanh}(\vec{c^k})$$.
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
** Literatura
Yoav Goldberg, /Neural Network Methods for Natural Language
Processing/, Morgan & Claypool Publishers, 2017

1
wyk/crf-viterbi.drawio Normal file
View File

@ -0,0 +1 @@
<mxfile host="app.diagrams.net" modified="2021-05-26T13:43:59.377Z" agent="5.0 (X11)" etag="4emVx1cQBqc02lQgOp_X" version="14.6.13" type="device"><diagram id="7pFB8Xg2-vPC_YrQG171" name="Page-1">5ZpNc5swEIZ/Dcd2jAQ2PsbYTQ/pTKaZTt3eFJCNWowYWY5xf31FEAa0dr4DDLl40IIk9Lyr1UrYwv4muxQkjb7xkMYWGoWZhecWQlPHVr+54VAYXIwLw1qwsDDZleGG/aPaONLWHQvptvGg5DyWLG0aA54kNJANGxGC75uPrXjc7DUlawoMNwGJofUnC2VUWD00qexfKVtHZc/2eFrc2ZDyYT2SbURCvq+Z8MLCvuBcFlebzKdxzq7kUtT7cubu8cUETeRTKiwvJ+j3gvFb5s3Dq/2PdDlzPulW7ki80wO+iIl+X3koIUiaqS5mkdzEymCry60U/C/1ecyFsiQ8UU/OViyODROJ2TpRxUC9JFX22R0Vkim8F/rGhoVh3s1sHzFJb1IS5H3ulS8pm+C7JKT5+49USb+qaoBmZxnYR7LKIynfUCkO6pGywliLob3RdnR5X2lbmqKarKWGRHvT+thyBVxdaObP4I8A/82A8SO3Z/gxwJ/yPaOW71rTmeU7ljcZrhq4b2o4QA0f0FfNqNB/jlFNFLJNi/VgxbKc2tuED7dJzIPE8Ali+L2IjQGx634TQyd8rFViE0Dse7+J4VHHxJAHAYUqR9JFLmTE1zwh8aKyGsGqeuaK81TD+0OlPOiEj+wkb6KlGZPL2vWvvKnPri7NM93yfeFQFhI13mW9UKuVF6tq96WyXjG+fFAPa6YY8J0I6AOwNCtJxJrKh/KuM04gaEwku2u+yJtL6vU90CIzT+s60E7fNdDWcwUL4ZUX0CAAiYW6c+u5jvtWyZjBuPPQXG75+hubTWSdx2Yb7t96NpOx27OUyYZbrp7lTCay7icm3Cb1bGKayLqfmHAvs7XcmQKp2hvZljsHAIezr/SMKT+GYrS6r7Rh0m8PFz844zoxF9rFD9NNNFz83uNZVbv0YeqKh0sfO0bs6dr5EUxqBxx7DOe3y5WgM/ow2Rtw6AHH6507P0wcBxx7DOfHo66d3wX08xwUFTkoGnQOCpZh1PVUgEf1NTGGvSEAy8K465kBNwQ1MfCHEgN1vUZjeIYF6NMkvMj/zJFjjMl2y4KmFi89QH38zB9yrHFyT3AqbU8+ydc9XHOmOq5kmhoyuQb/4lOErlVJABrC5jGvbTRUfKsADd1reRz2K+SFOcCz5X3dx6Dq+8+vso12PgaVpF/8NagdVzselpUeMn6pq03OLLptuRo8dLv9KIdujiGiPe044cGn11g1Cn/YSthTM9t5NyVUsfr/YDGLqj9h4sV/</diagram></mxfile>

BIN
wyk/crf-viterbi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB