34 KiB
Modelowanie języka – laboratoria
10 kwietnia 2024
6. Biblioteki do statystycznych modeli językowych
KENLM
W praktyce korzysta się z gotowych bibliotek do statystycznych modeli językowych. Najbardziej popularną biblioteką jest KENLM ( https://kheafield.com/papers/avenue/kenlm.pdf ). Repozytorium znajduje się https://github.com/kpu/kenlm a dokumentacja https://kheafield.com/code/kenlm/
Na komputerach wydziałowych nie powinno być problemu ze skompilowaniem biblioteki.
Instalacja
(Zob. też dokumentacja)
sudo apt-get install build-essential libboost-all-dev cmake zlib1g-dev libbz2-dev liblzma-dev
wget -O - https://kheafield.com/code/kenlm.tar.gz | tar xz
mkdir kenlm/build
cd kenlm/build
cmake ..
make -j2
Najprostszy scenariusz użycia
KENLM_BUILD_PATH='/home/pawel/kenlm/build' # ścieżka, w której jest zainstalowany KenLM (zob. dokumentacja - link powyżej)
!wget https://wolnelektury.pl/media/book/txt/lalka-tom-pierwszy.txt
--2024-04-10 12:13:27-- https://wolnelektury.pl/media/book/txt/lalka-tom-pierwszy.txt Resolving wolnelektury.pl (wolnelektury.pl)... 51.83.143.148, 2001:41d0:602:3294:: Connecting to wolnelektury.pl (wolnelektury.pl)|51.83.143.148|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 860304 (840K) [text/plain] Saving to: ‘lalka-tom-pierwszy.txt.1’ lalka-tom-pierwszy. 100%[===================>] 840.14K 3.59MB/s in 0.2s 2024-04-10 12:13:27 (3.59 MB/s) - ‘lalka-tom-pierwszy.txt.1’ saved [860304/860304]
!wget https://wolnelektury.pl/media/book/txt/lalka-tom-drugi.txt
--2024-04-10 12:13:30-- https://wolnelektury.pl/media/book/txt/lalka-tom-drugi.txt Resolving wolnelektury.pl (wolnelektury.pl)... 51.83.143.148, 2001:41d0:602:3294:: Connecting to wolnelektury.pl (wolnelektury.pl)|51.83.143.148|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 949497 (927K) [text/plain] Saving to: ‘lalka-tom-drugi.txt.1’ lalka-tom-drugi.txt 100%[===================>] 927.24K 3.39MB/s in 0.3s 2024-04-10 12:13:30 (3.39 MB/s) - ‘lalka-tom-drugi.txt.1’ saved [949497/949497]
budowa modelu
!$KENLM_BUILD_PATH/bin/lmplz -o 4 < lalka-tom-pierwszy.txt > lalka_tom_pierwszy_lm.arpa
=== 1/5 Counting and sorting n-grams === Reading /home/pawel/moj-2024/lab/lalka-tom-pierwszy.txt ----5---10---15---20---25---30---35---40---45---50---55---60---65---70---75---80---85---90---95--100 **************************************************************************************************** Unigram tokens 122871 types 33265 === 2/5 Calculating and sorting adjusted counts === Chain sizes: 1:399180 2:2261987584 3:4241227008 4:6785963520 Statistics: 1 33265 D1=0.737356 D2=1.15675 D3+=1.59585 2 93948 D1=0.891914 D2=1.20314 D3+=1.44945 3 115490 D1=0.964904 D2=1.40636 D3+=1.66751 4 116433 D1=0.986444 D2=1.50367 D3+=1.9023 Memory estimate for binary LM: type kB probing 7800 assuming -p 1.5 probing 9157 assuming -r models -p 1.5 trie 3902 without quantization trie 2378 assuming -q 8 -b 8 quantization trie 3649 assuming -a 22 array pointer compression trie 2125 assuming -a 22 -q 8 -b 8 array pointer compression and quantization === 3/5 Calculating and sorting initial probabilities === Chain sizes: 1:399180 2:1503168 3:2309800 4:2794392 ----5---10---15---20---25---30---35---40---45---50---55---60---65---70---75---80---85---90---95--100 #################################################################################################### === 4/5 Calculating and writing order-interpolated probabilities === Chain sizes: 1:399180 2:1503168 3:2309800 4:2794392 ----5---10---15---20---25---30---35---40---45---50---55---60---65---70---75---80---85---90---95--100 #################################################################################################### === 5/5 Writing ARPA model === ----5---10---15---20---25---30---35---40---45---50---55---60---65---70---75---80---85---90---95--100 **************************************************************************************************** Name:lmplz VmPeak:13142592 kB VmRSS:7564 kB RSSMax:2623832 kB user:0.28374 sys:1.02734 CPU:1.3111 real:1.25256
plik arpa
Powyższa komenda tworzy model językowy z wygładzaniem i zapisuje go do pliku tekstowego arpa. Parametr -o 4 odpowiada za maksymalną ilość n-gramów w modelu: 4-gramy.
Plik arpa zawiera w sobie prawdopodobieństwa dla poszczególnych n-gramów. W zasadzie są to logarytmy prawdopodbieństw o podstawie 10.
Podejrzyjmy plik arpa:
!head -n 30 lalka_tom_pierwszy_lm.arpa
\data\ ngram 1=33265 ngram 2=93948 ngram 3=115490 ngram 4=116433 \1-grams: -5.0133595 <unk> 0 0 <s> -0.99603957 -1.4302719 </s> 0 -4.7287908 Bolesław -0.049677044 -4.9033437 Prus -0.049677044 -4.9033437 Lalka -0.049677044 -4.9033437 ISBN -0.049677044 -4.9033437 978-83-288-2673-1 -0.049677044 -4.9033437 Tom -0.049677044 -3.0029354 I -0.17544968 -4.9033437 I. -0.049677044 -3.5526814 Jak -0.1410632 -3.8170912 wygląda -0.16308141 -4.608305 firma -0.049677044 -4.33789 J. -0.3295009 -3.9192266 Mincel -0.12910372 -1.624716 i -0.20128249 -4.1086636 S. -0.098223634 -2.6843808 Wokulski -0.19202113 -2.8196363 przez -0.15214005 -4.9033437 szkło -0.049677044 -4.9033437 butelek? -0.049677044 -2.848008 W -0.19964235
Linijka to kolejno: prawdopodobieństwo (log10), n-gram, waga back-off (log10).
Aby spradzić prawdopodobieństwo sekwencji (a także PPL modelu) należy użyć komendy query
test_str=!(head -n 17 lalka-tom-drugi.txt | tail -n 1)
test_str = test_str[0]
test_str
'Sytuacja polityczna jest tak niepewna, że wcale by mnie nie zdziwiło, gdyby około grudnia wybuchła wojna.'
test_str
'Sytuacja polityczna jest tak niepewna, że wcale by mnie nie zdziwiło, gdyby około grudnia wybuchła wojna.'
!echo $test_str | $KENLM_BUILD_PATH/bin/query lalka_tom_pierwszy_lm.arpa 2> /dev/null
Sytuacja=0 1 -6.009399 polityczna=21766 1 -4.9033437 jest=123 1 -2.6640298 tak=231 2 -1.7683144 niepewna,=0 1 -5.1248584 że=122 1 -2.1651394 wcale=5123 1 -4.167491 by=1523 1 -3.55168 mnie=2555 2 -1.6694618 nie=127 2 -1.4439836 zdziwiło,=0 1 -5.2158937 gdyby=814 1 -3.2300434 około=1462 1 -3.7384818 grudnia=0 1 -5.123236 wybuchła=0 1 -5.0133595 wojna.=1285 1 -4.9033437 </s>=2 2 -0.8501559 Total: -61.54222 OOV: 5 Perplexity including OOVs: 4169.948113875898 Perplexity excluding OOVs: 834.2371454470355 OOVs: 5 Tokens: 17
Zgodnie z dokumentacją polecenia query, format wyjściowy to dla każdego słowa:
word=vocab_id ngram_length log10(p(word|context))
A co jeśli trochę zmienimy początek zdania?
test2_str = "Lubię placki i wcale by mnie nie zdziwiło, gdyby około grudnia wybuchła wojna."
!echo $test2_str | $KENLM_BUILD_PATH/bin/query lalka_tom_pierwszy_lm.arpa 2> /dev/null
Lubię=17813 1 -5.899383 placki=0 1 -5.0630364 i=16 1 -1.624716 wcale=5123 2 -3.2397003 by=1523 1 -3.6538217 mnie=2555 2 -1.6694618 nie=127 2 -1.4439836 zdziwiło,=0 1 -5.2158937 gdyby=814 1 -3.2300434 około=1462 1 -3.7384818 grudnia=0 1 -5.123236 wybuchła=0 1 -5.0133595 wojna.=1285 1 -4.9033437 </s>=2 2 -0.8501559 Total: -50.668617 OOV: 4 Perplexity including OOVs: 4160.896818387522 Perplexity excluding OOVs: 1060.0079770155185 OOVs: 4 Tokens: 14
Trochę bardziej zaawansowane użycie
Pierwsza rzecz, która rzuca się w oczy: tokeny zawierają znaki interpunkcyjne. Użyjemy zatem popularnego tokenizera i detokenizera moses z https://github.com/moses-smt/mosesdecoder
https://github.com/moses-smt/mosesdecoder/tree/master/scripts/tokenizer
tokenizacja i lowercasing
TOKENIZER_SCRIPTS='/home/pawel/mosesdecoder/scripts/tokenizer'
!echo $test_str
Sytuacja polityczna jest tak niepewna, że wcale by mnie nie zdziwiło, gdyby około grudnia wybuchła wojna.
!echo $test_str | $TOKENIZER_SCRIPTS/tokenizer.perl --language pl
Tokenizer Version 1.1 Language: en Number of threads: 1 Sytuacja polityczna jest tak niepewna , że wcale by mnie nie zdziwiło , gdyby około grudnia wybuchła wojna .
W łatwy sposób można odzyskać tekst źródłowy:
!echo $test_str | $TOKENIZER_SCRIPTS/tokenizer.perl --language pl | $TOKENIZER_SCRIPTS/detokenizer.perl --language pl
Detokenizer Version $Revision: 4134 $ Language: en Tokenizer Version 1.1 Language: en Number of threads: 1 Sytuacja polityczna jest tak niepewna, że wcale by mnie nie zdziwiło, gdyby około grudnia wybuchła wojna.
W naszym przykładzie stworzymy model językowy lowercase. Można osobno wytrenować też truecaser (osobny model do przywracania wielkości liter), jeżeli jest taka potrzeba.
!echo $test_str | $TOKENIZER_SCRIPTS/tokenizer.perl --language pl | $TOKENIZER_SCRIPTS/lowercase.perl
Tokenizer Version 1.1 Language: en Number of threads: 1 sytuacja polityczna jest tak niepewna , że wcale by mnie nie zdziwiło , gdyby około grudnia wybuchła wojna .
!cat lalka-tom-pierwszy.txt | $TOKENIZER_SCRIPTS/tokenizer.perl --language pl | $TOKENIZER_SCRIPTS/lowercase.perl > lalka-tom-pierwszy-tokenized-lowercased.txt
Tokenizer Version 1.1 Language: en Number of threads: 1
!cat lalka-tom-drugi.txt | $TOKENIZER_SCRIPTS/tokenizer.perl --language pl | $TOKENIZER_SCRIPTS/lowercase.perl > lalka-tom-drugi-tokenized-lowercased.txt
Tokenizer Version 1.1 Language: en Number of threads: 1
!$KENLM_BUILD_PATH/bin/lmplz -o 4 --prune 1 1 1 1 < lalka-tom-pierwszy-tokenized-lowercased.txt > lalka_tom_pierwszy_lm.arpa
=== 1/5 Counting and sorting n-grams === Reading /home/pawel/moj-2024/lab/lalka-tom-pierwszy-tokenized-lowercased.txt ----5---10---15---20---25---30---35---40---45---50---55---60---65---70---75---80---85---90---95--100 **************************************************************************************************** Unigram tokens 149285 types 22230 === 2/5 Calculating and sorting adjusted counts === Chain sizes: 1:266760 2:2262010112 3:4241268992 4:6786030592 Statistics: 1 8857/22230 D1=0.664486 D2=1.14301 D3+=1.57055 2 14632/86142 D1=0.838336 D2=1.2415 D3+=1.40935 3 8505/128074 D1=0.931027 D2=1.29971 D3+=1.54806 4 3174/138744 D1=0.967887 D2=1.35058 D3+=1.70692 Memory estimate for binary LM: type kB probing 822 assuming -p 1.5 probing 993 assuming -r models -p 1.5 trie 480 without quantization trie 343 assuming -q 8 -b 8 quantization trie 459 assuming -a 22 array pointer compression trie 322 assuming -a 22 -q 8 -b 8 array pointer compression and quantization === 3/5 Calculating and sorting initial probabilities === Chain sizes: 1:106284 2:234112 3:170100 4:76176 ----5---10---15---20---25---30---35---40---45---50---55---60---65---70---75---80---85---90---95--100 **################################################################################################## === 4/5 Calculating and writing order-interpolated probabilities === Chain sizes: 1:106284 2:234112 3:170100 4:76176 ----5---10---15---20---25---30---35---40---45---50---55---60---65---70---75---80---85---90---95--100 #################################################################################################### === 5/5 Writing ARPA model === ----5---10---15---20---25---30---35---40---45---50---55---60---65---70---75---80---85---90---95--100 **************************************************************************************************** Name:lmplz VmPeak:13142612 kB VmRSS:7392 kB RSSMax:2624428 kB user:0.229863 sys:0.579255 CPU:0.809192 real:0.791505
test_str=!(head -n 17 lalka-tom-drugi-tokenized-lowercased.txt | tail -n 1)
test_str=test_str[0]
test_str
'sytuacja polityczna jest tak niepewna , że wcale by mnie nie zdziwiło , gdyby około grudnia wybuchła wojna .'
model binarny
Konwertując model do postaci binarnej, inferencja będzie szybsza
!$KENLM_BUILD_PATH/bin/build_binary lalka_tom_pierwszy_lm.arpa lalka_tom_pierwszy_lm.binary
Reading lalka_tom_pierwszy_lm.arpa ----5---10---15---20---25---30---35---40---45---50---55---60---65---70---75---80---85---90---95--100 **************************************************************************************************** SUCCESS
!echo $test_str | $KENLM_BUILD_PATH/bin/query lalka_tom_pierwszy_lm.binary
This binary file contains probing hash tables. sytuacja=0 1 -5.568051 polityczna=0 1 -4.4812803 jest=91 1 -2.6271343 tak=175 2 -1.7584295 niepewna=0 1 -4.603079 ,=22 1 -1.2027187 że=90 2 -1.2062931 wcale=375 1 -4.0545278 by=995 1 -3.5268068 mnie=1491 2 -1.6614945 nie=94 2 -1.4855772 zdziwiło=0 1 -4.708499 ,=22 1 -1.2027187 gdyby=555 2 -2.4179027 około=957 1 -3.7740536 grudnia=0 1 -4.605748 wybuchła=0 1 -4.4812803 wojna=849 1 -4.213117 .=42 1 -1.3757544 </s>=2 2 -0.46293145 Total: -59.417397 OOV: 6 Perplexity including OOVs: 935.1253434773644 Perplexity excluding OOVs: 162.9687064350829 OOVs: 6 Tokens: 20 Name:query VmPeak:8864 kB VmRSS:4504 kB RSSMax:5328 kB user:0.002388 sys:0 CPU:0.0024207 real:0.000614597
sprawdzanie dokumentacji
Najłatwiej sprawdzić wywołując bezpośrednio komendę
!$KENLM_BUILD_PATH/bin/lmplz
Builds unpruned language models with modified Kneser-Ney smoothing. Please cite: @inproceedings{Heafield-estimate, author = {Kenneth Heafield and Ivan Pouzyrevsky and Jonathan H. Clark and Philipp Koehn}, title = {Scalable Modified {Kneser-Ney} Language Model Estimation}, year = {2013}, month = {8}, booktitle = {Proceedings of the 51st Annual Meeting of the Association for Computational Linguistics}, address = {Sofia, Bulgaria}, url = {http://kheafield.com/professional/edinburgh/estimate\_paper.pdf}, } Provide the corpus on stdin. The ARPA file will be written to stdout. Order of the model (-o) is the only mandatory option. As this is an on-disk program, setting the temporary file location (-T) and sorting memory (-S) is recommended. Memory sizes are specified like GNU sort: a number followed by a unit character. Valid units are % for percentage of memory (supported platforms only) and (in increasing powers of 1024): b, K, M, G, T, P, E, Z, Y. Default is K (*1024). This machine has 16611971072 bytes of memory. Language model building options: -h [ --help ] Show this help message -o [ --order ] arg Order of the model --interpolate_unigrams [=arg(=1)] (=1) Interpolate the unigrams (default) as opposed to giving lots of mass to <unk> like SRI. If you want SRI's behavior with a large <unk> and the old lmplz default, use --interpolate_unigrams 0. --skip_symbols Treat <s>, </s>, and <unk> as whitespace instead of throwing an exception -T [ --temp_prefix ] arg (=/tmp/) Temporary file prefix -S [ --memory ] arg (=80%) Sorting memory --minimum_block arg (=8K) Minimum block size to allow --sort_block arg (=64M) Size of IO operations for sort (determines arity) --block_count arg (=2) Block count (per order) --vocab_estimate arg (=1000000) Assume this vocabulary size for purposes of calculating memory in step 1 (corpus count) and pre-sizing the hash table --vocab_pad arg (=0) If the vocabulary is smaller than this value, pad with <unk> to reach this size. Requires --interpolate_unigrams --verbose_header Add a verbose header to the ARPA file that includes information such as token count, smoothing type, etc. --text arg Read text from a file instead of stdin --arpa arg Write ARPA to a file instead of stdout --intermediate arg Write ngrams to intermediate files. Turns off ARPA output (which can be reactivated by --arpa file). Forces --renumber on. --renumber Renumber the vocabulary identifiers so that they are monotone with the hash of each string. This is consistent with the ordering used by the trie data structure. --collapse_values Collapse probability and backoff into a single value, q that yields the same sentence-level probabilities. See http://kheafield.com/professional/edinb urgh/rest_paper.pdf for more details, including a proof. --prune arg Prune n-grams with count less than or equal to the given threshold. Specify one value for each order i.e. 0 0 1 to prune singleton trigrams and above. The sequence of values must be non-decreasing and the last value applies to any remaining orders. Default is to not prune, which is equivalent to --prune 0. --limit_vocab_file arg Read allowed vocabulary separated by whitespace. N-grams that contain vocabulary items not in this list will be pruned. Can be combined with --prune arg --discount_fallback [=arg(=0.5 1 1.5)] The closed-form estimate for Kneser-Ney discounts does not work without singletons or doubletons. It can also fail if these values are out of range. This option falls back to user-specified discounts when the closed-form estimate fails. Note that this option is generally a bad idea: you should deduplicate your corpus instead. However, class-based models need custom discounts because they lack singleton unigrams. Provide up to three discounts (for adjusted counts 1, 2, and 3+), which will be applied to all orders where the closed-form estimates fail.
wrapper pythonowy
!pip install https://github.com/kpu/kenlm/archive/master.zip
Defaulting to user installation because normal site-packages is not writeable Collecting https://github.com/kpu/kenlm/archive/master.zip Downloading https://github.com/kpu/kenlm/archive/master.zip [2K [32m-[0m [32m553.6 kB[0m [31m851.1 kB/s[0m [33m0:00:00[0m [?25h Installing build dependencies ... [?25ldone [?25h Getting requirements to build wheel ... [?25ldone [?25h Preparing metadata (pyproject.toml) ... [?25ldone [?25hBuilding wheels for collected packages: kenlm Building wheel for kenlm (pyproject.toml) ... [?25ldone [?25h Created wheel for kenlm: filename=kenlm-0.2.0-cp310-cp310-linux_x86_64.whl size=3184348 sha256=c9da9a754aa07ffa26f8983ced2910a547d665006e39fd053d365b802b4135e9 Stored in directory: /tmp/pip-ephem-wheel-cache-e8zp2xqd/wheels/a5/73/ee/670fbd0cee8f6f0b21d10987cb042291e662e26e1a07026462 Successfully built kenlm Installing collected packages: kenlm Successfully installed kenlm-0.2.0
import kenlm
model = kenlm.Model('lalka_tom_pierwszy_lm.binary')
print(model.score(test_str, bos = True, eos = True))
-59.417396545410156
for i in model.full_scores(test_str):
print(i)
(-5.568050861358643, 1, True) (-4.481280326843262, 1, True) (-2.627134323120117, 1, False) (-1.7584295272827148, 2, False) (-4.603078842163086, 1, True) (-1.202718734741211, 1, False) (-1.2062931060791016, 2, False) (-4.054527759552002, 1, False) (-3.5268068313598633, 1, False) (-1.661494493484497, 2, False) (-1.4855772256851196, 2, False) (-4.708498954772949, 1, True) (-1.202718734741211, 1, False) (-2.417902708053589, 2, False) (-3.7740535736083984, 1, False) (-4.605748176574707, 1, True) (-4.481280326843262, 1, True) (-4.2131171226501465, 1, False) (-1.3757543563842773, 1, False) (-0.46293145418167114, 2, False)
Zadanie
Stworzyć model językowy za pomocą gotowej biblioteki (KenLM lub inna)
Rozwiązanie proszę umieścić na https://gonito.csi.wmi.amu.edu.pl/challenge/challenging-america-word-gap-prediction
Warunki zaliczenia:
- wynik widoczny na platformie zarówno dla dev i dla test
- wynik dla dev i test lepszy (niższy) niż 1024.00 (liczone przy pomocy geval)
- deadline: 24 kwietnia 2024
- commitując rozwiązanie proszę również umieścić rozwiązanie w pliku /run.py (czyli na szczycie katalogu). Można przekonwertować jupyter do pliku python przez File → Download as → Python. Rozwiązanie nie musi być w pythonie, może być w innym języku.
- zadania wykonujemy samodzielnie
- w nazwie commita podaj nr indeksu
- w tagach podaj kenlm!
- uwaga na specjalne znaki \\n w pliku 'in.tsv' oraz pierwsze kolumny pliku in.tsv (które należy usunąć)
Punktacja:
- podstawa: 40 punktów
- dodatkowo 50 (czyli 40 + 50 = 90) punktów z najlepszy wynik
- dodatkowo 20 (czyli 40 + 20 = 60) punktów za znalezienie się w pierwszej połowie, ale poza najlepszym wynikiem