Menu Zamknij

Jak maszyny piszą teksty: Modele językowe

modele językowe

Witam Was w trzecim poście z serii “Jak maszyny piszą teksty”. Rozmawialiśmy już o wektoracho sieciach neuronowych. Jeśli ktoś nie jest w temacie, to zdecydowanie polecam mu przeczytać te dwa wpisy, zanim zabierze się do dzisiejszego. A dziś powiemy sobie o modelach językowych.

Co to jest model języka?

Najprościej mówiąc, jest to zbiór ogólnych zasad rządzących językiem. Na przykład modele składniowe mówią nam o zasadach składni, takich jak to, że na początku zdania mamy podmiot, potem orzeczenie, a potem całą resztę.

Na potrzeby generacji języka również budujemy coś w rodzaju modelu składniowego. Konkretnie to musimy nauczyć naszą sieć neuronową, jakie słowa występują po sobie w szeregu. Dzięki temu potem możemy podać jej dowolne słowo i patrzeć, jak samodzielnie dodaje ona kolejne, tworząc nowe zdania 🙂

Tak właśnie określimy sobie nasz cel: nauczyć sieć kolejności występowania słów. To sprawi, że sieć nauczy się kolokacji, częstych powiedzonek, utartych fraz i tym podobnych i potem będzie je sklejała ze sobą w zdania. Tak samo jak człowiek uczy się obcego języka poprzez obserwowanie, jak posługują się nim rodzimi użytkownicy.

Jak się do tego zabrać?

Jak już mamy nasz cel, to po kolei omówmy sobie kroki, które nas do tego celu doprowadzą.

Przygotowanie danych treningowych

Jak mówiliśmy sobie w poprzednim wpisie, sieć neuronowa wymaga treningu. Wykorzystujemy do tego tak zwane dane treningowe – w przeciwieństwie do danych testowych, na których testujemy naszą sieć i które nie są wykorzystywane do uczenia (w ten sposób sprawdzamy, czy sieć daje sobie radę z nowymi problemami, których wcześniej nie napotkała).

Dane treningowe mają postać zbioru tekstów. Podobnie jak w przypadku trenowania wektorów, im większy zbiór, tym lepiej. Możemy na przykład wykorzystać zasoby biblioteczne w formie ebooków. Pamiętajmy, że dobór tekstów jest bardzo ważny – w zależności od tego, na co postawimy, sieć będzie produkowała inne zdania. Dlaczego? Ponieważ sposób, w jaki uczymy sieć, sprawia, że imituje ona styl podanych jej tekstów. Z tego powodu inaczej będzie “mówiła” sieć uczona na wierszach Szekspira, a inaczej ta, która “przeczytała” transkrypcje obrad sejmowych.

Słownik

O słowniku też już sobie mówiliśmy. Dla powtórzenia: jako słownik definiujemy zbiór wszystkch słów występujących w naszych tekstach i zwykle ograniczamy go do 10-15 tysięcy najczęściej występujących elementów. Słowa będą zaprezentowane albo za pomocą one-hot wektorów, albo wektorów typu word2vec. Jak wiemy z poprzednich wpisów, word2vec lepiej sobie radzi z przekazywaniem informacji o relacjach między słowami, dlatego będzie tu lepszym rozwiązaniem jako dane wejściowe.

Nasz słownik oprócz słów będzie zawierał jeszcze dwa inne tajemnicze elementy: <UNK> oraz <EOS>.

<UNK> jest skrótem od unknown word i oznacza wszystkie te słowa, które nie zmieściły się w słowniku, czyli w tych najczęstszych 10 tysiącach. Bo czasem tak będzie, że w naszych tekstach jakieś słowo wystąpi tylko raz czy dwa, więc nie ma sensu włączać go do procesu treningowego. Do słownika wybieramy tylko najczęściej występujące wyrazy, bo zakładamy, że to one są tu najważniejsze. Ale przy podawaniu tekstu komputerowi musimy jakoś zareprezentować również te słowa, których w słowniku nie ma. Dla nich właśnie tworzymy dodatkowe oznaczenie <UNK> i zastępujemy nim te słowa w procesie uczenia. A jak zareprezentować <UNK> w formie liczbowej? Przy one-hot wektorach każdy wektor ma długość słownika i jedynka występuje w miejscu oznaczanego słowa. Dlatego wystarczy do długości słownika dodać kolejny element specjalnie właśnie dla <UNK>. Czyli teraz nasz słownik zamiast 10,000 elementów będzie miał ich 10,001. W przypadku wektorów typu word2vec najczęściej tworzy się wektor o losowych wartościach, chociaż niektóre modele word2vec dają nam reprezentację również dla elementów <UNK>, którą możemy wykorzystać.

<EOS> to skrót od end of sentence i zwykle wstawia się go w miejsce kropki. Jak sama nazwa wskazuje, jest to element zaznaczający koniec zdania. Jako wektor będzie on reprezentowany analogicznie jak <UNK>. Możemy użyć też podobnych mu elementów, na przykład oznaczających koniec paragrafu lub koniec całego dokumentu. Wszystko zależy od tego, jak długi tekst chcemy stworzyć. O elementach typu <EOS> wspomnę w dalszej części przy okazji opisywania generacji tekstu.

Jak już mówimy o końcu zdania, to powiedzmy sobie również o znakach przestankowych takich jak kropka, przecinek czy wykrzyknik. Niektórzy ignorują je zupełnie i swoje teksty treningowe reprezentują jedynie jako ciąg wyrazów, elementów <UNK> oraz elementów <EOS>. Inni jednak uznają, że przyda im się wzięcie przecinków pod uwagę, i dla nich tworzą dodatkowe reprezentacje. Podobnie jak <UNK>, znaki przestankowe mogą mieć już ustalone wektory dzięki algorytmowi word2vec.

Trenowanie sieci

Pamiętacie, o czym mówiliśmy sobie w poprzednim wpisie? Jeśli nie, to przypomnę najważniejsze informacje:

  1. Rekurencyjna sieć neuronowa przyjmuje słowa jedno po drugim w sekwencji jako dane wejściowe.
  2. Sieć rekurencyjna składa się z pudełek, które zawsze przyjmują dwa rodzaje danych i zawsze produkują dwa rodzaje danych.
  3. Sieć neuronowa uczy się poprzez porównywanie tego, co jej wyszło, z tym, co powinno wyjść.

I co to oznacza dla naszego procesu treningowego?

Pamiętacie, jak w poprzednim wpisie przy klasyfikacji emocji tekstu po kolei wkładaliśmy do sieci słowa, a na końcu otrzymaliśmy wynik oznaczający konkretną emocję?klasyfikacja sieć rekurencyjna

Tak właśnie najczęściej działają sieci neuronowe: mamy dane treningowe będące danymi wejściowymi, z których na końcu powinno nam coś wyjść. Przy modelach językowych jest trochę inaczej. Otóż tutaj dane treningowe to wzór danych wyjściowych, czyli to, co chcemy od sieci otrzymać. No, ale przecież wiemy, że każde pudełko przyjmuje też dane wejściowe, a skąd je wziąć, skoro dane treningowe mają być na wyjściu? Otóż danymi wejściowymi do każdego pudełka będzie słowo wyrzucone przez poprzednie pudełko. Bo chcemy przecież, żeby sieć nauczyła się kolejności słów, tak? Czyli chcemy, żeby po jakimś słowie wyrzucała nam słowo najczęściej po nim występujące, a potem następne najczęściej występujące i tak dalej. W związku z tym proces treningu będzie wyglądał jak na poniższej ilustracji.

model językowy

Prześledźmy to sobie krok po kroku. Pierwszemu pudełku, które tak jak każde inne pudełko musi dostać jakieś dane, podajemy dwa wektory, zwykle składające się z samych zer, jako niby-dane z poprzedniego pudełka (strzałka granatowa) i niby-dane wejściowe (strzałka niebieska). Pierwsze pudełko produkuje nam z tego dane dla kolejnego pudełka (strzałka granatowa), które wykorzystujemy zgodnie z ich przeznaczeniem. Produkuje nam też dane wyjściowe w postaci pierwszego słowa, które sobie bierzemy i wkładamy do następnego pudełka jako kolejne słowo danych wejściowych (strzałka niebieska). Pudełko numer dwa przyjmuje to słowo i produkuje następne. To następne słowo wkładamy do pudełka trzeciego, które produkuje słowo kolejne, i tak dalej aż do końca. Potem sieć porównuje sobie wyprodukowany przez siebie ciąg wyrazów z ciągiem faktycznym i po modyfikacji obliczeń próbuje znowu.

Zauważcie, że wszystkie wyżej wymienione punkty nadal mają zastosowanie do tego procesu.

  1. Sieć przymuje jedno słowo po drugim, z tym, że w modelu językowym są to dane wyjściowe z każdego pudełka, a nie dane treningowe.
  2. Każde pudełko działa tak, jak należy, czyli przyjmuje dwa rodzaje danych i produkuje dwa rodzaje danych.
  3. Sieć porównuje produkowany przez siebie ciąg słów z tym, jak wygląda tekst napisany przez człowieka w postaci danych treningowych. Na tej podstawie modyfikuje swoje obliczenia.

Ale jak sieć oblicza, które słowo powinno być następne?

Jak wielokrotnie powtarzałam, każde pudełko dostaje dane wejściowe w postaci reprezentacji wektorowej jakiegoś słowa i produkuje dane wyjściowe również w postaci wektora. I teraz tak: dane wejściowe właściwie zawsze będą wektorem typu word2vec. Natomiast dane wyjściowe mogą być albo typu word2vec, albo typu one-hot. Tak, to drugie oznacza, że dla każdego słowa w słowniku będziemy musieli mieć obie reprezentacje, żeby odpowiednio ich używać.

W przypadku, gdy na wyjściu mamy wektory typu word2vec, każde pudełko sieci wyprodukuje wektor o takiej długości, jaką mają nasze wektory słów, czyli na przykład 300. Trzeba jednak zauważyć, że sieć dopiero się uczy, więc wyrzucony przez nią wektor może lekko różnić się od wektorów, które mamy w naszych danych. W związku z tym nie znajdziemy w słowniku wektora idealnie odpowiadającego temu, co dała nam sieć. Co w tej sytuacji zrobić? Ano możemy znaleźć wektor najbliższy, czyli najbardziej podobny. Konkretnie będzie to ten, który ma najmniejszą różnicę kosinusów względem naszego wektora wyjściowego. Słowo odpowiadające temu wektorowi wybieramy więc jako słowo wyjściowe z tego pudełka i podajemy je dalej jako dane wejściowe do pudełka następnego.

W przypadku wektorów one-hot każde pudełko sieci wyprodukuje wektor o długości słownika, ale z początku wektor ten nie będzie wypełniony samymi zerami i jedynką. Raczej będą to bardzo różne liczby. Spośród nich musimy zatem wybrać tę największą i zamienić ją na jedynkę, a resztę na zera. A jak wybieramy tę największą?

Wow, ale głupie pytanie, co nie? No jak to jak, normalnie 😀

Otóż nie!

W programowaniu do wybierania największego elementu wektora służą nam właściwie dwie funkcje: (hard) max oraz softmax. Funkcja hard max rzeczywiście daje nam po prostu najwyższą wartość wśród liczb wektora. Funkcja softmax natomiast najpierw przelicza wszystkie wartości razem i dla każdego elementu daje nam wysokość prawdopodobieństwa, że jest to największy element. Oczywiście wysokości tych prawdopodobieństw dodają się razem do jedynki, więc jest to rodzaj normalizacji danych.

hardmax softmax

Tak, softmax da nam ten sam element, co funkcja hard max. Ale rozkład prawdopodobieństwa jest dla nas bardziej przydatny niż po prostu największa wartość. Po pierwsze, ogólnie mówiąc, funkcji hard max nie możemy wykorzystać do trenowania sieci, bo nie można z niej obliczyć odpowiednich rzeczy, żeby potem móc przejść przez sieć od końca i dostosować wartości Wb. Powody matematyczne 😉 Funkcja softmax szczęśliwie takich ograniczeń nie posiada. Po drugie, prawdopodobieństwo jest dla nas przydatne przy produkcji tekstu, o czym poniżej.

Generacja tekstu

Zastanówmy się, jak możemy wykorzystać to, czego nauczyła się nasza sieć. A jest to prawdopodobieństwo wystąpienia pewnej sekwencji słów. Inaczej mówiąc, dla każdego wyrazu dostajemy prawdopodobieństwo, że wystąpi on na danej pozycji w sekwencji. Ponieważ nasze sieciowe pudełka komunikują się również między sobą, to prawdopodobieństwo to nie jest obliczane tylko na podstawie wyrazu poprzedniego, ale na podstawie wszystkich wyrazów występujących na wcześniejszych pozycjach.

Produkowanie sekwencji zaczyna się tak samo, jak trening. Na początku dajemy pierwszemu pudełku dwa wektory pseudo-danych i czekamy na wynik. Tym wynikiem jest wektor z rozkładem prawdopodobieństwa dotyczącym tego, które słowo ze słownika może być pierwsze. I teraz z tego wektora wyciągamy to słowo. Niekoniecznie to, dla którego prawdopodobieństwo jest największe. Raczej losowo wybieramy słowo ze słownika z wykorzystaniem wartości prawdopodobieństwa. To oznacza, że słowo o najwyższym prawdopodobieństwie nie ma 100% zagwarantowane, że zostanie wybrane, ale raczej ten procent pochodzi z funkcji softmax. Czyli posługując się przykładem z rysunku powyżej, będzie to 13% szans, że zostanie wybrane. Inne słowa mają mniejsze prawdopodobieństwo, na przykład 8% albo 10%.

Słowo, które wybraliśmy, zamieniamy na reprezentację word2vec i wkładamy do kolejnego pudełka jako dane wejściowe. Z tego pudełka otrzymujemy rozkład prawdopodobieństwa dotyczący kolejnego słowa, które znowu losujemy. I tak dalej, i tak dalej, aż nam się proces zakończy.generacja tekstuA co jeśli nie mamy rozkładu prawdopodobieństwa, bo wykorzystaliśmy jako dane wyjściowe wektory typu word2vec? Ano nic, po prostu za pomocą opisanej wcześniej metody wybieramy słowo reprezentowane przez wektor najbliższy temu, który nam wypadł z pudełka. Możemy też określić pożądany stopień podobieństwa i losować ze wszystkich wektorów, które się w tym ograniczeniu mieszczą. Zdecydowanie jednak częściej spotyka się one-hot wektory jako dane wyjściowe.

A gdzie w tym wszystkim <UNK> i <EOS>?

Gdzie <UNK>? Miejmy nadzieję, że nigdzie. No bo przecież nie chcemy, żeby nasz tekst pomiędzy zwykłymi słowami miał jakieś <UNK>. Ten element wykorzystywany jest raczej przy treningu z konieczności reprezentacji każdego słowa w tekście. Przy produkcji tekstu całkowicie go pomijamy. To znaczy, że jeśli wypadnie nam <UNK>, to je zwyczajnie odrzucamy i losujemy jeszcze raz.

<EOS> z kolei jest bardzo istotnym elementem. Oznacza on bowiem koniec zdania, który może być również końcem naszej produkcji. Bo przecież jeśli nie określimy końca, to nasza sieć może produkować i produkować 🙂 Dlatego warto zawczasu powiedzieć sieci, że jeśli napotka coś w stylu <EOS>, to ma zakończyć pracę. Zamiast <EOS> jako sygnału końca możemy także mieć <EOP> (end of paragraph, koniec akapitu) albo <EOT> (end of text, koniec tekstu), a samo <EOS> wykorzystywać tylko do ustalania granic między produkowanymi zdaniami. Przy produkcji wszystkie <EOS> możemy od razu zamienić na kropki.

Czego się dzisiaj dowiedzieliśmy

  1. Przy tworzeniu sieci neuronowej produkującej tekst tak naprawdę tworzymy model językowy.
  2. Sieć imituje styl tekstów, na których się uczyła.
  3. W naszym słowniku oprócz słów muszą znaleźć się specjalne elementy, przede wszystkim <UNK> oraz <EOS>.
  4. W sieci produkującej tekst jako dane wejściowe do kolejnego pudełka wykorzystujemy dane wyjściowe z pudełka poprzedniego.
  5. Model językowy tworzony przez sieć to model probabilistyczny, oparty na obliczaniu prawdopodobieństwa wystąpienia kolejnego słowa pod warunkiem wystąpienia sekwencji słów poprzednich. Te prawdopodobieństwa obliczamy za pomocą funkcji softmax.
  6. Produkcja tekstu odbywa się poprzez losowanie kolejnego słowa w sekwencji z uwzględnieniem wyuczonych wartości prawdopodobieństwa.

Jak widać, tak naprawdę proces generowania tekstu nie jest skomplikowany. Po prostu obliczamy prawdopodobieństwo, losujemy sobie i tyle. Po zakończeniu produkcji zwykle niezbędne jest doprowadzenie tekstu do porządku, na przykład poprzez wstawienie wielkich liter na początek zdania albo dopracowanie szczegółów gramatycznych. Oczywiście nawet po wygładzeniu tekst może nie mieć żadnego sensu, tak jak wspomniany już przeze mnie rozdział Harry’ego Pottera. Jednak zachowany pozostaje styl dokumentów, na których sieć się uczyła. To znaczy więc, że możemy na przykład wyprodukować poezję na wzór Szekspira, nawet jeśli miałaby ona przypominać zbiór przypadkowych wyrazów 😀

Zatem dzisiaj omówiliśmy sobie najprostszy model sieci generującej oryginalny tekst. Istnieją oczywiście inne zastosowania takich sieci, na przykład tworzenie tłumaczeń albo transkrypcja mowy. Te kwestie omówimy sobie w kolejnym, ostatnim już wpisie z tego cyklu.

Pytania? Skomentuj!

Podziel się tym wpisem w mediach społecznościowych

Powiązane wpisy

Powiadamiaj o
Powiadom o
guest
1 Komentarz
najstarszy
najnowszy oceniany
Inline Feedbacks
View all comments
trackback

[…] sieć rekurencyjną do tworzenia własnego tekstu? Tego dowiecie się z następnego wpisu. Tymczasem, jeśli macie pytania dotyczące działania sieci neuronowych, […]

Powrót do góry strony