This commit is contained in:
Jakub Pokrywka 2022-04-03 22:22:24 +02:00
parent 1c93cb8219
commit 8dd417f074
40 changed files with 2386 additions and 69 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

View File

@ -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}

View File

@ -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:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

657
wyk/03_Entropia.ipynb Normal file

File diff suppressed because one or more lines are too long

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1 +0,0 @@
<mxfile host="app.diagrams.net" modified="2022-03-05T13:05:15.969Z" agent="5.0 (X11)" etag="T1jv8GjlxjBRy82UXK3g" version="16.2.2" type="device"><diagram id="E-zPRpFz5prVeiZgI5WF" name="Page-1">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</diagram></mxfile>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1 +0,0 @@
<mxfile host="app.diagrams.net" modified="2022-03-05T10:14:12.443Z" agent="5.0 (X11)" etag="UjtYN9dZJ1n-mtJw7vZr" version="16.2.2" type="device"><diagram id="zkchOmJWayHcCaytrl_I" name="Page-1">jZPfb4MgEMf/Gh+bKFS3vs513bLsqUv2TIQKLXiO0mr31w/l/JVmydAofO444HtHRHPT7iyr5QdwoSMS8zaizxEhm3Xivx24BZCSNIDSKh5QMoG9+hEIY6QXxcV54egAtFP1EhZQVaJwC8ashWbpdgC9XLVmpbgD+4Lpe/qluJOBPpKHib8KVcph5STbBIthgzOGOEvGoQmoPxzdRjS3AC70TJsL3Wk36BIUePnDOm7Misr9Z8LpuNrG5ppe31ylDf1+P+6yFR7jyvQFDxyRtX8/ww937m6DHBYuFRddxDiiT41UTuxrVnTWxuffM+mM9qPEd8/OwmmUjXpyUFrnoMH20egh7Z6OQ+VmPLQxwsyS9c1bcN/COtH+KUgyyuzLU4ARzt68C04gFGsMS5OmOG6mRJM1MjlLcoaMYW2VY+hJft/BDAzDKdO9bXZd6PYX</diagram></mxfile>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -1 +0,0 @@
<mxfile host="app.diagrams.net" modified="2022-03-05T10:11:11.528Z" agent="5.0 (X11)" etag="-eu0Wo5sdhkbwVuXUHS7" version="16.2.2" type="device"><diagram id="zkchOmJWayHcCaytrl_I" name="Page-1">5ZVRT8IwEMc/zR5NtpUVeBRENMYnTHw0db1t1W7FUhj46b3RbmMwEk3UmJgQcv3f7a79/QvzyDTfzjVbZveKg/RCn289cuWF4XgQ4Hcl7KwQhZEVUi24lYJWWIh3cKLv1LXgsOoUGqWkEcuuGKuigNh0NKa1KrtliZLdqUuWwomwiJk8VR8FN5lVR+Gw1W9ApFk9OaBjm8lZXexarDLGVWml/eHIzCNTrZSxUb6dgqzY1Vwsgesz2WZjGgrzmQdeXy5mfr6JNremkDl5u3uZ04vQdtkwuXYHdps1u5qAVuuCQ9XE98ikzISBxZLFVbZEy1HLTC5xFWC4Mlq9NqQIKomQcqqk0vtuJEmAxnFTeZDhw/GzX41wWwJtYHv2rEFDEG8eqByM3mGJeyAk7vrsjtZl62HgePnZgX/Uacxdm7Rp3ZLFwMH9Amjy26A5g1HSC5rGI3hOvgc0if4a6KgHNJU4dcLFBsPU7E8+wM/DU2CDugIHdoqODEJMps+Fmm2hCjgywklMirTAZYxYAfVJBV3gv8ylS+SC82pMr+3di/EdPw965NogOnGN9phGfso0+hXTwv9pGhn9mmm4bN9L+9zBy53MPgA=</diagram></mxfile>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -1 +0,0 @@
<mxfile host="app.diagrams.net" modified="2022-03-18T21:08:49.892Z" agent="5.0 (X11)" etag="RzIxxXjLYk9oBS8CnMsQ" version="16.2.2" type="device"><diagram id="E-zPRpFz5prVeiZgI5WF" name="Page-1">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/</diagram></mxfile>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

1174
wyk/05_Wygladzanie.ipynb Normal file

File diff suppressed because it is too large Load Diff

486
wyk/05_Wygladzanie.org Normal file
View File

@ -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 (~<s>~ i ~</s>~).
#+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 '<s>'
for m in re.finditer(r'[\p{L}0-9\*]+|\p{P}+', line):
yield m.group(0).lower()
yield '</s>'
list(islice(get_words_from_file('opensubtitlesA.pl.txt'), 0, 100))
#+END_SRC
#+RESULTS:
:results:
['<s>', 'lubisz', 'curry', ',', 'prawda', '?', '</s>', '<s>', 'nałożę', 'ci', 'więcej', '.', '</s>', '<s>', 'hey', '!', '</s>', '<s>', 'smakuje', 'ci', '?', '</s>', '<s>', 'hey', ',', 'brzydalu', '.', '</s>', '<s>', 'spójrz', 'na', 'nią', '.', '</s>', '<s>', '-', 'wariatka', '.', '</s>', '<s>', '-', 'zadałam', 'ci', 'pytanie', '!', '</s>', '<s>', 'no', ',', 'tak', 'lepiej', '!', '</s>', '<s>', '-', 'wygląda', 'dobrze', '!', '</s>', '<s>', '-', 'tak', 'lepiej', '!', '</s>', '<s>', 'pasuje', 'jej', '.', '</s>', '<s>', '-', 'hey', '.', '</s>', '<s>', '-', 'co', 'do', '...?', '</s>', '<s>', 'co', 'do', 'cholery', 'robisz', '?', '</s>', '<s>', 'zejdź', 'mi', 'z', 'oczu', ',', 'zdziro', '.', '</s>', '<s>', '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]]