Split 6
This commit is contained in:
parent
17653fd6aa
commit
f2c0dc31dd
155
wyk/06_Podobienstwo_slow.org
Normal file
155
wyk/06_Podobienstwo_slow.org
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
* Podobieństwo słów
|
||||||
|
|
||||||
|
** Słabości $n$-gramowych modeli języka
|
||||||
|
|
||||||
|
Podstawowa słabość $n$-gramowych modeli języka polega na tym, że każde
|
||||||
|
słowo jest traktowane w izolacji. W, powiedzmy, bigramowym modelu
|
||||||
|
języka każda wartość $P(w_2|w_1)$ jest estymowana osobno, nawet dla —
|
||||||
|
w jakimś sensie podobnych słów. Na przykład:
|
||||||
|
|
||||||
|
- $P(\mathit{zaszczekał}|\mathit{pies})$, $P(\mathit{zaszczekał}|\mathit{jamnik})$, $P(\mathit{zaszczekał}|\mathit{wilczur})$ są estymowane osobno,
|
||||||
|
- $P(\mathit{zaszczekał}|\mathit{pies})$, $P(\mathit{zamerdał}|\mathit{pies})$, $P(\mathit{ugryzł}|\mathit{pies})$ są estymowane osobno,
|
||||||
|
- dla każdej pary $u$, $v$, gdzie $u$ jest przyimkiem (np. /dla/), a $v$ — osobową formą czasownika (np. /napisał/) model musi się uczyć, że $P(v|u)$ powinno mieć bardzo niską wartość.
|
||||||
|
|
||||||
|
** Podobieństwo słów jako sposób na słabości $n$-gramowych modeli języka?
|
||||||
|
|
||||||
|
Intuicyjnie wydaje się, że potrzebujemy jakiegoś sposobu określania podobieństwa słów, tak aby
|
||||||
|
w naturalny sposób, jeśli słowa $u$ i $u'$ oraz $v$ i $v'$ są bardzo podobne, wówczas
|
||||||
|
$P(u|v) \approx P(u'|v')$.
|
||||||
|
|
||||||
|
Można wskazać trzy sposoby określania podobieństwa słów: odległość
|
||||||
|
edycyjna Lewensztajna, hierarchie słów i odległość w przestrzeni wielowymiarowej.
|
||||||
|
|
||||||
|
*** Odległość Lewensztajna
|
||||||
|
|
||||||
|
Słowo /dom/ ma coś wspólnego z /domem/, /domkiem/, /domostwem/,
|
||||||
|
/domownikami/, /domowym/ i /udomowieniem/ (?? — tu już można mieć
|
||||||
|
wątpliwości). Więc może oprzeć podobieństwa na powierzchownym
|
||||||
|
podobieństwie?
|
||||||
|
|
||||||
|
Możemy zastosować tutaj *odległość Lewensztajna*, czyli minimalną liczbę operacji edycyjnych, które
|
||||||
|
są potrzebne, aby przekształcić jedno słowo w drugie. Zazwyczaj jako elementarne operacje edycyjne
|
||||||
|
definiuje się:
|
||||||
|
|
||||||
|
- usunięcie znaku,
|
||||||
|
- dodanie znaku,
|
||||||
|
- zamianu znaku.
|
||||||
|
|
||||||
|
Na przykład odległość edycyjna między słowami /domkiem/ i /domostwem/
|
||||||
|
wynosi 4: zamiana /k/ na /o/, /i/ na /s/, dodanie /t/, dodanie /w/.
|
||||||
|
|
||||||
|
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
||||||
|
import Levenshtein
|
||||||
|
Levenshtein.distance('domkiem', 'domostwem')
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+RESULTS:
|
||||||
|
:results:
|
||||||
|
4
|
||||||
|
:end:
|
||||||
|
|
||||||
|
Niestety, to nie jest tak, że słowa są podobne wtedy i tylko wtedy, gdy /wyglądają/ podobnie:
|
||||||
|
|
||||||
|
- /tapet/ nie ma nic wspólnego z /tapetą/,
|
||||||
|
- słowo /sowa/ nie wygląda jak /ptak/, /puszczyk/, /jastrząb/, /kura/ itd.
|
||||||
|
|
||||||
|
Powierzchowne podobieństwo słów łączy się zazwyczaj z relacjami
|
||||||
|
*fleksyjnymi* i *słowotwórczymi* (choć też nie zawsze, por. np. pary
|
||||||
|
słów będące przykładem *supletywizmu*: /człowiek/-/ludzie/,
|
||||||
|
/brać/-/zwiąć/, /rok/-/lata/). A co z innymi własnościami wyrazów czy
|
||||||
|
raczej bytów przez nie denotowanych (słowa oznaczające zwierzęta
|
||||||
|
należące do gromady ptaków chcemy traktować jako, w jakiejś mierze przynajmnie, podobne)?
|
||||||
|
|
||||||
|
Dodajmy jeszcze, że w miejsce odległości Lewensztajna warto czasami
|
||||||
|
używać podobieństwa Jaro-Winklera, które mniejszą wagę przywiązuje do zmian w końcówkach wyrazów:
|
||||||
|
|
||||||
|
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
||||||
|
import Levenshtein
|
||||||
|
Levenshtein.jaro_winkler('domu', 'domowy')
|
||||||
|
Levenshtein.jaro_winkler('domowy', 'maskowy')
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+RESULTS:
|
||||||
|
:results:
|
||||||
|
0.6626984126984127
|
||||||
|
:end:
|
||||||
|
|
||||||
|
*** Klasy i hierarchie słów
|
||||||
|
|
||||||
|
Innym sposobem określania podobieństwa między słowami jest zdefiniowanie klas słów.
|
||||||
|
Słowa należące do jednej klasy będą podobne, do różnych klas — niepodobne.
|
||||||
|
|
||||||
|
**** Klasy gramatyczne
|
||||||
|
|
||||||
|
Klasy mogą odpowiadać standardowym kategoriom gramatycznym znanym z
|
||||||
|
językoznawstwa, na przykład *częściom mowy* (rzeczownik, przymiotnik,
|
||||||
|
czasownik itd.). Wiele jest niejednoznacznych jeśli chodzi o kategorię części mowy:
|
||||||
|
|
||||||
|
- /powieść/ — rzeczownik czy czasownik?
|
||||||
|
- /komputerowi/ — rzeczownik czy przymiotnik?
|
||||||
|
- /lecz/ — spójnik, czasownik (!) czy rzeczownik (!!)?
|
||||||
|
|
||||||
|
Oznacza to, że musimy dysponować narzędziem, które pozwala
|
||||||
|
automatycznie, na podstawie kontekstu, tagować tekst częściami mowy
|
||||||
|
(ang. /POS tagger/). Takie narzędzia pozwalają na osiągnięcie wysokiej
|
||||||
|
dokładności, niestety zawsze wprowadzają jakieś błędy, które mogą
|
||||||
|
propagować się dalej.
|
||||||
|
|
||||||
|
**** Klasy indukowane automatycznie
|
||||||
|
|
||||||
|
Zamiast z góry zakładać klasy wyrazów można zastosować metody uczenia
|
||||||
|
nienadzorowanego (podobne do analizy skupień) w celu *wyindukowanie*
|
||||||
|
automatycznie klas (tagów) z korpusu.
|
||||||
|
|
||||||
|
**** Użycie klas słów w modelu języka
|
||||||
|
|
||||||
|
Najprostszy sposób uwzględnienia klas słów w $n$-gramowym modelowaniu
|
||||||
|
języka polega stworzeniu dwóch osobnych modeli:
|
||||||
|
|
||||||
|
- tradycyjnego modelu języka $M_W$ operującego na słowach,
|
||||||
|
- modelu języka $M_T$ wyuczonego na klasach słów (czy to częściach
|
||||||
|
mowy, czy klasach wyindukowanych automatycznie).
|
||||||
|
|
||||||
|
Zauważmy, że rząd modelu $M_T$ ($n_T$) może dużo większy niż rząd modelu $M_W$ ($n_W$) — klas będzie
|
||||||
|
dużo mniej niż wyrazów, więc problem rzadkości danych jest dużo mniejszy i można rozpatrywać dłuższe
|
||||||
|
$n$-gramy.
|
||||||
|
|
||||||
|
Dwa modele możemy połączyć za pomocą prostej kombinacji liniowej sterowanej hiperparametrem $\lambda$:
|
||||||
|
|
||||||
|
$$P(w_i|w_{i-n_T}+1\ldots w_{i-1}) = \lambda P_{M_T}(w_i|w_{i-n_W}+1\ldots w_{i-1}) + (1 - \lambda) P_{M_W}(w_i|w_{i-n_T}+1\ldots w_{i-1}).$$
|
||||||
|
|
||||||
|
**** Hierarchie słów
|
||||||
|
|
||||||
|
Zamiast płaskiej klasyfikacji słów można zbudować hierarchię słów czy
|
||||||
|
pojęć. Taka hierarchia może dotyczyć właściwości gramatycznych
|
||||||
|
(na przykład rzeczownik w liczbie pojedynczej w dopełniaczu będzie podklasą
|
||||||
|
rzeczownika) lub własności denotowanych bytów.
|
||||||
|
|
||||||
|
Niekiedy dość łatwo stworzyć hierarchie (taksonomię) pojęć. Na
|
||||||
|
przykład jamnik jest rodzajem psa (słowo /jamnik/ jest *hiponimem*
|
||||||
|
słowa /pies/, zaś słowo /pies/ hiperonimem słowa /jamnik/), pies —
|
||||||
|
ssaka, ssak — zwierzęcia, zwierzę — organizmu żywego, organizm — bytu
|
||||||
|
materialnego.
|
||||||
|
|
||||||
|
***** Analityczny język Johna Wilkinsa
|
||||||
|
|
||||||
|
Już od dawna filozofowie myśleli o stworzenie języka uniwersalnego, w
|
||||||
|
którym hierarchia bytów jest ułożona w „naturalny” sposób.
|
||||||
|
|
||||||
|
Przykładem jest angielski uczony John Wilkins (1614-1672). W dziele
|
||||||
|
/An Essay towards a Real Character and a Philosophical Language/
|
||||||
|
zaproponował on rozbudowaną hierarchię bytów.
|
||||||
|
|
||||||
|
#+CAPTION: Fragment dzieła Johna Wilkinsa
|
||||||
|
[[./06_Podobienstwo_slow/wilkins.png]]
|
||||||
|
|
||||||
|
***** Słowosieci
|
||||||
|
|
||||||
|
Współczesnym odpowiednik hierarchii Wilkinsa są *słowosieci* (ang. /wordnets).
|
||||||
|
Przykłady:
|
||||||
|
|
||||||
|
- dla języka polskiego: [[http://plwordnet.pwr.wroc.pl][Słowosieć]],
|
||||||
|
- dla języka angielskiego: [[https://wordnet.princeton.edu/][Princeton Wordnet]] (i Słowosieć!)
|
||||||
|
|
||||||
|
#+CAPTION: Fragment Słowosieci
|
||||||
|
[[./06_Podobienstwo_slow/slowosiec.png]]
|
@ -1,434 +0,0 @@
|
|||||||
* Zanurzenia słów
|
|
||||||
|
|
||||||
** Słabości $n$-gramowych modeli języka
|
|
||||||
|
|
||||||
Podstawowa słabość $n$-gramowych modeli języka polega na tym, że każde
|
|
||||||
słowo jest traktowane w izolacji. W, powiedzmy, bigramowym modelu
|
|
||||||
języka każda wartość $P(w_2|w_1)$ jest estymowana osobno, nawet dla —
|
|
||||||
w jakimś sensie podobnych słów. Na przykład:
|
|
||||||
|
|
||||||
- $P(\mathit{zaszczekał}|\mathit{pies})$, $P(\mathit{zaszczekał}|\mathit{jamnik})$, $P(\mathit{zaszczekał}|\mathit{wilczur})$ są estymowane osobno,
|
|
||||||
- $P(\mathit{zaszczekał}|\mathit{pies})$, $P(\mathit{zamerdał}|\mathit{pies})$, $P(\mathit{ugryzł}|\mathit{pies})$ są estymowane osobno,
|
|
||||||
- dla każdej pary $u$, $v$, gdzie $u$ jest przyimkiem (np. /dla/), a $v$ — osobową formą czasownika (np. /napisał/) model musi się uczyć, że $P(v|u)$ powinno mieć bardzo niską wartość.
|
|
||||||
|
|
||||||
** Podobieństwo słów jako sposób na słabości $n$-gramowych modeli języka?
|
|
||||||
|
|
||||||
Intuicyjnie wydaje się, że potrzebujemy jakiegoś sposobu określania podobieństwa słów, tak aby
|
|
||||||
w naturalny sposób, jeśli słowa $u$ i $u'$ oraz $v$ i $v'$ są bardzo podobne, wówczas
|
|
||||||
$P(u|v) \approx P(u'|v')$.
|
|
||||||
|
|
||||||
Można wskazać trzy sposoby określania podobieństwa słów: odległość
|
|
||||||
edycyjna Lewensztajna, hierarchie słów i odległość w przestrzeni wielowymiarowej.
|
|
||||||
|
|
||||||
*** Odległość Lewensztajna
|
|
||||||
|
|
||||||
Słowo /dom/ ma coś wspólnego z /domem/, /domkiem/, /domostwem/,
|
|
||||||
/domownikami/, /domowym/ i /udomowieniem/ (?? — tu już można mieć
|
|
||||||
wątpliwości). Więc może oprzeć podobieństwa na powierzchownym
|
|
||||||
podobieństwie?
|
|
||||||
|
|
||||||
Możemy zastosować tutaj *odległość Lewensztajna*, czyli minimalną liczbę operacji edycyjnych, które
|
|
||||||
są potrzebne, aby przekształcić jedno słowo w drugie. Zazwyczaj jako elementarne operacje edycyjne
|
|
||||||
definiuje się:
|
|
||||||
|
|
||||||
- usunięcie znaku,
|
|
||||||
- dodanie znaku,
|
|
||||||
- zamianu znaku.
|
|
||||||
|
|
||||||
Na przykład odległość edycyjna między słowami /domkiem/ i /domostwem/
|
|
||||||
wynosi 4: zamiana /k/ na /o/, /i/ na /s/, dodanie /t/, dodanie /w/.
|
|
||||||
|
|
||||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
|
||||||
import Levenshtein
|
|
||||||
Levenshtein.distance('domkiem', 'domostwem')
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
#+RESULTS:
|
|
||||||
:results:
|
|
||||||
4
|
|
||||||
:end:
|
|
||||||
|
|
||||||
Niestety, to nie jest tak, że słowa są podobne wtedy i tylko wtedy, gdy /wyglądają/ podobnie:
|
|
||||||
|
|
||||||
- /tapet/ nie ma nic wspólnego z /tapetą/,
|
|
||||||
- słowo /sowa/ nie wygląda jak /ptak/, /puszczyk/, /jastrząb/, /kura/ itd.
|
|
||||||
|
|
||||||
Powierzchowne podobieństwo słów łączy się zazwyczaj z relacjami
|
|
||||||
*fleksyjnymi* i *słowotwórczymi* (choć też nie zawsze, por. np. pary
|
|
||||||
słów będące przykładem *supletywizmu*: /człowiek/-/ludzie/,
|
|
||||||
/brać/-/zwiąć/, /rok/-/lata/). A co z innymi własnościami wyrazów czy
|
|
||||||
raczej bytów przez nie denotowanych (słowa oznaczające zwierzęta
|
|
||||||
należące do gromady ptaków chcemy traktować jako, w jakiejś mierze przynajmnie, podobne)?
|
|
||||||
|
|
||||||
Dodajmy jeszcze, że w miejsce odległości Lewensztajna warto czasami
|
|
||||||
używać podobieństwa Jaro-Winklera, które mniejszą wagę przywiązuje do zmian w końcówkach wyrazów:
|
|
||||||
|
|
||||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
|
||||||
import Levenshtein
|
|
||||||
Levenshtein.jaro_winkler('domu', 'domowy')
|
|
||||||
Levenshtein.jaro_winkler('domowy', 'maskowy')
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
#+RESULTS:
|
|
||||||
:results:
|
|
||||||
0.6626984126984127
|
|
||||||
:end:
|
|
||||||
|
|
||||||
*** Klasy i hierarchie słów
|
|
||||||
|
|
||||||
Innym sposobem określania podobieństwa między słowami jest zdefiniowanie klas słów.
|
|
||||||
Słowa należące do jednej klasy będą podobne, do różnych klas — niepodobne.
|
|
||||||
|
|
||||||
**** Klasy gramatyczne
|
|
||||||
|
|
||||||
Klasy mogą odpowiadać standardowym kategoriom gramatycznym znanym z
|
|
||||||
językoznawstwa, na przykład *częściom mowy* (rzeczownik, przymiotnik,
|
|
||||||
czasownik itd.). Wiele jest niejednoznacznych jeśli chodzi o kategorię części mowy:
|
|
||||||
|
|
||||||
- /powieść/ — rzeczownik czy czasownik?
|
|
||||||
- /komputerowi/ — rzeczownik czy przymiotnik?
|
|
||||||
- /lecz/ — spójnik, czasownik (!) czy rzeczownik (!!)?
|
|
||||||
|
|
||||||
Oznacza to, że musimy dysponować narzędziem, które pozwala
|
|
||||||
automatycznie, na podstawie kontekstu, tagować tekst częściami mowy
|
|
||||||
(ang. /POS tagger/). Takie narzędzia pozwalają na osiągnięcie wysokiej
|
|
||||||
dokładności, niestety zawsze wprowadzają jakieś błędy, które mogą
|
|
||||||
propagować się dalej.
|
|
||||||
|
|
||||||
**** Klasy indukowane automatycznie
|
|
||||||
|
|
||||||
Zamiast z góry zakładać klasy wyrazów można zastosować metody uczenia
|
|
||||||
nienadzorowanego (podobne do analizy skupień) w celu *wyindukowanie*
|
|
||||||
automatycznie klas (tagów) z korpusu.
|
|
||||||
|
|
||||||
**** Użycie klas słów w modelu języka
|
|
||||||
|
|
||||||
Najprostszy sposób uwzględnienia klas słów w $n$-gramowym modelowaniu
|
|
||||||
języka polega stworzeniu dwóch osobnych modeli:
|
|
||||||
|
|
||||||
- tradycyjnego modelu języka $M_W$ operującego na słowach,
|
|
||||||
- modelu języka $M_T$ wyuczonego na klasach słów (czy to częściach
|
|
||||||
mowy, czy klasach wyindukowanych automatycznie).
|
|
||||||
|
|
||||||
Zauważmy, że rząd modelu $M_T$ ($n_T$) może dużo większy niż rząd modelu $M_W$ ($n_W$) — klas będzie
|
|
||||||
dużo mniej niż wyrazów, więc problem rzadkości danych jest dużo mniejszy i można rozpatrywać dłuższe
|
|
||||||
$n$-gramy.
|
|
||||||
|
|
||||||
Dwa modele możemy połączyć za pomocą prostej kombinacji liniowej sterowanej hiperparametrem $\lambda$:
|
|
||||||
|
|
||||||
$$P(w_i|w_{i-n_T}+1\ldots w_{i-1}) = \lambda P_{M_T}(w_i|w_{i-n_W}+1\ldots w_{i-1}) + (1 - \lambda) P_{M_W}(w_i|w_{i-n_T}+1\ldots w_{i-1}).$$
|
|
||||||
|
|
||||||
**** Hierarchie słów
|
|
||||||
|
|
||||||
Zamiast płaskiej klasyfikacji słów można zbudować hierarchię słów czy
|
|
||||||
pojęć. Taka hierarchia może dotyczyć właściwości gramatycznych
|
|
||||||
(na przykład rzeczownik w liczbie pojedynczej w dopełniaczu będzie podklasą
|
|
||||||
rzeczownika) lub własności denotowanych bytów.
|
|
||||||
|
|
||||||
Niekiedy dość łatwo stworzyć hierarchie (taksonomię) pojęć. Na
|
|
||||||
przykład jamnik jest rodzajem psa (słowo /jamnik/ jest *hiponimem*
|
|
||||||
słowa /pies/, zaś słowo /pies/ hiperonimem słowa /jamnik/), pies —
|
|
||||||
ssaka, ssak — zwierzęcia, zwierzę — organizmu żywego, organizm — bytu
|
|
||||||
materialnego.
|
|
||||||
|
|
||||||
***** Analityczny język Johna Wilkinsa
|
|
||||||
|
|
||||||
Już od dawna filozofowie myśleli o stworzenie języka uniwersalnego, w
|
|
||||||
którym hierarchia bytów jest ułożona w „naturalny” sposób.
|
|
||||||
|
|
||||||
Przykładem jest angielski uczony John Wilkins (1614-1672). W dziele
|
|
||||||
/An Essay towards a Real Character and a Philosophical Language/
|
|
||||||
zaproponował on rozbudowaną hierarchię bytów.
|
|
||||||
|
|
||||||
#+CAPTION: Fragment dzieła Johna Wilkinsa
|
|
||||||
[[./06_Zanurzenia_slow/wilkins.png]]
|
|
||||||
|
|
||||||
***** Słowosieci
|
|
||||||
|
|
||||||
Współczesnym odpowiednik hierarchii Wilkinsa są *słowosieci* (ang. /wordnets).
|
|
||||||
Przykłady:
|
|
||||||
|
|
||||||
- dla języka polskiego: [[http://plwordnet.pwr.wroc.pl][Słowosieć]],
|
|
||||||
- dla języka angielskiego: [[https://wordnet.princeton.edu/][Princeton Wordnet]] (i Słowosieć!)
|
|
||||||
|
|
||||||
#+CAPTION: Fragment Słowosieci
|
|
||||||
[[./06_Zanurzenia_slow/slowosiec.png]]
|
|
||||||
|
|
||||||
W praktyce stosowalność słowosieci okazała się zaskakująco
|
|
||||||
ograniczona. Większy przełom w przetwarzaniu języka naturalnego przyniosły
|
|
||||||
wielowymiarowe reprezentacje słów, inaczej: zanurzenia słów.
|
|
||||||
|
|
||||||
** „Wymiary” słów
|
|
||||||
|
|
||||||
Moglibyśmy zanurzyć (ang. /embed/) w wielowymiarowej przestrzeni, tzn. zdefiniować odwzorowanie
|
|
||||||
$E \colon V \rightarrow \mathcal{R}^m$ dla pewnego $m$ i określić taki sposób estymowania
|
|
||||||
prawdopodobieństw $P(u|v)$, by dla par $E(v)$ i $E(v')$ oraz $E(u)$ i $E(u')$ znajdujących się w pobliżu
|
|
||||||
(według jakiejś metryki odległości, na przykład zwykłej odległości euklidesowej):
|
|
||||||
|
|
||||||
$$P(u|v) \approx P(u'|v').$$
|
|
||||||
|
|
||||||
$E(u)$ nazywamy zanurzeniem (embeddingiem) słowa.
|
|
||||||
|
|
||||||
*** Wymiary określone z góry?
|
|
||||||
|
|
||||||
Można by sobie wyobrazić, że $m$ wymiarów mogłoby być z góry
|
|
||||||
określonych przez lingwistę. Wymiary te byłyby związane z typowymi
|
|
||||||
„osiami” rozpatrywanymi w językoznawstwie, na przykład:
|
|
||||||
|
|
||||||
- czy słowo jest wulgarne, pospolite, potoczne, neutralne czy książkowe?
|
|
||||||
- czy słowo jest archaiczne, wychodzące z użycia czy jest neologizmem?
|
|
||||||
- czy słowo dotyczy kobiet, czy mężczyzn (w sensie rodzaju gramatycznego i/lub
|
|
||||||
socjolingwistycznym)?
|
|
||||||
- czy słowo jest w liczbie pojedynczej czy mnogiej?
|
|
||||||
- czy słowo jest rzeczownikiem czy czasownikiem?
|
|
||||||
- czy słowo jest rdzennym słowem czy zapożyczeniem?
|
|
||||||
- czy słowo jest nazwą czy słowem pospolitym?
|
|
||||||
- czy słowo opisuje konkretną rzecz czy pojęcie abstrakcyjne?
|
|
||||||
- …
|
|
||||||
|
|
||||||
W praktyce okazało się jednak, że lepiej, żeby komputer uczył się sam
|
|
||||||
możliwych wymiarów — z góry określamy tylko $m$ (liczbę wymiarów).
|
|
||||||
|
|
||||||
** Bigramowy model języka oparty na zanurzeniach
|
|
||||||
|
|
||||||
Zbudujemy teraz najprostszy model język oparty na zanurzeniach. Będzie to właściwie najprostszy
|
|
||||||
*neuronowy model języka*, jako że zbudowany model można traktować jako prostą sieć neuronową.
|
|
||||||
|
|
||||||
*** Słownik
|
|
||||||
|
|
||||||
W typowym neuronowym modelu języka rozmiar słownika musi być z góry
|
|
||||||
ograniczony. Zazwyczaj jest to liczba rzędu kilkudziesięciu wyrazów —
|
|
||||||
po prostu będziemy rozpatrywać $|V|$ najczęstszych wyrazów, pozostałe zamienimy
|
|
||||||
na specjalny token ~<unk>~ reprezentujący nieznany (/unknown/) wyraz.
|
|
||||||
|
|
||||||
Aby utworzyć taki słownik użyjemy gotowej klasy ~Vocab~ z pakietu torchtext:
|
|
||||||
|
|
||||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
|
||||||
from itertools import islice
|
|
||||||
import regex as re
|
|
||||||
import sys
|
|
||||||
from torchtext.vocab import build_vocab_from_iterator
|
|
||||||
|
|
||||||
|
|
||||||
def get_words_from_line(line):
|
|
||||||
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>'
|
|
||||||
|
|
||||||
|
|
||||||
def get_word_lines_from_file(file_name):
|
|
||||||
with open(file_name, 'r') as fh:
|
|
||||||
for line in fh:
|
|
||||||
yield get_words_from_line(line)
|
|
||||||
|
|
||||||
vocab_size = 20000
|
|
||||||
|
|
||||||
vocab = build_vocab_from_iterator(
|
|
||||||
get_word_lines_from_file('opensubtitlesA.pl.txt'),
|
|
||||||
max_tokens = vocab_size,
|
|
||||||
specials = ['<unk>'])
|
|
||||||
|
|
||||||
vocab['jest']
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
#+RESULTS:
|
|
||||||
:results:
|
|
||||||
16
|
|
||||||
:end:
|
|
||||||
|
|
||||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
|
||||||
vocab.lookup_tokens([0, 1, 2, 10, 12345])
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
#+RESULTS:
|
|
||||||
:results:
|
|
||||||
['<unk>', '</s>', '<s>', 'w', 'wierzyli']
|
|
||||||
:end:
|
|
||||||
|
|
||||||
*** Definicja sieci
|
|
||||||
|
|
||||||
Naszą prostą sieć neuronową zaimplementujemy używając frameworku PyTorch.
|
|
||||||
|
|
||||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
|
||||||
from torch import nn
|
|
||||||
import torch
|
|
||||||
|
|
||||||
embed_size = 100
|
|
||||||
|
|
||||||
class SimpleBigramNeuralLanguageModel(nn.Module):
|
|
||||||
def __init__(self, vocabulary_size, embedding_size):
|
|
||||||
super(SimpleBigramNeuralLanguageModel, self).__init__()
|
|
||||||
self.model = nn.Sequential(
|
|
||||||
nn.Embedding(vocabulary_size, embedding_size),
|
|
||||||
nn.Linear(embedding_size, vocabulary_size),
|
|
||||||
nn.Softmax()
|
|
||||||
)
|
|
||||||
|
|
||||||
def forward(self, x):
|
|
||||||
return self.model(x)
|
|
||||||
|
|
||||||
model = SimpleBigramNeuralLanguageModel(vocab_size, embed_size)
|
|
||||||
|
|
||||||
vocab.set_default_index(vocab['<unk>'])
|
|
||||||
ixs = torch.tensor(vocab.forward(['pies']))
|
|
||||||
out[0][vocab['jest']]
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
#+RESULTS:
|
|
||||||
:results:
|
|
||||||
:end:
|
|
||||||
|
|
||||||
Teraz wyuczmy model. Wpierw tylko potasujmy nasz plik:
|
|
||||||
|
|
||||||
#+BEGIN_SRC
|
|
||||||
shuf < opensubtitlesA.pl.txt > opensubtitlesA.pl.shuf.txt
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
|
||||||
from torch.utils.data import IterableDataset
|
|
||||||
import itertools
|
|
||||||
|
|
||||||
def look_ahead_iterator(gen):
|
|
||||||
prev = None
|
|
||||||
for item in gen:
|
|
||||||
if prev is not None:
|
|
||||||
yield (prev, item)
|
|
||||||
prev = item
|
|
||||||
|
|
||||||
class Bigrams(IterableDataset):
|
|
||||||
def __init__(self, text_file, vocabulary_size):
|
|
||||||
self.vocab = build_vocab_from_iterator(
|
|
||||||
get_word_lines_from_file(text_file),
|
|
||||||
max_tokens = vocabulary_size,
|
|
||||||
specials = ['<unk>'])
|
|
||||||
self.vocab.set_default_index(self.vocab['<unk>'])
|
|
||||||
self.vocabulary_size = vocabulary_size
|
|
||||||
self.text_file = text_file
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return look_ahead_iterator(
|
|
||||||
(self.vocab[t] for t in itertools.chain.from_iterable(get_word_lines_from_file(self.text_file))))
|
|
||||||
|
|
||||||
train_dataset = Bigrams('opensubtitlesA.pl.shuf.txt', vocab_size)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
#+RESULTS:
|
|
||||||
:results:
|
|
||||||
:end:
|
|
||||||
|
|
||||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
|
||||||
from torch.utils.data import DataLoader
|
|
||||||
|
|
||||||
next(iter(train_dataset))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
#+RESULTS:
|
|
||||||
:results:
|
|
||||||
(2, 5)
|
|
||||||
:end:
|
|
||||||
|
|
||||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
|
||||||
from torch.utils.data import DataLoader
|
|
||||||
|
|
||||||
next(iter(DataLoader(train_dataset, batch_size=5)))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
#+RESULTS:
|
|
||||||
:results:
|
|
||||||
[tensor([ 2, 5, 51, 3481, 231]), tensor([ 5, 51, 3481, 231, 4])]
|
|
||||||
:end:
|
|
||||||
|
|
||||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
|
||||||
device = 'cuda'
|
|
||||||
model = SimpleBigramNeuralLanguageModel(vocab_size, embed_size).to(device)
|
|
||||||
data = DataLoader(train_dataset, batch_size=5000)
|
|
||||||
optimizer = torch.optim.Adam(model.parameters())
|
|
||||||
criterion = torch.nn.NLLLoss()
|
|
||||||
|
|
||||||
model.train()
|
|
||||||
step = 0
|
|
||||||
for x, y in data:
|
|
||||||
x = x.to(device)
|
|
||||||
y = y.to(device)
|
|
||||||
optimizer.zero_grad()
|
|
||||||
ypredicted = model(x)
|
|
||||||
loss = criterion(torch.log(ypredicted), y)
|
|
||||||
if step % 100 == 0:
|
|
||||||
print(step, loss)
|
|
||||||
step += 1
|
|
||||||
loss.backward()
|
|
||||||
optimizer.step()
|
|
||||||
|
|
||||||
torch.save(model.state_dict(), 'model1.bin')
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
#+RESULTS:
|
|
||||||
:results:
|
|
||||||
None
|
|
||||||
:end:
|
|
||||||
|
|
||||||
Policzmy najbardziej prawdopodobne kontynuację dla zadanego słowa:
|
|
||||||
|
|
||||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
|
||||||
device = 'cuda'
|
|
||||||
model = SimpleBigramNeuralLanguageModel(vocab_size, embed_size).to(device)
|
|
||||||
model.load_state_dict(torch.load('model1.bin'))
|
|
||||||
model.eval()
|
|
||||||
|
|
||||||
ixs = torch.tensor(vocab.forward(['dla'])).to(device)
|
|
||||||
|
|
||||||
out = model(ixs)
|
|
||||||
top = torch.topk(out[0], 10)
|
|
||||||
top_indices = top.indices.tolist()
|
|
||||||
top_probs = top.values.tolist()
|
|
||||||
top_words = vocab.lookup_tokens(top_indices)
|
|
||||||
list(zip(top_words, top_indices, top_probs))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
#+RESULTS:
|
|
||||||
:results:
|
|
||||||
[('ciebie', 73, 0.1580502986907959), ('mnie', 26, 0.15395283699035645), ('<unk>', 0, 0.12862136960029602), ('nas', 83, 0.0410110242664814), ('niego', 172, 0.03281523287296295), ('niej', 245, 0.02104802615940571), ('siebie', 181, 0.020788608118891716), ('którego', 365, 0.019379809498786926), ('was', 162, 0.013852755539119244), ('wszystkich', 235, 0.01381855271756649)]
|
|
||||||
:end:
|
|
||||||
|
|
||||||
Teraz zbadajmy najbardziej podobne zanurzenia dla zadanego słowa:
|
|
||||||
|
|
||||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
|
||||||
vocab = train_dataset.vocab
|
|
||||||
ixs = torch.tensor(vocab.forward(['kłopot'])).to(device)
|
|
||||||
|
|
||||||
out = model(ixs)
|
|
||||||
top = torch.topk(out[0], 10)
|
|
||||||
top_indices = top.indices.tolist()
|
|
||||||
top_probs = top.values.tolist()
|
|
||||||
top_words = vocab.lookup_tokens(top_indices)
|
|
||||||
list(zip(top_words, top_indices, top_probs))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
#+RESULTS:
|
|
||||||
:results:
|
|
||||||
[('.', 3, 0.404473215341568), (',', 4, 0.14222915470600128), ('z', 14, 0.10945753753185272), ('?', 6, 0.09583134204149246), ('w', 10, 0.050338443368673325), ('na', 12, 0.020703863352537155), ('i', 11, 0.016762692481279373), ('<unk>', 0, 0.014571071602404118), ('...', 15, 0.01453721895813942), ('</s>', 1, 0.011769450269639492)]
|
|
||||||
:end:
|
|
||||||
|
|
||||||
#+BEGIN_SRC python :session mysession :exports both :results raw drawer
|
|
||||||
cos = nn.CosineSimilarity(dim=1, eps=1e-6)
|
|
||||||
|
|
||||||
embeddings = model.model[0].weight
|
|
||||||
|
|
||||||
vec = embeddings[vocab['poszedł']]
|
|
||||||
|
|
||||||
similarities = cos(vec, embeddings)
|
|
||||||
|
|
||||||
top = torch.topk(similarities, 10)
|
|
||||||
|
|
||||||
top_indices = top.indices.tolist()
|
|
||||||
top_probs = top.values.tolist()
|
|
||||||
top_words = vocab.lookup_tokens(top_indices)
|
|
||||||
list(zip(top_words, top_indices, top_probs))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
#+RESULTS:
|
|
||||||
:results:
|
|
||||||
[('poszedł', 1087, 1.0), ('idziesz', 1050, 0.4907470941543579), ('przyjeżdża', 4920, 0.45242372155189514), ('pojechałam', 12784, 0.4342481195926666), ('wrócił', 1023, 0.431664377450943), ('dobrać', 10351, 0.4312002956867218), ('stałeś', 5738, 0.4258835017681122), ('poszła', 1563, 0.41979148983955383), ('trafiłam', 18857, 0.4109022617340088), ('jedzie', 1674, 0.4091658890247345)]
|
|
||||||
:end:
|
|
Binary file not shown.
Before Width: | Height: | Size: 77 KiB |
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
Loading…
Reference in New Issue
Block a user