dev | ||
negativeSongs | ||
piosenki | ||
testSet | ||
trockizm | ||
war | ||
dev.txt | ||
main.py | ||
model.model | ||
perf | ||
pred.pred | ||
prepareTestFiles.py | ||
readme.md | ||
requirements.txt | ||
stalin_trockizm_crawler.py | ||
stalin_war_crawler.py | ||
stats.py | ||
test2.txt | ||
test.actual | ||
test.ap | ||
test.ap01 | ||
test.txt | ||
vowpal.txt | ||
vowpal.txt.cache | ||
vowpalize.py |
Czy tekst jest rewolucyjnym marszem wojennym lub przemową?
Od dawna zastanawiałem się nad stworzeniem małej gry w której wcielamy się w rolę propagandzisty totalitarnego państwa (takiego jak fikcyjna Arstotzka z Papers Please). Gra miałaby polegać na pisaniu odpowiednich przemów i tekstów marszy zależenie od sytuacji. W ramach projektu postanowiłem spróbować wykonać chociaż jeden z komponentów.
Sprawdzamy czy nasz tekst nadaje się do dalszej analizy poprzez binarną klasyfikację czy jest to marsz, przemowa lub nie.
Moje dane pochodzą głównie ze strony http://www.sovmusic.ru/english/, gdzie ktoś przez wiele lat gromadził teksty komunistycznych pieśni.
Drugim, mniejszym źródłem była strona na której umieszczone zostały przetłumaczone na angielski rozkazy i pisma Stalina z drugiej wojny światowej.
https://www.marxists.org/reference/archive/stalin/works/decades-index.htm. Jest tego dużo, więc zrobiłem bota do ściągania danych stalin_war_crawler.py
i stalin_trockizm_crawler.py
Przykłady innych tekstów, normalnych piosenek znalazłem na https://github.com/Lyrics/lyrics
Największym problemem okazał się format zbioru piosenek z rosyjskiej strony - jest to ogromny, tabelkowy dokument .html, z którego było cięzko wyciągnąć teksty. Do tego, teksty są w różnych językach - trzeba było je znormalizować do jednego języka. Wybrałem angielski, bo google translate najlepiej działa na tym języku. W celu masowego tłumaczenia napisałem skrypt (piosenki/translate.py
) wyciągający teksty i używający popularnego exploita w google translate poprzez bibliotekę googletrans
do pythona (pip3 install googletrans
). Przetłumaczone teksty są zapisywane do osobnych plików.
To samo musiałem zrobić z innymi tekstami, bo twórca zbioru danych był wielkim fanem niemieckiego metalu i około 1/3 tekstów była po niemiecku.
Skrypt vowpalize.py
wyciąga teksty, usuwa znaki nowych linii oraz znaki interpunkcyjne zostawiając tylko słowa. Następnie taki tekst jest tokenizowany i lematyzowany przez Spacy. Lista zlematyzowanych słów jest zapisywana do formatu vowpala. Marsze mają label +1 a inne teksty -1.
Bez czyszczenia i lematyzowania wyniki były około 2-3 razy gorsze.
Do trenowania modelu w vowpalu użyłem kilku podejść:
-
vw --binary vowpal.txt --passes 20 -c -k -f model.model
, ale average loss na zestawie dev wynosił ponad 4%. -
vw --binary vowpal.txt --passes 5 -c -k -f model.model --loss_function logistic -b 24 --nn 20
dodając ukryte warstwy do sieci neuronowej udało mi się zejśc do około 2% average loss. Dodanie opcji--inpass
, która dodaje połaczenie z neuronu na wejściu do wyjścia pogorszyła wyniki average loss o 1 punkt procentowy. Zmiana funckcji straty na hinge także znacznie pogarsza wyniki. Nim więcej warstw ukrytych w sieci tym lepsze wyniki, aż do granicy 20. Nim mniejszy weight array tym gorzej (-b
) Liczba przejśc nie wpływa na wynik. Zostawiam niski żeby szybciej trenowało i nie overfitowało
Dodatkowe features takie jak np liczba nazw geograficznych nie podniosłaby znacznie dokładności a mogłaby być myląca. Dla przykładu, poniższe fragmenty starego polskiego marszu "za Bug" zawierają dużo nazw geograficznych
Warczy próg Dniepru, pomrukuje Dźwina,
Wyje stepami polska Ukraina,
Dyszy niechęcią bagniste Polesie,
Burzami grożą naddniestrzańskie skały,
Lesista Litwa dąsa się i ćmi się
Na nasz pochód opieszały.
Wodzu nasz Janie! białe orły żebrzą
Ogniem Grochowa i Wawru napadem,
Niech się coprędzej brzegi Bugu srebrzą
Uderzcie w bębny, zagrajcie nam w rogi.
Uderzcie w bębny, zagrajcie nam w rogi,
Za Bug, za Bug, za Bug, za Bug, za Bug, za Bug!
Niech lotne serce nie wyprzedza nogi,
Oraz popularna szanta "hiszpańskie dziewczyny"
Niedługo ujrzymy znów w dali Cape Deadman
I Głowę Baranią sterczącą wśród wzgórz,
I statki stojące na redzie przed Plymouth.
Klarować kotwicę najwyższy czas już.
I smak waszych ust, hiszpańskie dziewczyny,
...
I znów białe żagle na masztach rozkwitną,
Kurs szyper wyznaczy do Portland i Wright,
I znów stara łajba potoczy się ciężko
Przez fale w kierunku na Beachie, Fairlee Light.
Zabłysną nam bielą skał zęby pod Dover
I znów noc w kubryku wśród legend i bajd.
Powoli i znojnie tak płynie nam życie
Na wodach i w portach przy South Foreland Light.
Mimo wielu nazw w obu tekstach, są one klasyfikowane na dwie różne klasy. Dodanie feature z liczbą nazw mogłoby wprowadzić program w błąd.
Ostatecznie wyniki
$python3 stats.py
Precision: 0.9636363636363636
Recall: 1.0
f1: 0.9814814814814815
Żeby samemu wszsytko włączyć:
$python3 vowpalize.py
$python3 prepareTestFiles.py
$vw --binary vowpal.txt --passes 5 -c -k -f model.model --loss_function logistic -b 24 --nn 20
$vw -t -i model.model -p pred.pred test.txt
$python3 stats.py
Jest zbiór test i dev. Co ciekawe, na test wychodzą lepsze wyniki niż na dev 😂