zondag 29 november 2015

LSTMs met Nietzsche

Long Short Time Memory is een meer uitontwikkelde vorm van RNN (Recurrent Neural Network) en met name bruikbaar voor tijd of 'volgordelijkheid' gerelateerde informatie. Een aardig voorbeeld is het automatisch genereren van nieuwe tekst op basis van bestaande teksten. Dit is o.a. beschreven in Andrej Karpathy's blog 'The unreasonable effectiveness of recurrent neural networks'.  

In mijn vorig blog bericht heb ik een Keras voorbeeld hiermee getest dat werkt op basis van teksten van Nietzsche. De sourcecode heb ik hier gevonden.


In hoofdlijnen lijkt het als volgt te werken.

De tekst van Nietzsche wordt binnengelezen. (Kan overigens elke tekst zijn. Aanbevolen is minimaal 100k aan karakters maar beter is een miljoen of meer - Deze is 600901 karakters lang)

Er wordt gekeken hoeveel soorten karakters in de tekst zitten. Deze krijgen een indexcijfer. In dit geval zijn dat er 59.

De tekst wordt opgesplitst in overlappende 'regels' van 20 karakters. Ze noemen ze hier 'sequences'. Uiteindelijk worden dat er 200294 doordat er een stapgrootte van 3 tekens wordt gekozen. (Dus steeds 17 tekens overlappend)

In het proces 'Vectorization' wordt een tabel van boolean waardes opgebouwd uit de bovengenoemde sequences.  Die moet er ongeveer zo uitzien:

0  1  2  3  4  5  ...  20 Sequence lengte
0  0  0  0  1  0  ...   0
1  0  0  0  0  1  ...   0
2  0  1  0  0  0  ...   0
3  0  0  0  0  0  ...   0
...
59 Karakter index

Deze vector is dan 200294 lang in ons voorbeeld en vormt uiteindelijk de 'te trainen X input'
De Y (Hier als kleine y geschreven. Conventie?) moet het 'eerst volgende karakter' gaan weergeven. Deze wordt ook in een vector van 59 booleans opgeslagen.

Nieuw voor mij is het hier veel gebruikte 'enumerate' commando dat een volgnummer en de waarde als resultaat geeft. Bijvoorbeeld

for x,y in  enumerate( ['a','b','d']):
     print x,y
0 a
1 b
2 d  

Handig want dan hoef er geen aparte 'index-variable' te worden bijgehouden.

Hierna wordt het Keras model gedefinieerd en gecompileerd:


print('Build model...')
model = Sequential()
model.add(LSTM(512, return_sequences=True, input_shape=(maxlen, len(chars))))
model.add(Dropout(0.2))
model.add(LSTM(512, return_sequences=False))
model.add(Dropout(0.2))
model.add(Dense(len(chars)))
model.add(Activation('softmax'))

model.compile(loss='categorical_crossentropy', optimizer='rmsprop')

Input_shape is hier dus 20 x 59 en de uitvoer (onderste 'Dense-layer') is 59 groot. Daartussen zitten 2 LSTM layers en twee Dropout layers. De dropout geeft 'ruis' tijdens het 'leren' zodat het netwerk robuuster wordt.

In het laatste deel van het programma wordt het netwerk 'geleerd' en tussendoor worden test-resultaten afgedrukt.

model.fit(X, y, batch_size=128, nb_epoch=1)

De nieuw te genereren tekst wordt gestart op basis van een random stukje tekst (20 karakters) uit de oorspronkelijke tekst van Nietzsche. ('the seed') Bijvoorbeeld (inclusief end-of-lines) :

"n him in
order to pr"

De code hiervoor is:

preds = model.predict(x, verbose=0)[0]

De [0] lijkt aan te geven dat er meer dan een karakter wordt voorspeld. Misschien later eens kijken of dat waar is. 

Er is ook een 'sample' functie gedefinieerd met een variabele 'diversity' of 'temperature'. Ik snap nog niet goed hoe die werkt maar het doel is meer of minder variatie (creativiteit? :-) in de uitvoerteksten te krijgen.

Een voorbeeld resultaat staat al in mijn vorig bericht.

Technisch aardigheidje om te onthouden hier is ook het gebruik van de sys.stdout.write in plaats van print.  







Geen opmerkingen:

Een reactie posten