moj-2024/lab/06_Biblioteki_stat_LM.ipynb
Paweł Skórzewski b108941612 Daty
2024-05-15 11:44:56 +02:00

34 KiB
Raw Blame History

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
     - 553.6 kB 851.1 kB/s 0:00:00
[?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