diff --git a/wyk/01_Jezyk/dante.jpg b/wyk/01_Jezyk/dante.jpg deleted file mode 100644 index 6b44156..0000000 Binary files a/wyk/01_Jezyk/dante.jpg and /dev/null differ diff --git a/wyk/01_Jezyk/digitalsignal.jpg b/wyk/01_Jezyk/digitalsignal.jpg deleted file mode 100644 index 8cffde0..0000000 Binary files a/wyk/01_Jezyk/digitalsignal.jpg and /dev/null differ diff --git a/wyk/01_Jezyk/evil-bom.png b/wyk/01_Jezyk/evil-bom.png deleted file mode 100644 index ce9961a..0000000 Binary files a/wyk/01_Jezyk/evil-bom.png and /dev/null differ diff --git a/wyk/01_Jezyk/hexl-mode.png b/wyk/01_Jezyk/hexl-mode.png deleted file mode 100644 index bb8d712..0000000 Binary files a/wyk/01_Jezyk/hexl-mode.png and /dev/null differ diff --git a/wyk/01_Jezyk/raster.png b/wyk/01_Jezyk/raster.png deleted file mode 100644 index 1113dca..0000000 Binary files a/wyk/01_Jezyk/raster.png and /dev/null differ diff --git a/wyk/01_Jezyk/zdzblo.pdf b/wyk/01_Jezyk/zdzblo.pdf deleted file mode 100644 index 9db7742..0000000 Binary files a/wyk/01_Jezyk/zdzblo.pdf and /dev/null differ diff --git a/wyk/01_Jezyk/zdzblo.png b/wyk/01_Jezyk/zdzblo.png deleted file mode 100644 index 0632af1..0000000 Binary files a/wyk/01_Jezyk/zdzblo.png and /dev/null differ diff --git a/wyk/01_Jezyk/zdzblo.tex b/wyk/01_Jezyk/zdzblo.tex deleted file mode 100644 index fefc053..0000000 --- a/wyk/01_Jezyk/zdzblo.tex +++ /dev/null @@ -1,39 +0,0 @@ - -\documentclass{article} -\usepackage[a6paper]{geometry} -\usepackage[T1]{fontenc} -\usepackage{bytefield} -\thispagestyle{empty} -\begin{document} - -\begin{bytefield}{8} - \bitheader[endianness=big]{0-7} \\ - \begin{leftwordgroup}{Ź} - \bitboxes{1}{11000101} \\ - \bitboxes{1}{10111001} - \end{leftwordgroup} \\ - \begin{leftwordgroup}{d} - \bitboxes{1}{01100100} - \end{leftwordgroup} \\ - \begin{leftwordgroup}{ź} - \bitboxes{1}{11000101} \\ - \bitboxes{1}{10111010} - \end{leftwordgroup} \\ - \begin{leftwordgroup}{b} - \bitboxes{1}{01100010} - \end{leftwordgroup} \\ - \begin{leftwordgroup}{ł} - \bitboxes{1}{11000101} \\ - \bitboxes{1}{10000010} - \end{leftwordgroup} \\ - \begin{leftwordgroup}{o} - \bitboxes{1}{01101111} - \end{leftwordgroup} \\ - \begin{leftwordgroup}{\textit{koniec wiersza}} - \bitboxes{1}{00001010} - \end{leftwordgroup} \\ - \begin{leftwordgroup}{\textit{koniec napisu}} - \bitboxes{1}{00000000} - \end{leftwordgroup} - \end{bytefield} -\end{document} diff --git a/wyk/02_Jezyki.org b/wyk/02_Jezyki.org index ca42c72..8f95bd5 100644 --- a/wyk/02_Jezyki.org +++ b/wyk/02_Jezyki.org @@ -9,7 +9,7 @@ Używać będziemy generatorów. *Pytanie* Dlaczego generatory zamiast list? -#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer +#+BEGIN_SRC python :session mysession :exports both :results raw drawer import requests url = 'https://wolnelektury.pl/media/book/txt/pan-tadeusz.txt' @@ -31,7 +31,7 @@ Powrót pani *** Znaki -#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer +#+BEGIN_SRC python :session mysession :exports both :results raw drawer from itertools import islice def get_characters(t): @@ -45,7 +45,7 @@ Powrót pani ['K', 's', 'i', 'ę', 'g', 'a', ' ', 'p', 'i', 'e', 'r', 'w', 's', 'z', 'a', '\r', '\n', '\r', '\n', '\r', '\n', '\r', '\n', 'G', 'o', 's', 'p', 'o', 'd', 'a', 'r', 's', 't', 'w', 'o', '\r', '\n', '\r', '\n', 'P', 'o', 'w', 'r', 'ó', 't', ' ', 'p', 'a', 'n', 'i'] :end: -#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer +#+BEGIN_SRC python :session mysession :exports both :results raw drawer from collections import Counter c = Counter(get_characters(pan_tadeusz)) @@ -65,7 +65,7 @@ Napiszmy pomocniczą funkcję, która zwraca *listę frekwencyjną*. Counter({' ': 63444, 'a': 30979, 'i': 29353, 'e': 25343, 'o': 23050, 'z': 22741, 'n': 15505, 'r': 15328, 's': 15255, 'w': 14625, 'c': 14153, 'y': 13732, 'k': 12362, 'd': 11465, '\r': 10851, '\n': 10851, 't': 10757, 'm': 10269, 'ł': 10059, ',': 9130, 'p': 8031, 'u': 7699, 'l': 6677, 'j': 6586, 'b': 5753, 'ę': 5534, 'ą': 4794, 'g': 4775, 'h': 3915, 'ż': 3334, 'ó': 3097, 'ś': 2524, '.': 2380, 'ć': 1956, ';': 1445, 'P': 1265, 'W': 1258, ':': 1152, '!': 1083, 'S': 1045, 'T': 971, 'I': 795, 'N': 793, 'Z': 785, 'J': 729, '—': 720, 'A': 698, 'K': 683, 'ń': 651, 'M': 585, 'B': 567, 'O': 567, 'C': 556, 'D': 552, '«': 540, '»': 538, 'R': 489, '?': 441, 'ź': 414, 'f': 386, 'G': 358, 'L': 316, 'H': 309, 'Ż': 219, 'U': 184, '…': 157, '*': 150, '(': 76, ')': 76, 'Ś': 71, 'F': 47, 'é': 43, '-': 33, 'Ł': 24, 'E': 23, '/': 19, 'Ó': 13, '8': 10, '9': 8, '2': 6, 'v': 5, 'Ź': 4, '1': 4, '3': 3, 'x': 3, 'V': 3, '7': 2, '4': 2, '5': 2, 'q': 2, 'æ': 2, 'à': 1, 'Ć': 1, '6': 1, '0': 1}) :end: -#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer +#+BEGIN_SRC python :session mysession :exports both :results raw drawer from collections import Counter from collections import OrderedDict @@ -88,7 +88,7 @@ OrderedDict([(' ', 63444), ('a', 30979), ('i', 29353), ('e', 25343), ('o', 23050 :end: -#+BEGIN_SRC ipython :session mysession :results file +#+BEGIN_SRC python :session mysession :results file import matplotlib.pyplot as plt from collections import OrderedDict @@ -119,7 +119,7 @@ Co rozumiemy pod pojęciem słowa czy wyrazu, nie jest oczywiste. W praktyce zal Załóżmy, że przez wyraz rozumieć będziemy nieprzerwany ciąg liter bądź cyfr (oraz gwiazdek — to za chwilę ułatwi nam analizę pewnego tekstu…). -#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer +#+BEGIN_SRC python :session mysession :exports both :results raw drawer from itertools import islice import regex as re @@ -138,7 +138,7 @@ Załóżmy, że przez wyraz rozumieć będziemy nieprzerwany ciąg liter bądź Zobaczmy 20 najczęstszych wyrazów. -#+BEGIN_SRC ipython :session mysession :results file +#+BEGIN_SRC python :session mysession :results file rang_freq_with_labels('pt-words-20', get_words(pan_tadeusz), top=20) #+END_SRC @@ -147,7 +147,7 @@ Zobaczmy 20 najczęstszych wyrazów. Zobaczmy pełny obraz, już bez etykiet. -#+BEGIN_SRC ipython :session mysession :results file +#+BEGIN_SRC python :session mysession :results file import matplotlib.pyplot as plt from math import log @@ -172,7 +172,7 @@ Zobaczmy pełny obraz, już bez etykiet. Widać, jak różne skale obejmuje ten wykres. Zastosujemy logarytm, najpierw tylko do współrzędnej $y$. -#+BEGIN_SRC ipython :session mysession :results file +#+BEGIN_SRC python :session mysession :results file import matplotlib.pyplot as plt from math import log @@ -222,7 +222,7 @@ logarytmicznej dla **obu** osi, otrzymamy kształt zbliżony do linii prostej. Tę własność tekstów nazywamy **prawem Zipfa**. -#+BEGIN_SRC ipython :session mysession :results file +#+BEGIN_SRC python :session mysession :results file import matplotlib.pyplot as plt from math import log @@ -249,7 +249,7 @@ Tę własność tekstów nazywamy **prawem Zipfa**. Powiązane z prawem Zipfa prawo językowe opisuje zależność między częstością użycia słowa a jego długością. Generalnie im krótsze słowo, tym częstsze. -#+BEGIN_SRC ipython :session mysession :results file +#+BEGIN_SRC python :session mysession :results file def freq_vs_length(name, g, top=None): freq = freq_list(g) @@ -294,7 +294,7 @@ po prostu na jednostkach, nie na ich podciągach. Statystyki, które policzyliśmy dla pojedynczych liter czy wyrazów, możemy powtórzyć dla n-gramów. -#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer +#+BEGIN_SRC python :session mysession :exports both :results raw drawer def ngrams(iter, size): ngram = [] for item in iter: @@ -317,7 +317,7 @@ Zawsze powinniśmy się upewnić, czy jest jasne, czy chodzi o n-gramy znakowe c *** 3-gramy znakowe -#+BEGIN_SRC ipython :session mysession :results file +#+BEGIN_SRC python :session mysession :results file log_rang_log_freq('pt-3-char-ngrams-log-log', ngrams(get_characters(pan_tadeusz), 3)) #+END_SRC @@ -326,7 +326,7 @@ Zawsze powinniśmy się upewnić, czy jest jasne, czy chodzi o n-gramy znakowe c *** 2-gramy wyrazowe -#+BEGIN_SRC ipython :session mysession :results file +#+BEGIN_SRC python :session mysession :results file log_rang_log_freq('pt-2-word-ngrams-log-log', ngrams(get_words(pan_tadeusz), 2)) #+END_SRC @@ -348,7 +348,7 @@ transkrybować manuskrypt, pozostaje sprawą dyskusyjną, natomiast wybór takiego czy innego systemu transkrypcji nie powinien wpływać dramatycznie na analizę statystyczną. -#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer +#+BEGIN_SRC python :session mysession :exports both :results raw drawer import requests voynich_url = 'http://www.voynich.net/reeds/gillogly/voynich.now' @@ -370,28 +370,28 @@ dramatycznie na analizę statystyczną. 9 OR 9FAM ZO8 QOAR9 Q*R 8ARAM 29 [O82*]OM OPCC9 OP :end: -#+BEGIN_SRC ipython :session mysession :results file +#+BEGIN_SRC python :session mysession :results file rang_freq_with_labels('voy-chars', get_characters(voynich)) #+END_SRC #+RESULTS: [[file:02_Jezyki/voy-chars.png]] -#+BEGIN_SRC ipython :session mysession :results file +#+BEGIN_SRC python :session mysession :results file log_rang_log_freq('voy-log-log', get_words(voynich)) #+END_SRC #+RESULTS: [[file:02_Jezyki/voy-log-log.png]] -#+BEGIN_SRC ipython :session mysession :results file +#+BEGIN_SRC python :session mysession :results file rang_freq_with_labels('voy-words-20', get_words(voynich), top=20) #+END_SRC #+RESULTS: [[file:02_Jezyki/voy-words-20.png]] -#+BEGIN_SRC ipython :session mysession :results file +#+BEGIN_SRC python :session mysession :results file log_rang_log_freq('voy-words-log-log', get_words(voynich)) #+END_SRC @@ -406,7 +406,7 @@ Podstawowe litery są tylko cztery, reprezentują one nukleotydy, z których zbu a, g, c, t. -#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer +#+BEGIN_SRC python :session mysession :exports both :results raw drawer import requests dna_url = 'https://raw.githubusercontent.com/egreen18/NanO_GEM/master/rawGenome.txt' @@ -423,7 +423,7 @@ a, g, c, t. TATAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTA :end: -#+BEGIN_SRC ipython :session mysession :results file +#+BEGIN_SRC python :session mysession :results file rang_freq_with_labels('dna-chars', get_characters(dna)) #+END_SRC @@ -436,7 +436,7 @@ Nukleotydy rzeczywiście są jak litery, same w sobie nie niosą znaczenia. Dopiero ciągi trzech nukleotydów, /tryplety/, kodują jeden z dwudziestu aminokwasów. -#+BEGIN_SRC ipython :session mysession :results file +#+BEGIN_SRC python :session mysession :results file genetic_code = { 'ATA':'I', 'ATC':'I', 'ATT':'I', 'ATG':'M', 'ACA':'T', 'ACC':'T', 'ACG':'T', 'ACT':'T', @@ -472,7 +472,7 @@ Z aminokwasów zakodowanych przez tryplet budowane są białka. Maszyneria budująca białka czyta sekwencję aż do napotkania trypletu STOP (_ powyżej). Taka sekwencja to /gen/. -#+BEGIN_SRC ipython :session mysession :results file +#+BEGIN_SRC python :session mysession :results file def get_genes(triplets): gene = [] for ammino in triplets: diff --git a/wyk/02_Jezyki/dna-aminos.png b/wyk/02_Jezyki/dna-aminos.png deleted file mode 100644 index dd6c1ef..0000000 Binary files a/wyk/02_Jezyki/dna-aminos.png and /dev/null differ diff --git a/wyk/02_Jezyki/dna-chars.png b/wyk/02_Jezyki/dna-chars.png deleted file mode 100644 index 8a49877..0000000 Binary files a/wyk/02_Jezyki/dna-chars.png and /dev/null differ diff --git a/wyk/02_Jezyki/dna_length.png b/wyk/02_Jezyki/dna_length.png deleted file mode 100644 index 3121f7e..0000000 Binary files a/wyk/02_Jezyki/dna_length.png and /dev/null differ diff --git a/wyk/02_Jezyki/pt-2-word-ngrams-log-log.png b/wyk/02_Jezyki/pt-2-word-ngrams-log-log.png deleted file mode 100644 index 2411a11..0000000 Binary files a/wyk/02_Jezyki/pt-2-word-ngrams-log-log.png and /dev/null differ diff --git a/wyk/02_Jezyki/pt-3-char-ngrams-log-log.png b/wyk/02_Jezyki/pt-3-char-ngrams-log-log.png deleted file mode 100644 index a95ed14..0000000 Binary files a/wyk/02_Jezyki/pt-3-char-ngrams-log-log.png and /dev/null differ diff --git a/wyk/02_Jezyki/pt-chars.png b/wyk/02_Jezyki/pt-chars.png deleted file mode 100644 index ac48e05..0000000 Binary files a/wyk/02_Jezyki/pt-chars.png and /dev/null differ diff --git a/wyk/02_Jezyki/pt-lengths.png b/wyk/02_Jezyki/pt-lengths.png deleted file mode 100644 index df7e5c5..0000000 Binary files a/wyk/02_Jezyki/pt-lengths.png and /dev/null differ diff --git a/wyk/02_Jezyki/pt-words-20.png b/wyk/02_Jezyki/pt-words-20.png deleted file mode 100644 index e8b21b1..0000000 Binary files a/wyk/02_Jezyki/pt-words-20.png and /dev/null differ diff --git a/wyk/02_Jezyki/pt-words-log-log.png b/wyk/02_Jezyki/pt-words-log-log.png deleted file mode 100644 index e19243b..0000000 Binary files a/wyk/02_Jezyki/pt-words-log-log.png and /dev/null differ diff --git a/wyk/02_Jezyki/pt-words-log.png b/wyk/02_Jezyki/pt-words-log.png deleted file mode 100644 index 329215d..0000000 Binary files a/wyk/02_Jezyki/pt-words-log.png and /dev/null differ diff --git a/wyk/02_Jezyki/pt-words.png b/wyk/02_Jezyki/pt-words.png deleted file mode 100644 index f869123..0000000 Binary files a/wyk/02_Jezyki/pt-words.png and /dev/null differ diff --git a/wyk/02_Jezyki/voy-chars.png b/wyk/02_Jezyki/voy-chars.png deleted file mode 100644 index ad36fd0..0000000 Binary files a/wyk/02_Jezyki/voy-chars.png and /dev/null differ diff --git a/wyk/02_Jezyki/voy-log-log.png b/wyk/02_Jezyki/voy-log-log.png deleted file mode 100644 index fd38cc1..0000000 Binary files a/wyk/02_Jezyki/voy-log-log.png and /dev/null differ diff --git a/wyk/02_Jezyki/voy-words-20.png b/wyk/02_Jezyki/voy-words-20.png deleted file mode 100644 index e16ef70..0000000 Binary files a/wyk/02_Jezyki/voy-words-20.png and /dev/null differ diff --git a/wyk/02_Jezyki/voy-words-log-log.png b/wyk/02_Jezyki/voy-words-log-log.png deleted file mode 100644 index fd38cc1..0000000 Binary files a/wyk/02_Jezyki/voy-words-log-log.png and /dev/null differ diff --git a/wyk/02_Jezyki/voynich135.jpg b/wyk/02_Jezyki/voynich135.jpg deleted file mode 100644 index b8bd37a..0000000 Binary files a/wyk/02_Jezyki/voynich135.jpg and /dev/null differ diff --git a/wyk/03_Entropia.ipynb b/wyk/03_Entropia.ipynb new file mode 100644 index 0000000..287be54 --- /dev/null +++ b/wyk/03_Entropia.ipynb @@ -0,0 +1,657 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Entropia\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Entropia** ($E$) to miara nieuporządkowania, niepewności, niewiedzy. Im\n", + "większa entropia, tym mniej wiemy. Pojęcie to pierwotnie wywodzi się z\n", + "termodynamiki, później znaleziono wiele zaskakujących analogii i zastosowań w\n", + "innych dyscyplinach nauki.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Entropia w fizyce\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "W termodynamice entropia jest miarą nieuporządkowania układów\n", + "fizycznych, na przykład pojemników z gazem. Przykładowo, wyobraźmy\n", + "sobie dwa pojemniki z gazem, w którym panuje różne temperatury.\n", + "\n", + "![img](./03_Entropia/gas-low-entropy.drawio.png)\n", + "\n", + "Jeśli usuniemy przegrodę między pojemnikami, temperatura się wyrówna,\n", + "a uporządkowanie się zmniejszy.\n", + "\n", + "![img](./03_Entropia/gas-high-entropy.drawio.png)\n", + "\n", + "Innymi słowy, zwiększy się stopień nieuporządkowania układu, czyli właśnie entropia.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### II prawo termodynamiki\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Jedno z najbardziej fundamentalnych praw fizyki, II prawo\n", + "termodynamiki głosi, że w układzie zamkniętym entropia nie spada.\n", + "\n", + "****Pytanie****: Czy to, że napisałem te materiały do wykładu i\n", + "*uporządkowałem* wiedzę odnośnie do statystycznych własności języka, nie\n", + "jest sprzeczne z II prawem termodynamiki?\n", + "\n", + "Konsekwencją II prawa termodynamiki jest śmierć cieplna Wszechświata\n", + "(zob. [wizualizacja przyszłości Wszechświata]([https://www.youtube.com/watch?v=uD4izuDMUQA](https://www.youtube.com/watch?v=uD4izuDMUQA))).\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Entropia w teorii informacji\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Pojęcie entropii zostało „odkryte” na nowo przez Claude'a Shannona,\n", + "gdy wypracował ogólną teorię informacji.\n", + "\n", + "Teoria informacji zajmuje się między innymi zagadnieniem optymalnego kodowania komunikatów.\n", + "\n", + "Wyobraźmy sobie pewne źródło (generator) losowych komunikatów z\n", + "zamkniętego zbioru symboli ($\\Sigma$; nieprzypadkowo używamy oznaczeń\n", + "z poprzedniego wykładu). Nadawca $N$ chce przesłać komunikat o wyniku\n", + "losowania do odbiorcy $O$ używając zer i jedynek (bitów).\n", + "Teorioinformacyjną entropię można zdefiniować jako średnią liczbę\n", + "bitów wymaganych do przesłania komunikatu.\n", + "\n", + "![img](./03_Entropia/communication.drawio.png)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Obliczanie entropii — proste przykłady\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Załóżmy, że nadawca chce przekazać odbiorcy informację o wyniku rzutu monetą.\n", + "Entropia wynosi wówczas rzecz jasna 1 — na jedno losowanie wystarczy jeden bit\n", + "(informację o tym, że wypadł orzeł, możemy zakodować na przykład za pomocą zera,\n", + "zaś to, że wypadła reszka — za pomocą jedynki).\n", + "\n", + "Rozpatrzmy przypadek, gdy nadawca rzuca ośmiościenną kością. Aby przekazać\n", + "wynik, potrzebuje wówczas 3 bity (a więc entropia ośmiościennej kości\n", + "wynosi 3 bity). Przykładowe kodowanie może mieć następującą postać:\n", + "\n", + "| Wynik|Kodowanie|\n", + "|---|---|\n", + "| 1|001|\n", + "| 2|010|\n", + "| 3|011|\n", + "| 4|100|\n", + "| 5|101|\n", + "| 6|110|\n", + "| 7|111|\n", + "| 8|000|\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Obliczenie entropii — trudniejszy przykład\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Załóżmy, że $\\Sigma = \\{A, B, C, D\\}$, natomiast poszczególne komunikaty\n", + "są losowane zgodnie z następującym rozkładem prawdopodobieństwa:\n", + "$P(A)=1/2$, $P(B)=1/4$, $P(C)=1/8$, $P(D)=1/8$. Ile wynosi entropia w\n", + "takim przypadku? Można by sądzić, że 2, skoro wystarczą 2 bity do\n", + "przekazania wyniku losowania przy zastosowaniu następującego kodowania:\n", + "\n", + "| Wynik|Kodowanie|\n", + "|---|---|\n", + "| A|00|\n", + "| B|01|\n", + "| C|10|\n", + "| D|11|\n", + "\n", + "Problem w tym, że w rzeczywistości nie jest to *optymalne* kodowanie.\n", + "Możemy sprytnie zmniejszyć średnią liczbę bitów wymaganych do\n", + "przekazania losowego wyniku przypisując częstszym wynikom krótsze\n", + "kody, rzadszym zaś — dłuższe. Oto takie optymalne kodowanie:\n", + "\n", + "| Wynik|Kodowanie|\n", + "|---|---|\n", + "| A|0|\n", + "| B|10|\n", + "| C|110|\n", + "| D|111|\n", + "\n", + "Używając takiego kodowanie średnio potrzebujemy:\n", + "\n", + "$$\\frac{1}{2}1 + \\frac{1}{4}2 + \\frac{1}{8}3 + \\frac{1}{8}3 = 1,75$$\n", + "\n", + "bita. Innymi słowy, entropia takiego źródła wynosi 1,75 bita.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Kodowanie musi być jednoznaczne!\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Można by sądzić, że da się stworzyć jeszcze krótsze kodowanie dla omawianego rozkładu nierównomiernego:\n", + "\n", + "| Wynik|Kodowanie|\n", + "|---|---|\n", + "| A|0|\n", + "| B|1|\n", + "| C|01|\n", + "| D|11|\n", + "\n", + "Niestety, nie jest to właściwe rozwiązanie — kodowanie musi być\n", + "jednoznaczne nie tylko dla pojedynczego komunikatu, lecz dla całej sekwencji.\n", + "Na przykład ciąg 0111 nie jest jednoznaczny przy tym kodowaniu (ABBB czy CD?).\n", + "Podane wcześniej kodowanie spełnia warunek jednoznaczności, ciąg 0111 można odkodować tylko\n", + "jako AD.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Ogólny wzór na entropię.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Na podstawie poprzedniego przykładu można dojść do intuicyjnego wniosku, że\n", + "optymalny kod dla wyniku o prawdopodobieństwie $p$ ma długość $-\\log_2(p)$, a zatem ogólnie\n", + "entropia źródła o rozkładzie prawdopodobieństwa $\\{p_1,\\ldots,p_|\\Sigma|\\}$ wynosi:\n", + "\n", + "$$E = -\\sum_{i=1}^{|\\Sigma|} p_i\\log_2(p_i)$$.\n", + "\n", + "Zauważmy, że jest to jeden z nielicznych przypadków, gdy w nauce naturalną\n", + "podstawą logarytmu jest 2 zamiast… podstawy logarytmu naturalnego ($e$).\n", + "\n", + "Teoretycznie można mierzyć entropię używając logarytmu naturalnego\n", + "($\\ln$), jednostką entropii będzie wówczas **nat** zamiast bita,\n", + "niewiele to jednak zmienia i jest mniej poręczne i trudniejsze do interpretacji\n", + "(przynajmniej w kontekście informatyki) niż operowanie na bitach.\n", + "\n", + "****Pytanie**** Ile wynosi entropia zwykłej sześciennej kostki? Jak wygląda\n", + "optymalne kodowanie wyników rzutu taką kostką?\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Entropia dla próby Bernoulliego\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Wiemy już, że entropia dla rzutu monetą wynosi 1 bit. A jaki będzie wynik dla źle wyważonej monety?\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "from math import log\n", + "import numpy as np\n", + "\n", + "def binomial_entropy(p):\n", + " return -(p * log(p, 2) + (1-p) * log(1-p, 2))\n", + "\n", + "x = list(np.arange(0.001,1,0.001))\n", + "y = [binomial_entropy(x) for x in x]\n", + "plt.figure().clear()\n", + "plt.xlabel('prawdopodobieństwo wylosowania orła')\n", + "plt.ylabel('entropia')\n", + "plt.plot(x, y)\n", + "\n", + "fname = f'03_Entropia/binomial-entropy.png'\n", + "\n", + "plt.savefig(fname)\n", + "\n", + "fname" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Pytanie** Dla oszukańczej monety (np. dla której wypada zawsze orzeł) entropia\n", + "wynosi 0, czy to wynik zgodny z intuicją?\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Entropia a język\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Tekst w danym języku możemy traktować jako ciąg symboli (komunikatów) losowanych według jakiegoś\n", + "rozkładu prawdopodobieństwa. W tym sensie możemy mówić o entropii języka.\n", + "\n", + "Oczywiście, jak zawsze, musimy jasno stwierdzić, czym są symbole\n", + "języka: literami, wyrazami czy jeszcze jakimiś innymi jednostkami.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Pomiar entropii języka — pierwsze przybliżenie\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Załóżmy, że chcemy zmierzyć entropię języka polskiego na przykładzie\n", + "„Pana Tadeusza” — na poziomie znaków. W pierwszym przybliżeniu można\n", + "by policzyć liczbę wszystkich znaków…\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['K', 's', 'i', 'ę', 'g', 'a', ' ', 'p', 'i', 'e', 'r', 'w', 's', 'z', 'a', '\\r', '\\n', '\\r', '\\n', '\\r', '\\n', '\\r', '\\n', 'G', 'o', 's', 'p', 'o', 'd', 'a', 'r', 's', 't', 'w', 'o', '\\r', '\\n', '\\r', '\\n', 'P', 'o', 'w', 'r', 'ó', 't', ' ', 'p', 'a', 'n', 'i']" + ] + } + ], + "source": [ + "import requests\n", + "from itertools import islice\n", + "\n", + "url = 'https://wolnelektury.pl/media/book/txt/pan-tadeusz.txt'\n", + "pan_tadeusz = requests.get(url).content.decode('utf-8')\n", + "\n", + "def get_characters(t):\n", + " yield from t\n", + "\n", + "list(islice(get_characters(pan_tadeusz), 100, 150))" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "95" + ] + } + ], + "source": [ + "chars_in_pan_tadeusz = len(set(get_characters(pan_tadeusz)))\n", + "chars_in_pan_tadeusz" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "… założyć jednostajny rozkład prawdopodobieństwa i w ten sposób policzyć entropię:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6.569855608330948" + ] + } + ], + "source": [ + "from math import log\n", + "\n", + "95 * (1/95) * log(95, 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Mniej rozrzutne kodowanie\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Przypomnijmy sobie jednak, że rozkład jednostek języka jest zawsze\n", + "skrajnie nierównomierny! Jeśli uwzględnić ten nierównomierny rozkład\n", + "znaków, można opracować o wiele efektywniejszy sposób zakodowania znaków składających się na „Pana Tadeusza”\n", + "(częste litery, np. „a” i „e” powinny mieć krótkie kody, a rzadkie, np. „ź” — dłuższe).\n", + "\n", + "Policzmy entropię przy takim założeniu:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4.938605272823633" + ] + } + ], + "source": [ + "from collections import Counter\n", + "from math import log\n", + "\n", + "def unigram_entropy(t):\n", + " counter = Counter(t)\n", + "\n", + " total = counter.total()\n", + " return -sum((p := count / total) * log(p, 2) for count in counter.values())\n", + "\n", + "unigram_entropy(get_characters(pan_tadeusz))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "(Jak dowiemy się na kolejnym wykładzie, zastosowaliśmy tutaj **unigramowy model języka**).\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Ile wynosi entropia rękopisu Wojnicza?\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "9 OR 9FAM ZO8 QOAR9 Q*R 8ARAM 29 [O82*]OM OPCC9 OP" + ] + } + ], + "source": [ + "import requests\n", + "import re\n", + "\n", + "voynich_url = 'http://www.voynich.net/reeds/gillogly/voynich.now'\n", + "voynich = requests.get(voynich_url).content.decode('utf-8')\n", + "\n", + "voynich = re.sub(r'\\{[^\\}]+\\}|^<[^>]+>|[-# ]+', '', voynich, flags=re.MULTILINE)\n", + "\n", + "voynich = voynich.replace('\\n\\n', '#')\n", + "voynich = voynich.replace('\\n', ' ')\n", + "voynich = voynich.replace('#', '\\n')\n", + "\n", + "voynich = voynich.replace('.', ' ')\n", + "\n", + "voynich[100:150]" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3.902708104423842" + ] + } + ], + "source": [ + "unigram_entropy(get_characters(voynich))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Rzeczywista entropia?\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "W rzeczywistości entropia jest jeszcze mniejsza, tekst nie jest\n", + "generowany przecież według rozkładu wielomianowego. Istnieją rzecz\n", + "jasna pewne zależności między znakami, np. niemożliwe, żeby po „ń”\n", + "wystąpiły litera „a” czy „e”. Na poziomie wyrazów zależności mogę mieć\n", + "jeszcze bardziej skrajny charakter, np. po wyrazie „przede” prawie na\n", + "pewno wystąpi „wszystkim”, co oznacza, że w takiej sytuacji słowo\n", + "„wszystkim” może zostać zakodowane za pomocą 0 (!) bitów.\n", + "\n", + "Można uwzględnić takie zależności i uzyskać jeszcze lepsze kodowanie,\n", + "a co za tym idzie lepsze oszacowanie entropii. (Jak wkrótce się\n", + "dowiemy, oznacza to użycie digramowego, trigramowego, etc. modelu języka).\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Rozmiar skompresowanego pliku jako przybliżenie entropii\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Celem algorytmów kompresji jest właściwie wyznaczanie efektywnych\n", + "sposobów kodowania danych. Możemy więc użyć rozmiaru skompresowanego pliku w bitach\n", + "(po podzieleniu przez oryginalną długość) jako dobrego przybliżenia entropii.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3.673019884633768" + ] + } + ], + "source": [ + "import zlib\n", + "\n", + "def entropy_by_compression(t):\n", + " compressed = zlib.compress(t.encode('utf-8'))\n", + " return 8 * len(compressed) / len(t)\n", + "\n", + "entropy_by_compression(pan_tadeusz)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dla porównania wynik dla rękopisu Wojnicza:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.942372881355932" + ] + } + ], + "source": [ + "entropy_by_compression(voynich)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Gra Shannona\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Innym sposobem oszacowania entropii tekstu jest użycie… ludzi. Można poprosić rodzimych użytkowników\n", + "danego języka o przewidywanie kolejnych liter (bądź wyrazów) i w ten sposób oszacować entropię.\n", + "\n", + "**Projekt** Zaimplementuj aplikację webową, która umożliwi „rozegranie” gry Shannona.\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.2" + }, + "org": null + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/wyk/03_Entropia.org b/wyk/03_Entropia.org index e0f4a45..0c4397b 100644 --- a/wyk/03_Entropia.org +++ b/wyk/03_Entropia.org @@ -11,12 +11,12 @@ W termodynamice entropia jest miarą nieuporządkowania układów fizycznych, na przykład pojemników z gazem. Przykładowo, wyobraźmy sobie dwa pojemniki z gazem, w którym panuje różne temperatury. -[[./03_Jezyki/gas-low-entropy.drawio.png]] +[[./03_Entropia/gas-low-entropy.drawio.png]] Jeśli usuniemy przegrodę między pojemnikami, temperatura się wyrówna, a uporządkowanie się zmniejszy. -[[./03_Jezyki/gas-high-entropy.drawio.png]] +[[./03_Entropia/gas-high-entropy.drawio.png]] Innymi słowy, zwiększy się stopień nieuporządkowania układu, czyli właśnie entropia. @@ -46,7 +46,7 @@ losowania do odbiorcy $O$ używając zer i jedynek (bitów). Teorioinformacyjną entropię można zdefiniować jako średnią liczbę bitów wymaganych do przesłania komunikatu. -[[./03_Jezyki/communication.drawio.png]] +[[./03_Entropia/communication.drawio.png]] *** Obliczanie entropii — proste przykłady @@ -187,6 +187,25 @@ Załóżmy, że chcemy zmierzyć entropię języka polskiego na przykładzie „Pana Tadeusza” — na poziomie znaków. W pierwszym przybliżeniu można by policzyć liczbę wszystkich znaków… +#+BEGIN_SRC python :session mysession :exports both :results raw drawer + import requests + from itertools import islice + + url = 'https://wolnelektury.pl/media/book/txt/pan-tadeusz.txt' + pan_tadeusz = requests.get(url).content.decode('utf-8') + + def get_characters(t): + yield from t + + list(islice(get_characters(pan_tadeusz), 100, 150)) +#+END_SRC + +#+RESULTS: +:results: +['K', 's', 'i', 'ę', 'g', 'a', ' ', 'p', 'i', 'e', 'r', 'w', 's', 'z', 'a', '\r', '\n', '\r', '\n', '\r', '\n', '\r', '\n', 'G', 'o', 's', 'p', 'o', 'd', 'a', 'r', 's', 't', 'w', 'o', '\r', '\n', '\r', '\n', 'P', 'o', 'w', 'r', 'ó', 't', ' ', 'p', 'a', 'n', 'i'] +:end: + + #+BEGIN_SRC python :session mysession :exports both :results raw drawer chars_in_pan_tadeusz = len(set(get_characters(pan_tadeusz))) chars_in_pan_tadeusz @@ -241,6 +260,30 @@ Policzmy entropię przy takim założeniu: *** Ile wynosi entropia rękopisu Wojnicza? +#+BEGIN_SRC python :session mysession :exports both :results raw drawer + import requests + import re + + voynich_url = 'http://www.voynich.net/reeds/gillogly/voynich.now' + voynich = requests.get(voynich_url).content.decode('utf-8') + + voynich = re.sub(r'\{[^\}]+\}|^<[^>]+>|[-# ]+', '', voynich, flags=re.MULTILINE) + + voynich = voynich.replace('\n\n', '#') + voynich = voynich.replace('\n', ' ') + voynich = voynich.replace('#', '\n') + + voynich = voynich.replace('.', ' ') + + voynich[100:150] +#+END_SRC + +#+RESULTS: +:results: +9 OR 9FAM ZO8 QOAR9 Q*R 8ARAM 29 [O82*]OM OPCC9 OP +:end: + + #+BEGIN_SRC python :session mysession :exports both :results raw drawer unigram_entropy(get_characters(voynich)) #+END_SRC diff --git a/wyk/03_Entropia/binomial-entropy.png b/wyk/03_Entropia/binomial-entropy.png deleted file mode 100644 index e16dd0f..0000000 Binary files a/wyk/03_Entropia/binomial-entropy.png and /dev/null differ diff --git a/wyk/03_Entropia/communication.drawio b/wyk/03_Entropia/communication.drawio deleted file mode 100644 index 13aa4d0..0000000 --- a/wyk/03_Entropia/communication.drawio +++ /dev/null @@ -1 +0,0 @@ -1VbbcpswEP0aHpMxEGznMbFzmTZtPc1Dk0cZrUGJ0LpC2JCvrwTC3OzcO9P6waM9klbsObtaOf4sya8kWcffkAJ3vBHNHX/ueN7piav/DVBUQOAFFRBJRivIbYBb9gQWHFk0YxTSzkKFyBVbd8EQhYBQdTAiJW67y1bIu6euSQQD4DYkfIj+YlTFFTr1Jg1+DSyK65Pd8Wk1k5B6sY0kjQnFbQvyLxx/JhFVNUryGXDDXc1Lte/ywOzuwyQI9ZoNX6X3O78fXS/phv3kIss2X7wj62VDeGYD/k4o2YbEfrMqaiL056/NMEv4WahQOv75BqRimqobsgS+wJQphkIvWaJSmLQWnHEWmQmFa43GKuHacPUQM8WZgNlOvJEG7SfpvZAfjNXdMagzDzABJQu9xG7wTizpRc/eNhr6Fopb8o0tRmzWRDvPDbF6YLl9A88nA55/0CVD+b8THfj/GNHBgOiDBK845GfmitBUgKB2OA85SVMWdvmTmAkKtEMc0MHV8SJtLVqCPbTUmAROFNt03e/jyp6wQKYPbtJ/2lNl0qM7xUyGYHe174yeo8B7wZEiMgI1cFRKtwv7/WqOB2o+EkGcWeBMDf6ISSbYIwkf2EBlo05ZL10hia2QUCsFck/pJIxS4+NcQsqeyLL0Z0RfmyDLsINzJ5g/Vz+2GdnNTQtoJ8jh7D1YbEejY7dun+/NkHoJrlYp/BXNpnuluLUmShVjhILwiwbtlVez5gbNRVYK9wBKFfZ9QDKFXVkhZ+rObD8OrHXfmpnn1nNpFLUhdKB3beO+9OD3fvVs46W0Om4WIJkmzmTUm24IzVNZis/waV8aVaW91MqHufPqRPnQtTsZFOoVCJDE9C8dY5EskQ9r9ON9rb7LdTov9FPM9reyaXZzKq0yp8qPT2h8bu+F4U6GjW/8OY1Pm80rsarS5qntX/wB \ No newline at end of file diff --git a/wyk/03_Entropia/communication.drawio.png b/wyk/03_Entropia/communication.drawio.png deleted file mode 100644 index 3338d9e..0000000 Binary files a/wyk/03_Entropia/communication.drawio.png and /dev/null differ diff --git a/wyk/03_Entropia/gas-high-entropy.drawio b/wyk/03_Entropia/gas-high-entropy.drawio deleted file mode 100644 index 667bb82..0000000 --- a/wyk/03_Entropia/gas-high-entropy.drawio +++ /dev/null @@ -1 +0,0 @@ -jZPfb4MgEMf/Gh+bKFS3vs513bLsqUv2TIQKLXiO0mr31w/l/JVmydAofO444HtHRHPT7iyr5QdwoSMS8zaizxEhm3Xivx24BZCSNIDSKh5QMoG9+hEIY6QXxcV54egAtFP1EhZQVaJwC8ashWbpdgC9XLVmpbgD+4Lpe/qluJOBPpKHib8KVcph5STbBIthgzOGOEvGoQmoPxzdRjS3AC70TJsL3Wk36BIUePnDOm7Misr9Z8LpuNrG5ppe31ylDf1+P+6yFR7jyvQFDxyRtX8/ww937m6DHBYuFRddxDiiT41UTuxrVnTWxuffM+mM9qPEd8/OwmmUjXpyUFrnoMH20egh7Z6OQ+VmPLQxwsyS9c1bcN/COtH+KUgyyuzLU4ARzt68C04gFGsMS5OmOG6mRJM1MjlLcoaMYW2VY+hJft/BDAzDKdO9bXZd6PYX \ No newline at end of file diff --git a/wyk/03_Entropia/gas-high-entropy.drawio.png b/wyk/03_Entropia/gas-high-entropy.drawio.png deleted file mode 100644 index 69022e4..0000000 Binary files a/wyk/03_Entropia/gas-high-entropy.drawio.png and /dev/null differ diff --git a/wyk/03_Entropia/gas-low-entropy.drawio b/wyk/03_Entropia/gas-low-entropy.drawio deleted file mode 100644 index 5ab76e1..0000000 --- a/wyk/03_Entropia/gas-low-entropy.drawio +++ /dev/null @@ -1 +0,0 @@ -5ZVRT8IwEMc/zR5NtpUVeBRENMYnTHw0db1t1W7FUhj46b3RbmMwEk3UmJgQcv3f7a79/QvzyDTfzjVbZveKg/RCn289cuWF4XgQ4Hcl7KwQhZEVUi24lYJWWIh3cKLv1LXgsOoUGqWkEcuuGKuigNh0NKa1KrtliZLdqUuWwomwiJk8VR8FN5lVR+Gw1W9ApFk9OaBjm8lZXexarDLGVWml/eHIzCNTrZSxUb6dgqzY1Vwsgesz2WZjGgrzmQdeXy5mfr6JNremkDl5u3uZ04vQdtkwuXYHdps1u5qAVuuCQ9XE98ikzISBxZLFVbZEy1HLTC5xFWC4Mlq9NqQIKomQcqqk0vtuJEmAxnFTeZDhw/GzX41wWwJtYHv2rEFDEG8eqByM3mGJeyAk7vrsjtZl62HgePnZgX/Uacxdm7Rp3ZLFwMH9Amjy26A5g1HSC5rGI3hOvgc0if4a6KgHNJU4dcLFBsPU7E8+wM/DU2CDugIHdoqODEJMps+Fmm2hCjgywklMirTAZYxYAfVJBV3gv8ylS+SC82pMr+3di/EdPw965NogOnGN9phGfso0+hXTwv9pGhn9mmm4bN9L+9zBy53MPgA= \ No newline at end of file diff --git a/wyk/03_Entropia/gas-low-entropy.drawio.png b/wyk/03_Entropia/gas-low-entropy.drawio.png deleted file mode 100644 index 0217e1c..0000000 Binary files a/wyk/03_Entropia/gas-low-entropy.drawio.png and /dev/null differ diff --git a/wyk/04_Ngramowy_model/lm-communication.drawio b/wyk/04_Ngramowy_model/lm-communication.drawio deleted file mode 100644 index 36a18c8..0000000 --- a/wyk/04_Ngramowy_model/lm-communication.drawio +++ /dev/null @@ -1 +0,0 @@ -7VjbUtswEP0aP4bxJU7IIyQUhkJhYIbCEyPbii0iW0GWE4evr2TJVzmBcm1n4CFoj+S1tGePdhPDmcb5MQXL6JwEEBu2GeSGMzNsezK0+KcANhJwbVcCIUWBhKwauEZPUIGmQjMUwLS1kBGCGVq2QZ8kCfRZCwOUknV72Zzg9luXIIQacO0DrKO/UcAiie7b4xo/gSiMyjdbo4mciUG5WJ0kjUBA1g3IOTKcKSWEyVGcTyEWsSvjIp/7sWW22hiFCXvJA3R1NohuBmByf3NxZV/f0+Or04Gj9sY25YFhwM+vTEJZREKSAHxUo4eUZEkAhVeTW/WaM0KWHLQ4+AAZ2ygyQcYIhyIWYzULc8RuxeN7rrLulDMxnuVNY9MwLiFFMWSQlljC6Oa2adw1jcKR61amcDWuZrvO5iRhasf2kNsyMCIaW+OtoJRk1FerflL7Mb8zT7xgha5wkmWrU3tQ5i2gIWQ7yLCqrOBqgoTvjm74cxRiwNCqvQ+g8jqs1tXU84Fivz8Tdm1yBXCm3vQLBGDtAy1DeCIvxTCL8YHPCA/g4QpShrhozoAH8SVJEUMk4Us8whiJGwsOMArFBBOp0swJkjGMEjitZGxWHPSEXLiDeQPSo6Zm7aGS36Zjr2s1OwqKGkIemW+Pcy/J7rfiPl5xwxcqbviVihtqirsIPETo/y451/k6yfUG2tUCvTXAcwzzA9E2FEkbqOHMxyBNkd+OX1uVWwPXSWg9bI2wuD1hKbEXJ6V6wyVBfCf1RbjfYWXcCbcUlXqq2Ud0HLn2M46k6jRHBXXVsV/P5khjcwESYExdY1/gCxJnCVoA/wH13rSFXtpEAqUQn1MHaY90YhQE8iKGKXoCXuFPkL4UhyyO7R4a7qw3DXamZFdUVSOrXtLqFfvENjD3rLKlfm2GlEvIfJ7CD+Fs/98petbuotdf4Paczl+r4pmtgre13r2+nI31crarh3v2xvmc+jbWhHoME0iBqF/80JvYI1jX6NvrWnmX83S+5F/PVH0rimY7p1KZOTI/3qHwWWb7ZrRGeuEbfWavqff0hj3CTPVerbiPHjNSTgxkYA74Anu4zOtJPgrF//PSDd+V9CTxrWXVzzwh6XWEGLxegiLZ1xR0iPMkO2deBQB/ERacXcheReEBoIsL7gaxQnB7ptsG7QK1qh31XLMa0du/QXRZdXRWJz2sdsviu7Gq943frP41q8PJp7HKzfpXHllR65/KnKM/ \ No newline at end of file diff --git a/wyk/04_Ngramowy_model/lm-communication.drawio.png b/wyk/04_Ngramowy_model/lm-communication.drawio.png deleted file mode 100644 index 6b9a8d4..0000000 Binary files a/wyk/04_Ngramowy_model/lm-communication.drawio.png and /dev/null differ diff --git a/wyk/04_Ngramowy_model/tabelka.pdf b/wyk/04_Ngramowy_model/tabelka.pdf deleted file mode 100644 index c44d268..0000000 Binary files a/wyk/04_Ngramowy_model/tabelka.pdf and /dev/null differ diff --git a/wyk/04_Ngramowy_model/tabelka.png b/wyk/04_Ngramowy_model/tabelka.png deleted file mode 100644 index 5f29011..0000000 Binary files a/wyk/04_Ngramowy_model/tabelka.png and /dev/null differ diff --git a/wyk/05_Wygladzanie.ipynb b/wyk/05_Wygladzanie.ipynb new file mode 100644 index 0000000..d3fb5f9 --- /dev/null +++ b/wyk/05_Wygladzanie.ipynb @@ -0,0 +1,1174 @@ +{ + "cells": [ + { + "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](./05_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](./05_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": [ + { + "data": { + "text/plain": [ + "['',\n", + " 'lubisz',\n", + " 'curry',\n", + " ',',\n", + " 'prawda',\n", + " '?',\n", + " '',\n", + " '',\n", + " 'nałożę',\n", + " 'ci',\n", + " 'więcej',\n", + " '.',\n", + " '',\n", + " '',\n", + " 'hey',\n", + " '!',\n", + " '',\n", + " '',\n", + " 'smakuje',\n", + " 'ci',\n", + " '?',\n", + " '',\n", + " '',\n", + " 'hey',\n", + " ',',\n", + " 'brzydalu',\n", + " '.',\n", + " '',\n", + " '',\n", + " 'spójrz',\n", + " 'na',\n", + " 'nią',\n", + " '.',\n", + " '',\n", + " '',\n", + " '-',\n", + " 'wariatka',\n", + " '.',\n", + " '',\n", + " '',\n", + " '-',\n", + " 'zadałam',\n", + " 'ci',\n", + " 'pytanie',\n", + " '!',\n", + " '',\n", + " '',\n", + " 'no',\n", + " ',',\n", + " 'tak',\n", + " 'lepiej',\n", + " '!',\n", + " '',\n", + " '',\n", + " '-',\n", + " 'wygląda',\n", + " 'dobrze',\n", + " '!',\n", + " '',\n", + " '',\n", + " '-',\n", + " 'tak',\n", + " 'lepiej',\n", + " '!',\n", + " '',\n", + " '',\n", + " 'pasuje',\n", + " 'jej',\n", + " '.',\n", + " '',\n", + " '',\n", + " '-',\n", + " 'hey',\n", + " '.',\n", + " '',\n", + " '',\n", + " '-',\n", + " 'co',\n", + " 'do',\n", + " '...?',\n", + " '',\n", + " '',\n", + " 'co',\n", + " 'do',\n", + " 'cholery',\n", + " 'robisz',\n", + " '?',\n", + " '',\n", + " '',\n", + " 'zejdź',\n", + " 'mi',\n", + " 'z',\n", + " 'oczu',\n", + " ',',\n", + " 'zdziro',\n", + " '.',\n", + " '',\n", + " '',\n", + " 'przestań',\n", + " 'dokuczać']" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from collections import Counter\n", + "\n", + "counterA = Counter(get_words_from_file('opensubtitlesA.pl.txt'))" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "48113" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "counterA['taki']" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "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": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "926594" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
liczba tokenówśrednia częstość w części Bestymacje +1estymacje +0.01
03883341.9004950.9935860.009999
14038700.5927701.9871721.009935
21175291.5658092.9807592.009870
3628002.5142683.9743453.009806
4408563.5049444.9679314.009741
5294434.4540985.9615175.009677
6227095.2320236.9551036.009612
7182556.1579297.9486897.009548
8150767.3080398.9422768.009483
9128598.0456499.9358629.009418
\n", + "
" + ], + "text/plain": [ + " 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" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
liczba tokenówśrednia częstość w części Bestymacje +1Good-Turing
03883341.9004950.9935861.040007
14038700.5927701.9871720.582014
21175291.5658092.9807591.603009
3628002.5142683.9743452.602293
4408563.5049444.9679313.603265
5294434.4540985.9615174.627721
6227095.2320236.9551035.627064
7182556.1579297.9486896.606847
8150767.3080398.9422767.676506
9128598.0456499.9358628.557431
\n", + "
" + ], + "text/plain": [ + " 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" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[('k', 'o', 't'), ('o', 't', 'e'), ('t', 'e', 'k')]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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": 9, + "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": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "321" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(histories['jork'])\n", + "len(histories['zielony'])" + ] + }, + { + "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](./05_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](./05_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](./05_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.2" + }, + "org": null + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/wyk/05_Wygladzanie.org b/wyk/05_Wygladzanie.org new file mode 100644 index 0000000..e636ec7 --- /dev/null +++ b/wyk/05_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$): + +[[./05_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. + +[[./05_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 python :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 python :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 python :session mysession :exports both :results raw drawer +counterA['taki'] +#+END_SRC + +#+RESULTS: +:results: +48113 +:end: + +#+BEGIN_SRC python :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 python :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 python :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 python :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 python :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 python :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 python :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 +[[./05_Wygladzanie/size-perplexity.gif]] + + +**** Zmiana perplexity przy zwiększaniu zbioru uczącego + +#+CAPTION: Perplexity dla różnych rozmiarów zbioru uczącego +[[./05_Wygladzanie/size-perplexity2.gif]] + +**** Zmiana perplexity przy zwiększaniu rządu modelu + +#+CAPTION: Perplexity dla różnych wartości rządu modelu +[[./05_Wygladzanie/order-perplexity.gif]]