From 9cc475b7c256f824c572615647fe182971c15c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Grali=C5=84ski?= Date: Wed, 3 Apr 2024 11:46:46 +0200 Subject: [PATCH] =?UTF-8?q?Wyk=C5=82ad=20wyg=C5=82adzanie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wyk/07_Wygladzanie.ipynb | 862 ++++++++++++++++++++++ wyk/07_Wygladzanie.org | 486 ++++++++++++ wyk/07_Wygladzanie/order-perplexity.gif | Bin 0 -> 4200 bytes wyk/07_Wygladzanie/size-perplexity.gif | Bin 0 -> 4643 bytes wyk/07_Wygladzanie/size-perplexity2.gif | Bin 0 -> 4913 bytes wyk/07_Wygladzanie/urna-wyrazy.drawio | 1 + wyk/07_Wygladzanie/urna-wyrazy.drawio.png | Bin 0 -> 24674 bytes wyk/07_Wygladzanie/urna.drawio | 1 + wyk/07_Wygladzanie/urna.drawio.png | Bin 0 -> 17526 bytes 9 files changed, 1350 insertions(+) create mode 100644 wyk/07_Wygladzanie.ipynb create mode 100644 wyk/07_Wygladzanie.org create mode 100644 wyk/07_Wygladzanie/order-perplexity.gif create mode 100644 wyk/07_Wygladzanie/size-perplexity.gif create mode 100644 wyk/07_Wygladzanie/size-perplexity2.gif create mode 100644 wyk/07_Wygladzanie/urna-wyrazy.drawio create mode 100644 wyk/07_Wygladzanie/urna-wyrazy.drawio.png create mode 100644 wyk/07_Wygladzanie/urna.drawio create mode 100644 wyk/07_Wygladzanie/urna.drawio.png diff --git a/wyk/07_Wygladzanie.ipynb b/wyk/07_Wygladzanie.ipynb new file mode 100644 index 0000000..2515fa2 --- /dev/null +++ b/wyk/07_Wygladzanie.ipynb @@ -0,0 +1,862 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Logo 1](https://git.wmi.amu.edu.pl/AITech/Szablon/raw/branch/master/Logotyp_AITech1.jpg)\n", + "
\n", + "

Modelowanie języka

\n", + "

07. Wygładzanie w n-gramowych modelach języka [wykład]

\n", + "

Filip Graliński (2022)

\n", + "
\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": [ + "## Wygładzanie w n-gramowych modelach języka\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Dlaczego wygładzanie?\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Wyobraźmy sobie urnę, w której znajdują się kule w $m$ kolorach\n", + "(ściślej: w co najwyżej $m$ kolorach, może w ogóle nie być kul w danym\n", + "kolorze). Nie wiemy, ile jest ogółem kul w urnie i w jakiej liczbie\n", + "występuje każdy z kolorów.\n", + "\n", + "Losujemy ze zwracaniem (to istotne!) $T$ kul, załóżmy, że\n", + "wylosowaliśmy w poszczególnych kolorach $\\{k_1,\\dots,k_m\\}$ kul\n", + "(tzn. pierwszą kolor wylosowaliśmy $k_1$ razy, drugi kolor — $k_2$ razy itd.).\n", + "Rzecz jasna, $\\sum_{i=1}^m k_i = T$.\n", + "\n", + "Jak powinniśmy racjonalnie szacować prawdopodobieństwa wylosowania kuli w $i$-tym kolorze ($p_i$)?\n", + "\n", + "Wydawałoby się, że wystarczy liczbę wylosowanych kul w danym kolorze\n", + "podzielić przez liczbę wszystkich prób:\n", + "\n", + "$$p_i = \\frac{k_i}{T}.$$\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Wygładzanie — przykład\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Rozpatrzmy przykład z 3 kolorami (wiemy, że w urnie mogą być kule\n", + "żółte, zielone i czerwone, tj. $m=3$) i 4 losowaniami ($T=4$):\n", + "\n", + "![img](./07_Wygladzanie/urna.drawio.png)\n", + "\n", + "Gdybyśmy w prosty sposób oszacowali prawdopodobieństwa, doszlibyśmy do\n", + "wniosku, że prawdopodobieństwo wylosowania kuli czerwonej wynosi 3/4, żółtej — 1/4,\n", + "a zielonej — 0. Wartości te są jednak dość problematyczne:\n", + "\n", + "- Za bardzo przywiązujemy się do naszej skromnej próby,\n", + " potrzebowalibyśmy większej liczby losowań, żeby być bardziej pewnym\n", + " naszych estymacji.\n", + "- W szczególności stwierdzenie, że prawdopodobieństwo wylosowania kuli\n", + " zielonej wynosi 0, jest bardzo mocnym stwierdzeniem (twierdzimy, że\n", + " **NIEMOŻLIWE** jest wylosowanie kuli zielonej), dopiero większa liczba\n", + " prób bez wylosowania zielonej kuli mogłaby sugerować\n", + " prawdopodobieństwo bliskie zeru.\n", + "- Zauważmy, że niemożliwe jest wylosowanie ułamka kuli, jeśli w\n", + " rzeczywistości 10% kul jest żółtych, to nie oznacza się wylosujemy\n", + " $4\\frac{1}{10} = \\frac{2}{5}$ kuli. Prawdopodobnie wylosujemy jedną\n", + " kulę żółtą albo żadną. Wylosowanie dwóch kul żółtych byłoby możliwe,\n", + " ale mniej prawdopodobne. Jeszcze mniej prawdopodobne byłoby\n", + " wylosowanie 3 lub 4 kul żółtych.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Idea wygładzania\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Wygładzanie (ang. *smoothing*) polega na tym, że „uszczknąć” nieco\n", + "masy prawdopodobieństwa zdarzeniom wskazywanym przez eksperyment czy\n", + "zbiór uczący i rozdzielić ją między mniej prawdopodobne zdarzenia.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Wygładzanie +1\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Najprostszy sposób wygładzania to wygładzania +1, nazywane też wygładzaniem\n", + "Laplace'a, zdefiniowane za pomocą następującego wzoru:\n", + "\n", + "$$p_i = \\frac{k_i+1}{T+m}.$$\n", + "\n", + "W naszym przykładzie z urną prawdopodobieństwo wylosowania kuli\n", + "czerwonej określimy na $\\frac{3+1}{4+3} = \\frac{4}{7}$, kuli żółtej —\n", + "$\\frac{1+1}{4+3}=2/7$, zielonej — $\\frac{0+1}{4+3}=1/7$. Tym samym,\n", + "kula zielona uzyskała niezerowe prawdopodobieństwo, żółta — nieco\n", + "zyskała, zaś czerwona — straciła.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Własności wygładzania +1\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Zauważmy, że większa liczba prób $m$, tym bardziej ufamy naszemu eksperymentowi\n", + "(czy zbiorowi uczącemu) i tym bardziej zbliżamy się do niewygładzonej wartości:\n", + "\n", + "$$\\lim_{m \\rightarrow \\infty} \\frac{k_i +1}{T + m} = \\frac{k_i}{T}.$$\n", + "\n", + "Inna dobra, zdroworozsądkowo, własność to to, że prawdopodobieństwo nigdy nie będzie zerowe:\n", + "\n", + "$$\\frac{k_i + 1}{T + m} > 0.$$\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Wygładzanie w unigramowym modelu języku\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Analogia do urny\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Unigramowy model języka, abstrakcyjnie, dokładnie realizuje scenariusz\n", + "losowania kul z urny: $m$ to liczba wszystkich wyrazów (czyli rozmiar słownika $|V|$),\n", + "$k_i$ to ile razy w zbiorze uczącym pojawił się $i$-ty wyraz słownika,\n", + "$T$ — długość zbioru uczącego.\n", + "\n", + "![img](./07_Wygladzanie/urna-wyrazy.drawio.png)\n", + "\n", + "A zatem przy użyciu wygładzania +1 w następujący sposób estymować\n", + "będziemy prawdopodobieństwo słowa $w$:\n", + "\n", + "$$P(w) = \\frac{\\# w + 1}{|C| + |V|}.$$\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Wygładzanie $+\\alpha$\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "W modelowaniu języka wygładzanie $+1$ daje zazwyczaj niepoprawne\n", + "wyniki, dlatego częściej zamiast wartości 1 używa się współczynnika $0\n", + "< \\alpha < 1$:\n", + "\n", + "$$P(w) = \\frac{\\# w + \\alpha}{|C| + \\alpha|V|}.$$\n", + "\n", + "W innych praktycznych zastosowaniach statystyki\n", + "przyjmuje się $\\alpha = \\frac{1}{2}$, ale w przypadku n-gramowych\n", + "modeli języka i to będzie zbyt duża wartość.\n", + "\n", + "W jaki sposób ustalić wartość $\\alpha$? Można $\\alpha$ potraktować $\\alpha$\n", + "jako hiperparametr i dostroić ją na odłożonym zbiorze.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Jak wybrać wygładzanie?\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Jak ocenić, który sposób wygładzania jest lepszy? Jak wybrać $\\alpha$\n", + "w czasie dostrajania?\n", + "\n", + "Najprościej można sprawdzić estymowane prawdopodobieństwa na zbiorze\n", + "strojącym (developerskim). Dla celów poglądowych bardziej czytelny\n", + "będzie podział zbioru uczącego na dwie równe części — będziemy\n", + "porównywać częstości estymowane na jednej połówce korpusu z\n", + "rzeczywistymi, empirycznymi częstościami z drugiej połówki.\n", + "\n", + "Wyniki będziemy przedstawiać w postaci tabeli, gdzie w poszczególnych\n", + "wierszach będziemy opisywać częstości estymowane dla wszystkich\n", + "wyrazów, które pojawiły się określoną liczbę razy w pierwszej połówce korpusu.\n", + "\n", + "Ostatecznie możemy też po prostu policzyć perplexity na zbiorze testowym\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Przykład\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Użyjemy polskiej części z korpusu równoległego Open Subtitles:\n", + "\n", + " wget -O en-pl.txt.zip 'https://opus.nlpl.eu/download.php?f=OpenSubtitles/v2018/moses/en-pl.txt.zip'\n", + " unzip en-pl.txt.zip\n", + "\n", + "Usuńmy duplikaty (zachowując kolejność):\n", + "\n", + " nl OpenSubtitles.en-pl.pl | sort -k 2 -u | sort -k 1 | cut -f 2- > opensubtitles.pl.txt\n", + "\n", + "Korpus zawiera ponad 28 mln słów, zdania są krótkie, jest to język potoczny, czasami wulgarny.\n", + "\n", + " $ wc opensubtitles.pl.txt\n", + " 28154303 178866171 1206735898 opensubtitles.pl.txt\n", + " $ head -n 10 opensubtitles.pl.txt\n", + " Lubisz curry, prawda?\n", + " Nałożę ci więcej.\n", + " Hey!\n", + " Smakuje ci?\n", + " Hey, brzydalu.\n", + " Spójrz na nią.\n", + " - Wariatka.\n", + " - Zadałam ci pytanie!\n", + " No, tak lepiej!\n", + " - Wygląda dobrze!\n", + "\n", + "Podzielimy korpus na dwie części:\n", + "\n", + " head -n 14077151 < opensubtitles.pl.txt > opensubtitlesA.pl.txt\n", + " tail -n 14077151 < opensubtitles.pl.txt > opensubtitlesB.pl.txt\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Tokenizacja\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Stwórzmy generator, który będzie wczytywał słowa z pliku, dodatkowo:\n", + "\n", + "- ciągi znaków interpunkcyjnych będziemy traktować jak tokeny,\n", + "- sprowadzimy wszystkie litery do małych,\n", + "- dodamy specjalne tokeny na początek i koniec zdania (`` i ``).\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['', 'lubisz', 'curry', ',', 'prawda', '?', '', '', 'nałożę', 'ci', 'więcej', '.', '', '', 'hey', '!', '', '', 'smakuje', 'ci', '?', '', '', 'hey', ',', 'brzydalu', '.', '', '', 'spójrz', 'na', 'nią', '.', '', '', '-', 'wariatka', '.', '', '', '-', 'zadałam', 'ci', 'pytanie', '!', '', '', 'no', ',', 'tak', 'lepiej', '!', '', '', '-', 'wygląda', 'dobrze', '!', '', '', '-', 'tak', 'lepiej', '!', '', '', 'pasuje', 'jej', '.', '', '', '-', 'hey', '.', '', '', '-', 'co', 'do', '...?', '', '', 'co', 'do', 'cholery', 'robisz', '?', '', '', 'zejdź', 'mi', 'z', 'oczu', ',', 'zdziro', '.', '', '', 'przestań', 'dokuczać']" + ] + } + ], + "source": [ + "from itertools import islice\n", + "import regex as re\n", + "import sys\n", + "\n", + "def get_words_from_file(file_name):\n", + " with open(file_name, 'r') as fh:\n", + " for line in fh:\n", + " line = line.rstrip()\n", + " yield ''\n", + " for m in re.finditer(r'[\\p{L}0-9\\*]+|\\p{P}+', line):\n", + " yield m.group(0).lower()\n", + " yield ''\n", + "\n", + "list(islice(get_words_from_file('opensubtitlesA.pl.txt'), 0, 100))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Empiryczne wyniki\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Zobaczmy, ile razy, średnio w drugiej połówce korpusu występują\n", + "wyrazy, które w pierwszej wystąpiły określoną liczbę razy.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from collections import Counter\n", + "\n", + "counterA = Counter(get_words_from_file('opensubtitlesA.pl.txt'))" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "48113" + ] + } + ], + "source": [ + "counterA['taki']" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "max_r = 10\n", + "\n", + "buckets = {}\n", + "for token in counterA:\n", + " buckets.setdefault(counterA[token], 0)\n", + " buckets[counterA[token]] += 1\n", + "\n", + "bucket_counts = {}\n", + "\n", + "counterB = Counter(get_words_from_file('opensubtitlesB.pl.txt'))\n", + "\n", + "for token in counterB:\n", + " bucket_id = counterA[token] if token in counterA else 0\n", + " if bucket_id <= max_r:\n", + " bucket_counts.setdefault(bucket_id, 0)\n", + " bucket_counts[bucket_id] += counterB[token]\n", + " if bucket_id == 0:\n", + " buckets.setdefault(0, 0)\n", + " buckets[0] += 1\n", + "\n", + "nb_of_types = [buckets[ix] for ix in range(0, max_r+1)]\n", + "empirical_counts = [bucket_counts[ix] / buckets[ix] for ix in range(0, max_r)]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Policzmy teraz jakiej liczby wystąpień byśmy oczekiwali, gdyby użyć wygładzania +1 bądź +0.01.\n", + "(Uwaga: zwracamy liczbę wystąpień, a nie względną częstość, stąd przemnażamy przez rozmiar całego korpusu).\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "926594" + ] + } + ], + "source": [ + "def plus_alpha_smoothing(alpha, m, t, k):\n", + " return t*(k + alpha)/(t + alpha * m)\n", + "\n", + "def plus_one_smoothing(m, t, k):\n", + " return plus_alpha_smoothing(1.0, m, t, k)\n", + "\n", + "vocabulary_size = len(counterA)\n", + "corpus_size = counterA.total()\n", + "\n", + "plus_one_counts = [plus_one_smoothing(vocabulary_size, corpus_size, ix) for ix in range(0, max_r)]\n", + "\n", + "plus_alpha_counts = [plus_alpha_smoothing(0.01, vocabulary_size, corpus_size, ix) for ix in range(0, max_r)]\n", + "\n", + "data = list(zip(nb_of_types, empirical_counts, plus_one_counts, plus_alpha_counts))\n", + "\n", + "vocabulary_size" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "liczba tokenów średnia częstość w części B estymacje +1 estymacje +0.01\n", + "0 388334 1.900495 0.993586 0.009999\n", + "1 403870 0.592770 1.987172 1.009935\n", + "2 117529 1.565809 2.980759 2.009870\n", + "3 62800 2.514268 3.974345 3.009806\n", + "4 40856 3.504944 4.967931 4.009741\n", + "5 29443 4.454098 5.961517 5.009677\n", + "6 22709 5.232023 6.955103 6.009612\n", + "7 18255 6.157929 7.948689 7.009548\n", + "8 15076 7.308039 8.942276 8.009483\n", + "9 12859 8.045649 9.935862 9.009418" + ] + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "pd.DataFrame(data, columns=[\"liczba tokenów\", \"średnia częstość w części B\", \"estymacje +1\", \"estymacje +0.01\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Wygładzanie Gooda-Turinga\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Inna metoda — wygładzanie Gooda-Turinga — polega na zliczaniu, ile\n", + "$n$-gramów (na razie rozpatrujemy model unigramowy, więc po prostu pojedynczych\n", + "wyrazów) wystąpiło zadaną liczbę razy. Niech $N_r$ oznacza właśnie,\n", + "ile $n$-gramów wystąpiło dokładnie $r$ razy; na przykład $N_1$ oznacza liczbę *hapax legomena*.\n", + "\n", + "W metodzie Gooda-Turinga używamy następującej estymacji:\n", + "\n", + "$$p(w) = \\frac{\\# w + 1}{|C|}\\frac{N_{r+1}}{N_r}.$$\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Przykład\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "liczba tokenów średnia częstość w części B estymacje +1 Good-Turing\n", + "0 388334 1.900495 0.993586 1.040007\n", + "1 403870 0.592770 1.987172 0.582014\n", + "2 117529 1.565809 2.980759 1.603009\n", + "3 62800 2.514268 3.974345 2.602293\n", + "4 40856 3.504944 4.967931 3.603265\n", + "5 29443 4.454098 5.961517 4.627721\n", + "6 22709 5.232023 6.955103 5.627064\n", + "7 18255 6.157929 7.948689 6.606847\n", + "8 15076 7.308039 8.942276 7.676506\n", + "9 12859 8.045649 9.935862 8.557431" + ] + } + ], + "source": [ + "good_turing_counts = [(ix+1)*nb_of_types[ix+1]/nb_of_types[ix] for ix in range(0, max_r)]\n", + "\n", + "data2 = list(zip(nb_of_types, empirical_counts, plus_one_counts, good_turing_counts))\n", + "\n", + "pd.DataFrame(data2, columns=[\"liczba tokenów\", \"średnia częstość w części B\", \"estymacje +1\", \"Good-Turing\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Wygładzanie metodą Gooda-Turinga, mimo prostoty, daje wyniki zaskakująco zbliżone do rzeczywistych.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Wygładzanie dla $n$-gramów\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Rzadkość danych\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "W wypadku bigramów, trigramów, tetragramów itd. jeszcze dotkliwy staje się problem\n", + "**rzadkości** danych (*data sparsity*). Przestrzeń możliwych zdarzeń\n", + "jest jeszcze większa ($|V|^2$ dla bigramów), więc estymacje stają się\n", + "jeszcze mniej pewne.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Back-off\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dla $n$-gramów, gdzie $n>1$, nie jesteśmy ograniczeni do wygładzania $+1$, $+k$ czy Gooda-Turinga.\n", + "W przypadku rzadkich $n$-gramów, w szczególności, gdy $n$-gram w ogóle się nie pojawił w korpusie,\n", + "możemy „zejść” na poziom krótszych $n$-gramów. Na tym polega **back-off**.\n", + "\n", + "Otóż jeśli $\\# w_{i-n+1}\\ldots w_{i-1} > 0$, wówczas estymujemy prawdopodobieństwa\n", + " w tradycyjny sposób:\n", + "\n", + "$$P_B(w_i|w_{i-n+1}\\ldots w_{i-1}) = d_n(w_{i-n+1}\\ldots w_{i-1}\\ldots w_{i-1}) P(w_i|w_{i-n+1}\\ldots w_{i-1})$$\n", + "\n", + "W przeciwnym razie rozpatrujemy rekurencyjnie krótszy $n$-gram:\n", + "\n", + "$$P_B(w_i|w_{i-n+1}\\ldots w_{i-1}) = \\delta_n(w_{i-n+1}\\ldots w_{i-1}\\ldots w_{i-1}) P_B(w_i|w_{i-n+2}\\ldots w_{i-1}).$$\n", + "\n", + "Technicznie, aby $P_B$ stanowiło rozkład prawdopodobieństwa, trzeba dobrać współczynniki $d$ i $\\delta$.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Interpolacja\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alternatywą do metody back-off jest **interpolacja** — zawsze z pewnym współczynnikiem uwzględniamy\n", + "prawdopodobieństwa dla krótszych $n$-gramów:\n", + "\n", + "$$P_I(w_i|w_{i-n+1}\\ldots w_{i-1}) = \\lambda P(w_i|w_{i-n+1}\\dots w_{i-1}) + (1-\\lambda)\n", + " P_I(w_i|w_{i-n+2}\\dots w_{i-1}).$$\n", + "\n", + "Na przykład, dla trigramów:\n", + "\n", + "$$P_I(w_i|w_{i-2}w_{i-1}) = \\lambda P_(w_i|w_{i-2}w_{i-1}) + (1-\\lambda)(\\lambda P(w_i|w_{i-1}) + (1-\\lambda)P_I(w_i)).$$\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Uwzględnianie różnorodności\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Różnorodność kontynuacji\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Zauważmy, że słowa mogą bardzo różnić się co do różnorodności\n", + "kontynuacji. Na przykład po słowie *szop* spodziewamy się raczej tylko\n", + "słowa *pracz*, każde inne, niewidziane w zbiorze uczącym, będzie\n", + "zaskakujące. Dla porównania słowo *seledynowy* ma bardzo dużo\n", + "możliwych kontynuacji i powinniśmy przeznaczyć znaczniejszą część masy\n", + "prawdopodobieństwa na kontynuacje niewidziane w zbiorze uczącym.\n", + "\n", + "Różnorodność kontynuacji bierze pod uwagę metoda wygładzania\n", + "Wittena-Bella, będącą wersją interpolacji.\n", + "\n", + "Wprowadźmy oznaczenie na liczbę możliwych kontynuacji $n-1$-gramu $w_1\\ldots w_{n-1}$:\n", + "\n", + "$$N_{1+}(w_1\\ldots w_{n-1}\\dot\\bullet) = |\\{w_n : \\# w_1\\ldots w_{n-1}w_n > 0\\}|.$$\n", + "\n", + "Teraz zastosujemy interpolację z następującą wartością parametru\n", + "$1-\\lambda$, sterującego wagą, jaką przypisujemy do krótszych $n$-gramów:\n", + "\n", + "$$1 - \\lambda = \\frac{N_{1+}(w_1\\ldots w_{n-1}\\dot\\bullet)}{N_{1+}(w_1\\ldots w_{n-1}\\dot\\bullet) + \\# w_1\\ldots w_{n-1}}.$$\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Wygładzanie Knesera-Neya\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Zamiast brać pod uwagę różnorodność kontynuacji, możemy rozpatrywać\n", + "różnorodność **historii** — w momencie liczenia prawdopodobieństwa dla\n", + "unigramów dla interpolacji (nie ma to zastosowania dla modeli\n", + "unigramowych). Na przykład dla wyrazu *Jork* spodziewamy się tylko\n", + "bigramu *Nowy Jork*, a zatem przy interpolacji czy back-off prawdopodobieństwo\n", + "unigramowe powinno być niskie.\n", + "\n", + "Wprowadźmy oznaczenia na liczbę możliwych historii:\n", + "\n", + "$$N_{1+}(\\bullet w) = |\\{w_j : \\# w_jw > 0\\}|$$.\n", + "\n", + "W metodzie Knesera-Neya w następujący sposób estymujemy prawdopodobieństwo unigramu:\n", + "\n", + "$$P(w) = \\frac{N_{1+}(\\bullet w)}{\\sum_{w_j} N_{1+}(\\bullet w_j)}.$$\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[('k', 'o', 't'), ('o', 't', 'e'), ('t', 'e', 'k')]" + ] + } + ], + "source": [ + "def ngrams(iter, size):\n", + " ngram = []\n", + " for item in iter:\n", + " ngram.append(item)\n", + " if len(ngram) == size:\n", + " yield tuple(ngram)\n", + " ngram = ngram[1:]\n", + "\n", + "list(ngrams(\"kotek\", 3))" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "histories = { }\n", + "for prev_token, token in ngrams(get_words_from_file('opensubtitlesA.pl.txt'), 2):\n", + " histories.setdefault(token, set())\n", + " histories[token].add(prev_token)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "len(histories['jork'])\n", + "len(histories['zielony'])\n", + "histories['jork']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Narzędzia $n$-gramowego modelowania języka\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Istnieje kilka narzędzie do modelowania, ze starszych warto wspomnieć\n", + "pakiety [SRILM](http://www.speech.sri.com/projects/srilm/) i [IRSTLM](https://github.com/irstlm-team/irstlm).\n", + "Jest to oprogramowanie bogate w opcje, można wybierać różne opcje wygładzania.\n", + "\n", + "Szczytowym osiągnięciem w zakresie $n$-gramowego modelowania języka\n", + "jest wspomniany już KenLM. Ma on mniej opcji niż SRILM czy ISRLM, jest\n", + "za to precyzyjnie zoptymalizowany zarówno jeśli chodzi jakość, jak i\n", + "szybkość działania. KenLM implementuje nieco zmodyfikowane wygładzanie\n", + "Knesera-Neya połączone z **przycinaniem** słownika n-gramów (wszystkie\n", + "*hapax legomena* dla $n \\geq 3$ są domyślnie usuwane).\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Przykładowe wyniki dla KenLM i korpusu Open Subtitles\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Zmiana perplexity przy zwiększaniu zbioru testowego\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![img](./07_Wygladzanie/size-perplexity.gif \"Perplexity dla różnych rozmiarów zbioru testowego\")\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Zmiana perplexity przy zwiększaniu zbioru uczącego\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![img](./07_Wygladzanie/size-perplexity2.gif \"Perplexity dla różnych rozmiarów zbioru uczącego\")\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Zmiana perplexity przy zwiększaniu rządu modelu\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![img](./07_Wygladzanie/order-perplexity.gif \"Perplexity dla różnych wartości rządu modelu\")\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 +} diff --git a/wyk/07_Wygladzanie.org b/wyk/07_Wygladzanie.org new file mode 100644 index 0000000..4128060 --- /dev/null +++ b/wyk/07_Wygladzanie.org @@ -0,0 +1,486 @@ + +* Wygładzanie w n-gramowych modelach języka + +** Dlaczego wygładzanie? + +Wyobraźmy sobie urnę, w której znajdują się kule w $m$ kolorach +(ściślej: w co najwyżej $m$ kolorach, może w ogóle nie być kul w danym +kolorze). Nie wiemy, ile jest ogółem kul w urnie i w jakiej liczbie +występuje każdy z kolorów. + +Losujemy ze zwracaniem (to istotne!) $T$ kul, załóżmy, że +wylosowaliśmy w poszczególnych kolorach $\{k_1,\dots,k_m\}$ kul +(tzn. pierwszą kolor wylosowaliśmy $k_1$ razy, drugi kolor — $k_2$ razy itd.). +Rzecz jasna, $\sum_{i=1}^m k_i = T$. + +Jak powinniśmy racjonalnie szacować prawdopodobieństwa wylosowania kuli w $i$-tym kolorze ($p_i$)? + +Wydawałoby się, że wystarczy liczbę wylosowanych kul w danym kolorze +podzielić przez liczbę wszystkich prób: + +$$p_i = \frac{k_i}{T}.$$ + +*** Wygładzanie — przykład + +Rozpatrzmy przykład z 3 kolorami (wiemy, że w urnie mogą być kule +żółte, zielone i czerwone, tj. $m=3$) i 4 losowaniami ($T=4$): + +[[./07_Wygladzanie/urna.drawio.png]] + +Gdybyśmy w prosty sposób oszacowali prawdopodobieństwa, doszlibyśmy do +wniosku, że prawdopodobieństwo wylosowania kuli czerwonej wynosi 3/4, żółtej — 1/4, +a zielonej — 0. Wartości te są jednak dość problematyczne: + +- Za bardzo przywiązujemy się do naszej skromnej próby, + potrzebowalibyśmy większej liczby losowań, żeby być bardziej pewnym + naszych estymacji. +- W szczególności stwierdzenie, że prawdopodobieństwo wylosowania kuli + zielonej wynosi 0, jest bardzo mocnym stwierdzeniem (twierdzimy, że + *NIEMOŻLIWE* jest wylosowanie kuli zielonej), dopiero większa liczba + prób bez wylosowania zielonej kuli mogłaby sugerować + prawdopodobieństwo bliskie zeru. +- Zauważmy, że niemożliwe jest wylosowanie ułamka kuli, jeśli w + rzeczywistości 10% kul jest żółtych, to nie oznacza się wylosujemy + $4\frac{1}{10} = \frac{2}{5}$ kuli. Prawdopodobnie wylosujemy jedną + kulę żółtą albo żadną. Wylosowanie dwóch kul żółtych byłoby możliwe, + ale mniej prawdopodobne. Jeszcze mniej prawdopodobne byłoby + wylosowanie 3 lub 4 kul żółtych. + +*** Idea wygładzania + +Wygładzanie (ang. /smoothing/) polega na tym, że „uszczknąć” nieco +masy prawdopodobieństwa zdarzeniom wskazywanym przez eksperyment czy +zbiór uczący i rozdzielić ją między mniej prawdopodobne zdarzenia. + +*** Wygładzanie +1 + +Najprostszy sposób wygładzania to wygładzania +1, nazywane też wygładzaniem +Laplace'a, zdefiniowane za pomocą następującego wzoru: + +$$p_i = \frac{k_i+1}{T+m}.$$ + +W naszym przykładzie z urną prawdopodobieństwo wylosowania kuli +czerwonej określimy na $\frac{3+1}{4+3} = \frac{4}{7}$, kuli żółtej — +$\frac{1+1}{4+3}=2/7$, zielonej — $\frac{0+1}{4+3}=1/7$. Tym samym, +kula zielona uzyskała niezerowe prawdopodobieństwo, żółta — nieco +zyskała, zaś czerwona — straciła. + +**** Własności wygładzania +1 + +Zauważmy, że większa liczba prób $m$, tym bardziej ufamy naszemu eksperymentowi +(czy zbiorowi uczącemu) i tym bardziej zbliżamy się do niewygładzonej wartości: + +$$\lim_{m \rightarrow \infty} \frac{k_i +1}{T + m} = \frac{k_i}{T}.$$ + +Inna dobra, zdroworozsądkowo, własność to to, że prawdopodobieństwo nigdy nie będzie zerowe: + +$$\frac{k_i + 1}{T + m} > 0.$$ + +** Wygładzanie w unigramowym modelu języku + +*** Analogia do urny + +Unigramowy model języka, abstrakcyjnie, dokładnie realizuje scenariusz +losowania kul z urny: $m$ to liczba wszystkich wyrazów (czyli rozmiar słownika $|V|$), +$k_i$ to ile razy w zbiorze uczącym pojawił się $i$-ty wyraz słownika, +$T$ — długość zbioru uczącego. + +[[./07_Wygladzanie/urna-wyrazy.drawio.png]] + +A zatem przy użyciu wygładzania +1 w następujący sposób estymować +będziemy prawdopodobieństwo słowa $w$: + +$$P(w) = \frac{\# w + 1}{|C| + |V|}.$$ + +*** Wygładzanie $+\alpha$ + +W modelowaniu języka wygładzanie $+1$ daje zazwyczaj niepoprawne +wyniki, dlatego częściej zamiast wartości 1 używa się współczynnika $0 +< \alpha < 1$: + +$$P(w) = \frac{\# w + \alpha}{|C| + \alpha|V|}.$$ + +W innych praktycznych zastosowaniach statystyki +przyjmuje się $\alpha = \frac{1}{2}$, ale w przypadku n-gramowych +modeli języka i to będzie zbyt duża wartość. + +W jaki sposób ustalić wartość $\alpha$? Można $\alpha$ potraktować $\alpha$ +jako hiperparametr i dostroić ją na odłożonym zbiorze. + +*** Jak wybrać wygładzanie? + +Jak ocenić, który sposób wygładzania jest lepszy? Jak wybrać $\alpha$ +w czasie dostrajania? + +Najprościej można sprawdzić estymowane prawdopodobieństwa na zbiorze +strojącym (developerskim). Dla celów poglądowych bardziej czytelny +będzie podział zbioru uczącego na dwie równe części — będziemy +porównywać częstości estymowane na jednej połówce korpusu z +rzeczywistymi, empirycznymi częstościami z drugiej połówki. + +Wyniki będziemy przedstawiać w postaci tabeli, gdzie w poszczególnych +wierszach będziemy opisywać częstości estymowane dla wszystkich +wyrazów, które pojawiły się określoną liczbę razy w pierwszej połówce korpusu. + +Ostatecznie możemy też po prostu policzyć perplexity na zbiorze testowym + +*** Przykład + +Użyjemy polskiej części z korpusu równoległego Open Subtitles: + +#+BEGIN_SRC +wget -O en-pl.txt.zip 'https://opus.nlpl.eu/download.php?f=OpenSubtitles/v2018/moses/en-pl.txt.zip' +unzip en-pl.txt.zip +#+END_SRC + +Usuńmy duplikaty (zachowując kolejność): + +#+BEGIN_SRC +nl OpenSubtitles.en-pl.pl | sort -k 2 -u | sort -k 1 | cut -f 2- > opensubtitles.pl.txt +#+END_SRC + +Korpus zawiera ponad 28 mln słów, zdania są krótkie, jest to język potoczny, czasami wulgarny. + +#+BEGIN_SRC +$ wc opensubtitles.pl.txt + 28154303 178866171 1206735898 opensubtitles.pl.txt +$ head -n 10 opensubtitles.pl.txt +Lubisz curry, prawda? +Nałożę ci więcej. +Hey! +Smakuje ci? +Hey, brzydalu. +Spójrz na nią. +- Wariatka. +- Zadałam ci pytanie! +No, tak lepiej! +- Wygląda dobrze! +#+END_SRC + +Podzielimy korpus na dwie części: + +#+BEGIN_SRC +head -n 14077151 < opensubtitles.pl.txt > opensubtitlesA.pl.txt +tail -n 14077151 < opensubtitles.pl.txt > opensubtitlesB.pl.txt +#+END_SRC + +**** Tokenizacja + +Stwórzmy generator, który będzie wczytywał słowa z pliku, dodatkowo: + +- ciągi znaków interpunkcyjnych będziemy traktować jak tokeny, +- sprowadzimy wszystkie litery do małych, +- dodamy specjalne tokeny na początek i koniec zdania (~~ i ~~). + + +#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer + from itertools import islice + import regex as re + import sys + + def get_words_from_file(file_name): + with open(file_name, 'r') as fh: + for line in fh: + line = line.rstrip() + yield '' + for m in re.finditer(r'[\p{L}0-9\*]+|\p{P}+', line): + yield m.group(0).lower() + yield '' + + list(islice(get_words_from_file('opensubtitlesA.pl.txt'), 0, 100)) +#+END_SRC + +#+RESULTS: +:results: +['', 'lubisz', 'curry', ',', 'prawda', '?', '', '', 'nałożę', 'ci', 'więcej', '.', '', '', 'hey', '!', '', '', 'smakuje', 'ci', '?', '', '', 'hey', ',', 'brzydalu', '.', '', '', 'spójrz', 'na', 'nią', '.', '', '', '-', 'wariatka', '.', '', '', '-', 'zadałam', 'ci', 'pytanie', '!', '', '', 'no', ',', 'tak', 'lepiej', '!', '', '', '-', 'wygląda', 'dobrze', '!', '', '', '-', 'tak', 'lepiej', '!', '', '', 'pasuje', 'jej', '.', '', '', '-', 'hey', '.', '', '', '-', 'co', 'do', '...?', '', '', 'co', 'do', 'cholery', 'robisz', '?', '', '', 'zejdź', 'mi', 'z', 'oczu', ',', 'zdziro', '.', '', '', 'przestań', 'dokuczać'] +:end: + +**** Empiryczne wyniki + +Zobaczmy, ile razy, średnio w drugiej połówce korpusu występują +wyrazy, które w pierwszej wystąpiły określoną liczbę razy. + +#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer + from collections import Counter + + counterA = Counter(get_words_from_file('opensubtitlesA.pl.txt')) +#+END_SRC + +#+RESULTS: +:results: +:end: + +#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer +counterA['taki'] +#+END_SRC + +#+RESULTS: +:results: +48113 +:end: + +#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer + max_r = 10 + + buckets = {} + for token in counterA: + buckets.setdefault(counterA[token], 0) + buckets[counterA[token]] += 1 + + bucket_counts = {} + + counterB = Counter(get_words_from_file('opensubtitlesB.pl.txt')) + + for token in counterB: + bucket_id = counterA[token] if token in counterA else 0 + if bucket_id <= max_r: + bucket_counts.setdefault(bucket_id, 0) + bucket_counts[bucket_id] += counterB[token] + if bucket_id == 0: + buckets.setdefault(0, 0) + buckets[0] += 1 + + nb_of_types = [buckets[ix] for ix in range(0, max_r+1)] + empirical_counts = [bucket_counts[ix] / buckets[ix] for ix in range(0, max_r)] +#+END_SRC + +#+RESULTS: +:results: +:end: + +Policzmy teraz jakiej liczby wystąpień byśmy oczekiwali, gdyby użyć wygładzania +1 bądź +0.01. +(Uwaga: zwracamy liczbę wystąpień, a nie względną częstość, stąd przemnażamy przez rozmiar całego korpusu). + +#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer + def plus_alpha_smoothing(alpha, m, t, k): + return t*(k + alpha)/(t + alpha * m) + + def plus_one_smoothing(m, t, k): + return plus_alpha_smoothing(1.0, m, t, k) + + vocabulary_size = len(counterA) + corpus_size = counterA.total() + + plus_one_counts = [plus_one_smoothing(vocabulary_size, corpus_size, ix) for ix in range(0, max_r)] + + plus_alpha_counts = [plus_alpha_smoothing(0.01, vocabulary_size, corpus_size, ix) for ix in range(0, max_r)] + + data = list(zip(nb_of_types, empirical_counts, plus_one_counts, plus_alpha_counts)) + + vocabulary_size +#+END_SRC + +#+RESULTS: +:results: +926594 +:end: + +#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer + import pandas as pd + + pd.DataFrame(data, columns=["liczba tokenów", "średnia częstość w części B", "estymacje +1", "estymacje +0.01"]) +#+END_SRC + +#+RESULTS: +:results: + liczba tokenów średnia częstość w części B estymacje +1 estymacje +0.01 +0 388334 1.900495 0.993586 0.009999 +1 403870 0.592770 1.987172 1.009935 +2 117529 1.565809 2.980759 2.009870 +3 62800 2.514268 3.974345 3.009806 +4 40856 3.504944 4.967931 4.009741 +5 29443 4.454098 5.961517 5.009677 +6 22709 5.232023 6.955103 6.009612 +7 18255 6.157929 7.948689 7.009548 +8 15076 7.308039 8.942276 8.009483 +9 12859 8.045649 9.935862 9.009418 +:end: + +*** Wygładzanie Gooda-Turinga + +Inna metoda — wygładzanie Gooda-Turinga — polega na zliczaniu, ile +$n$-gramów (na razie rozpatrujemy model unigramowy, więc po prostu pojedynczych +wyrazów) wystąpiło zadaną liczbę razy. Niech $N_r$ oznacza właśnie, +ile $n$-gramów wystąpiło dokładnie $r$ razy; na przykład $N_1$ oznacza liczbę /hapax legomena/. + +W metodzie Gooda-Turinga używamy następującej estymacji: + +$$p(w) = \frac{\# w + 1}{|C|}\frac{N_{r+1}}{N_r}.$$ + +**** Przykład + +#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer + good_turing_counts = [(ix+1)*nb_of_types[ix+1]/nb_of_types[ix] for ix in range(0, max_r)] + + data2 = list(zip(nb_of_types, empirical_counts, plus_one_counts, good_turing_counts)) + + pd.DataFrame(data2, columns=["liczba tokenów", "średnia częstość w części B", "estymacje +1", "Good-Turing"]) +#+END_SRC + +#+RESULTS: +:results: + liczba tokenów średnia częstość w części B estymacje +1 Good-Turing +0 388334 1.900495 0.993586 1.040007 +1 403870 0.592770 1.987172 0.582014 +2 117529 1.565809 2.980759 1.603009 +3 62800 2.514268 3.974345 2.602293 +4 40856 3.504944 4.967931 3.603265 +5 29443 4.454098 5.961517 4.627721 +6 22709 5.232023 6.955103 5.627064 +7 18255 6.157929 7.948689 6.606847 +8 15076 7.308039 8.942276 7.676506 +9 12859 8.045649 9.935862 8.557431 +:end: + +Wygładzanie metodą Gooda-Turinga, mimo prostoty, daje wyniki zaskakująco zbliżone do rzeczywistych. + + +** Wygładzanie dla $n$-gramów + +*** Rzadkość danych + +W wypadku bigramów, trigramów, tetragramów itd. jeszcze dotkliwy staje się problem +*rzadkości* danych (/data sparsity/). Przestrzeń możliwych zdarzeń +jest jeszcze większa ($|V|^2$ dla bigramów), więc estymacje stają się +jeszcze mniej pewne. + +*** Back-off + +Dla $n$-gramów, gdzie $n>1$, nie jesteśmy ograniczeni do wygładzania $+1$, $+k$ czy Gooda-Turinga. +W przypadku rzadkich $n$-gramów, w szczególności, gdy $n$-gram w ogóle się nie pojawił w korpusie, +możemy „zejść” na poziom krótszych $n$-gramów. Na tym polega *back-off*. + +Otóż jeśli $\# w_{i-n+1}\ldots w_{i-1} > 0$, wówczas estymujemy prawdopodobieństwa + w tradycyjny sposób: + +$$P_B(w_i|w_{i-n+1}\ldots w_{i-1}) = d_n(w_{i-n+1}\ldots w_{i-1}\ldots w_{i-1}) P(w_i|w_{i-n+1}\ldots w_{i-1})$$ + +W przeciwnym razie rozpatrujemy rekurencyjnie krótszy $n$-gram: + +$$P_B(w_i|w_{i-n+1}\ldots w_{i-1}) = \delta_n(w_{i-n+1}\ldots w_{i-1}\ldots w_{i-1}) P_B(w_i|w_{i-n+2}\ldots w_{i-1}).$$ + +Technicznie, aby $P_B$ stanowiło rozkład prawdopodobieństwa, trzeba dobrać współczynniki $d$ i $\delta$. + +*** Interpolacja + +Alternatywą do metody back-off jest *interpolacja* — zawsze z pewnym współczynnikiem uwzględniamy +prawdopodobieństwa dla krótszych $n$-gramów: + +$$P_I(w_i|w_{i-n+1}\ldots w_{i-1}) = \lambda P(w_i|w_{i-n+1}\dots w_{i-1}) + (1-\lambda) + P_I(w_i|w_{i-n+2}\dots w_{i-1}).$$ + + +Na przykład, dla trigramów: + +$$P_I(w_i|w_{i-2}w_{i-1}) = \lambda P_(w_i|w_{i-2}w_{i-1}) + (1-\lambda)(\lambda P(w_i|w_{i-1}) + (1-\lambda)P_I(w_i)).$$ + +** Uwzględnianie różnorodności + +*** Różnorodność kontynuacji + +Zauważmy, że słowa mogą bardzo różnić się co do różnorodności +kontynuacji. Na przykład po słowie /szop/ spodziewamy się raczej tylko +słowa /pracz/, każde inne, niewidziane w zbiorze uczącym, będzie +zaskakujące. Dla porównania słowo /seledynowy/ ma bardzo dużo +możliwych kontynuacji i powinniśmy przeznaczyć znaczniejszą część masy +prawdopodobieństwa na kontynuacje niewidziane w zbiorze uczącym. + +Różnorodność kontynuacji bierze pod uwagę metoda wygładzania +Wittena-Bella, będącą wersją interpolacji. + +Wprowadźmy oznaczenie na liczbę możliwych kontynuacji $n-1$-gramu $w_1\ldots w_{n-1}$: + +$$N_{1+}(w_1\ldots w_{n-1}\dot\bullet) = |\{w_n : \# w_1\ldots w_{n-1}w_n > 0\}|.$$ + +Teraz zastosujemy interpolację z następującą wartością parametru +$1-\lambda$, sterującego wagą, jaką przypisujemy do krótszych $n$-gramów: + + +$$1 - \lambda = \frac{N_{1+}(w_1\ldots w_{n-1}\dot\bullet)}{N_{1+}(w_1\ldots w_{n-1}\dot\bullet) + \# w_1\ldots w_{n-1}}.$$ + +*** Wygładzanie Knesera-Neya + +Zamiast brać pod uwagę różnorodność kontynuacji, możemy rozpatrywać +różnorodność *historii* — w momencie liczenia prawdopodobieństwa dla +unigramów dla interpolacji (nie ma to zastosowania dla modeli +unigramowych). Na przykład dla wyrazu /Jork/ spodziewamy się tylko +bigramu /Nowy Jork/, a zatem przy interpolacji czy back-off prawdopodobieństwo +unigramowe powinno być niskie. + +Wprowadźmy oznaczenia na liczbę możliwych historii: + +$$N_{1+}(\bullet w) = |\{w_j : \# w_jw > 0\}|$$. + +W metodzie Knesera-Neya w następujący sposób estymujemy prawdopodobieństwo unigramu: + +$$P(w) = \frac{N_{1+}(\bullet w)}{\sum_{w_j} N_{1+}(\bullet w_j)}.$$ + +#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer + def ngrams(iter, size): + ngram = [] + for item in iter: + ngram.append(item) + if len(ngram) == size: + yield tuple(ngram) + ngram = ngram[1:] + + list(ngrams("kotek", 3)) +#+END_SRC + +#+RESULTS: +:results: +[('k', 'o', 't'), ('o', 't', 'e'), ('t', 'e', 'k')] +:end: + + +#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer + histories = { } + for prev_token, token in ngrams(get_words_from_file('opensubtitlesA.pl.txt'), 2): + histories.setdefault(token, set()) + histories[token].add(prev_token) +#+END_SRC + +#+RESULTS: +:results: +:end: + +#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer + len(histories['jork']) + len(histories['zielony']) + histories['jork'] +#+END_SRC + +#+RESULTS: +:results: +:end: + +** Narzędzia $n$-gramowego modelowania języka + +Istnieje kilka narzędzie do modelowania, ze starszych warto wspomnieć +pakiety [[http://www.speech.sri.com/projects/srilm/][SRILM]] i [[https://github.com/irstlm-team/irstlm][IRSTLM]]. +Jest to oprogramowanie bogate w opcje, można wybierać różne opcje wygładzania. + +Szczytowym osiągnięciem w zakresie $n$-gramowego modelowania języka +jest wspomniany już KenLM. Ma on mniej opcji niż SRILM czy ISRLM, jest +za to precyzyjnie zoptymalizowany zarówno jeśli chodzi jakość, jak i +szybkość działania. KenLM implementuje nieco zmodyfikowane wygładzanie +Knesera-Neya połączone z *przycinaniem* słownika n-gramów (wszystkie +/hapax legomena/ dla $n \geq 3$ są domyślnie usuwane). + +*** Przykładowe wyniki dla KenLM i korpusu Open Subtitles + +**** Zmiana perplexity przy zwiększaniu zbioru testowego + +#+CAPTION: Perplexity dla różnych rozmiarów zbioru testowego +[[./07_Wygladzanie/size-perplexity.gif]] + + +**** Zmiana perplexity przy zwiększaniu zbioru uczącego + +#+CAPTION: Perplexity dla różnych rozmiarów zbioru uczącego +[[./07_Wygladzanie/size-perplexity2.gif]] + +**** Zmiana perplexity przy zwiększaniu rządu modelu + +#+CAPTION: Perplexity dla różnych wartości rządu modelu +[[./07_Wygladzanie/order-perplexity.gif]] diff --git a/wyk/07_Wygladzanie/order-perplexity.gif b/wyk/07_Wygladzanie/order-perplexity.gif new file mode 100644 index 0000000000000000000000000000000000000000..1c52fbfd34d2bc045c3ba8766a9b1c73edec2d77 GIT binary patch literal 4200 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

FZ)5(I|h9+94B zR=nsBxxIqVo3gS3VsJ+v<2o0GH1t)3%yG|el!{m3q)(Ow+;^e$A%*u#l`v&z_VeYJ z1svHP3HxalsmmmHzGoFn z?w4ZavDa2B?mOY_qVWzGyi*6>5+%Hl4|3fP;;e$c@(Mm&&o-?DRbIR>+&~Dk`TL0p z9AU(JcZ3}hJK|0RMgjTvK;IZd4wWcE1jU+@5}g!MFd!Qb-4DV!V~eC@_lmbx5FIYn zB>BW)>B=EoC0krjE<|-6TJlc1D%)6aiARw57#z)DXEh z2$~i2EwwrsQH?z5qMHVy?pOC#RS#;4g`KRPz?nI=gT|af)uP-(_g}nOgwEd&3sT=B z&6(G}(X3_i)=4#yUPlW`&I!>wz-cEz5;V{z2Kb8(`tDQ<*Q`I)tJQ*mKCY^tehfN8 ztKtEdtTjfTcDl*MDBawV9$^I2dm8u+1pmN6BC5FIh6_jWxuej=aG46X_3(IKpar_| z^e{*!T}*Bs)NM-|M^rF|Kxzgxc4z#BS) zj5oA}cN@72pO_UAEd>Z>!!298AcbDMT@2ZwknHq|{9vB!dO&tR)#_=`>h0X>6Vuuw I3)M~AIidsD0QsamlT5mjOZDKR5d?Gb_)CH7W( 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 literal 0 HcmV?d00001 diff --git a/wyk/07_Wygladzanie/size-perplexity2.gif b/wyk/07_Wygladzanie/size-perplexity2.gif new file mode 100644 index 0000000000000000000000000000000000000000..53f2002f1d63b79c82b1fe528a3de61ab591c1e1 GIT binary patch literal 4913 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/v63csXxwQ5fVXgYrsO08wdXw3qR3w2fH9VeCpfy00rQB92AqpIEkreYOwIUdsRNeoJUnwYVJQck4lKaZizBnDsZxoSAheTYvtOJ32WqAUW8ImRtRWf5JEZq269BeD/g2TNOt69sJVG8lRV9jM5JChhFcjCTw4IBKcyzaV1xGmml3Hpa335UK0H5jATL6nwuNL/fojptX66SX9haq0eiXgg2nlN6KlmbAZrGw6AmrchU7GDSUswQI4YF1lROJNgWIdqJTxSstkTlXOU8ktL1XJ5HHbCyjep0KrT6VUzWCjH1q/PajSO0JpxCkXp27BDuqf1jmTI739dF0p+B6PIuHpUxEzJywkri/C8noL1NLFPMdSNKpIVwEY18yy9V2Tr4ZF0GvZaAH4gRGRWXhp3/bgjUoYe/7BqqVlFbK8Ei14Q/cvLl0DE3yDaWVjguco3QqS59uUtmiP2OxQAe/eqIJzqJwIOuuoFPPjBe7NC9p75QlW40SBs1zMj9ji3sTCS8SOrJkfrrvvXQsL10f9WaTUnOUUx/QsZFwfsJMD1UiIkpTps1wxwkpfa4JE3XY+mUBOkkR3c5b/4JB7JQsCOLUgsC0Iz1gAbmaBfchWFn7Vjrp9XoI0cgUdivZKuiO1xnYNZAAGE2TeO5GFN0O2spA98yNDjJTzIhcE7rzIdRvJfBcb9OeGzH6vPCOaEyQYmRe60P1v6FR2eEWeYqOnOHj4Aw== \ No newline at end of file diff --git a/wyk/07_Wygladzanie/urna-wyrazy.drawio.png b/wyk/07_Wygladzanie/urna-wyrazy.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..d300e14b1fae110603544fc09ff65295e888633b GIT binary patch literal 24674 zcmXtf1z45M^ELt!qNIRy$)V%W-QArBknV1zySqE3B&1VXxzg|0TRYO*7&`%NZQx!W24_tnVQ#oW;N z?JKh=(3St`3^Zl_KP{cC)oqw%^f?sS*^L?5^<<39+-?6OvC+5wABh5>C>{7e5)UmC z84Ei{S8+*o6Eka72UjUmOLrD6c_t1uRarJUNe6W;HF;xUK{0h%3pY0vNk&dXc~%Q8 zd9bv#s+^FZoueZ;lb8z|v!SDgo2w$LiJFF~rh}tABZsSzhAXqZ60<9li3F#bv9O(q zJ0qimq7WyOpuL@>sHKCkro5}9s*$6ToUyHxs*S6rnv|lUC5x4`zLvR%kd26=mYc1z zpqa6Xri+uj6f={$y`ZW(horTVlajg#tCXmgJrf(JkTEcPVx~+ga`H}WoQ_gz;v7~= zYMd6btWv^eW@3gG!YZyTl0wpk?jHJTU^j7VYb$06JAGzmR#j<6304b16&W@gS7$jH zfW^et!p=#9jZImD(@fu<(b!zW)Rf7_N{&t1%Gy9s%R$_gP0-rG&`gzGTtr#b(N$5B zMNrvVwI3E5O%jPwqulbP+?P3^-veK zkO12QjVb6`D9bZS*|`Z@YS=Nc3yC<}n}98>RV3M&IgP{^?Z}yggxC}$oh9s4-9$a4 z++8gl%zS`9I&W5(k ziuTH^oX)CF*5(RK@@|TPZxxO0C8a#}oEGNHjw-^+9Ln+zQkEtT4lXVlijFd1MiWkTeHB$3c~w<6fK1U*OU%{8QA$<> ztZFW4X{Tc62zGUIwX`$QXOR+R*3bkxZ>=gMZ)vH-Y{+T=)L@YkVwX~M6qc7!HFXqb zl=J|b3L04J3z-<2sf*dLt64bMacbB~G8&qy8kxIjNjsa%gWYU2<+KC^#YLG^jOAIy z#pD>}nH=o|IXT?rL@cCKJS5EwMcExZB#o3@q)dbi4LIeE#XTh0Toi=m^+nwkHLaD5 z*%%eTE^J~-Ova+>#+vr>Hu4e{EP}3YQ}OMOFreB0pMn239fAMf`TH3 zk`xhCann7{g8%kebuJi%Rp4K{Yhpg-)Mr?cUz6o5?~=+S&_rObh098EKck^40DsZW zl?4P8p9*pB1PgChwGj}>xB2%@#!N8KYcjIZveLY&=vx=Ens=J_#=LChG5Vv|1)yN@ z#fYPCPL{;+*sTgfLp#jpDpwMzltEgpE_6EW9!X_$C2|>TR{1{{YLt!_>#4Z7(y3KS z1XPL2q-HJu=GGVwE8&9jdGWU$&A3V8#Lnj%T^X#G>*8KsxcyyD7x#BtqASI8i*QGr zw?MnNV-O+r&wmZl8x$_3kkCBk2K4@h?pGS`GKlzu9D9c|q>`)|GH2wCH~&&&{K@q3dt!c- zeRJhM>N!@6rXzWpIS*}J+|aNIZxPb#G16*_S!%6%2TZT$>$yZu9;ABPxnzf(A>p0j zwent}a#GWdt+G#&ihm3>Fhp)eZ9R3A;<*#`^DM${fGRO z*@l}!WzaJgwBFHG56+ksMh%X8p0b*$maQ(wlWg|@AX$lFaPEb$`opmtB4rh1}Pntg`9fh#!JI8$BPZ>PeUoL z=fm-wNX9!K7&8~dM0E-%E5C?J92sOhrRb&f(YN3K()sK2WZx*uRsQjNol={(-Fboo zZ^|)4#C%TT;qpi&k1FLU^_*tkOA|y~Cqk;zwttME(t+33yY1OM$x=PdCFw)}zgL9# z%8mg6HqDm@&Ss@elE~`{O*WO$@1*sV?_jb3ezr_C;sZ9-rs_RK<*&Ba-?ePqE>~Zk z>u(qyj%vpf2mZ}f5w*WO944~qv_KJY3c~GLo<(k({ERs4jiFE=XQ&iSwQO_bJzt6X z6Zo4V?{hQ(x0KIw{ohe%c*o9lxm%VnzYoH$jvG71S@tyr)J>YhKR{imdE}}WK;YH#gi_?HYI;SP5-m9_#Gxi?_Jo|j}8snVJ%DVwF1O>!9{PLaYo%SeA|>VU4W$Z# z!}#uvm_Fe=U|nYDOMAN}TQ+p4FTAjvmp*D}2q``d8g1+u`;=A_g@&mJ^w1H1<9b(B zY_DE$k(&AcVwNN`9Ic{Qh>^!}d=St{wI0P}wH-~c^9f8PJ zOmO-b8(67hg)Y<@7vL}-bw@!ik4^4e$J-Iv-P7(UHqvkAPYwL~=BPy?D*%HLL8B*1=FK2hPE@@qx`|QNa0drl4pz zl}7$Rnq$Q9+nuiN_gd{ijN^26TN9YdN{!*6$QI|riK(Jthzph6ZSvL1WmiuO|Ih8P z?9mL?V*Yvh@?Sbyf190qe?(dNI4`^uLM(4g9SV5~i1BTpAIo=l_X2+Sr806(qv9jn z2@90ZW6?iE6S476*Og9F(#lxY-)$wl%t3 z)*bETm>;mP&k@s*(%o6kRW>&%)g z^t#_kX+L7R*2Q(-b4BVX$VqvYnQn%d)}eM#?%sxZ*uvnv!NR zZTXY=qDZ@o3E3`=pImf~3How^)>8DQ8j-NI)I=hn)=`ku36>nC+s? zB9aY!lqz#%e>n-rXwbf*5}<}sp!Q>~j$g5!S~L1|c^>-P^AqHJul&@0)*Uf(wh;Vo z^aDFr9=c8`f-!mL(|z|WDJf}Kp!CQh-z~nQmme;hl^F{z(UYsJ$5Yz-lR3xTmZPb% zyXA9>t=+i+=%+S31bXkw#OnI|>CFcSUPA}AUao1{+KRykuznvzBvUBc_w%;sa!}j1 zE@-U*I(}}BFh^+{N!R@;(b1ynvV^=S+v7h;+MzmJt<7-F7jm5p1NP^Gf0pU2M(G1^ zjgg3xRxN$2k+$kC-5A6&m#!+Z5 zZuxEb=Yfnb*jDa8d{&UYrk#Z!iyKbl#LzLRR7>kUh(R1=8+?C(tzha4+fTVUqqIGo zQpMO2`69A{J~za+^8UlNx~WDeb1Ou0VPIr$Ykum5N$blSixl)R0_b&=Q&cA&=sXyUYu!;TI1$>TbTL@Mp;G5qeL-h+%Vj)VD`x^TV9p=+N*5Y785ebNU zkWeQgI@#M`w#M6LZ0K!kt#WK8dP`k-`Iav9MJlu!^7q`JA=O!%L(-Oe7lrKHd;?qe zedowA$0f$)Y8D1RIO(HZ8#0TNeZzHHGM_DkE0nOkE41prrfHZ-F>$OnU;b8%F0tvGT-55DBO)VJLJ&UipSBMYgRHWDh5mjIMlgVU>GlBteMp9|me03ZphwS`>$>83gwwdac^b~PT zp`eL#@!nDSIy{&LU+tTB`uerpTO!7A*b{l)y&DX8yq?ozXP3gic_i{&-{i9MZPv%b zPoEzN$z((OT-CuhP(8ut_Ua{-Lju#z_RTks&aDSMHnZOV&niWU1lK$Tx8vQ&xZ+qj z1ZCTS&H>AyTah!ecRooVuRF+0x)+ODqdK=h%U5ZifZrpMu=E;%fY)PY*+v%`V!yJv zYoWd+LAn}#2;O}N{RKh7rWQdPsZw}nx==*S3wr9MkEzxYsHwJABs7)xGF|NDn6mj`Lyvp z@A3DPycou$VIXci&^YcS*Js*TREC2o!qBCjAFj>ktMyf4nh&hmX%^^<`el;u4~`q% z15XI!&mK`OnY-!L&-bfv`Nc=4>9n%N(Kf;H!Zxd&yW3dnYq^!YVCh%Vt_+ z@k03Ow(dSgzP}p3@iDt9nfzgVnPL08w5h>-dQl&pj&S42{Za=aemJgrr)ub3fp3{2 za(QQ!eEe!<>RsT}C>`@$Z?p9i%ReMhy_t{`*FeY>$v=d6ox+b)G-B${C%hR48j1#} zB235aGyO94DnbG-rvZ7c7rjbZXqtmv>S5)q17u4G3X^(Pq`0Ls7M)Teo7G}&vf{yR zb05>@8Ct_|%k@9vr6RdINY=Nb-WN72BX7BSe!1e!{vEAz;qkz}pLmmqRM6jVPl|%0 za3@!JTdmt9RfjY|<8w?!($iHPFH%KP$xID4o1MT99O)=rwtU|2x4q(HywP-zAh#Qs zn};t?#w>_iJIcp@8WZWacD2Hr4>mW=PFKC?y}zgC*wp`|kDFdem?}zLv+YnURU&sn zlQlSeWO>B!)|JDssH}P#J`KEGB@lk9e_3|M=4Z8yr(`H+2Zuulqc9}OY;;RgeM(UWP`^Kd=WChF*MyW(1^UZsmDe95ENPFNR~-5f^L zRoSt!2J&>gulOBdyGm{&__Fr;oO3`pp1^L4{)q18z|5EZG}~l9a{bY9rzqNk15q@Y zDO~3RzhUO#S9w);kng`wqs~zrWY>}NJLZi#2WZiMs@%>EuC!iX5DnILo7t`;Mnl|7 zYv%Rvt>>$=(^+lvj!umk9;L0sNkO_s;K{(gz`wOWK+d4vogr%Wnc0q4xwliI6K&>} zwfyiXEJ+E2aZJWX93A*3cYX$z1fAzc3qtg=VIdwJ=!Co2`z+q?aJ(3QK7YRZ0R0;^ zv>^)*oW(VVR`&&lKT#@$aZ1(lGD`F1M0+f+whB7D^RI3yN_sNLiD2zTD@`U=ikA^+>0@h5AF|V@~Q<&afscW?(2hR*ybZiPmW-92>au@@5b1*-aZf3>u#yaXaPB^6<1Lc#lOJGv*mI+ z?v_+sYd4q)>MZkO++XY!L=$k8t;PURjLEMJwQ0;I7f;DiDphg>4<@TTvzKvEKHQ^h zG!2yFsYDBW1_twMaPrk1dNe-T)g7PkSPNtL9#VsAb2XdnNNycee~j~Ee~-??8qJvA zmIqyWD8ZrMIyD{H!u19!zJLGX@5=zIRc9qh6!R*fDks)bHd{9NXxOh5K!K|e z7EeCwj+I&CzM(PdpXrM&ojDd^$)SRC)Z5ty$^(l*X&63(6S8v=AVE4Rs7(7qNM*PK}l1dyt5445L{-dicUmA8B7AvPlgohUi3JoIw z4mscDc&Susjdf%)@nUCOe%$p)gvg6WcOj66|8Lcrz-=;vJP?`^Vm2LBk`Agh9Ue=1 zm3*+7bRAktT6U92q37it{ApZJ>vBA=*U(-d9$iwGvdzh!&ReD38f~y;GY!K-%Xyp8 ze07H*uW)lj3zExL;=cCajv@-?ys9C*gMofQUx&94v`MpGYDv1lrd&T=@rH~K%fok+ zBAa{s-nyJAFhyw@3AyqdL&?%he(lZ}$KR@AT>X+n9hQpl30WhK9qt*dQrb!ruTH+~ zC+ZxGG00gfC4J`ql|tmL@pE@?`M1}Sp${oj5e0Gb-ud=GT$WLiTz@n{sdBO0Os(m- z8+ft~CnC)AYA?sKTzro{Yag-luGMLOb6+yn&StGoPcv## zFWB(zh0S`Ynm45YT(FS6(79tY+d#{;ov2V@D%1dfOW&bXNwi7ci;5wpMg0Wl_ZdOr z_GF*Ac(yb$SheaYHkKp`W%bp!RjPGjjDVYp`T>_e@}y{o7NmRdK+0;_jnVos0!nzD z0}>TpC=?W~@U@ZelQVwu$GuF>Q3ytTz6i%awf~ij8)&)}K4|UBRqz4Z%ec?#dE}ny zSf*%L1yA#3$!H%CEcnBVrcG%Y4WUM~*5IkzRBNjv?8`tVSX9nXt0<+4v6de1_P6a& zZ55Z|!=}{`a2Avv@8y4eDu9h#zQjQ(vU+uGE#t^wGsj&62F2i01ma-wHBY1A1YQ7y z7ZVYJpEHIlS~I)IzAP$GK>k`YZTgDy&cB=7?v+K7Lr|^W#z$ zDJffR$BtW5!n0RBJ3F;U*+v2~0T-oU#ZlUtcD+@R2c6NEYT?Hl3x6{jz6(CQTc=Q; zw04k_6rmWEcMvfM8Z5Mo1nr0JmgyS%=zMLl6BXy&oVl!7XNFeqkOy`x*WC-qNT$6P zq}yL?w#04>c7RR)oO6;w0I%LUeK`H8w2gAmZn+1a)p&#}j9GyXmtXf+?75!1#8BK4 zQ_NQ&i2WU#UIFt&=q0^By@-78q(~h_&wakZ`Y}_>hT0$>^OY`P;ZGD^f6~r}p-~~D zu{lSx{i!0Bn7+U(842UF{h3l~XC{U%y_fIkZ#H0q=DQFW4jZc(Dv(5>5?7}DMv-O{W+MJwE{ifj$i!dsnBfg4*fbH!`k z+7;RwgXb9ECxaKzi_5XqPn7)RcQN#t9bz=+Ds@Uh|n^i-k!OXataJF7{D_ln23QLpcGVkGW$5yJHpF9Jd1Y3L{D$H5&`^LFxVu*LQ!S~pX1!h z*zFJ--#$U(sivrLgHc-e!fe)Mv+(kBa& zBl6ghPx(4Jv2S1H=#=Js>E+>56Ne`+f!Cxo-DFK{F% zK3klsm!T##)}b<(bJ;+MZ*6bKwqmhwKLaA*xDkUUKQ$z~WT-s16KAFO zsn?W=#ADYTT|o26@%f#Aj%3imR4lwud-T31?&2hE;z^pv>EEV0fq_s+K0Jc}@hq&C zV}!tmhRJeH9yBL*c%5O=_P-CsT)~oi+u>^0+lX$t&v$=k6_To0 z-uAhD={v0bTPHWuEtAQ1Iy|!y)^@gh^73M{3c?iMH;=#6!8)ju66;27#kY;u@tE2^ z0N)cTEHnwk9CZgWZwBnCEVeJ>_@Z7LROC?h#*-3QLIIr5bFc%?qgf)f*{@H4u z3*6N+uuM8l8ZAYULi(OuG^Fmzbo-#Bv);nG;e4KVywa#w5iB$=vVMa=BtV=53we~e z+F)HD$88!Hp(ZOnwSE$KU^rzaxkO8Tx+~yAIc}Xnt#MiHyT-Z2P+8m7=wo*u8|Fi- z8SjmeekqR;GnB{V*TsE|oZ}-Aiu2Ry!PRm(AyKzt}B;xNJ`QfgOqX9 zMtuVh&P%!OBtkt)fx_;!-5tb1Z#DlS-!fI8xNZA+r0vC;4`#!Al}_o8VU%3?p;h#X zCtJTAbAp+N25DyCA&gC>hp52av9Y%tKl7iym~pn!BfcdOik^>{j|pJZ1+bcnUr_2q z`Bm5dwZK1kbG+8Cjm5sLjJx9`xYZx}9F#~_{6a7i zGwmMg%ACZD4jk~-7fNIpq8%odxmGK6#~_oXJ;GHb`&%(*+@1=@_M|ku#GtFL4|;F& zAs87)2!-b^*K&)Dg3skHk4zf>b!R8Gz^FL~QSP9paTzlDMGZ_M=?Y6?p2=_fdy&_1 zrbMwtdkl5=_8Vq{^g`!ATz%eIM*pzW>yB|rjZf%rn*oJgiXE-~f{p7a{oLPu=OC-g zX2`2_ftx!+uShPBJwF1hfB<#iY{!dOL4Ty0NmS4tImC{ezI^KI)ICCS&61 zv)Xu@r{&BS)k5%>b~KDr!dwexn6oM!=x+8Hy{+)xE;wENqxM4dk$LIhz5N6%5bU}l zcebi`>~gdlZPJ+e!K~+3mxL8_qJ%#hx7=167it%SEb)n$$F%j3)N2E7F;Y%bS~225 zbf@}5;?j6&l^T3B!qMi~-obF&-w&Kyl|10d@(I6$xhy?^>(n~%%G=3bqv}kS>^>ZlvG3vkb;cU$4|Rkbpf-(++tfp++4Vt z=jq*ZSx_6e1Q^1?a2@8E6>dikbzI(yh%Ti&aa2#se|IS#9bvA{wyf(|L$VQ-v!M`R zVUP|$8#7%OO-8u9tCN0%Qx*0{%h9uu0b7nIf z4amcIzDPR7%{EZU>3mbPy-nsXk*w!nSR94?d`^x@O3@Hw$K}YlnV}s_qL}}D4%txs zJWPk%cfP)-uvnh0zF0mTsk`Gjw%=~cPtOszhtxbDa`E+-hrHVC ziM`)@oI>-7L84)E`XM{M%v+Xm0*0AS&lUHmwKoJ@O>!PAZEQ|`!gpTdPgoqC^gAkZ zH=rfsZU^g3>(mWL2O!?q{qX?-2G)~!-9i}S`rm3m4781?JnwZKimT=^`dp>ka0*>9 zQSy_(bg`VH=)^y)bscb%hgZw(Ke>vfy1e2Xk0FZ*rRW&#S6c0c$Yfegq3IvZ%|ASg zZPcAkft%TyPrUoP*fX@(+^Y1x!}Ihhw3;%?#j@-xU%xZ*|Ki^;WpIJk-rB3_D7Eql zn}z~!G%9Ywp)$t$woW0SpcL1djwvOd(Za!*M9!&KLh=mS%J+7pQ=&4Kc5P?co3nYP z(`t==t-$DPd4x5m>)^(p>6`|0#&%tbTu_`>1rh>0fUaN5d8)5^*x#XXg*^zXag zzz!602nsX^_3rUhmc{*o;%X;FFC8_VPq5@e$m{-0GX?{>XBNt z{H5!LJ?+?*Z7FBLe`4+E=C|(VYI8z4guio#iwYi$hM*7pToFUHQ+MEhI)eeOPE#z9 zgXr-4TP15jGqvI<7Rx1JG(%Y54L&TAN31}weX1VaKep^8Xy>t-FV6JU+b85&<`T8t zY0?Lr4nWPJswe!AEfF8jo+-31$lYtEo}&e+G&gA6OMJ-b{UwP(KQ~fkU+`z}4~~J* z?Gy@%pa2T=2^hHO+m9IMF9r^HmizEAy*JH!iwg4Dr<3c0aDqPHC^BG9cA`5N=f;B$ z8%Jh0D?d3NPqr6t;vNkaXq`@9=pA**FOZKb|C_9Z=UrHrd>O`FQ5;^mY4klFzj{{p ztao+QRD9AH;Je>mxfhhV`+zm)ClHJ9)aFDrnh6#j{h|H|$C3wy_dNC=%TBWNnzz?1 z%o6paG+!$O3>ku$j%1>(T^c5ZO7kqYb4`&REj2PAg;501e?`B9Jk& z!XH+`7X6fn5n1D-Z>`f}@QqlLY3MlRy(e&T(M40((b_5w>|G(~6_E-&<3@l@@+}We zsGJminn+`FrXJiL$>nzKqe+1N-m$2i6*-^U8cTQE`x}Xpb-N4F;p#XVAJnXsx0FWa z!g=wbeKDiC!MJj&{s;?OaQs#TEtVwa)V;}!ddV28K{{fc?3Xl3$DgPQ3cHm}OK_E& z*3&cTE_+tDyOPPoI}rVxr-9WL$mTI1wHmmP@Dw0+hBdlaH;m!^aU*T#%vpmZWx06$ z71Hp84g5Tydp>_8Iw&!2ZRubK&pm`SrDuJq`g?V;Gpr#}SHS;rCrlt~8}xyT6W>B9 zkDb2)zSF8zW~7SX(s8M1LGgUnsm`s`VYE1fewVylp3N*5)?H!q2-yh-rK`>mjppWd z9p=R0H~=l*w0kbI-EtYpiW^;ZMOLs!Asw8O3C**n?!cK)!ZZ3 zTuYDk>_w)oBlKeHzmaK)voe)R&Szf*5nD6CeAV)7-#W!0E^=~va(w@E9vEZZ8;W+|Hy7{PaFy2;uY{0%w^ET{ zmX~oDxdHIFA&q%H7df<9X`Qrd2zq&bY@ObiJ%|^?88~r60OvRJ=pTsSlt;#($ZT8k#+71JBf5*7|=D>{0L>I#6-(u4+Y+!nyG z`}(RPG%aC!emuK7-!k9q4O0Qag1UcZ6D(#u0>e`%Hx9^eJ*3DzQJb|u2i)V#i}eII z8v!9^&kxQPU$#2CU}iejELpy-LuvE>^crJ5P8&Ym>W?W1OGvfE*&XU3j^VjLNTSs3 zer9p~buptIMlEUET=eX8hU%!(nB6Q_6abIHH1`s2>F;^LnUD2bK_=q$vSIjSqzda2 zqg4hn_q{Q(-Fl|gKj}L%xWoAn;^KY=b-A@#@D3)^I3=6Kv6--c#{)g(utur!bUh`o z3;Z>zsGqGK8Nr+1DfPa2_`2(PB<4H6uHoafp*wXiEM5tPblauaFjUo_bU2=K7%eF= ze=Fy;a@KDro+V!?Mlmo67)bVyQ`As--z(6hyFyN#MU~_mM9iIvx(VV_^w>V%V|fo9 z;MI$Oh7-oTi<4;7r@%jtf|_yNe#Dyuo_q;ufx&$n4=xxs0fL#%S$0qIu0Y`JRHhc|nVLZ%Cg zZU~l?$MESZlc?1SN7|0v|NdN{#It25aA%Eg7Gs*TtcyrHng5lbjt%+omV$tCj7-3N z(M;1_AKhA?y?iN9+zZ=>R~3Tf^P~gh5B$>kA(^2@0F^Rrn1VTbGi8vnFi#(p)t0~f zPTxm)%}7zorZ->9pL>M|TUCZ|f!8Y)8-=@4RI2!kh|TE-Y&@JFkF z6XH~TiKWxl?xz+)$E-@d4nZ8I!eSteVWJ{~eIL|Oxz;4e*#woj+kRksu{YnO@%t2O zXDBX0E{jj}*RLnnBf9X2c3I$(W{=EM{_OM@wOXTXzv5s#xr7$A{Z9ea9~Y0jx68uc zZ;y7MDS`sEd7qNiUN+Q$bf(k7cuLKhuLS(wkyAy|@;Fn)#dZ5riXPtb{ig@DkG7g( zlyrf>`B>@C$ETq$VmLia3ST-*2T}#SC@`h9=-1PjjPpm*n3R8vJdGvE{iq%pI)&Bh zt6+yzz}s?e*%a*?r@pYd-=e-Hwl%tn?NWK-Vi}$fU>Ql_>Tx-9Sju-Etpcag>zSFg2oF z#TQ7dR9-XP+YO-ME2yoN zZc=ovGqAhAIyA*5r_pFfblbD^V89ZO#m;3}rhlg)=RF&Oku-i=a z4P#SLGlkFEeBK#Jinuvl+CxX4oP>m#{P1+YoRGzW#_8#9LjP3I=6*@tm8;rx(TTd_ z`gmiFg^9z%Q0iM0pCZM>8}X@hg4ymp!+|!nv+Crf>Ag-RV5-(oerG}C2L^5E;Slb-GDk0tHV@%`l|(3 zjKE)^_u*=r22bbiNhrF-`67Hd9Csr(H_%ll4+zl|8Vz(hhPz}H)eB|_;Wyd{*Jja? z)Zt$PFZb-cQ*f3^Wwv>hcSioxg@=ev-K6HK7Kc~-l`IHvOqpkrc73lSvusX%z7>lb zh=k2hVneUVMwaxtKV3XKXsdolUi^BzP#aQNU6vGHAraSI)}3O*Tza=X(}%y{Q1Vw8 zt8#tYqx54BQ-qmaeQ*2sp%VpbS03#xX7FH$FuFVz18?C`cZz-hY#&|PMPiA>pIeNt z;i>(p%YGEvjcz~L_)rWqt06J^_FbF89u|d%GA*_{vNf%YC43 z(H5^-$6D7~r(p4e2k4E|= zydq4|_ewf3BkSZ= z&a9W1RyEk?OblcWK|z<2=60l>mHD)3-!om=0SUTXUI%rH^V??|J!O{9AII5CdJVaz zWOFFw35WXeHjKuz14KpWS@B6K7GQ;O!!j05jD(2`sb4=eQ~6a(C_9GE0?x|&yPv3r3&UTx2iJL~NM7Oisd{Syz`FS9t(GI0`y zMjla~uV(w83k=mjl;(fc(k$OmC(-(a)%M0~OE`Swu8lG|ydj8FA9<|EhkHL@(~k`Q z7^+^U%)No8)Xo1gZRf22Epkym*l!zXUL)C8&w#Wv{FxR-aX{E>e3jQJ)HKFaB1f>o zH#WqxRH>*bg9` zY`6?Go%WB9Nn1%^ayQxUgxNBO3&(^9GcE2>seVuh^U1!p2Hl76Xe)XNxLqf*SH8Px zhFfaJ;9rpSppi>`-qVG*!C3{<@J#N~H}nOCY6o}f?3xquxYVT(_!gBf&w%^Tr ztKUmh_L(e53yb^TRq&;1#v*<-_IIHC8`mRlws(^Cc`&0fa}Tlo?b*WZnoq|8?==1h z`#@UAx*!y+l{E1}HsV62`Di+;Q(|k3aY!33s|Dq`g6|$rsI&=Tp|v3KCNjl(ef8c) z2D;>C9_X{VL+ zS8D}D0)JMb?yc8)T^W7ouo5PYra;u&CO~=xak5bI+KT;cQY6bBDE&0M03r#ceDfXH zBmvx2&B@yM)7Bs)^V8M#ne{L@r?uy#wjVtOFdQY#ft{a7Vmr(3QPT8CmS(WFX2s~V z8@k&*2$%FI14{Ixt?v~dq{Zrx7t3XpAzzgaJYI62B5?N`6QCQxzPl|P^J%m#eto{D zxIVco;~It~BNXcl&H=0}M;aU4SQP!}G*mp(sHgWFjpB$-Qb_(4Q6glea4^BV)3QqO zOU^7b{M(cxj{W#1A~DK;muX=J!=8zcc>Enoh6C|W+SQKZ!rJIW`_-J-r{;n$o{PpN_`v_AHml* zq_Zmk-h8T1lEv%6N#Wi9tw~KlV#s8HcqT#^aIn90P>^8_sQ2|n4c&nm0Lf*RBu5I# zbb^2e1=desr$SR>ZI*#>k75>=^FKd$LMUcb1as)uTc1~PpXY0A=i|ks;Ey=aV6>^f zzP$m!tQ8Un3ya}@(a>o%8bpt;sKsM3%XN7l2;-+fTJ3zOruEC<{YnpfAgkqH8S-aV z-BG{nFohcELQGfzVpdr6P{eZqIo&595FwHL2Xluk%kuBfQsi@gn0FNDO>XC;Ih?h+ z3tlZw`~OOTha{wJ=1IH6pv=@m2@IgKtFR}i7+O#c98JfWbg zXqYFMcgl#FJnr$x_#6UwY*x8e3pJ&K{V4oTj5`Axi)^b)f-zy(gf z^nI!_s2F)hR1!1@z5*7AcV9tmyeT#tyx(t1nlUf3*{rq7l#0U9F{ip|6!S$aw)`Ij z1hAn=y<^VI8x6{t(v;-jFgFw$tTf5}9e0Ly963-i_zJp%0Cypg>9hsMrOv+RD}w|J z74A@=*r8#w+bk<=jTK2H<@2~-l;ol9%LT&#lSo8NEdHaWgnYmeqW#f4cqox7LF+G7 zw_2|-kTm?ZW0fLf8IaEYvm*nvL_kB(konpt1*#pC)(V~qAVKkTFVXAp&H{d47xN9@ z)p;hTAfR%fN`(6&1UT-EO#=6{lEE}0jG3H{VqtJ-zerU?StQ0&WxmiunyM>_NkC40 zh~sb1qog`f!Ev9<%hjup0fEG>P0W%K@KNZ7p9=Hi$4S`=yW=q_fK1;5@+9RykvLS2yCd1BJ6ikH zDcB+s7o5cT0;$8&!KFs5z4exJlw4d|&vf_1Yvv4S9u#t!3LRmc2|K+PnixJ)J;WpA z@`WjazZ+|Sz=*>Mu?;ZrY9!x~z#BBQrQ3z%wB15{mYB=HD-2Z{4QhdVUFvG#5M+01 zNi>Y9B>FjXQsI~6utHLyx|C%KA#&m1kJ%|Ik(-6my1JVwPxMJ;o;Tiu3ltaaZEMHm zc)=^jU*vRLNJt`?<8=sHAU;P(b%IG~vWgTk2ZThJ?Fp)VOkEhM~J zOJi82B{!iqo{kP>?~E$jm{nb|!OL;xirAYbJo z@Z(EE8Fc^Bj0gnw@P86e214SgXG899z?5RZ!F+vR-hiebhzJ0dZo#y$LSTFPA_mBe zb!YR4Z@}0H2^au43CNM4kFVCUK;hqokO5#dO?|8Mbpqi}Hc)t!W*f>10Gk1MWG8CE z>W#$&C-~o+jzNIp3e{u^t*E}K)zWe#*?`-0eFZup^MJ5)0o0()5e+2++|*8x%ldY?dK0Eq%1Zw4rOnIr(DKP+Jvpyzxz zz*$j)6vvtY2n)Svgg|Wqp!Pg?4%KuT&@D9>_ubohC>1D=Vn$J9&jy6^NHk~w`1}Us zM~Q=m0fa9I*t&p;ID&z4!y0r%usvnLzR;3!psxX-5t$rvKhdu+kJrB?0gQ<_8{w!RV3@koVhZ23PZ_}erYcR0p8{S9D;-U?|YkOOEoa3Z$>&Ho7jUWpxf z)>{e`W~SFlU<3Vr^QWsq#14FM0BEln4hsOk2mmrv$=MX=8!#b70ucb(19GGxQVzje z;k&fZuW#=A#%|Y(&^ZPa9%X(HeVb#NfILDPWoIP`6eceOOT4-7TW^fk5yo`ifJu6x zbbyX_z}Ucsa0}HV0$!D2^f@09u#XDR3BkUY&Wbnc1J1xZKpzc1z^IrG@9e8-0pzgB zCsH`Tt4IJO*3d_hJ`aHNX9`8=+a$Ywb7}^8R5Bzqz^N5Xh^YayC6fbAz3@}8_vf4L zADVqR-j4NeBQ@)YaE?R`m_K(@d>ufty}5rvq!3GhFfgDT%V^X9XFI?-Pt$>_od8%m z^&@^4pwc|BfCSRjeu`s@00hlKG-Lpn1(eTo-lDqp0K%8(FYkb6fTnr_VVKY)8Q&(6 zz10RY0CarwN(zQJbQnPR0)?pyObCv*@~xWBh*JrGkj=93Haf}x5eKVj9K8+z{O6CB z{l=K-OJcAIyq3Hg<362ecHA=q4%`ib8jUtOZ-jV%8UUHChipJ@wa&>tk$x!Rb6U4Ju+i9kEAorvH00a_qL5}g2AAbf&ui3mG8vkQ-isO4>EF`rUUQAs?W_Jf55 z)-X~=?6Yyj-bGfzK^s*efpcM9CprB|9}SQ<1R_$(ChsWGxlS(I`Sm>8R`>q?Dahzx(Z+@A>|g@4vru zbzR5x&O7fs&-*<0eSbcm`@Wx~2jH%MwGP4{&a&PS#8e$=EVa&1d;zTc)avIUr>1iO z(3B^(JoEBNiXl@i@jne5s0$-psBYD4dA2`;F1F{i6g(yQvZ(4U1APi=i1xP6jDk9H zLJ|jI&r<`Yib}hOM!av8_;w~)dy%6S*hGy8*n{^18>!7LLE`rTu+b}qZ5(hN1I9-| z8N8{S3BE*Elh$Kphl|)>7bSD5!tE32YS*+)kte9aW?( z$6(9qaAlD`P@daa98$2YS~)!o1zopnk<38O1wgosj1RIU0Xf+NJ3IyO*a>iP`f<-% zr61o{kDZ-*0?k+q*IJUu73remIxyRMsw_fJ4TQivI;`@bRHC6IrB@^9-JJj}5|evl zwz7JTUPh@;2DfyEjH*V^uZ?k-dVIfB^6iWiqS${&9z*Sf^XGNj+uPTrlE9Sv0B4_q zwzRz?q3a7i0I%dcn$N%Z5V_fcHt^iA>CXPsjHs)5jHU(OI^^|TUMOR3b6m~Tm(ykf@RR_DuDIe2;@hfxmXP0k2261^%7srY1<>Z7)+gP{UF zxq6PD90fs(1`1QMBX-k1`gCtW{OlihgWo5Voy2Kqq;8L#Q;+!CF51uRoM@v_Gl0Pq z3&~seRybEO&dl^_o#+;;0Fsc}jKz0rnSD&)Q1=>YHtB|m|8CZ*rEXv0q_FWk_J^5< zNhrW}Ex%X0K=REJ!T_^3RB>ydW>t3zd*n0VY+wQKE#ADfE@DX5PFHz!G~>R$Jh zO~^0=WlI=B@a9^uA~2F!FZ7s9(da~Hnl+J_qeX?rkAg3Q_s?Eg`0)Io1(*Vhm29^=cfx3r#IIZ)3twd zf$jsY+X3%fsF}r55qM?aDiewjlYlOewS={LC#YsijDlCFb#Bh}X$qFDbIK0dj+ZG^ zd7DheFn5p9+UP~U{L65Rg8jAaM!Rsh3(#0#15K@tX5u(}$W*r#^nDcoSMDv#kEw{z zX3C2uQ>f5VP(F+fuFH1$___*<_di)TwO@?X=042L2OA-eod{Hb&AyZF8wgh$vge{`}r}a@sUvcUxN<1I9JGR}f=XLM>FkD#c-dKZDHr%Q|wf^@F!qXa%#JtFalgkqPS&oeTy zHJwWDyu_yNrP17&S+Nu#hOAFM^IN4g5C?crr^)873hvb28-} z7B7`bvqXkg2!<|$=haqgf>fN>suW=p-bXiJJ{EPagd1;kcqyoNC!}!*62|@I7`4?` z(dlFGHC~IWTM++X4E_PzrQ@SeraaS7`R@K%rKIgfIVYay1^g)|Gpr8*v==1@$yIca z781t&E&D4EspjYgm> zI>+-y*XG$zx=w(^HYJyCQmxsUZUCxYI>BVnt~@QkeWLXGL!l+%jaw?oe58a-!I%VxyU)YYXNq+9l)oV zlB}ae*_tO)mrh@mHpq0l?N^ejcdAcrW9|L5e%ho)`68SnAa^(%8>4cx+0`R-amp5g zik8QNsBHYfyx%vo?n#bMxGsXaQSlJ88uR)SJw=C(hF|g)_YGlXCzmm1UNRXy$(>*S>Jm3_wJp<&EAKWYKx)0L@mxC4zT9mt<#i>KE_ ziC{0lHV~0PrVqW)kq2R={*7zCzqlKGT+zOWEW`%!!P&LAEh}q|p)m5{dH%6w&yGO~ zn0+7k0K>J+-5wP>Id7Z8>xH-#s3<`tfxT;^9$(FfuC4kiXbuW$t2b*A&>7Egm>m^q zvH4$~EK;9BXE>Dbbk66be0ii)5jJaT2uK4zo`>~;8@Emej|R-g-V!{+Ki(G~GsL~L?qJ_>BC@2rU?dl;Zx$; z#D$f2fsu8lOhlSVXP{TxwyHAt7!7)uZ!4c1V@!wxgAulry-YGHMG)b-pO7DQYdl@W zza{NrtM^_6QzDJUuj+hkv`>yF?HMjmKmNEE3hBWk&X~~m#XiXKz8AF~B9AT>HA^sr z$X{RB20uew)csP;Y6uOJL?uyOGh zLywYEPZ}jTd8NA^y*Ym9>v+3Wgb5+NL#TIul0>ITEXDnpIz_^f& zYOv+SI7JV0?y1WAaX8*V>c|=|s3uZi+np2gOmgT|!<2MDiKYUpKx4=}2q2`|)h;`g zU`ROUfgliMjVa*4fCQ+gt*YKbV}5*X>xY>XS|O;&(ah{+N^XuH1ywes+{Xc}>pKw_ zH|MHgv(E>`@VJrt1{>+r@p>?*5cCjS!{Ux|E=Z>(#eF83CJWB@S7y1rJ$x_3XN2Tc zSP70W_2u7=P+1txh!O)Qd%;B73sD??ePs)!;QYs`##iDJb$?#;a38vVw%=u_Pl*NV zmF2FflZVDUUbr-mxJ?;P3O$HZqL`JP0z{nyF8x0db!7!SPFD65?6IXUj|9?kX|)iV zkz$F-?(DR4a|4A3P#I|^^XEJ~U4!;ON&rBt5co3$xMm7tuImI8J=q?474%kPFE6ij zNXog@c(Q2w_Qt_^E4Nmv#o!#Cp-=bI(~^~tub?psnr+*V*?3y1$NRIeR3bW&^#Pgf zaLonr>yv{fgBO5(e&^2vh$ySGduTO)&a6LX^8i9{_{Yy$GLcE(n&Z zQQ!{~&oPh);%>qdbrlV0Vl8mvw5{+Dc%m*1KF5S^JJpv2f1KVmhVcH2@VTyTD6OFt z{$PKG-ir|BeE58EQA~jsWmTc=L3p$Y zUz{odgS7+v?~e&2ywZi2^|8wA3JmiRkT3X6)0sJ-^Z*pNEi) z79Tm^^5;$=$iiU-g#lSE&1|IazCgF6+WBtss|!l3Uv&bjVc*{xLLv<}hc7*r3_R~} zcASZFYd)|39k!|ySl#T%!-pH6pw_E&dfHcFUyNZ-{=Q>X+o@Nt7EeK6FVOB?j-^Bv zT0EVbJOB9U1bPtB1&4(<$|+QnOaF49lABU!mgqnrkaHf&C@I4cCxyH3{rvH*e+1Z2 z>BjQMmmpEx+Pmks*H^*Q+JYP7<(6a;jMt<>HUCok02D!WEX?Wa`zPfQ%d-tBT3l^p%E6*)l)Ey6&X`nO@2 zvG-D9!wD)z8Dq(S z<0?NEbD7=w^ekJm?0zF4cT|Ly!$koSPWx4Em;e~Tg0KSc^4|QoNhSd~DQA{XPu46H zO3X@+wnUWgQ*vSyWX3+rGkUl94I;(?=!$Y!R3x}|FuS4bhm)}NKfIt260W4-b-80A zqNdnh*Rh@Hi;IDlivP3BN%WL(!JnIp0!FaC{V1}%(Q*!OOb|_|uy;+Ggwt9OF9#uk z`)XVOlRT&Q^{G-dgY7Ql?L=HFqVGzQ+PEySK`17H+gl?Q3=6G>C^T`7vt?YMzOsf+ zPogpWJ+5+Fp_Snf0!4ErDt`OXS}%ivcoV`Ya~?qb5i~;8{jQEZN_2$$m_Lh5xh0gA zWq4O_o!=&V$Cekj9SyE6?#tK-K*4Cl)^#J67;caCg2xGG)v|^lLmKma;|08nS1UvR zAr4*eXgnm4>WBo<4bBYK#CR0pckmzwus;@tn8YC6puoia3MTip>Qm^CTn#W zC*^&N0?x3)Dj$BR81xCnScFHPe4gk0gx2NY{B^aead-g*y5B*%RIDHfuJvLl*nPe6 z#=Y%K$Pj0U4K#;q0)|dPT#<{z=6vkWn*fZy{b#+tYEIm4_+-9DG(Zmf4x$2n75Pi3Az`GoJ_b zW(R6-E?5u}s`*K6ha8?gfd9Q&zw!$9%Cs50D^r&!!^Lh*r?Lam5um7GiuWfdf^=vJ z6}S4GD}#N1Azd5Jp?Ks|f< z+pyvAMCL;{Y@$OoRtCS>HaTzw*!uO`mSTVulG(wZdRv-K?c0Rq^OLvmyU|U9ih`n? zQH4q)Z=b|{@H%(6J!^r3jqZhCNk`tQ1;jv8yvOB~lpY4F9;f?C>D~DU-xy~ zb^b@{3%EDn5#pmnfilp2-_Q~-Fa9l%`b7rb?L~4!Z`ZHA0^3{LkH`hriJsI{@g;OgcDYdhKaR+H8W_R<44tO1}j_${GT z;Rnz)?D1Op(JP1DK@(#@jaw!Kjbnine$bauleaZqY%NQDHZD}5aTcUzCsQtEYPP>J z7{CT{vW)|1qRQe{1RnNoJX%uH24VKg^CS5hC!hJB`@${24lCksE@Laz1h@-AlmDI1V-N`Vl!v?RvDugHw*vRV^;%iZ7v7NnFi@BBE5!t=yO zTPR&99&!bJkU5%u@a^+WP!>zh=*7Sa!$g8GmNNK>rsGeNUjTD- z1=DKWN@L%Bw3#two~s*tLBqSl7(~XRBJ(Ty8da9UNZ1oIALUms3_=C{a=NSqKcaqj%6YHz{#7A0x-Z_lo!oe6 zVRQ(@>DyoO^**a*$Nj3rDgP4~JR;0mOC4g^#apNE@(S^X z!y*wiU=4;ToJlcSYd7{9MQc(7LC%dvv zfuM2*DsmA@HUya2+IZq=M%QU8(L#;twqn<5yhqimI7`1vb$!HaJxIhdbEB9R!D zG@PHS98}o&`N{#x&g6{mcyld|_hDyBJ0#6s1?6_H(vE6o&&%G~Ic~zN8j$%lKqS-3 zDuS^xx7|JqeG<+B>qAXpm0S;&+&Y9_!IYE=u}0$S3zLqjGedD~@x|N}IP3ef6r7Oc zvaCf3xS|F}D z2G2h(R5U6X8XAhdFsCDXYYQutn~ukS-hVW~x_DV(zHdYL1vbCLl=jqY7*wQNd%Nn4 z-jqB35P5+e0oU&weIRSMw8j9Aogj5tZB*LDIq&Gb5cBUU?{X~XFGTIEMiiPb!6c1) zhsYNu%A+!mDs>0eSSH{kkRO8*w@h$HlPOL*eb=$7;)=BGeCx{F(&y}U)i0I&D#Zba zSK?N+koq~OJXhmHH{ld3VE>{pg!IpZ=4bY~fY{7bh;xdEiu)pAb7YzCxZGGRXOHDok>^t23BQJsw16EOdIwbsM1lf?K6;zy%Lnt&}qo=)kZPa%kKV}Q)lzJ zHNvf`FRaNMnc;dTJR+`6c*G?BwH+WCn=FJW@6jmty#2cIHEY&9!Pj?NZL>|gf)NgP zU?MC$%i2-Vt32g4k2@jp>bKuAnxj-hO^Vw;s|bOgDAhTbIzQnSp4CaN)7X!ZUf8XK z@vbXuoKB%82qoZ2gqjX?e78X_ds4H_U0e-;_tdz>_)eV3wkfo`B4h`fZxZGr!+%nuN5nR;IMFD>`0G5`Po literal 0 HcmV?d00001 diff --git a/wyk/07_Wygladzanie/urna.drawio b/wyk/07_Wygladzanie/urna.drawio new file mode 100644 index 0000000..802ffae --- /dev/null +++ b/wyk/07_Wygladzanie/urna.drawio @@ -0,0 +1 @@ +3Zddb5swFEB/DY+Tgo0pvDbrtodOVRVpU58mgy9g1WDkOIPs18/EBsJClU4KbVQiRfbxJ+deiOPhddl+VbQuvksGwkMr1nr4s4dQHPjmuwN7CwgiFuSKM4v8EWz4H3Bw5eiOM9hOOmopheb1FKayqiDVE0aVks20WybFdNWa5nACNikVp/QnZ7qwNEI3I/8GPC/6lf0wti0l7Tu7O9kWlMnmCOE7D6+VlNqWynYNonPXe7HjvrzQOmxMQaVfM+D+sX36kYrm9uEx/0WbvHni+JOb5TcVO3fDbrN63xsw+667YroXvGKgsIdvm4Jr2NQ07RoaE3jDCl0KU/NNMZE705PdJwOg6XOuOvqw02YacHxr4+0TU864EGsppDosizPSfTouK33E7dWN1Uo+w1FLeLhMi7snUBraF2X5QwhM6oIsQau96dIPwC5qLm3RytWbMQkGVhwlAAocpC7x8mHuMTam4MLzH6FC50NlZjHPBZwPEN3W9mHJeAtsRn6UQprOSU4iEpDVZSRjNJXsk1PJZMYxWUpx8PEUR/F1KSZvqTjL0LxiFiYhudCrIgivLIvDD5fFBF+Z4ui8YmV/A53HM5rfJXEHi84qfqVVtJTVeFmrjEDEgjmrEUrwxU4O/1q9eWer/Ul6sWR9k1fAidZ4Ma2mOp7KD21Hf23w3V8= \ No newline at end of file diff --git a/wyk/07_Wygladzanie/urna.drawio.png b/wyk/07_Wygladzanie/urna.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..e69fcd1f53ce1412f52cb12db2e7148634c414eb GIT binary patch literal 17526 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 Ml(J-nxXJtf0x+n6od5s; literal 0 HcmV?d00001