This commit is contained in:
Filip Gralinski 2022-07-06 08:14:22 +02:00
parent 6909b422a4
commit eedb3f7741
7 changed files with 632 additions and 219 deletions

View File

@ -185,222 +185,3 @@ Zauważmy, że względem $n$ oznacza to bardzo korzystną złożoność
$O(n)$! Oznacza to, że nasz model może działać dla dużo większych $O(n)$! Oznacza to, że nasz model może działać dla dużo większych
wartości $n$ niż tradycyjny, statystyczny n-gramowy model języka (dla którego wartości $n$ niż tradycyjny, statystyczny n-gramowy model języka (dla którego
wartości $n > 5$ zazwyczaj nie mają sensu). wartości $n > 5$ zazwyczaj nie mają sensu).
** Model worka słów
Jak stwierdziliśmy przed chwilą, dwuwarstwowy n-gramowy model języka
może działać dla stosunkowo dużego $n$. Zauważmy jednak, że istnieje
pewna słabość tego modelu. Otóż o ile intuicyjnie ma sens odróżniać
słowo poprzedzające, słowo występujące dwie pozycje wstecz i zapewne
trzy pozycje wstecz, a zatem uczyć się osobnych macierzy $C_{-1}$,
$C_{-2}$, $C_{-3}$ to różnica między wpływem słowa
występującego cztery pozycje wstecz i pięć pozycji wstecz jest już
raczej nieistotna; innymi słowy różnica między macierzami $C_{-4}$ i
$C_{-5}$ będzie raczej niewielka i sieć niepotrzebnie będzie uczyła
się dwukrotnie podobnych wag. Im dalej wstecz, tym różnica wpływu
będzie jeszcze mniej istotna, można np. przypuszczać, że różnica
między $C_{-10}$ i $C_{-13}$ nie powinna być duża.
Spróbujmy najpierw zaproponować radykalne podejście, w którym nie
będziemy w ogóle uwzględniać pozycji słów (lub będziemy je uwzględniać
w niewielkim stopniu), później połączymy to z omówionym wcześniej
modelem $n$-gramowym.
*** Agregacja wektorów
Zamiast patrzeć na kilka poprzedzających słów, można przewidywać na
podstawie *całego* ciągu słów poprzedzających odgadywane słowo. Zauważmy jednak, że
sieć neuronowa musi mieć ustaloną strukturę, nie możemy zmieniać jej
rozmiaru. Musimy zatem najpierw zagregować cały ciąg do wektora o
*stałej* długości. Potrzebujemy zatem pewnej funkcji agregującej $A$, takiej by
$A(w_1,\dots,w_{i-1})$ było wektorem o stałej długości, niezależnie od $i$.
*** Worek słów
Najprostszą funkcją agregującą jest po prostu… suma. Dodajemy po
prostu zanurzenia słów:
$$A(w_1,\dots,w_{i-1}) = E(w_1) + \dots + E(w_{i-1}) = \sum_{j=1}^{i-1} E(w_j).$$
*Uwaga*: zanurzenia słów nie zależą od pozycji słowa (podobnie było w wypadku n-gramowego modelu!).
Jeśli rozmiar zanurzenia (embeddingu) wynosi $m$, wówczas rozmiar
wektora uzyskanego dla całego poprzedzającego tekstu wynosi również $m$.
Proste dodawanie wydaje się bardzo „prostacką” metodą, a jednak
suma wektorów słów jest *zaskakująco skuteczną metodą zanurzenia
(embedowania) całych tekstów (doc2vec)*. Prostym wariantem dodawania jest obliczanie *średniej wektorów*:
$$A(w_1,\dots,w_{i-1}) = \frac{E(w_1) + \dots + E(w_{i-1})}{i-1} = \frac{\sum_{j=1}^{i-1} E(w_j)}{i-1}.$$
Tak czy siak uzyskany wektor *nie zależy od kolejności słów*
(dodawanie jest przemienne i łączne!). Mówimy więc o *worku słów*
(/bag of words/, /BoW/) — co ma symbolizować fakt, że słowa są
przemieszane, niczym produkty w torbie na zakupy.
**** Schemat graficzny modelu typu worek słów
Po zanurzeniu całego poprzedzającego tekstu postępujemy podobnie jak w
modelu bigramowym — rzutujemy embedding na długi wektor wartości, na
którym stosujemy funkcję softmax:
#+CAPTION: Model typu worek słów
[[./08_Neuronowy_ngramowy_model/bow1.drawio.png]]
Odpowiada to wzorowi:
$$y = \operatorname{softmax}(C\sum_{j=1}^{i-1} E(w_j)).$$
*** Jak traktować powtarzające się słowa?
Według wzoru podanego wyżej, jeśli słowo w poprzedzającym tekście
pojawia się więcej niż raz, jego embedding zostanie zsumowany odpowiednią liczbę razy.
Na przykład embedding tekstu /to be or not to be/ będzie wynosił:
$$E(\mathrm{to}) + E(\mathrm{be}) + E(\mathrm{or}) + E(\mathrm{not}) + E(\mathrm{to}) + E(\mathrm{be}) = 2E(\mathrm{to}) + 2E(\mathrm{be}) + E(\mathrm{or}) + E(\mathrm{not}).$$
Innymi słowy, choć w worku słów nie uwzględniamy kolejności słów, to
*liczba wystąpień* ma dla nas ciągle znaczenie. Można powiedzieć, że
traktujemy poprzedzający tekst jako *multizbiór* (struktura
matematyczna, w której nie uwzględnia się kolejności, choć zachowana
jest informacja o liczbie wystąpień).
**** Zbiór słów
Oczywiście moglibyśmy przy agregowaniu zanurzeń pomijać powtarzające
się słowa, a zatem zamiast multizbioru słów rozpatrywać po prostu ich zbiór:
$$A(w_1,\dots,w_{i-1}) = \sum_{w \in \{w_1,\dots,w_{i-1}\}} E(w).$$
Jest kwestią dyskusyjną, czy to lepsze czy gorsze podejście — w końcu
liczba wystąpień np. słów /Ukraina/ czy /Polska/ może wpływać w jakimś
stopniu na prawdopodobieństwo kolejnego słowa (/Kijów/ czy
/Warszawa/?).
*** Worek słów a wektoryzacja tf
Wzór na sumę zanurzeń słów można przekształcić w taki sposób, by
sumować po wszystkich słowach ze słownika, zamiast po słowach rzeczywiście występujących w tekście:
$$A(w_1,\dots,w_{i-1}) = \sum_{j=1}^{i-1} E(w_j) = \sum_{w \in V} \#wE(w)$$
gdzie $\#w$ to liczba wystąpień słowa $w$ w ciagu $w_1,\dots,w_{i-1}$ (w wielu przypadkach równa zero!).
Jeśli teraz zanurzenia będziemy reprezentować jako macierz $E$ (por. poprzedni wykład),
wówczas sumę można przedstawić jako iloczyn macierzy $E$ i pewnego wektora:
$$A(w_1,\dots,w_{i-1}) = E(w) [\#w^1,\dots,\#w^{|V|}]^T.$$
(Odróżniamy $w^i$ jako $i$-ty wyraz w słowniku $V$ od $w_i$ jako $i$-tego wyraz w rozpatrywanym ciągu).
Zwróćmy uwagę, że wektor $[\#w_1,\dots,\#w_{|V|}]$ to po prostu
reprezentacja wektora poprzedzającego tekstu (tj. ciągu
$(w_1,\dots,w_{i-1})$) przy użyciu schematu wektoryzacji tf (/term
frequency/). Przypomnijmy, że tf to reprezentacja tekstu przy użyciu
wektorów o rozmiarze $|V|$ — na każdej pozycji odnotowujemy liczbę wystąpień.
Wektory tf są *rzadkie*, tj. na wielu pozycjach zawierają zera.
Innymi słowy, nasz model języka /bag of words/ można przedstawić za pomocą wzoru:
$$y = \operatorname{softmax}(C\operatorname{tf}(w_1,\dots,w_{i-1})),$$
co można zilustrować w następujący sposób:
#+CAPTION: Model typu worek słów — alternatywna reprezentacja
[[./08_Neuronowy_ngramowy_model/bow2.drawio.png]]
Można stwierdzić, że zanurzenie tekstu przekształca rzadki, długi wektor
tf w gęsty, krótki wektor.
** Ważenie słów
Czy wszystkie słowa są tak samo istotne? Rzecz jasna, nie:
- jak już wiemy z naszych rozważań dotyczących n-gramowych modeli języka, słowa bezpośrednio
poprzedzające odgadywany wyraz mają większy wpływ niż słowa wcześniejsze;
intuicyjnie, wpływ słów stopniowo spada — tym bardziej, im bardziej słowo jest oddalone od słowa odgadywanego;
- jak wiemy z wyszukiwania informacji, słowa, które występują w wielu tekstach czy dokumentach, powinny mieć
mniejsze znaczenie, w skrajnym przypadku słowa występujące w prawie każdym tekście (/że/, /w/, /i/ itd.) powinny
być praktycznie pomijane jako /stop words/ (jeśli rozpatrywać je w „masie” worka słów — oczywiście
to, czy słowo poprzedzające odgadywane słowo to /że/, /w/ czy /i/ ma olbrzymie znaczenie!).
Zamiast po prostu dodawać zanurzenia, można operować na sumie (bądź średniej) ważonej:
$$\sum_{j=1}^{i-1} \omega(j, w_j)E(w_j),$$
gdzie $\omega(j, w_j)$ jest pewną wagą, która może zależeć od pozycji $j$ lub samego słowa $w_j$.
*** Uwzględnienie pozycji
Można w pewnym stopniu złamać „workowatość” naszej sieci przez proste
uwzględnienie pozycji słowa, np. w taki sposób:
$$\omega(j, w_j) = \beta^{i-j-1},$$
dla pewnego hiperparametru $\beta$. Na przykład jeśli $\beta=0,9$,
wówczas słowo bezpośrednio poprzedzające dane słowo ma $1 / 0,9^9 \approx 2,58$
większy wpływ niż słowo występujące 10 pozycji wstecz.
*** Odwrócona częstość dokumentowa
Aby większą wagę przykładać do słów występujących w mniejszej liczbie
dokumentów, możemy użyć, znanej z wyszukiwania informacji,
odwrotnej częstości dokumentowej (/inverted document frequency/, /idf/):
$$\omega(j, w_j) = \operatorname{idf}_S(w_j) = \operatorname{log}\frac{|S|}{\operatorname{df}_S(w_j)},$$
gdzie:
- $S$ jest pewną kolekcją dokumentów czy tekstów, z którego pochodzi przedmiotowy ciąg słów,
- $\operatorname{df}_S(w)$ to częstość dokumentowa słowa $w$ w kolekcji $S$, tzn. odpowiedź na pytanie,
w ilu dokumentach występuje $w$.
Rzecz jasna, ten sposób ważenia oznacza tak naprawdę zastosowanie wektoryzacji tf-idf zamiast tf,
nasza sieć będzie dana zatem wzorem:
$$y = \operatorname{softmax}(C\operatorname{tfidf}(w_1,\dots,w_{i-1})).$$
*** Bardziej skomplikowane sposoby ważenia słów
Można oczywiście połączyć odwrotną częstość dokumentową z uwzględnieniem pozycji słowa:
$$\omega(j, w_j) = \beta^{i-j-1}\operatorname{idf}_S(w_j).$$
*Uwaga*: „wagi” $\omega(j, w_j)$ nie są tak naprawdę wyuczalnymi
wagami (parametrami) naszej sieci neuronowej, terminologia może być
tutaj myląca. Z punktu widzenia sieci neuronowej $\omega(j, w_j)$ są
stałe i *nie* są optymalizowane w procesie propagacji wstecznej. Innymi
słowy, tak zdefiniowane $\omega(j, w_j)$ zależą tylko od:
- hiperparametru $\beta$, który może być optymalizowany już poza siecią (w procesie *hiperoptymalizacji*),
- wartości $\operatorname{idf}_S(w_j)$ wyliczanych wcześniej na podstawie kolekcji $S$.
*Pytanie*: czy wagi $\omega(j, w_j)$ mogłyby sensownie uwzględniać
jakieś parametry wyuczalne z całą siecią?
** Modelowanie języka przy użyciu bardziej złożonych neuronowych sieci /feed-forward/
Można połączyć zalety obu ogólnych podejść (n-gramowego modelu i worka
słów) — można *równocześnie* traktować w specjalny sposób (na
przykład) dwa poprzedzające wyrazy, wszystkie zaś inne wyrazy
reprezentować jako „tło” modelowane za pomocą worka słów lub podobnej
reprezentacji. Osiągamy to poprzez konkatenację wektora
poprzedzającego słowa, słowa występującego dwie pozycje wstecz oraz
zagregowanego zanurzenia całego wcześniejszego tekstu:
$$y = \operatorname{softmax}(C[E(w_{i-1}),E(w_{i-2}),A(w_1,\dots,w_{i-3})]),$$
czy lepiej z dodatkową warstwą ukrytą:
$$y = \operatorname{softmax}(C\operatorname{tgh}(W[E(w_{i-1}),E(w_{i-2}),A(w_1,\dots,w_{i-3})])),$$
W tak uzyskanym dwuwarstwowym neuronowym modelu języka, łączącym model
trigramowy z workiem słów, macierz $W$ ma rozmiar $h \times 3m$.
*Pytanie*: jakie mamy możliwości, jeśli zamiast przewidywać kolejne słowo, mamy za zadanie
odgadywać słowo w luce (jak w wyzwaniach typu /word gap/)?
** Literatura
Skuteczny n-gramowy neuronowy model języka opisano po raz pierwszy
w pracy [[https://www.jmlr.org/papers/volume3/bengio03a/bengio03a.pdf][A Neural Probabilistic Language Model]] autorstwa Yoshua Bengio i in.

414
wyk/11_Worek_slow.ipynb Normal file
View File

@ -0,0 +1,414 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![Logo 1](https://git.wmi.amu.edu.pl/AITech/Szablon/raw/branch/master/Logotyp_AITech1.jpg)\n",
"<div class=\"alert alert-block alert-info\">\n",
"<h1> Modelowanie języka</h1>\n",
"<h2> 11. <i>Model worka słów w sieci feed-forward</i> [wykład]</h2> \n",
"<h3> Filip Graliński (2022)</h3>\n",
"</div>\n",
"\n",
"![Logo 2](https://git.wmi.amu.edu.pl/AITech/Szablon/raw/branch/master/Logotyp_AITech2.jpg)\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Model worka słów w sieci feed-forward\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Jak stwierdziliśmy w poprzednim wykładzie, dwuwarstwowy n-gramowy model języka\n",
"może działać dla stosunkowo dużego $n$. Zauważmy jednak, że istnieje\n",
"pewna słabość tego modelu. Otóż o ile intuicyjnie ma sens odróżniać\n",
"słowo poprzedzające, słowo występujące dwie pozycje wstecz i zapewne\n",
"trzy pozycje wstecz, a zatem uczyć się osobnych macierzy $C_{-1}$,\n",
"$C_{-2}$, $C_{-3}$ to różnica między wpływem słowa\n",
"występującego cztery pozycje wstecz i pięć pozycji wstecz jest już\n",
"raczej nieistotna; innymi słowy różnica między macierzami $C_{-4}$ i\n",
"$C_{-5}$ będzie raczej niewielka i sieć niepotrzebnie będzie uczyła\n",
"się dwukrotnie podobnych wag. Im dalej wstecz, tym różnica wpływu\n",
"będzie jeszcze mniej istotna, można np. przypuszczać, że różnica\n",
"między $C_{-10}$ i $C_{-13}$ nie powinna być duża.\n",
"\n",
"Spróbujmy najpierw zaproponować radykalne podejście, w którym nie\n",
"będziemy w ogóle uwzględniać pozycji słów (lub będziemy je uwzględniać\n",
"w niewielkim stopniu), później połączymy to z omówionym wcześniej\n",
"modelem $n$-gramowym.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Agregacja wektorów\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Zamiast patrzeć na kilka poprzedzających słów, można przewidywać na\n",
"podstawie **całego** ciągu słów poprzedzających odgadywane słowo. Zauważmy jednak, że\n",
"sieć neuronowa musi mieć ustaloną strukturę, nie możemy zmieniać jej\n",
"rozmiaru. Musimy zatem najpierw zagregować cały ciąg do wektora o\n",
"**stałej** długości. Potrzebujemy zatem pewnej funkcji agregującej $A$, takiej by\n",
"$A(w_1,\\dots,w_{i-1})$ było wektorem o stałej długości, niezależnie od $i$.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Worek słów\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Najprostszą funkcją agregującą jest po prostu… suma. Dodajemy po\n",
"prostu zanurzenia słów:\n",
"\n",
"$$A(w_1,\\dots,w_{i-1}) = E(w_1) + \\dots + E(w_{i-1}) = \\sum_{j=1}^{i-1} E(w_j).$$\n",
"\n",
"**Uwaga**: zanurzenia słów nie zależą od pozycji słowa (podobnie było w wypadku n-gramowego modelu!).\n",
"\n",
"Jeśli rozmiar zanurzenia (embeddingu) wynosi $m$, wówczas rozmiar\n",
"wektora uzyskanego dla całego poprzedzającego tekstu wynosi również $m$.\n",
"\n",
"Proste dodawanie wydaje się bardzo „prostacką” metodą, a jednak\n",
"suma wektorów słów jest **zaskakująco skuteczną metodą zanurzenia\n",
"(embedowania) całych tekstów (doc2vec)**. Prostym wariantem dodawania jest obliczanie **średniej wektorów**:\n",
"\n",
"$$A(w_1,\\dots,w_{i-1}) = \\frac{E(w_1) + \\dots + E(w_{i-1})}{i-1} = \\frac{\\sum_{j=1}^{i-1} E(w_j)}{i-1}.$$\n",
"\n",
"Tak czy siak uzyskany wektor **nie zależy od kolejności słów**\n",
"(dodawanie jest przemienne i łączne!). Mówimy więc o **worku słów**\n",
"(*bag of words*, *BoW*) — co ma symbolizować fakt, że słowa są\n",
"przemieszane, niczym produkty w torbie na zakupy.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Schemat graficzny modelu typu worek słów\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Po zanurzeniu całego poprzedzającego tekstu postępujemy podobnie jak w\n",
"modelu bigramowym — rzutujemy embedding na długi wektor wartości, na\n",
"którym stosujemy funkcję softmax:\n",
"\n",
"![img](./11_Worek_slow/bow1.drawio.png \"Model typu worek słów\")\n",
"\n",
"Odpowiada to wzorowi:\n",
"\n",
"$$y = \\operatorname{softmax}(C\\sum_{j=1}^{i-1} E(w_j)).$$\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Jak traktować powtarzające się słowa?\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Według wzoru podanego wyżej, jeśli słowo w poprzedzającym tekście\n",
"pojawia się więcej niż raz, jego embedding zostanie zsumowany odpowiednią liczbę razy.\n",
"Na przykład embedding tekstu *to be or not to be* będzie wynosił:\n",
"\n",
"$$E(\\mathrm{to}) + E(\\mathrm{be}) + E(\\mathrm{or}) + E(\\mathrm{not}) + E(\\mathrm{to}) + E(\\mathrm{be}) = 2E(\\mathrm{to}) + 2E(\\mathrm{be}) + E(\\mathrm{or}) + E(\\mathrm{not}).$$\n",
"\n",
"Innymi słowy, choć w worku słów nie uwzględniamy kolejności słów, to\n",
"**liczba wystąpień** ma dla nas ciągle znaczenie. Można powiedzieć, że\n",
"traktujemy poprzedzający tekst jako **multizbiór** (struktura\n",
"matematyczna, w której nie uwzględnia się kolejności, choć zachowana\n",
"jest informacja o liczbie wystąpień).\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Zbiór słów\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Oczywiście moglibyśmy przy agregowaniu zanurzeń pomijać powtarzające\n",
"się słowa, a zatem zamiast multizbioru słów rozpatrywać po prostu ich zbiór:\n",
"\n",
"$$A(w_1,\\dots,w_{i-1}) = \\sum_{w \\in \\{w_1,\\dots,w_{i-1}\\}} E(w).$$\n",
"\n",
"Jest kwestią dyskusyjną, czy to lepsze czy gorsze podejście — w końcu\n",
"liczba wystąpień np. słów *Ukraina* czy *Polska* może wpływać w jakimś\n",
"stopniu na prawdopodobieństwo kolejnego słowa (*Kijów* czy\n",
"*Warszawa*?).\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Worek słów a wektoryzacja tf\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Wzór na sumę zanurzeń słów można przekształcić w taki sposób, by\n",
"sumować po wszystkich słowach ze słownika, zamiast po słowach rzeczywiście występujących w tekście:\n",
"\n",
"$$A(w_1,\\dots,w_{i-1}) = \\sum_{j=1}^{i-1} E(w_j) = \\sum_{w \\in V} \\#wE(w)$$\n",
"\n",
"gdzie $\\#w$ to liczba wystąpień słowa $w$ w ciagu $w_1,\\dots,w_{i-1}$ (w wielu przypadkach równa zero!).\n",
"\n",
"Jeśli teraz zanurzenia będziemy reprezentować jako macierz $E$ (por. poprzedni wykład),\n",
"wówczas sumę można przedstawić jako iloczyn macierzy $E$ i pewnego wektora:\n",
"\n",
"$$A(w_1,\\dots,w_{i-1}) = E(w) [\\#w^1,\\dots,\\#w^{|V|}]^T.$$\n",
"\n",
"(Odróżniamy $w^i$ jako $i$-ty wyraz w słowniku $V$ od $w_i$ jako $i$-tego wyraz w rozpatrywanym ciągu).\n",
"\n",
"Zwróćmy uwagę, że wektor $[\\#w_1,\\dots,\\#w_{|V|}]$ to po prostu\n",
"reprezentacja wektora poprzedzającego tekstu (tj. ciągu\n",
"$(w_1,\\dots,w_{i-1})$) przy użyciu schematu wektoryzacji tf (*term\n",
"frequency*). Przypomnijmy, że tf to reprezentacja tekstu przy użyciu\n",
"wektorów o rozmiarze $|V|$ — na każdej pozycji odnotowujemy liczbę wystąpień.\n",
"Wektory tf są **rzadkie**, tj. na wielu pozycjach zawierają zera.\n",
"\n",
"Innymi słowy, nasz model języka *bag of words* można przedstawić za pomocą wzoru:\n",
"\n",
"$$y = \\operatorname{softmax}(C\\operatorname{tf}(w_1,\\dots,w_{i-1})),$$\n",
"\n",
"co można zilustrować w następujący sposób:\n",
"\n",
"![img](./11_Worek_slow/bow2.drawio.png \"Model typu worek słów — alternatywna reprezentacja\")\n",
"\n",
"Można stwierdzić, że zanurzenie tekstu przekształca rzadki, długi wektor\n",
"tf w gęsty, krótki wektor.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Ważenie słów\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Czy wszystkie słowa są tak samo istotne? Rzecz jasna, nie:\n",
"\n",
"- jak już wiemy z naszych rozważań dotyczących n-gramowych modeli języka, słowa bezpośrednio\n",
" poprzedzające odgadywany wyraz mają większy wpływ niż słowa wcześniejsze;\n",
" intuicyjnie, wpływ słów stopniowo spada — tym bardziej, im bardziej słowo jest oddalone od słowa odgadywanego;\n",
"- jak wiemy z wyszukiwania informacji, słowa, które występują w wielu tekstach czy dokumentach, powinny mieć\n",
" mniejsze znaczenie, w skrajnym przypadku słowa występujące w prawie każdym tekście (*że*, *w*, *i* itd.) powinny\n",
" być praktycznie pomijane jako *stop words* (jeśli rozpatrywać je w „masie” worka słów — oczywiście\n",
" to, czy słowo poprzedzające odgadywane słowo to *że*, *w* czy *i* ma olbrzymie znaczenie!).\n",
"\n",
"Zamiast po prostu dodawać zanurzenia, można operować na sumie (bądź średniej) ważonej:\n",
"\n",
"$$\\sum_{j=1}^{i-1} \\omega(j, w_j)E(w_j),$$\n",
"\n",
"gdzie $\\omega(j, w_j)$ jest pewną wagą, która może zależeć od pozycji $j$ lub samego słowa $w_j$.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Uwzględnienie pozycji\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Można w pewnym stopniu złamać „workowatość” naszej sieci przez proste\n",
"uwzględnienie pozycji słowa, np. w taki sposób:\n",
"\n",
"$$\\omega(j, w_j) = \\beta^{i-j-1},$$\n",
"\n",
"dla pewnego hiperparametru $\\beta$. Na przykład jeśli $\\beta=0,9$,\n",
"wówczas słowo bezpośrednio poprzedzające dane słowo ma $1 / 0,9^9 \\approx 2,58$\n",
"większy wpływ niż słowo występujące 10 pozycji wstecz.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Odwrócona częstość dokumentowa\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Aby większą wagę przykładać do słów występujących w mniejszej liczbie\n",
"dokumentów, możemy użyć, znanej z wyszukiwania informacji,\n",
"odwrotnej częstości dokumentowej (*inverted document frequency*, *idf*):\n",
"\n",
"$$\\omega(j, w_j) = \\operatorname{idf}_S(w_j) = \\operatorname{log}\\frac{|S|}{\\operatorname{df}_S(w_j)},$$\n",
"\n",
"gdzie:\n",
"\n",
"- $S$ jest pewną kolekcją dokumentów czy tekstów, z którego pochodzi przedmiotowy ciąg słów,\n",
"- $\\operatorname{df}_S(w)$ to częstość dokumentowa słowa $w$ w kolekcji $S$, tzn. odpowiedź na pytanie,\n",
" w ilu dokumentach występuje $w$.\n",
"\n",
"Rzecz jasna, ten sposób ważenia oznacza tak naprawdę zastosowanie wektoryzacji tf-idf zamiast tf,\n",
"nasza sieć będzie dana zatem wzorem:\n",
"\n",
"$$y = \\operatorname{softmax}(C\\operatorname{tfidf}(w_1,\\dots,w_{i-1})).$$\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Bardziej skomplikowane sposoby ważenia słów\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Można oczywiście połączyć odwrotną częstość dokumentową z uwzględnieniem pozycji słowa:\n",
"\n",
"$$\\omega(j, w_j) = \\beta^{i-j-1}\\operatorname{idf}_S(w_j).$$\n",
"\n",
"**Uwaga**: „wagi” $\\omega(j, w_j)$ nie są tak naprawdę wyuczalnymi\n",
"wagami (parametrami) naszej sieci neuronowej, terminologia może być\n",
"tutaj myląca. Z punktu widzenia sieci neuronowej $\\omega(j, w_j)$ są\n",
"stałe i **nie** są optymalizowane w procesie propagacji wstecznej. Innymi\n",
"słowy, tak zdefiniowane $\\omega(j, w_j)$ zależą tylko od:\n",
"\n",
"- hiperparametru $\\beta$, który może być optymalizowany już poza siecią (w procesie **hiperoptymalizacji**),\n",
"- wartości $\\operatorname{idf}_S(w_j)$ wyliczanych wcześniej na podstawie kolekcji $S$.\n",
"\n",
"**Pytanie**: czy wagi $\\omega(j, w_j)$ mogłyby sensownie uwzględniać\n",
"jakieś parametry wyuczalne z całą siecią?\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Modelowanie języka przy użyciu bardziej złożonych neuronowych sieci *feed-forward*\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Można połączyć zalety obu ogólnych podejść (n-gramowego modelu i worka\n",
"słów) — można **równocześnie** traktować w specjalny sposób (na\n",
"przykład) dwa poprzedzające wyrazy, wszystkie zaś inne wyrazy\n",
"reprezentować jako „tło” modelowane za pomocą worka słów lub podobnej\n",
"reprezentacji. Osiągamy to poprzez konkatenację wektora\n",
"poprzedzającego słowa, słowa występującego dwie pozycje wstecz oraz\n",
"zagregowanego zanurzenia całego wcześniejszego tekstu:\n",
"\n",
"$$y = \\operatorname{softmax}(C[E(w_{i-1}),E(w_{i-2}),A(w_1,\\dots,w_{i-3})]),$$\n",
"\n",
"czy lepiej z dodatkową warstwą ukrytą:\n",
"\n",
"$$y = \\operatorname{softmax}(C\\operatorname{tgh}(W[E(w_{i-1}),E(w_{i-2}),A(w_1,\\dots,w_{i-3})])),$$\n",
"\n",
"W tak uzyskanym dwuwarstwowym neuronowym modelu języka, łączącym model\n",
"trigramowy z workiem słów, macierz $W$ ma rozmiar $h \\times 3m$.\n",
"\n",
"**Pytanie**: jakie mamy możliwości, jeśli zamiast przewidywać kolejne słowo, mamy za zadanie\n",
"odgadywać słowo w luce (jak w wyzwaniach typu *word gap*)?\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Literatura\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Skuteczny n-gramowy neuronowy model języka opisano po raz pierwszy\n",
"w pracy [A Neural Probabilistic Language Model](https://www.jmlr.org/papers/volume3/bengio03a/bengio03a.pdf) autorstwa Yoshua Bengio i in.\n",
"\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.5"
},
"org": null
},
"nbformat": 4,
"nbformat_minor": 1
}

218
wyk/11_Worek_slow.org Normal file
View File

@ -0,0 +1,218 @@
* Model worka słów w sieci feed-forward
Jak stwierdziliśmy w poprzednim wykładzie, dwuwarstwowy n-gramowy model języka
może działać dla stosunkowo dużego $n$. Zauważmy jednak, że istnieje
pewna słabość tego modelu. Otóż o ile intuicyjnie ma sens odróżniać
słowo poprzedzające, słowo występujące dwie pozycje wstecz i zapewne
trzy pozycje wstecz, a zatem uczyć się osobnych macierzy $C_{-1}$,
$C_{-2}$, $C_{-3}$ to różnica między wpływem słowa
występującego cztery pozycje wstecz i pięć pozycji wstecz jest już
raczej nieistotna; innymi słowy różnica między macierzami $C_{-4}$ i
$C_{-5}$ będzie raczej niewielka i sieć niepotrzebnie będzie uczyła
się dwukrotnie podobnych wag. Im dalej wstecz, tym różnica wpływu
będzie jeszcze mniej istotna, można np. przypuszczać, że różnica
między $C_{-10}$ i $C_{-13}$ nie powinna być duża.
Spróbujmy najpierw zaproponować radykalne podejście, w którym nie
będziemy w ogóle uwzględniać pozycji słów (lub będziemy je uwzględniać
w niewielkim stopniu), później połączymy to z omówionym wcześniej
modelem $n$-gramowym.
** Agregacja wektorów
Zamiast patrzeć na kilka poprzedzających słów, można przewidywać na
podstawie *całego* ciągu słów poprzedzających odgadywane słowo. Zauważmy jednak, że
sieć neuronowa musi mieć ustaloną strukturę, nie możemy zmieniać jej
rozmiaru. Musimy zatem najpierw zagregować cały ciąg do wektora o
*stałej* długości. Potrzebujemy zatem pewnej funkcji agregującej $A$, takiej by
$A(w_1,\dots,w_{i-1})$ było wektorem o stałej długości, niezależnie od $i$.
** Worek słów
Najprostszą funkcją agregującą jest po prostu… suma. Dodajemy po
prostu zanurzenia słów:
$$A(w_1,\dots,w_{i-1}) = E(w_1) + \dots + E(w_{i-1}) = \sum_{j=1}^{i-1} E(w_j).$$
*Uwaga*: zanurzenia słów nie zależą od pozycji słowa (podobnie było w wypadku n-gramowego modelu!).
Jeśli rozmiar zanurzenia (embeddingu) wynosi $m$, wówczas rozmiar
wektora uzyskanego dla całego poprzedzającego tekstu wynosi również $m$.
Proste dodawanie wydaje się bardzo „prostacką” metodą, a jednak
suma wektorów słów jest *zaskakująco skuteczną metodą zanurzenia
(embedowania) całych tekstów (doc2vec)*. Prostym wariantem dodawania jest obliczanie *średniej wektorów*:
$$A(w_1,\dots,w_{i-1}) = \frac{E(w_1) + \dots + E(w_{i-1})}{i-1} = \frac{\sum_{j=1}^{i-1} E(w_j)}{i-1}.$$
Tak czy siak uzyskany wektor *nie zależy od kolejności słów*
(dodawanie jest przemienne i łączne!). Mówimy więc o *worku słów*
(/bag of words/, /BoW/) — co ma symbolizować fakt, że słowa są
przemieszane, niczym produkty w torbie na zakupy.
*** Schemat graficzny modelu typu worek słów
Po zanurzeniu całego poprzedzającego tekstu postępujemy podobnie jak w
modelu bigramowym — rzutujemy embedding na długi wektor wartości, na
którym stosujemy funkcję softmax:
#+CAPTION: Model typu worek słów
[[./11_Worek_slow/bow1.drawio.png]]
Odpowiada to wzorowi:
$$y = \operatorname{softmax}(C\sum_{j=1}^{i-1} E(w_j)).$$
** Jak traktować powtarzające się słowa?
Według wzoru podanego wyżej, jeśli słowo w poprzedzającym tekście
pojawia się więcej niż raz, jego embedding zostanie zsumowany odpowiednią liczbę razy.
Na przykład embedding tekstu /to be or not to be/ będzie wynosił:
$$E(\mathrm{to}) + E(\mathrm{be}) + E(\mathrm{or}) + E(\mathrm{not}) + E(\mathrm{to}) + E(\mathrm{be}) = 2E(\mathrm{to}) + 2E(\mathrm{be}) + E(\mathrm{or}) + E(\mathrm{not}).$$
Innymi słowy, choć w worku słów nie uwzględniamy kolejności słów, to
*liczba wystąpień* ma dla nas ciągle znaczenie. Można powiedzieć, że
traktujemy poprzedzający tekst jako *multizbiór* (struktura
matematyczna, w której nie uwzględnia się kolejności, choć zachowana
jest informacja o liczbie wystąpień).
*** Zbiór słów
Oczywiście moglibyśmy przy agregowaniu zanurzeń pomijać powtarzające
się słowa, a zatem zamiast multizbioru słów rozpatrywać po prostu ich zbiór:
$$A(w_1,\dots,w_{i-1}) = \sum_{w \in \{w_1,\dots,w_{i-1}\}} E(w).$$
Jest kwestią dyskusyjną, czy to lepsze czy gorsze podejście — w końcu
liczba wystąpień np. słów /Ukraina/ czy /Polska/ może wpływać w jakimś
stopniu na prawdopodobieństwo kolejnego słowa (/Kijów/ czy
/Warszawa/?).
** Worek słów a wektoryzacja tf
Wzór na sumę zanurzeń słów można przekształcić w taki sposób, by
sumować po wszystkich słowach ze słownika, zamiast po słowach rzeczywiście występujących w tekście:
$$A(w_1,\dots,w_{i-1}) = \sum_{j=1}^{i-1} E(w_j) = \sum_{w \in V} \#wE(w)$$
gdzie $\#w$ to liczba wystąpień słowa $w$ w ciagu $w_1,\dots,w_{i-1}$ (w wielu przypadkach równa zero!).
Jeśli teraz zanurzenia będziemy reprezentować jako macierz $E$ (por. poprzedni wykład),
wówczas sumę można przedstawić jako iloczyn macierzy $E$ i pewnego wektora:
$$A(w_1,\dots,w_{i-1}) = E(w) [\#w^1,\dots,\#w^{|V|}]^T.$$
(Odróżniamy $w^i$ jako $i$-ty wyraz w słowniku $V$ od $w_i$ jako $i$-tego wyraz w rozpatrywanym ciągu).
Zwróćmy uwagę, że wektor $[\#w_1,\dots,\#w_{|V|}]$ to po prostu
reprezentacja wektora poprzedzającego tekstu (tj. ciągu
$(w_1,\dots,w_{i-1})$) przy użyciu schematu wektoryzacji tf (/term
frequency/). Przypomnijmy, że tf to reprezentacja tekstu przy użyciu
wektorów o rozmiarze $|V|$ — na każdej pozycji odnotowujemy liczbę wystąpień.
Wektory tf są *rzadkie*, tj. na wielu pozycjach zawierają zera.
Innymi słowy, nasz model języka /bag of words/ można przedstawić za pomocą wzoru:
$$y = \operatorname{softmax}(C\operatorname{tf}(w_1,\dots,w_{i-1})),$$
co można zilustrować w następujący sposób:
#+CAPTION: Model typu worek słów — alternatywna reprezentacja
[[./11_Worek_slow/bow2.drawio.png]]
Można stwierdzić, że zanurzenie tekstu przekształca rzadki, długi wektor
tf w gęsty, krótki wektor.
** Ważenie słów
Czy wszystkie słowa są tak samo istotne? Rzecz jasna, nie:
- jak już wiemy z naszych rozważań dotyczących n-gramowych modeli języka, słowa bezpośrednio
poprzedzające odgadywany wyraz mają większy wpływ niż słowa wcześniejsze;
intuicyjnie, wpływ słów stopniowo spada — tym bardziej, im bardziej słowo jest oddalone od słowa odgadywanego;
- jak wiemy z wyszukiwania informacji, słowa, które występują w wielu tekstach czy dokumentach, powinny mieć
mniejsze znaczenie, w skrajnym przypadku słowa występujące w prawie każdym tekście (/że/, /w/, /i/ itd.) powinny
być praktycznie pomijane jako /stop words/ (jeśli rozpatrywać je w „masie” worka słów — oczywiście
to, czy słowo poprzedzające odgadywane słowo to /że/, /w/ czy /i/ ma olbrzymie znaczenie!).
Zamiast po prostu dodawać zanurzenia, można operować na sumie (bądź średniej) ważonej:
$$\sum_{j=1}^{i-1} \omega(j, w_j)E(w_j),$$
gdzie $\omega(j, w_j)$ jest pewną wagą, która może zależeć od pozycji $j$ lub samego słowa $w_j$.
*** Uwzględnienie pozycji
Można w pewnym stopniu złamać „workowatość” naszej sieci przez proste
uwzględnienie pozycji słowa, np. w taki sposób:
$$\omega(j, w_j) = \beta^{i-j-1},$$
dla pewnego hiperparametru $\beta$. Na przykład jeśli $\beta=0,9$,
wówczas słowo bezpośrednio poprzedzające dane słowo ma $1 / 0,9^9 \approx 2,58$
większy wpływ niż słowo występujące 10 pozycji wstecz.
*** Odwrócona częstość dokumentowa
Aby większą wagę przykładać do słów występujących w mniejszej liczbie
dokumentów, możemy użyć, znanej z wyszukiwania informacji,
odwrotnej częstości dokumentowej (/inverted document frequency/, /idf/):
$$\omega(j, w_j) = \operatorname{idf}_S(w_j) = \operatorname{log}\frac{|S|}{\operatorname{df}_S(w_j)},$$
gdzie:
- $S$ jest pewną kolekcją dokumentów czy tekstów, z którego pochodzi przedmiotowy ciąg słów,
- $\operatorname{df}_S(w)$ to częstość dokumentowa słowa $w$ w kolekcji $S$, tzn. odpowiedź na pytanie,
w ilu dokumentach występuje $w$.
Rzecz jasna, ten sposób ważenia oznacza tak naprawdę zastosowanie wektoryzacji tf-idf zamiast tf,
nasza sieć będzie dana zatem wzorem:
$$y = \operatorname{softmax}(C\operatorname{tfidf}(w_1,\dots,w_{i-1})).$$
*** Bardziej skomplikowane sposoby ważenia słów
Można oczywiście połączyć odwrotną częstość dokumentową z uwzględnieniem pozycji słowa:
$$\omega(j, w_j) = \beta^{i-j-1}\operatorname{idf}_S(w_j).$$
*Uwaga*: „wagi” $\omega(j, w_j)$ nie są tak naprawdę wyuczalnymi
wagami (parametrami) naszej sieci neuronowej, terminologia może być
tutaj myląca. Z punktu widzenia sieci neuronowej $\omega(j, w_j)$ są
stałe i *nie* są optymalizowane w procesie propagacji wstecznej. Innymi
słowy, tak zdefiniowane $\omega(j, w_j)$ zależą tylko od:
- hiperparametru $\beta$, który może być optymalizowany już poza siecią (w procesie *hiperoptymalizacji*),
- wartości $\operatorname{idf}_S(w_j)$ wyliczanych wcześniej na podstawie kolekcji $S$.
*Pytanie*: czy wagi $\omega(j, w_j)$ mogłyby sensownie uwzględniać
jakieś parametry wyuczalne z całą siecią?
** Modelowanie języka przy użyciu bardziej złożonych neuronowych sieci /feed-forward/
Można połączyć zalety obu ogólnych podejść (n-gramowego modelu i worka
słów) — można *równocześnie* traktować w specjalny sposób (na
przykład) dwa poprzedzające wyrazy, wszystkie zaś inne wyrazy
reprezentować jako „tło” modelowane za pomocą worka słów lub podobnej
reprezentacji. Osiągamy to poprzez konkatenację wektora
poprzedzającego słowa, słowa występującego dwie pozycje wstecz oraz
zagregowanego zanurzenia całego wcześniejszego tekstu:
$$y = \operatorname{softmax}(C[E(w_{i-1}),E(w_{i-2}),A(w_1,\dots,w_{i-3})]),$$
czy lepiej z dodatkową warstwą ukrytą:
$$y = \operatorname{softmax}(C\operatorname{tgh}(W[E(w_{i-1}),E(w_{i-2}),A(w_1,\dots,w_{i-3})])),$$
W tak uzyskanym dwuwarstwowym neuronowym modelu języka, łączącym model
trigramowy z workiem słów, macierz $W$ ma rozmiar $h \times 3m$.
*Pytanie*: jakie mamy możliwości, jeśli zamiast przewidywać kolejne słowo, mamy za zadanie
odgadywać słowo w luce (jak w wyzwaniach typu /word gap/)?
** Literatura
Skuteczny n-gramowy neuronowy model języka opisano po raz pierwszy
w pracy [[https://www.jmlr.org/papers/volume3/bengio03a/bengio03a.pdf][A Neural Probabilistic Language Model]] autorstwa Yoshua Bengio i in.

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB