This commit is contained in:
kubapok 2022-04-24 17:41:48 +02:00
parent f4b2dead07
commit 2d43e1e66e
16 changed files with 2681 additions and 0 deletions

View File

@ -0,0 +1,159 @@
{
"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> 8. <i>Model neuronowy typu word2vec</i> [ćwiczenia]</h2> \n",
"<h3> Jakub Pokrywka (2022)</h3>\n",
"</div>\n",
"\n",
"![Logo 2](https://git.wmi.amu.edu.pl/AITech/Szablon/raw/branch/master/Logotyp_AITech2.jpg)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Zadania"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Proszę wykonać zadanie 1 lub zadanie 2 (nie oba naraz). Zadanie 3 można zrobić niezależnie."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Zadanie 1\n",
"\n",
"Wzorując się na materiałach z wykładu stworzyć 5-gramowy model neuronowy oparty na jednym ze schematów z wykładu, np.\n",
"\n",
"\n",
"![img](bow1.drawio.png \"Model typu worek słów\")\n",
"\n",
"\n",
"Warunkiem koniecznym jest, żeby przewidywać słowo środkowe, np. Mając tekst ['Ala', 'ma', '[MASK]'\n",
" 'i', 'psa'] chcemy przewidzieć kontekst środkowego słowa (tutaj '[MASK]')\n",
"\n",
"\n",
"\n",
"\n",
"Warunki zaliczenia:\n",
"- wynik widoczny na platformie zarówno dla dev i dla test\n",
"- wynik dla dev i test lepszy (niższy) niż 6.50 (liczone przy pomocy geval)\n",
"- deadline do końca dnia 08.05\n",
"- commitując rozwiązanie proszę również umieścić rozwiązanie w pliku /run.py (czyli na szczycie katalogu). Można przekonwertować jupyter do pliku python przez File → Download as → Python. Rozwiązanie nie musi być w pythonie, może być w innym języku.\n",
"- zadania wykonujemy samodzielnie\n",
"- w nazwie commita podaj nr indeksu\n",
"- w tagach podaj **neural-network** oraz **5gram**!\n",
"- zadanie tym razem jest dla polskiego odpowiednika word-gap https://gonito.net/challenge-my-submissions/retro-gap\n",
"- metryka to LogLossHashed (praktycznie to samo, co PerlpexityHased). Przelicznik, to LogLossHased = log(PerplexityHashed). Podając równe prawd. dla każdego słowa dostaniemy 6.93, bo log(1024) = 6.93\n",
"\n",
"Punktacja:\n",
"- podstawa: 60 punktów\n",
"- 40 punktów z najlepszy wynik z 2 grup\n",
"- 20 punktów z 3 kolejno najlepszych wyników z 2 grup\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Jak stworzyć model?\n",
"- warto bazować na kodzie ze wykładu 7 Zanurzenia słów\n",
"- elementy, które na pewno trzeba będzie wykorzystać to: nn.Embedding, nn.Linear, nn.Softmax\n",
"- w odróżnieniu do materiałów z wykładu lepiej nie korzystać z nn.Sequential, tylko wszystki operacje zapisywać w model.forward. Przy użyciu sequential może być problem np. z dodawaniem lub konkatenacją tensorów"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### W jaki sposób uzyskać lepszy wynik?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- Po pierwsze proszę stosować sie do rad z poprzednich cwiczeń (trenowanie przez kilka epok i monitorowanie wyniku na zbiorze deweloperskim)\n",
"- dobry start to zawsze zaczęcie od jak najprostszego modelu (czyli 1 warstwa liniowa, zwykłe dodawanie embeddingów słów) i dopiero później go rozbudowywać monitorując wynik. Jest to rada uniwersalna w uczeniu maszynowym.\n",
"- Poza tym warto wypróbować przynajmniej kilka modeli z wykładu. Mając zaimplementowany cały kod dla jednego modelu, wystarczy jedynie delikatnie zmienić architekturę modelu i wytrenować go od nowa. Cała reszta kodu zostaje bez zmian.\n",
"- warto spróbować dodanie np 2 warstw liniowych (lub nawet 3) zamiast jednej warstwy (koniecznie trzeba dodać między nimi funkcję aktywacji, np RELU).\n",
"- poza tym można zmieniać różne parametry (np. wielkość słownika, wielkość warstwy ukrytej, różne funkcje aktywacji)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Zadanie 2\n",
"\n",
"Proszę zrobić parameter Hyperparameter Tuning dla zadania 1 i zaprezentować na forum grupy razem z wnioskami\n",
"\n",
"- wymóg wyniku najlepszego modelu, conajwyżej 6.10\n",
"- wnioski nie muszą być specjalnie rozbudowane, prezentacja może trwać 3-5minut lub dłużej\n",
"- należy wybrać dla siebie metodę hypermarameter tuningu\n",
"- należy stworzyć conajmniej 10 modeli, należy pokazać wyniku dla conajmniej paru\n",
"- oczywiście kod musi być automatyczny (a nie ręcznie zmieniamy paratery), natomiast nie ma wymogu korzystania ze specjalnych bibliotek\n",
"- podstawa punktów 100\n",
"- za wynik lepszy (niższy) niż 5.50 +20 punktów\n",
"- użycie GPU na dowolnym cloud lub od WMI + 30 punktów\n",
"- termin 16.05 na zajęciach\n",
"- punkty przyznam na MS TEAMS, ale proszę zgłosić te rozwiązanie na gonito (nie trzeba w żadnym challenge ani z konkretymi tagami)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Zadanie 3\n",
"\n",
"Zaangażowanie się w pygonito.\n",
"\n",
"- https://github.com/filipggg/pygonito\n",
"- dobra okazja, żeby nauczyć się tworzyć paczki pythonowe\n",
"- wsparcie ode mnie lub prof. Gralińskiego przy tworzeniu\n",
"- może się przydać przy pracy magisterskiej (jeżeli wyzwanie będzie na gonito)\n",
"- ilość punktów zależy od zakresu pracy. Sczególnie warto samemu zaproponować co zrobić) Zakres prac i punktów do dogdania\n",
"- w celu ustalenia szczegółów proszę sie zgłosić do mnie\n",
"- termin dowolny"
]
}
],
"metadata": {
"author": "Jakub Pokrywka",
"email": "kubapok@wmi.amu.edu.pl",
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"lang": "pl",
"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.7"
},
"subtitle": "0.Informacje na temat przedmiotu[ćwiczenia]",
"title": "Ekstrakcja informacji",
"year": "2021"
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@ -0,0 +1,992 @@
{
"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> 7. <i>Zanurzenia słów</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": [
"## Zanurzenia słów\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"W praktyce stosowalność słowosieci okazała się zaskakująco\n",
"ograniczona. Większy przełom w przetwarzaniu języka naturalnego przyniosły\n",
"wielowymiarowe reprezentacje słów, inaczej: zanurzenia słów.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### „Wymiary” słów\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Moglibyśmy zanurzyć (ang. *embed*) w wielowymiarowej przestrzeni, tzn. zdefiniować odwzorowanie\n",
"$E \\colon V \\rightarrow \\mathcal{R}^m$ dla pewnego $m$ i określić taki sposób estymowania\n",
"prawdopodobieństw $P(u|v)$, by dla par $E(v)$ i $E(v')$ oraz $E(u)$ i $E(u')$ znajdujących się w pobliżu\n",
"(według jakiejś metryki odległości, na przykład zwykłej odległości euklidesowej):\n",
"\n",
"$$P(u|v) \\approx P(u'|v').$$\n",
"\n",
"$E(u)$ nazywamy zanurzeniem (embeddingiem) słowa.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Wymiary określone z góry?\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Można by sobie wyobrazić, że $m$ wymiarów mogłoby być z góry\n",
"określonych przez lingwistę. Wymiary te byłyby związane z typowymi\n",
"„osiami” rozpatrywanymi w językoznawstwie, na przykład:\n",
"\n",
"- czy słowo jest wulgarne, pospolite, potoczne, neutralne czy książkowe?\n",
"- czy słowo jest archaiczne, wychodzące z użycia czy jest neologizmem?\n",
"- czy słowo dotyczy kobiet, czy mężczyzn (w sensie rodzaju gramatycznego i/lub\n",
" socjolingwistycznym)?\n",
"- czy słowo jest w liczbie pojedynczej czy mnogiej?\n",
"- czy słowo jest rzeczownikiem czy czasownikiem?\n",
"- czy słowo jest rdzennym słowem czy zapożyczeniem?\n",
"- czy słowo jest nazwą czy słowem pospolitym?\n",
"- czy słowo opisuje konkretną rzecz czy pojęcie abstrakcyjne?\n",
"- …\n",
"\n",
"W praktyce okazało się jednak, że lepiej, żeby komputer uczył się sam\n",
"możliwych wymiarów — z góry określamy tylko $m$ (liczbę wymiarów).\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Bigramowy model języka oparty na zanurzeniach\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Zbudujemy teraz najprostszy model język oparty na zanurzeniach. Będzie to właściwie najprostszy\n",
"**neuronowy model języka**, jako że zbudowany model można traktować jako prostą sieć neuronową.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Słownik\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"W typowym neuronowym modelu języka rozmiar słownika musi być z góry\n",
"ograniczony. Zazwyczaj jest to liczba rzędu kilkudziesięciu wyrazów —\n",
"po prostu będziemy rozpatrywać $|V|$ najczęstszych wyrazów, pozostałe zamienimy\n",
"na specjalny token `<unk>` reprezentujący nieznany (*unknown*) wyraz.\n",
"\n",
"Aby utworzyć taki słownik użyjemy gotowej klasy `Vocab` z pakietu torchtext:\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/media/kuba/ssdsam/anaconda3/envs/lmzajecia/lib/python3.10/site-packages/torch/_masked/__init__.py:223: UserWarning: Failed to initialize NumPy: No module named 'numpy' (Triggered internally at /opt/conda/conda-bld/pytorch_1646755897462/work/torch/csrc/utils/tensor_numpy.cpp:68.)\n",
" example_input = torch.tensor([[-3, -2, -1], [0, 1, 2]])\n"
]
},
{
"data": {
"text/plain": [
"16"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from itertools import islice\n",
"import regex as re\n",
"import sys\n",
"from torchtext.vocab import build_vocab_from_iterator\n",
"\n",
"\n",
"def get_words_from_line(line):\n",
" line = line.rstrip()\n",
" yield '<s>'\n",
" for m in re.finditer(r'[\\p{L}0-9\\*]+|\\p{P}+', line):\n",
" yield m.group(0).lower()\n",
" yield '</s>'\n",
"\n",
"\n",
"def get_word_lines_from_file(file_name):\n",
" with open(file_name, 'r') as fh:\n",
" for line in fh:\n",
" yield get_words_from_line(line)\n",
"\n",
"vocab_size = 20000\n",
"\n",
"vocab = build_vocab_from_iterator(\n",
" get_word_lines_from_file('opensubtitlesA.pl.txt'),\n",
" max_tokens = vocab_size,\n",
" specials = ['<unk>'])\n",
"\n",
"vocab['jest']"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['<unk>', '</s>', '<s>', 'w', 'policyjny']"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vocab.lookup_tokens([0, 1, 2, 10, 12345])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Definicja sieci\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Naszą prostą sieć neuronową zaimplementujemy używając frameworku PyTorch.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"ename": "NameError",
"evalue": "name 'out' is not defined",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
"Input \u001b[0;32mIn [4]\u001b[0m, in \u001b[0;36m<cell line: 22>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 20\u001b[0m vocab\u001b[38;5;241m.\u001b[39mset_default_index(vocab[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m<unk>\u001b[39m\u001b[38;5;124m'\u001b[39m])\n\u001b[1;32m 21\u001b[0m ixs \u001b[38;5;241m=\u001b[39m torch\u001b[38;5;241m.\u001b[39mtensor(vocab\u001b[38;5;241m.\u001b[39mforward([\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mpies\u001b[39m\u001b[38;5;124m'\u001b[39m]))\n\u001b[0;32m---> 22\u001b[0m \u001b[43mout\u001b[49m[\u001b[38;5;241m0\u001b[39m][vocab[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mjest\u001b[39m\u001b[38;5;124m'\u001b[39m]]\n",
"\u001b[0;31mNameError\u001b[0m: name 'out' is not defined"
]
}
],
"source": [
"from torch import nn\n",
"import torch\n",
"\n",
"embed_size = 100\n",
"\n",
"class SimpleBigramNeuralLanguageModel(nn.Module):\n",
" def __init__(self, vocabulary_size, embedding_size):\n",
" super(SimpleBigramNeuralLanguageModel, self).__init__()\n",
" self.model = nn.Sequential(\n",
" nn.Embedding(vocabulary_size, embedding_size),\n",
" nn.Linear(embedding_size, vocabulary_size),\n",
" nn.Softmax()\n",
" )\n",
"\n",
" def forward(self, x):\n",
" return self.model(x)\n",
"\n",
"model = SimpleBigramNeuralLanguageModel(vocab_size, embed_size)\n",
"\n",
"vocab.set_default_index(vocab['<unk>'])\n",
"ixs = torch.tensor(vocab.forward(['pies']))\n",
"out[0][vocab['jest']]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Teraz wyuczmy model. Wpierw tylko potasujmy nasz plik:\n",
"\n",
" shuf < opensubtitlesA.pl.txt > opensubtitlesA.pl.shuf.txt\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"!shuf < opensubtitlesA.pl.txt > opensubtitlesA.pl.shuf.txt"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"from torch.utils.data import IterableDataset\n",
"import itertools\n",
"\n",
"def look_ahead_iterator(gen):\n",
" prev = None\n",
" for item in gen:\n",
" if prev is not None:\n",
" yield (prev, item)\n",
" prev = item\n",
"\n",
"class Bigrams(IterableDataset):\n",
" def __init__(self, text_file, vocabulary_size):\n",
" self.vocab = build_vocab_from_iterator(\n",
" get_word_lines_from_file(text_file),\n",
" max_tokens = vocabulary_size,\n",
" specials = ['<unk>'])\n",
" self.vocab.set_default_index(self.vocab['<unk>'])\n",
" self.vocabulary_size = vocabulary_size\n",
" self.text_file = text_file\n",
"\n",
" def __iter__(self):\n",
" return look_ahead_iterator(\n",
" (self.vocab[t] for t in itertools.chain.from_iterable(get_word_lines_from_file(self.text_file))))\n",
"\n",
"train_dataset = Bigrams('opensubtitlesA.pl.shuf.txt', vocab_size)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(2, 72)"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from torch.utils.data import DataLoader\n",
"\n",
"next(iter(train_dataset))"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[tensor([ 2, 72, 615, 11, 92]), tensor([ 72, 615, 11, 92, 4])]"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from torch.utils.data import DataLoader\n",
"\n",
"next(iter(DataLoader(train_dataset, batch_size=5)))"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/media/kuba/ssdsam/anaconda3/envs/lmzajecia/lib/python3.10/site-packages/torch/nn/modules/container.py:141: UserWarning: Implicit dimension choice for softmax has been deprecated. Change the call to include dim=X as an argument.\n",
" input = module(input)\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"0 tensor(10.2158, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"100 tensor(6.9743, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"200 tensor(6.2186, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"300 tensor(5.6430, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"400 tensor(5.3539, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"500 tensor(5.0689, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"600 tensor(4.9418, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"700 tensor(4.8142, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"800 tensor(4.6436, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"900 tensor(4.6770, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"1000 tensor(4.6069, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"1100 tensor(4.5514, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"1200 tensor(4.5288, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"1300 tensor(4.4578, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"1400 tensor(4.5290, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"1500 tensor(4.5229, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"1600 tensor(4.4973, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"1700 tensor(4.3793, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"1800 tensor(4.5056, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"1900 tensor(4.3709, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"2000 tensor(4.3841, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"2100 tensor(4.4515, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"2200 tensor(4.3367, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"2300 tensor(4.4187, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"2400 tensor(4.3672, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"2500 tensor(4.3117, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"2600 tensor(4.2908, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"2700 tensor(4.3188, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"2800 tensor(4.2870, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"2900 tensor(4.2855, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"3000 tensor(4.2927, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"3100 tensor(4.3358, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"3200 tensor(4.2719, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"3300 tensor(4.2606, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"3400 tensor(4.2953, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"3500 tensor(4.3175, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"3600 tensor(4.2448, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"3700 tensor(4.2430, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"3800 tensor(4.2586, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"3900 tensor(4.2905, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"4000 tensor(4.2455, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"4100 tensor(4.2214, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"4200 tensor(4.2325, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"4300 tensor(4.3036, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"4400 tensor(4.2335, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"4500 tensor(4.2377, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"4600 tensor(4.2109, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"4700 tensor(4.2942, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"4800 tensor(4.2234, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"4900 tensor(4.1918, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"5000 tensor(4.3084, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"5100 tensor(4.1666, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"5200 tensor(4.2307, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"5300 tensor(4.2050, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"5400 tensor(4.1853, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"5500 tensor(4.1917, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"5600 tensor(4.1453, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"5700 tensor(4.2423, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"5800 tensor(4.1972, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"5900 tensor(4.2143, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"6000 tensor(4.2172, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"6100 tensor(4.2463, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"6200 tensor(4.1756, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"6300 tensor(4.1223, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"6400 tensor(4.1852, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"6500 tensor(4.1559, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"6600 tensor(4.1833, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"6700 tensor(4.2090, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"6800 tensor(4.1896, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"6900 tensor(4.2057, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"7000 tensor(4.1523, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"7100 tensor(4.2645, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"7200 tensor(4.1974, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"7300 tensor(4.2031, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"7400 tensor(4.1613, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"7500 tensor(4.2018, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"7600 tensor(4.2197, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"7700 tensor(4.1976, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"7800 tensor(4.1650, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"7900 tensor(4.1380, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"8000 tensor(4.1014, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"8100 tensor(4.2058, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"8200 tensor(4.1514, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"8300 tensor(4.1187, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"8400 tensor(4.2438, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"8500 tensor(4.2094, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"8600 tensor(4.2077, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"8700 tensor(4.0819, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"8800 tensor(4.1766, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"8900 tensor(4.1805, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"9000 tensor(4.1847, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"9100 tensor(4.1929, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"9200 tensor(4.1434, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"9300 tensor(4.1678, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"9400 tensor(4.1699, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"9500 tensor(4.0885, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"9600 tensor(4.1544, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"9700 tensor(4.1828, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"9800 tensor(4.1314, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"9900 tensor(4.1473, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"10000 tensor(4.0948, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"10100 tensor(4.1396, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"10200 tensor(4.1999, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"10300 tensor(4.1027, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"10400 tensor(4.2049, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"10500 tensor(4.1470, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"10600 tensor(4.0974, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"10700 tensor(4.1239, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"10800 tensor(4.1381, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"10900 tensor(4.0569, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"11000 tensor(4.1138, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"11100 tensor(4.2053, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"11200 tensor(4.1404, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"11300 tensor(4.0741, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"11400 tensor(4.0090, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"11500 tensor(4.1568, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"11600 tensor(4.1498, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"11700 tensor(4.1052, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"11800 tensor(4.0600, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"11900 tensor(4.1274, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"12000 tensor(4.1346, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"12100 tensor(4.1024, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"12200 tensor(4.0966, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"12300 tensor(4.1036, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"12400 tensor(4.0127, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"12500 tensor(4.0575, device='cuda:0', grad_fn=<NllLossBackward0>)\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"12600 tensor(4.1542, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"12700 tensor(4.1810, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"12800 tensor(4.1948, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"12900 tensor(4.1085, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"13000 tensor(4.1283, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"13100 tensor(4.1548, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"13200 tensor(4.1015, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"13300 tensor(4.1342, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"13400 tensor(4.0724, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"13500 tensor(4.1006, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"13600 tensor(4.0998, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"13700 tensor(4.1021, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"13800 tensor(4.1175, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"13900 tensor(4.1017, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"14000 tensor(4.1877, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"14100 tensor(4.1664, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"14200 tensor(4.1582, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"14300 tensor(4.1526, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"14400 tensor(4.1208, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"14500 tensor(4.0752, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"14600 tensor(4.1907, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"14700 tensor(4.0496, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"14800 tensor(4.1371, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"14900 tensor(4.1215, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"15000 tensor(4.1059, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"15100 tensor(4.0888, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"15200 tensor(4.1359, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"15300 tensor(4.1328, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"15400 tensor(4.1044, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"15500 tensor(4.1167, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"15600 tensor(4.0449, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"15700 tensor(4.1159, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"15800 tensor(4.1082, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"15900 tensor(4.1653, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"16000 tensor(4.1111, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"16100 tensor(4.0870, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"16200 tensor(4.1085, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"16300 tensor(4.1216, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"16400 tensor(4.1307, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"16500 tensor(4.0872, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"16600 tensor(4.0754, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"16700 tensor(4.0067, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"16800 tensor(4.0413, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"16900 tensor(4.1242, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"17000 tensor(4.1169, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"17100 tensor(4.0942, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"17200 tensor(4.1518, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"17300 tensor(4.0968, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"17400 tensor(4.0476, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"17500 tensor(4.0230, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"17600 tensor(4.1268, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"17700 tensor(4.0388, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"17800 tensor(4.1741, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"17900 tensor(4.1147, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"18000 tensor(4.2020, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"18100 tensor(4.0304, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"18200 tensor(4.1171, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"18300 tensor(4.0945, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"18400 tensor(4.1019, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"18500 tensor(4.1301, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"18600 tensor(4.0979, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"18700 tensor(4.0755, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"18800 tensor(4.0760, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"18900 tensor(4.0553, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"19000 tensor(4.1530, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"19100 tensor(4.1403, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"19200 tensor(4.1449, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"19300 tensor(4.0105, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"19400 tensor(4.0742, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"19500 tensor(4.0666, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"19600 tensor(4.1549, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"19700 tensor(4.0930, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"19800 tensor(4.1271, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"19900 tensor(4.1169, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"20000 tensor(4.1053, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"20100 tensor(4.1070, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"20200 tensor(4.0848, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"20300 tensor(4.1330, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"20400 tensor(3.9828, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"20500 tensor(4.1411, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"20600 tensor(4.0537, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"20700 tensor(4.1171, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"20800 tensor(4.0510, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"20900 tensor(4.1230, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"21000 tensor(4.1241, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"21100 tensor(4.1600, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"21200 tensor(4.0699, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"21300 tensor(4.0870, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"21400 tensor(4.0774, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"21500 tensor(4.1492, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"21600 tensor(4.0883, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"21700 tensor(4.0358, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"21800 tensor(4.0569, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"21900 tensor(4.0832, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"22000 tensor(4.0827, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"22100 tensor(4.0534, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"22200 tensor(4.0173, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"22300 tensor(4.0549, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"22400 tensor(4.0613, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"22500 tensor(4.1058, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"22600 tensor(4.1230, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"22700 tensor(4.1114, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"22800 tensor(4.0541, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"22900 tensor(4.0732, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"23000 tensor(4.0983, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"23100 tensor(4.0547, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"23200 tensor(4.1198, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"23300 tensor(4.0687, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"23400 tensor(4.0676, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"23500 tensor(4.0834, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"23600 tensor(4.0996, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"23700 tensor(4.0791, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"23800 tensor(4.0700, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"23900 tensor(4.0388, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"24000 tensor(4.0625, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"24100 tensor(4.1095, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"24200 tensor(4.1589, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"24300 tensor(4.1565, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"24400 tensor(4.1396, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"24500 tensor(4.1642, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"24600 tensor(4.0868, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"24700 tensor(4.0770, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"24800 tensor(4.0577, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"24900 tensor(4.0662, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"25000 tensor(4.0877, device='cuda:0', grad_fn=<NllLossBackward0>)\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"25100 tensor(4.0505, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"25200 tensor(4.1542, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"25300 tensor(4.0740, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"25400 tensor(4.0893, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"25500 tensor(4.0370, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"25600 tensor(4.1480, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"25700 tensor(4.1070, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"25800 tensor(4.0381, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"25900 tensor(4.0800, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"26000 tensor(4.0842, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"26100 tensor(4.1127, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"26200 tensor(4.1184, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"26300 tensor(4.0885, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"26400 tensor(4.1423, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"26500 tensor(4.1359, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"26600 tensor(4.0986, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"26700 tensor(4.0580, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"26800 tensor(4.0806, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"26900 tensor(4.0169, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"27000 tensor(4.1111, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"27100 tensor(4.1417, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"27200 tensor(4.1497, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"27300 tensor(4.1093, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"27400 tensor(4.0306, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"27500 tensor(4.1214, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"27600 tensor(4.0745, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"27700 tensor(4.0559, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"27800 tensor(4.0286, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"27900 tensor(4.1266, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"28000 tensor(3.9690, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"28100 tensor(4.1141, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"28200 tensor(4.0565, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"28300 tensor(4.0682, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"28400 tensor(4.0646, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"28500 tensor(4.0386, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"28600 tensor(4.0903, device='cuda:0', grad_fn=<NllLossBackward0>)\n",
"28700 tensor(4.1060, device='cuda:0', grad_fn=<NllLossBackward0>)\n"
]
}
],
"source": [
" device = 'cuda'\n",
" model = SimpleBigramNeuralLanguageModel(vocab_size, embed_size).to(device)\n",
" data = DataLoader(train_dataset, batch_size=5000)\n",
" optimizer = torch.optim.Adam(model.parameters())\n",
" criterion = torch.nn.NLLLoss()\n",
" \n",
" model.train()\n",
" step = 0\n",
" for x, y in data:\n",
" x = x.to(device)\n",
" y = y.to(device)\n",
" optimizer.zero_grad()\n",
" ypredicted = model(x)\n",
" loss = criterion(torch.log(ypredicted), y)\n",
" if step % 100 == 0:\n",
" print(step, loss)\n",
" step += 1\n",
" loss.backward()\n",
" optimizer.step()\n",
" \n",
" torch.save(model.state_dict(), 'model1.bin')\n",
"\n",
"#Policzmy najbardziej prawdopodobne kontynuację dla zadanego słowa:\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"SimpleBigramNeuralLanguageModel(\n",
" (model): Sequential(\n",
" (0): Embedding(20000, 100)\n",
" (1): Linear(in_features=100, out_features=20000, bias=True)\n",
" (2): Softmax(dim=None)\n",
" )\n",
")"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[('mnie', 26, 0.16004179418087006),\n",
" ('ciebie', 73, 0.13592898845672607),\n",
" ('<unk>', 0, 0.12769868969917297),\n",
" ('nas', 83, 0.04033529385924339),\n",
" ('niego', 172, 0.033195145428180695),\n",
" ('niej', 247, 0.021507620811462402),\n",
" ('was', 162, 0.017743170261383057),\n",
" ('siebie', 181, 0.01618184894323349),\n",
" ('nich', 222, 0.01589815877377987),\n",
" ('pana', 156, 0.014923062175512314)]"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"device = 'cuda'\n",
"model = SimpleBigramNeuralLanguageModel(vocab_size, embed_size).to(device)\n",
"model.load_state_dict(torch.load('model1.bin'))\n",
"model.eval()\n",
"\n",
"ixs = torch.tensor(vocab.forward(['dla'])).to(device)\n",
"\n",
"out = model(ixs)\n",
"top = torch.topk(out[0], 10)\n",
"top_indices = top.indices.tolist()\n",
"top_probs = top.values.tolist()\n",
"top_words = vocab.lookup_tokens(top_indices)\n",
"list(zip(top_words, top_indices, top_probs))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Teraz zbadajmy najbardziej podobne zanurzenia dla zadanego słowa:\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[('.', 3, 0.3327740728855133),\n",
" ('z', 14, 0.191472589969635),\n",
" (',', 4, 0.18250100314617157),\n",
" ('w', 10, 0.06395534425973892),\n",
" ('?', 6, 0.059775471687316895),\n",
" ('i', 11, 0.019332991912961006),\n",
" ('ze', 60, 0.016418060287833214),\n",
" ('<unk>', 0, 0.014098692685365677),\n",
" ('na', 12, 0.01183203887194395),\n",
" ('...', 15, 0.010537521913647652)]"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vocab = train_dataset.vocab\n",
"ixs = torch.tensor(vocab.forward(['kłopot'])).to(device)\n",
"\n",
"out = model(ixs)\n",
"top = torch.topk(out[0], 10)\n",
"top_indices = top.indices.tolist()\n",
"top_probs = top.values.tolist()\n",
"top_words = vocab.lookup_tokens(top_indices)\n",
"list(zip(top_words, top_indices, top_probs))"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[('poszedł', 1088, 1.0),\n",
" ('wsiąść', 9766, 0.46510031819343567),\n",
" ('pojedzie', 6485, 0.4598822593688965),\n",
" ('wyjeżdża', 6459, 0.4378735423088074),\n",
" ('szedłem', 8969, 0.4232063889503479),\n",
" ('zadzwoniłem', 4889, 0.41752171516418457),\n",
" ('dotrzemy', 6098, 0.40929487347602844),\n",
" ('spóźnić', 9923, 0.4015277922153473),\n",
" ('pójdę', 635, 0.3992091119289398),\n",
" ('wrócimy', 2070, 0.39785560965538025)]"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"cos = nn.CosineSimilarity(dim=1, eps=1e-6)\n",
"\n",
"embeddings = model.model[0].weight\n",
"\n",
"vec = embeddings[vocab['poszedł']]\n",
"\n",
"similarities = cos(vec, embeddings)\n",
"\n",
"top = torch.topk(similarities, 10)\n",
"\n",
"top_indices = top.indices.tolist()\n",
"top_probs = top.values.tolist()\n",
"top_words = vocab.lookup_tokens(top_indices)\n",
"list(zip(top_words, top_indices, top_probs))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Zapis przy użyciu wzoru matematycznego\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Powyżej zaprogramowaną sieć neuronową można opisać następującym wzorem:\n",
"\n",
"$$\\vec{y} = \\operatorname{softmax}(CE(w_{i-1}),$$\n",
"\n",
"gdzie:\n",
"\n",
"- $w_{i-1}$ to pierwszy wyraz w bigramie (poprzedzający wyraz),\n",
"- $E(w)$ to zanurzenie (embedding) wyrazy $w$ — wektor o rozmiarze $m$,\n",
"- $C$ to macierz o rozmiarze $|V| \\times m$, która rzutuje wektor zanurzenia w wektor o rozmiarze słownika,\n",
"- $\\vec{y}$ to wyjściowy wektor prawdopodobieństw o rozmiarze $|V|$.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### Hiperparametry\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Zauważmy, że nasz model ma dwa hiperparametry:\n",
"\n",
"- $m$ — rozmiar zanurzenia,\n",
"- $|V|$ — rozmiar słownika, jeśli zakładamy, że możemy sterować\n",
" rozmiarem słownika (np. przez obcinanie słownika do zadanej liczby\n",
" najczęstszych wyrazów i zamiany pozostałych na specjalny token, powiedzmy, `<UNK>`.\n",
"\n",
"Oczywiście możemy próbować manipulować wartościami $m$ i $|V|$ w celu\n",
"polepszenia wyników naszego modelu.\n",
"\n",
"**Pytanie**: dlaczego nie ma sensu wartość $m \\approx |V|$ ? dlaczego nie ma sensu wartość $m = 1$?\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Diagram sieci\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Jako że mnożenie przez macierz ($C$) oznacza po prostu zastosowanie\n",
"warstwy liniowej, naszą sieć możemy interpretować jako jednowarstwową\n",
"sieć neuronową, co można zilustrować za pomocą następującego diagramu:\n",
"\n",
"![img](./07_Zanurzenia_slow/bigram1.drawio.png \"Diagram prostego bigramowego neuronowego modelu języka\")\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Zanurzenie jako mnożenie przez macierz\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Uzyskanie zanurzenia ($E(w)$) zazwyczaj realizowane jest na zasadzie\n",
"odpytania (<sub>look</sub>-up\\_). Co ciekawe, zanurzenie można intepretować jako\n",
"mnożenie przez macierz zanurzeń (embeddingów) $E$ o rozmiarze $m \\times |V|$ — jeśli słowo będziemy na wejściu kodowali przy użyciu\n",
"wektora z gorącą jedynką (<sub>one</sub>-hot encoding\\_), tzn. słowo $w$ zostanie\n",
"podane na wejściu jako wektor $\\vec{1_V}(w) = [0,\\ldots,0,1,0\\ldots,0]$ o rozmiarze $|V|$\n",
"złożony z samych zer z wyjątkiem jedynki na pozycji odpowiadającej indeksowi wyrazu $w$ w słowniku $V$.\n",
"\n",
"Wówczas wzór przyjmie postać:\n",
"\n",
"$$\\vec{y} = \\operatorname{softmax}(CE\\vec{1_V}(w_{i-1})),$$\n",
"\n",
"gdzie $E$ będzie tym razem macierzą $m \\times |V|$.\n",
"\n",
"**Pytanie**: czy $\\vec{1_V}(w)$ intepretujemy jako wektor wierszowy czy kolumnowy?\n",
"\n",
"W postaci diagramu można tę interpretację zilustrować w następujący sposób:\n",
"\n",
"![img](./07_Zanurzenia_slow/bigram2.drawio.png \"Diagram prostego bigramowego neuronowego modelu języka z wejściem w postaci one-hot\")\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"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.4"
},
"org": null
},
"nbformat": 4,
"nbformat_minor": 1
}

338
wyk/07_Zanurzenia_slow.org Normal file
View File

@ -0,0 +1,338 @@
* Zanurzenia słów
W praktyce stosowalność słowosieci okazała się zaskakująco
ograniczona. Większy przełom w przetwarzaniu języka naturalnego przyniosły
wielowymiarowe reprezentacje słów, inaczej: zanurzenia słów.
** „Wymiary” słów
Moglibyśmy zanurzyć (ang. /embed/) w wielowymiarowej przestrzeni, tzn. zdefiniować odwzorowanie
$E \colon V \rightarrow \mathcal{R}^m$ dla pewnego $m$ i określić taki sposób estymowania
prawdopodobieństw $P(u|v)$, by dla par $E(v)$ i $E(v')$ oraz $E(u)$ i $E(u')$ znajdujących się w pobliżu
(według jakiejś metryki odległości, na przykład zwykłej odległości euklidesowej):
$$P(u|v) \approx P(u'|v').$$
$E(u)$ nazywamy zanurzeniem (embeddingiem) słowa.
*** Wymiary określone z góry?
Można by sobie wyobrazić, że $m$ wymiarów mogłoby być z góry
określonych przez lingwistę. Wymiary te byłyby związane z typowymi
„osiami” rozpatrywanymi w językoznawstwie, na przykład:
- czy słowo jest wulgarne, pospolite, potoczne, neutralne czy książkowe?
- czy słowo jest archaiczne, wychodzące z użycia czy jest neologizmem?
- czy słowo dotyczy kobiet, czy mężczyzn (w sensie rodzaju gramatycznego i/lub
socjolingwistycznym)?
- czy słowo jest w liczbie pojedynczej czy mnogiej?
- czy słowo jest rzeczownikiem czy czasownikiem?
- czy słowo jest rdzennym słowem czy zapożyczeniem?
- czy słowo jest nazwą czy słowem pospolitym?
- czy słowo opisuje konkretną rzecz czy pojęcie abstrakcyjne?
-
W praktyce okazało się jednak, że lepiej, żeby komputer uczył się sam
możliwych wymiarów — z góry określamy tylko $m$ (liczbę wymiarów).
** Bigramowy model języka oparty na zanurzeniach
Zbudujemy teraz najprostszy model język oparty na zanurzeniach. Będzie to właściwie najprostszy
*neuronowy model języka*, jako że zbudowany model można traktować jako prostą sieć neuronową.
*** Słownik
W typowym neuronowym modelu języka rozmiar słownika musi być z góry
ograniczony. Zazwyczaj jest to liczba rzędu kilkudziesięciu wyrazów —
po prostu będziemy rozpatrywać $|V|$ najczęstszych wyrazów, pozostałe zamienimy
na specjalny token ~<unk>~ reprezentujący nieznany (/unknown/) wyraz.
Aby utworzyć taki słownik użyjemy gotowej klasy ~Vocab~ z pakietu torchtext:
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
from itertools import islice
import regex as re
import sys
from torchtext.vocab import build_vocab_from_iterator
def get_words_from_line(line):
line = line.rstrip()
yield '<s>'
for m in re.finditer(r'[\p{L}0-9\*]+|\p{P}+', line):
yield m.group(0).lower()
yield '</s>'
def get_word_lines_from_file(file_name):
with open(file_name, 'r') as fh:
for line in fh:
yield get_words_from_line(line)
vocab_size = 20000
vocab = build_vocab_from_iterator(
get_word_lines_from_file('opensubtitlesA.pl.txt'),
max_tokens = vocab_size,
specials = ['<unk>'])
vocab['jest']
#+END_SRC
#+RESULTS:
:results:
16
:end:
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
vocab.lookup_tokens([0, 1, 2, 10, 12345])
#+END_SRC
#+RESULTS:
:results:
['<unk>', '</s>', '<s>', 'w', 'wierzyli']
:end:
*** Definicja sieci
Naszą prostą sieć neuronową zaimplementujemy używając frameworku PyTorch.
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
from torch import nn
import torch
embed_size = 100
class SimpleBigramNeuralLanguageModel(nn.Module):
def __init__(self, vocabulary_size, embedding_size):
super(SimpleBigramNeuralLanguageModel, self).__init__()
self.model = nn.Sequential(
nn.Embedding(vocabulary_size, embedding_size),
nn.Linear(embedding_size, vocabulary_size),
nn.Softmax()
)
def forward(self, x):
return self.model(x)
model = SimpleBigramNeuralLanguageModel(vocab_size, embed_size)
vocab.set_default_index(vocab['<unk>'])
ixs = torch.tensor(vocab.forward(['pies']))
out[0][vocab['jest']]
#+END_SRC
#+RESULTS:
:results:
:end:
Teraz wyuczmy model. Wpierw tylko potasujmy nasz plik:
#+BEGIN_SRC
shuf < opensubtitlesA.pl.txt > opensubtitlesA.pl.shuf.txt
#+END_SRC
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
from torch.utils.data import IterableDataset
import itertools
def look_ahead_iterator(gen):
prev = None
for item in gen:
if prev is not None:
yield (prev, item)
prev = item
class Bigrams(IterableDataset):
def __init__(self, text_file, vocabulary_size):
self.vocab = build_vocab_from_iterator(
get_word_lines_from_file(text_file),
max_tokens = vocabulary_size,
specials = ['<unk>'])
self.vocab.set_default_index(self.vocab['<unk>'])
self.vocabulary_size = vocabulary_size
self.text_file = text_file
def __iter__(self):
return look_ahead_iterator(
(self.vocab[t] for t in itertools.chain.from_iterable(get_word_lines_from_file(self.text_file))))
train_dataset = Bigrams('opensubtitlesA.pl.shuf.txt', vocab_size)
#+END_SRC
#+RESULTS:
:results:
:end:
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
from torch.utils.data import DataLoader
next(iter(train_dataset))
#+END_SRC
#+RESULTS:
:results:
(2, 5)
:end:
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
from torch.utils.data import DataLoader
next(iter(DataLoader(train_dataset, batch_size=5)))
#+END_SRC
#+RESULTS:
:results:
[tensor([ 2, 5, 51, 3481, 231]), tensor([ 5, 51, 3481, 231, 4])]
:end:
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
device = 'cuda'
model = SimpleBigramNeuralLanguageModel(vocab_size, embed_size).to(device)
data = DataLoader(train_dataset, batch_size=5000)
optimizer = torch.optim.Adam(model.parameters())
criterion = torch.nn.NLLLoss()
model.train()
step = 0
for x, y in data:
x = x.to(device)
y = y.to(device)
optimizer.zero_grad()
ypredicted = model(x)
loss = criterion(torch.log(ypredicted), y)
if step % 100 == 0:
print(step, loss)
step += 1
loss.backward()
optimizer.step()
torch.save(model.state_dict(), 'model1.bin')
#+END_SRC
#+RESULTS:
:results:
None
:end:
Policzmy najbardziej prawdopodobne kontynuację dla zadanego słowa:
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
device = 'cuda'
model = SimpleBigramNeuralLanguageModel(vocab_size, embed_size).to(device)
model.load_state_dict(torch.load('model1.bin'))
model.eval()
ixs = torch.tensor(vocab.forward(['dla'])).to(device)
out = model(ixs)
top = torch.topk(out[0], 10)
top_indices = top.indices.tolist()
top_probs = top.values.tolist()
top_words = vocab.lookup_tokens(top_indices)
list(zip(top_words, top_indices, top_probs))
#+END_SRC
#+RESULTS:
:results:
[('ciebie', 73, 0.1580502986907959), ('mnie', 26, 0.15395283699035645), ('<unk>', 0, 0.12862136960029602), ('nas', 83, 0.0410110242664814), ('niego', 172, 0.03281523287296295), ('niej', 245, 0.02104802615940571), ('siebie', 181, 0.020788608118891716), ('którego', 365, 0.019379809498786926), ('was', 162, 0.013852755539119244), ('wszystkich', 235, 0.01381855271756649)]
:end:
Teraz zbadajmy najbardziej podobne zanurzenia dla zadanego słowa:
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
vocab = train_dataset.vocab
ixs = torch.tensor(vocab.forward(['kłopot'])).to(device)
out = model(ixs)
top = torch.topk(out[0], 10)
top_indices = top.indices.tolist()
top_probs = top.values.tolist()
top_words = vocab.lookup_tokens(top_indices)
list(zip(top_words, top_indices, top_probs))
#+END_SRC
#+RESULTS:
:results:
[('.', 3, 0.404473215341568), (',', 4, 0.14222915470600128), ('z', 14, 0.10945753753185272), ('?', 6, 0.09583134204149246), ('w', 10, 0.050338443368673325), ('na', 12, 0.020703863352537155), ('i', 11, 0.016762692481279373), ('<unk>', 0, 0.014571071602404118), ('...', 15, 0.01453721895813942), ('</s>', 1, 0.011769450269639492)]
:end:
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
cos = nn.CosineSimilarity(dim=1, eps=1e-6)
embeddings = model.model[0].weight
vec = embeddings[vocab['poszedł']]
similarities = cos(vec, embeddings)
top = torch.topk(similarities, 10)
top_indices = top.indices.tolist()
top_probs = top.values.tolist()
top_words = vocab.lookup_tokens(top_indices)
list(zip(top_words, top_indices, top_probs))
#+END_SRC
#+RESULTS:
:results:
[('poszedł', 1087, 1.0), ('idziesz', 1050, 0.4907470941543579), ('przyjeżdża', 4920, 0.45242372155189514), ('pojechałam', 12784, 0.4342481195926666), ('wrócił', 1023, 0.431664377450943), ('dobrać', 10351, 0.4312002956867218), ('stałeś', 5738, 0.4258835017681122), ('poszła', 1563, 0.41979148983955383), ('trafiłam', 18857, 0.4109022617340088), ('jedzie', 1674, 0.4091658890247345)]
:end:
*** Zapis przy użyciu wzoru matematycznego
Powyżej zaprogramowaną sieć neuronową można opisać następującym wzorem:
$$\vec{y} = \operatorname{softmax}(CE(w_{i-1}),$$
gdzie:
- $w_{i-1}$ to pierwszy wyraz w bigramie (poprzedzający wyraz),
- $E(w)$ to zanurzenie (embedding) wyrazy $w$ — wektor o rozmiarze $m$,
- $C$ to macierz o rozmiarze $|V| \times m$, która rzutuje wektor zanurzenia w wektor o rozmiarze słownika,
- $\vec{y}$ to wyjściowy wektor prawdopodobieństw o rozmiarze $|V|$.
**** Hiperparametry
Zauważmy, że nasz model ma dwa hiperparametry:
- $m$ — rozmiar zanurzenia,
- $|V|$ — rozmiar słownika, jeśli zakładamy, że możemy sterować
rozmiarem słownika (np. przez obcinanie słownika do zadanej liczby
najczęstszych wyrazów i zamiany pozostałych na specjalny token, powiedzmy, ~<UNK>~.
Oczywiście możemy próbować manipulować wartościami $m$ i $|V|$ w celu
polepszenia wyników naszego modelu.
*Pytanie*: dlaczego nie ma sensu wartość $m \approx |V|$ ? dlaczego nie ma sensu wartość $m = 1$?
*** Diagram sieci
Jako że mnożenie przez macierz ($C$) oznacza po prostu zastosowanie
warstwy liniowej, naszą sieć możemy interpretować jako jednowarstwową
sieć neuronową, co można zilustrować za pomocą następującego diagramu:
#+CAPTION: Diagram prostego bigramowego neuronowego modelu języka
[[./07_Zanurzenia_slow/bigram1.drawio.png]]
*** Zanurzenie jako mnożenie przez macierz
Uzyskanie zanurzenia ($E(w)$) zazwyczaj realizowane jest na zasadzie
odpytania (_look-up_). Co ciekawe, zanurzenie można intepretować jako
mnożenie przez macierz zanurzeń (embeddingów) $E$ o rozmiarze $m \times |V|$ — jeśli słowo będziemy na wejściu kodowali przy użyciu
wektora z gorącą jedynką (_one-hot encoding_), tzn. słowo $w$ zostanie
podane na wejściu jako wektor $\vec{1_V}(w) = [0,\ldots,0,1,0\ldots,0]$ o rozmiarze $|V|$
złożony z samych zer z wyjątkiem jedynki na pozycji odpowiadającej indeksowi wyrazu $w$ w słowniku $V$.
Wówczas wzór przyjmie postać:
$$\vec{y} = \operatorname{softmax}(CE\vec{1_V}(w_{i-1})),$$
gdzie $E$ będzie tym razem macierzą $m \times |V|$.
*Pytanie*: czy $\vec{1_V}(w)$ intepretujemy jako wektor wierszowy czy kolumnowy?
W postaci diagramu można tę interpretację zilustrować w następujący sposób:
#+CAPTION: Diagram prostego bigramowego neuronowego modelu języka z wejściem w postaci one-hot
[[./07_Zanurzenia_slow/bigram2.drawio.png]]

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,406 @@
* Neuronowy n-gramowy model języka
Omówiony w poprzedniej części neuronowy bigramowy model języka
warunkuje kolejny wyraz jedynie względem bezpośrednio poprzedzającego
— jak w każdym bigramowym modelu przyjmujemy założenie, że $w_i$
zależy tylko od $w_{i-1}$. Rzecz jasna jest to bardzo duże
ograniczenie, w rzeczywistości bardzo często prawdopodobieństwo
kolejnego wyrazu zależy od wyrazu dwie, trzy, cztery itd. pozycje
wstecz czy w ogólności od wszystkich wyrazów poprzedzających (bez
względu na ich pozycje).
*Pytanie*: Wskaż zależności o zasięgu większym niż 1 wyraz w zdaniu
/Zatopieni w kłębach dymu cygar i pochyleni nad butelkami z ciemnego
szkła obywatele tej dzielnicy, jedni zakładali się o wygranę lub
przegranę Anglii, drudzy o bankructwo Wokulskiego; jedni nazywali
geniuszem Bismarcka, drudzy — awanturnikiem Wokulskiego; jedni
krytykowali postępowanie prezydenta MacMahona, inni twierdzili, że
Wokulski jest zdecydowanym wariatem, jeżeli nie czymś gorszym…/
** Trigramowy neuronowy model języka
Spróbujmy najpierw rozszerzyć nasz model na trigramy, to znaczy
będziemy przewidywać słowo $w_i$ na podstawie słów $w_{i-2}$ i
$w_{i-1}$.
Najprostsze rozwiązanie polegałoby na zanurzeniu pary $(w_{i-2},
w_{i-1})$ w całości i postępowaniu jak w przypadku modelu bigramowego.
Byłoby to jednak zupełnie niepraktyczne, jako że:
- liczba zanurzeń do wyuczenia byłaby olbrzymia ($|V|^2$ — byłoby to
ewentualnie akceptowalne dla modeli operujących na krótszych
jednostkach niż słowa, np. na znakach),
- w szczególności zanurzenia dla par $(v, u)$, $(u, v)$, $(u, u)$ i
$(v, v)$ nie miałyby ze sobą nic wspólnego.
*** Konketanacja zanurzeń
Właściwsze rozwiązanie polega na zanurzeniu dalej pojedynczych słów i
następnie ich *konkatenowaniu*.
Przypomnijmy, że konkatenacja wektorów $\vec{x_1}$ i $\vec{x_2}$ to wektor o rozmiarze
$|\vec{x_1}| + |\vec{x_2}|$ powstały ze „sklejania” wektorów $\vec{x_1}$ i $\vec{x_2}$.
Konkatenację wektorów $\vec{x_1}$ i $\vec{x_2}$ będziemy oznaczać za pomocą $[\vec{x_1}, \vec{x_2}]$.
Przykład: jeśli $\vec{x_1} = [-1, 2, 0]$ i $\vec{x_2} = [3, -3]$,
wówczas $[\vec{x_1}, \vec{x_2}] = [-1, 2, 0, 3, -3]$
Oznacza to, że nasza macierz „kontekstowa” $C$ powinna mieć w modelu trigramowym rozmiar nie
$|V| \times m$, lecz $|V| \times (m+m)$ = $|V| \times 2m$ i wyjście będzie zdefiniowane za pomocą wzoru:
$$\vec{y} = \operatorname{softmax}(C[E(w_{i-2}),E(w_{i-1})]),$$
co można przedstawić za pomocą następującego schematu:
#+CAPTION: Diagram prostego bigramowego neuronowego modelu języka
[[./08_Neuronowy_ngramowy_model/trigram1.drawio.png]]
**** Rozbicie macierzy $C$
Zamiast mnożyć macierz $C$ przez konkatenację dwóch wektorów, można
rozbić macierz $C$ na dwie, powiedzmy $C_{-2}$ i $C_{-1}$, przemnażać
je osobno przez odpowiadające im wektory i następnie *dodać* macierze,
tak aby:
$$C[E(w_{i-2}),E(w_{i-1})] = C_{-2}E(w_{i-2}) + C_{-1}E(w_{i-1}).$$
Macierze $C_{-2}$ i $C_{-1}$ będą miały rozmiar $|V| \times m$.
Przy tym podejściu możemy powiedzieć, że ostatni i przedostatni wyraz
mają swoje osobne macierze o potencjalnie różnych wagach — co ma sens,
jako że na inne aspekty zwracamy uwagę przewidując kolejne słowo na
podstawie wyrazu bezpośrednio poprzedzającego, a na inne — na
podstawie słowa występującego dwie pozycje wcześniej.
** Uogólnienie na $n$-gramowy model języka dla dowolnego $n$
Łatwo uogólnić opisany wyżej trigramowy model języka dla dowolnego $n$.
Uogólniony model można przedstawić za pomocą wzoru:
$$\vec{y} = \operatorname{softmax}(C[E(w_{i-n+1}),\dots,E(w_{i-1})]),$$
gdzie macierz $C$ ma rozmiar $|V| \times nm$ lub za pomocą wzoru:
$$\vec{y} = \operatorname{softmax}(C_{-(n-1)}E(w_{i-n+1}) + \dots + C_{-1}E(w_{i-1}),$$
gdzie macierze $C_{-(n-1)}$, \dots, $C_{-1}$ mają rozmiary $|V| \times m$.
Por. diagram:
#+CAPTION: Diagram prostego n-gramowego neuronowego modelu języka
[[./08_Neuronowy_ngramowy_model/ngram.drawio.png]]
** Dodanie kolejnej warstwy
W wypadku trigramowego czy — ogólniej — n-gramowego modelu języka dla
$n \geq 3$ warto dodać kolejną (*ukrytą*) warstwę, na którą będziemy rzutować
skonkatenowane embeddingi, zanim zrzutujemy je do długiego wektora
prawdopodobieństw.
Zakładamy, że warstwa ukryta zawiera $h$ neuronów. Wartość $h$ powinna być mniejsza
niż $nm$ (a może nawet od $m$).
*Pytanie*: Dlaczego wartość $h > nm$ nie jest racjonalnym wyborem?
*Pytanie*: Dlaczego dodanie kolejnej warstwy nie ma sensu dla modelu bigramowego?
*** Funkcja aktywacji
Aby warstwa ukryta wnosiła coś nowego, na wyjściu z tej funkcji musimy (dlaczego?)
zastosować nieliniową *funkcji aktywacji*. Zazwyczaj jako funkcji
aktywacji w sieciach neuronowych używa się funkcji ReLU albo funkcji
sigmoidalnej. W prostych neuronowych modelach języka sprawdza się też
*tangens hiperboliczny* (tgh, w literaturze anglojęzycznej tanh):
$$\operatorname{tgh}(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}.$$
#+BEGIN_SRC ipython :session mysession :results file
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
x = torch.linspace(-5,5,100)
plt.xlabel("x")
plt.ylabel("y")
a = torch.Tensor(x.size()[0]).fill_(2.)
m = torch.stack([x, a])
plt.plot(x, nn.functional.tanh(m)[0])
fname = '08_Neuronowy_ngramowy_model/tanh.png'
plt.savefig(fname)
fname
#+END_SRC
#+RESULTS:
[[file:08_Neuronowy_ngramowy_model/tanh.png]]
**** Tangens hiperboliczny zastosowany dla wektora
Tangens hiperboliczny wektora będzie po prostu wektorem tangensów
hiperbolicznych poszczególnych wartości.
#+BEGIN_SRC ipython :session mysession :results file
import torch
import torch.nn as nn
v = torch.Tensor([-100, -2.0, 0.0, 0.5, 1000.0])
nn.functional.tanh(v)
#+END_SRC
#+RESULTS:
[[file:tensor([-1.0000, -0.9640, 0.0000, 0.4621, 1.0000])]]
*** Wzór i schemat dwuwarstwowego n-gramowego neuronowego modelu języka
Dwuwarstwowy model języka będzie określony następującym wzorem:
$$\vec{y} = \operatorname{softmax}(C\operatorname{tgh}(W[E(w_{i-n+1}),\dots,E(w_{i-1})])),$$
gdzie:
- $W$ jest wyuczalną macierzą wag o rozmiarze $h \times nm$,
- $C$ będzie macierzą o rozmiarze $|V| \times h$.
Zmodyfikowaną sieć można przedstawić za pomocą następującego schematu:
#+CAPTION: Dwuwarstwowy n-gramowy neuronowy model języka
[[./08_Neuronowy_ngramowy_model/ngram-tgh.drawio.png]]
*** Liczba wag w modelu dwuwarstwowym
Na wagi w modelu dwuwarstwowym składają się:
- zanurzenia: $m|V|$,
- wagi warstwy ukrytej: $hnm$,
- wagi warstwy wyjściowej: $|V|h$,
a zatem łącznie:
$$m|V| + hnm + |V|h$$
Jeśli $h \approx m$ (co jest realistyczną opcją), wówczas otrzymamy oszacowanie:
$$O(m|V| + nm^2).$$
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
wartości $n$ niż tradycyjny, statystyczny n-gramowy model języka (dla którego
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.

View File

@ -0,0 +1 @@
<mxfile host="app.diagrams.net" modified="2022-04-23T08:10:01.287Z" agent="5.0 (X11)" etag="DuwX-avzVRfuxOlr24Vt" version="17.4.6" type="device"><diagram id="UNyY5TJjaPirS19H5Hif" name="Page-1">7VpZd9o4GP01PJLjDWM/BgKZmaaTtjmnU+alR8XCVjCWI4sY8utHQjLexNKwk3kB6Wr/tvvJdsPsTmb3BMTBZ+zBsGFo3qxh3jUMw7V09suBuQB027UE4hPkSSwHntAblKAm0SnyYFLqSDEOKYrL4BBHERzSEgYIwWm52wiH5VVj4MMa8DQEYR39B3k0EKhjtHP8D4j8IFuZHVC0TEDWWU6RBMDDqYAWhzN7DbNLMKaiNJl1YciFl8lFSKC/onW5MQIjutWAbu/xCwqcTz//fvZerAc4TN6aUhkJnWcHhh47v6xiQgPs4wiEvRztEDyNPMhn1Vgt7/OAccxAnYHPkNK5VCaYUsyggE5C2QpniP7gw29asjaQk/Hy3axYmWeViJL5j2JlkM/Aq/mwRS0bN8IRlRvhIu+I8/JDrhRjpi88JUPZ6+vj5/Txfjy+f/gz+vbS96nuPDQNaY6A+JCukbG+VDbzEognkO2PjSMwBBS9lvcBpLn6y365RllBKlWt4HWbfAXhVK5U03iuT66cNEAUPsVgcfSUOXVZdyvF9QoJhbMCVD+xbDU06dsyJpiurKe5g+mmxIKCc2XY3oVkKoRkh2zVjodeWdGni5MzZ7HSn412BzGxtu8EkPVkC5c6V4TMZEPLkkwowWPYxSEmDIlwxH1rhMKwAoEQ+RGrDpnEIcM7XNKIRahb2TBBnrdwTJXqys66B+1ZFe1ZTl179jGV515KCHt/pLHrkUbZz1FGt1OFHvv8Qo/ulo1Xb9WNd2nQR7Fe/QIZOCfdQTbfGgZ+v9k7W5q9bp6V3Tvnb/enN3ttJeWijENDjMfNaVwgWHT99GrrFXq1Tkyvej2DTPCITsDsepVgVZSga4oMVTuqFrZKUQl+Gze6rYZjAI/LgIDUwzH+heACtRKaAn4ZD/nvGHCw0/Wgjxv87ijSW8TLrS6K2P/39SmuIkUW0C9SRT5Kdmw4BmPHou0sQ13Bdgy3dRjbUd4/zYvLMJYpxTHv+EPsPPe/uoM++itwv7eM3sTrZLI7mzv+uk2eU8LR0k94x1cK6ePm2ftLi+XQLxixJXJdt91yytKu6FB4mRxVfEK5aSKjMpFww9pEt4SAeaFbzDskv7FhS/u9fZX7s4LYQW6cS+G+315Vt+f1z6T0D/tMqmW1K6x7zKdSSkKoh+ArCzf7Id30SXvRSWwPevff/gWfsNW3ps3sPc+5kO66TZ4T6Zp22QmOSrpKIV1e6nkppGsZbjXkvZd2FVMdiHgVK22g3o0jdiZfpd3Wyffm5uZ6+dPcIls/FHsq5d9WBFeR6ehZhnOtujDM02UySl04/8fwA8XwpR53jeC1iQ4Uv2vrbIjeG/rvHLuVWWHrUux1Pxn0QTLjFWRsVmhC287M9qVZVcotLrpclCWd2y9TnDU0k4WQb1kHZuKzvDG/GBefU4u5VlyZ2S5RnKwig4JBgCQWn6ON0IwbVmcFDyj0u/VrCdOsU4OloAbrUJfc9qW42mkvuUrZbftNh72jK++k4MN8wrGTE1QvuUb7iJdcpZDq7/tTQOSrthBFCPPSGaeru8UkqxyTDMXrrn29KmXV/HNZQSr5R8dm7z8=</diagram></mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1 @@
<mxfile host="app.diagrams.net" modified="2022-04-23T09:02:43.180Z" agent="5.0 (X11)" etag="6a48B_aLc62Epg4yxUQr" version="17.4.6" type="device"><diagram id="UNyY5TJjaPirS19H5Hif" name="Page-1">5Vpdc+I2FP01nmkfkkGyAftxcYC2m22ym5lt05cdrS1sgbGIEDHw6ythGX/IEDYECOQBkI6vJOueo6srDYbpjud9hibhF+rjyIANf26YNwaEjgXEtwQWKQBajpUiASO+wnLggSyxAhsKnREfT0uGnNKIk0kZ9GgcY4+XMMQYTcpmAxqVR52gAGvAg4ciHf2H+DxMURu2c/wPTIIwG1lMMH0yRpmx6mIaIp8mKbSanNk1TJdRytPSeO7iSDov80vqgd6Gp+sXYzjmuzT4evclueuPRv3bP+NvT72AA/v2ykx7eUbRTE3YgK1I9NfxybMoBrJoQMGZlfww2h1yBY32TQpklmLIkvFqsnyReZDjucRDPo4EAERxyhkdYZdGlAkkprGw7AxIFFUgFJEgFlVPzBALvPOMGSeCm0/qwZj4vhymk4SE44cJ8uSYiVCiwBidxT6Wk2+sX6voLeVA2SeeFyDlvT6mY8zZQpiop1ZLaVJp2bJVPcmFkZmEBU2YCkNKisG655wtUVCE/QJ5juZr7AvxqiplPKQBjVHUzdGKV3KbW0onip4h5nyhViKacVomD88J/1c2v26q2qPqTJZv5sXK4iXnT+mMeXjLFFtqySMWYL7Fzk7t5Py3UslwhDh5Li/uNyemVbOqKkzlPIAN+i34/A3EC5yyeEFTF+9a0EdRL7DORb7C62xRaCSrj1l/spI3W9X2l729o+yB+a50b79/3Z9e9lBz0pQO+BjNL3jnBJXg09BZWGPHYWGnxIfR5chwm4YNkS99wFDi0wn9SfAKtaY8QTK5jeT3CEmw4/o4oIbMxdKkichy0yWx+P2+PXGqSbxS6CerIh8l5xKZtoi5Re1cAV070GkeRjsetYe9r85jj/wVOt+bsDv2O7W7+/acGXzYnLlptSv8HTNrTh4aT4BNWo/d/rf/0Gdq9axZduQ5q7TDcpxfSTxWtXvMiPCYFMF2NivJQzntAG737p6E9ucffw/9J+sWe9NlevDYI8lQTe8pEa+S7xLQqWqlXRFBmjipdsWz7stdwUpX6SS1rj4xhhYFs4k0mGqiW8/69TrUw8j19fXlRgKzkgOYzonjQLsmjKcxG2Sx+lK5gObpYnJtQGmeS0we0JirDqWDOxsi6AtnvmMGVbOy7Bq7xcGXO6qKYUNsfkW4rHVP+1wk8trbggNKa9dbtNaeEtwrBhzm0kwLijUe3LxntcpxErZrzq3mMQOlfsOSIKaOoRGJCV2VfnN/f9eb116kWFY5EMGaAyGAxyTF0UgRI7nFD6jU5QdusKmzPdQnG2uVebqNky+46m2dWZOYHOy6rpbbTFxnlKbvF/Iq2/zJU0MANAISPOKULZbIG8poxweXS4el35+YOiEHuzutZ0S/wV6ieMaWOCZY4EM0khegtRtT9wNtTGYNU/C4TOm33GyJ/JG8k04X0eWyUT2u1MSxIy8bSyMjMFzLcBzh/o9HSG0u/UaEiGr+v5r0AJr/O8ns/g8=</diagram></mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1 @@
<mxfile host="app.diagrams.net" modified="2022-04-23T07:25:35.437Z" agent="5.0 (X11)" etag="wQ0k74k1QtIDXs4Ob3ey" version="17.4.6" type="device"><diagram id="UNyY5TJjaPirS19H5Hif" name="Page-1">7Vpbc5s4FP41PCZjxMXwWBzb6W4y3TQz3c2+dFSjgGoZUVkOdn99pSDMTb6kviVxZzKxdDi68J2PcxEYVm8yHzKYxrc0RMQAnXBuWFcGAKYNOuJHShZK4ttWLokYDpWsFNzjn0gJ1cBohkM0rSlySgnHaV04okmCRrwmg4zRrK72SEl91RRGqCW4H0HSlv6LQx7nUg90S/k1wlFcrGy6fn5lAgtlNcU0hiHNctHzzVl9w+oxSnnemsx7iEj0ClxyBAYrri43xlDCtxlw9+k2+zQcj4c3H5PPPwYRN72bC5DP8gTJTN2w2ixfFAgwOktCJCcxDSvIYszRfQpH8momjC5kMZ8Qdbm9KbXPJ8Q4mldEapNDRCeIs4VQUVeBpUyvKANs1c9K/M1CJ65gX8igMnm0nLpERTQUMC8AydKA5BKxahDiJ9GM+POd2+Iv+2p0Ayxg7V7lgkJTLFxTboAssOF1JKec0THqUUKZkCQ0EZrBIyakIYIER4nojgTiSMgDiTQWBP6gLkxwGMpltKYrjdvZj/WWT7yynmW1rece03h+C2sUiidcdSnjMY1oAkm/lDZQKXVuKE2Veb4jzhfKXcEZp3XjoTnm/8nhl47qPajJZPtqXu0sNoE/pTM2Qmtu0VV+EbII8TV6Xq4n73+tKRkikOOnugfcu2HcV+h6OnXyXjht8treMdlr2m+FvgJ1tqgMkt2HYj7ZKYc993anvbcl7U3rVfHeewO8N09P/M7KoIuLKEooHV/M0kqIxe8/wLp2Iz3yTxxgTbNlqQyyKc+gEBKcYCpb79UcdjNb7Wiy1c5R7dHO6af0kU/g/B1bATQd2OnNsFXVwOjPsdFzDA/AUILAYBbSlH7D6Flqq8coJPL/GEph0AtRRA15x3nFgWXb6eFE/H5ZX3VoqpZc9I01JedSsAAfiISlRp7l4UOFPMB3DkOeEfW+D+78hwH+K/a/OKA/CQNtxXniHMEx6ygdtSzXgnS+qfH+Mlk19B+KxRKlrb1GEQ8aNsxzczWqeua0YSLQbUyUJ++tiT4wBhcVtVQqTLffcJEUbb2vur5o5DsoybkE9/f5qit41x8jmWd7jOQ4DQYe8RiJumlggX7wlc5ur3vh3dXH61FxUHoqZwOO5W0armVjZZ7dd36YLHUf+sPP/8O/qT2wZxfFQX2lMteCeqw6fN0mX1OMtbwTxlgtSNafGHugGGvbfiPv/O0o257qUHFWs9KGSLtxxM6xVsvbdqy9vLx8v+HSah4KaRzHocKlFv/ui3MdUb0G55vvALfhC46Y72gN6P1x/Ady/MsD213dfnOiQzn91jobXP4G/Z0dvjaV7J4HX1+anq9Ou7dIz50d+b9TwXWY9LzlkTUIro6yTt1Jm5pzQrN7zKrUfSus3yN7nTZ7V39fcCr2Oi328ihuG4sQnE5XJRkVyOE0zT9ue8Rzabp9sLn5zkT3ylfDZftQXG6/F5+N2YLLFx9v633iblaxukW9VPUqzn68iuiWHxzmobf8btPq/wI=</diagram></mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1 @@
<mxfile host="app.diagrams.net" modified="2022-04-23T06:52:34.125Z" agent="5.0 (X11)" etag="F7oHlS30BOIVpfAPOky4" version="17.1.3" type="device"><diagram id="UNyY5TJjaPirS19H5Hif" name="Page-1">7VpZc9owEP41fkwGn9iPhQA90mmTzKRNXzoqVmwV4XWFiKG/vhKW4zMYypUmfQHp0+r6dtlDg2b2p4sRQ3H4EXxMNaPjLzTzQjMMz9LFpwSWKaA7npUiASO+wnLghvzGCuwodE58PCsJcgDKSVwGxxBFeMxLGGIMkrLYPdDyrjEKcA24GSNaR78Qn4cp6hrdHH+LSRBmO4sLpiNTlAmrJWYh8iFJodXlzIFm9hkAT1vTRR9TSV7GS8rA8InRx4MxHPFNJlx9+ph8Gk0mo8t30fWvYcB19/LMSFd5QHSuLqwOy5cZAwzmkY/lIrpm9pKQcHwTo7EcTYTOBRbyKVXD9UOpcz5gxvGiAKlDjjBMMWdLIaJGDVOpXpmMYal+kvOvZzJhgfsMQ0rlwePSOSuioYjZgiSzgSSHil17PnkQzYCvbi4M20q+a90eEbR2L1IgkxQbl4QrJAtueJnJGWcwwX2gwAQSQSQke/eE0gqEKAki0R0LxrHAe5JpIgz4jRqYEt+X2zSqLlduZz/as4yy9kyzrj3nmMrzalxjX/zCVRcYDyGACNFBjlZYyWUuAWKlnp+Y86VyV2jOoaw8vCD8q5x+bqvenVpMti8Wxc6yjfwZzNkYr7mio/wiYgHma+TcVE7ef60qGaaIk4eyB9y7Ypzn53p0r2y8ul03Xss9pvXq1r9ivoJ1tixMkt27bD3Zyaeterubvbuh2evms7J79/nb/enNvvNkyCVZDKUAk7N5XAiw5OWHV8eqJEfeicOrrtc0lSA24wkSICURAdl6qeqwqrlqpyFX7RxVH/WMfgb3fIoWL1gLeiVsn14LG5UMDH5PtL6tuQbyJQcMJT7E8IPgFWqpX5FP5ecESbDX93EAmkyy03KDyLbdJ5H4vl1fcjSULCn0g1WR11KtiEJeZCtF23kMPQXbMTz7MLYzBvfn8Mq7G5L3oXdrG4Op32usNk+cH9h6maSjluSNJL3etHh/Waya+hmI2CLXtVsp4I2KDtO8XM0qvje1LGR0KwuliXttoTeMoWVBLJYCs80PnKVEG5+rLC8a6Qly43wk9+/ttanYXf+EpL/aJyTbrljgEZ+QkpvOL53Fzt1gdP0NfQBraM3P7NM6G2PbN6SKi2itrhsvnT22t1XXzo5eaCfN1IuPfcTKmqk2MPik9ZruCWNlI0nm64iV7Va/91hpWV4lffzraFlf6lDxsmGnlojZOmPnmNlot/WYeX5+/qzD3m6Oo/q00+A4jhr2ulvnLKII7f3TectOCjScii84dd7i/nf8B3L8hrenIqm60KGcfm2fFpffIr+twxfd/I8OqXj+dxFz8Ac=</diagram></mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -0,0 +1 @@
<mxfile host="app.diagrams.net" modified="2022-04-22T20:26:07.083Z" agent="5.0 (X11)" etag="6MNvURN8OzIm5K8R91Ac" version="17.1.3" type="device"><diagram id="UNyY5TJjaPirS19H5Hif" name="Page-1">5VldU+IwFP01fdTpJ5RHQXB3R8d1ndldn3YiiW0k7WVDsOCv34SmtKWBRVERfYHk5Puec3OT1PJ6yeyMo3F8AZgwy7XxzPJOLdft+I78VcA8B5xWx8+RiFOssRK4po9Eg7ZGpxSTSa2iAGCCjuvgENKUDEUNQ5xDVq92B6w+6hhFpAFcDxFror8oFnGOhm67xL8QGsXFyHKBeUmCisq6i0mMMGQ5tFic17e8HgcQeSqZ9QhTxivskltgsKZ0OTFOUrFNgyGE94Orzs2Afos7PwO3n+DukV7GRMyLBRMs16+zwEUMEaSI9Uu0y2GaYqJ6tWWurHMOMJagI8F7IsRck4mmAiQUi4TpUjKj4nclfaO7UunTWTUzLzKp4HPVxD4OiuxN0YPKlM0WuaJdvjq1pLVGK9iBKR/qWleXF9nl2Wh0dv41/fF3EAknPD9ytfgQj4jYUK+1pFb6BIGEyPnIdpwwJOhDfR5IizNa1iv5kwlNoZnOTZN8QGyqR2rwW7KnjJfFVJDrMVosPZMuXGdqrbkeCBdkVoGaK9alnq09We8Arq/zWelOjqexuOJKBbaLkYyab9rkDTWvBXwoqve2VL27o+oXTU84R/NKhTHQVEwqPX9XQEVaraAmLa++A8pE3mMpleXUnu9insHFWkzapovpg0xGYsGAjHJ+9sdqd6m0Tfs0B4qacuBa5RU5Ss8SdfVMBIcR6QEDLpEUUqXJO8rYCoQYjVKZHUqiicS7yk+pjGYnuiChGC8EbXL8ushfwvd920hQ1fdbr+T6RvI6h+L6z/fY1pYeGxp3iX0Frtb7C1zFeaoQrxM0xeu7b6lexz8U+e4WuZ4j+3BL2Tveu9J9+P51v3/Z22tDLi1iKAMYHU3HlQBLP354DZyVo3W45/DqOA2mMsQnIkMSZDSloFIflQ7fW6HDNtx07Dflo3kfnMCdSNDsA7Ow4hTO/lnY6srA4XFk9QIrdBFWNuAowzCGW0oWqK+9CDP1O0IK7PYwicBSm3F+3aAqHfRoKv9/br5yGK4sOXTLV5HPcltxQ/e4fqFchp6KdtxO8DraMb5VmKSz+/mgYROD5bbf5vb+oPNJjsX/f9DZ9QGm8cLih/6x+Y1l2Ul+MtftSiK36Mptr3SVH94bXT31WcgwUsd+6tzqLXZ+SjLq1nTp3fyU5BzyU9Juu06wRocvf9aV2fKrTM51+W3L6/8D</diagram></mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB