Rymogenerator

Generator zobacz też:   generator limeryków  i  niezdeterminowanie przekładu .

Ostrzeżenie

Tym razem będzie stricte technicznie.
Wynurzeń filozoficznych (jak przy niezdeterminowaniu przekładu) brak.

Ogólna idea

Miało to działać mniejwięcej tak:

Przymiotnik rzeczownik[-rym1],
przymiotnik rzeczownik[-rym2],
przymiotnik rzeczownik[-rym1],
przymiotnik rzeczownik[-rym2].

Przetwarzanie języka naturalnego i korpusy językowe

Jednym z moich poważniejszych zainteresowań jest dziedzina sztucznej inteligencji zwana przetwarzaniem języka angielskiego, lub w skrócie NLP (natural language programming). To na kanwie tych zainteresowań zająłem się w ogóle pisaniem generatorów, moje poprzednie (opublikwoane) generatory jednak tak na prawdę nie korzystały w pełni z narzędzi jakie oferuje informatyka, były raczej prostą implementacją gramatyk generatyw­nych, co było jednak zamierzone. Od dawna jednak eksperymentowałem z programowaniem generatorów wykorzystujących korpusy językowe.

O korpusy języka polskiego przez pewien czas było stosunkowo trudno. Istniał co prawda korpus polszczyzny lat 60 IPI PAN, ale narzędzia dla niego były przeznaczone głównie do bezpośredniego przeglądania i przeszukiwania korpusu, a nie do pisania generatorów. Sytuację poprawiło jednak pojawienie się Pythonowej biblioteki NLTK (Natural Language Toolkit), w skład której weszło wiele korpusów, w tym właśnie korpus polszczyzny lat 60.

Co ważne, korpusy o których mówią to korpusy z oznaczeniami gramatycznymi. Oznacza to że w przeciwieństwie do na przykład bazy danych portalu Wolne Lektury, na których bazuje leśmianator, każde słowo ma tu oznaczenia, jaką jest częścią mowy i w jaką ma formę. W wersji korpusu wchodzącej w skład NLTK dostępna jest w szczególności funkcja nltk.corpus.pl196x.tagged_words(), zwracająca listę wszystkich słów korpusu w formie par (słowo, oznaczenia).

Co właściwie zrobiłem?

Uruchomiłem interpteter Pythona, zaimportowałem bibliotekę NLTK i przefiltrowałem korpus, uzyskując dwie listy słów: rzeczowników w mianowniku liczby pojedyńczej, oraz przymiotników w tej samej formie gramatycznej. Używając wyrażenia regularnego dopa­sowującego sylabę przefiltrowałem te listy zostawiając tylko słowa czterosylabowe (tj. mające dokładnie 4 dopasowania wyrażenia regularnego). Następnie pozbyłem się duplikatów (rzutując na zbiór i z powrotem na listę: list(set(…))), uprzednio zamieniając wszystko na małe litery:

map(lambda slowo: unicode(slowo, 'utf-8').lower(), d)

Ponieważ wiersze miały się rymować napisałem prostą funkcję wyodrębniającą rym:

lambda slowo: re.compile(
	ur"[aoieyuąę][^aoieyuąęó]*(?:(?:i?[aoeyuąęó])|i)[^aoieyuąęó]*$",
	re.I | re.U
).findall(slowo)[0]

Rzeczowniki podzieliłem dalej na grupy według rymu i wygenerowałem sobie z nich plik JS z całą strukturą w formacie JSON. Z przymiotnikami postąpiłem podobnie, tylko zamiast według rymu podzieliłem je na męskie, żeńskei i nijakie według oznaczeń gramatycznych. Rzeczowniki też na takie grupy podzieliłem, ale już ręcznie (trzeba było uwzględnić sytuację, gdy ten sam rym zawiera zarówno rzeczowniki żeńskie, jak i męskie żeńskiej deklinacji).

Potem uruchomiłem interpreter Node.js i zaimportowałem strukturę z rzeczownikikami i policzyłem w kilku liniach kodu ilość słów dla każdego rodzaju rzeczowników. Po odrobinie ekwilibrystyki z JavaScriptem i edytorem kodu uzyskałem ostateczną strukturę danych:

var rzeczowniki = {
	m: [91, {
		'alizm': […],
		'unek': […],
		'ony': […],
		etc.
	}],
	f: [248, {
		etc.
	}],
	n: [402, {
		etc.
	}]
};

(i oczywiście drugą, nieco prostszą, dla przymiotników).

Generowanie

Skrypt wybiera słowa, najpierw rzeczownik, albo losowy, albo zgodny z zadaną formą, a potem przymiotnik w tym samym rodzaju gramatycznym. Konstytuuje to wers. Dla wersów 1 i 2 strofki rodzaj i rym jest losowy, dla wersów 3 i 4 zgodny z odpowiednio wersem 1. i 2. Każdy wers dostaje na końcu przecinek, a ostatni kropkę, lub inny zdefiniowany znak. Pierwsza litera pierwszego wersu jest zamieniana na wielką. Ostatecznie moduł generujący zwraca strofkę wiersza w formie tablicy czteroelementowej.

Plik HTML generatora zawiera funkcję wywołującą generator dwa razy i generującą w ten sposób dwie strofki, pierwszą ze średnikiem na końcu, drugą z domyślną kropką. Wersy są w nich następnie łączone <br>'ami, a same strofki łączone są znacznikiem <p>. Tyle.

Efekt możecie podziwiać tutaj.

Surowe listy słów

Wybaczcie, tym razem ładnych tabelek nie zrobię, bo słów w każdej kategorii jest po kilkaset. Zamiast tego link do źródła skryptu generującego: rymgen.js (utf-8).

No chyba, że bardzo chcecie: .

ja, bo ja