Keras LSTM Tutorial

Pronti, partenza, via!

In questo Tutorial Keras analiziamo un semplice esempio di modello LSTM ossia di Long Short Term Memory che avrà lo scopo di apprendere una sorta di linguaggio e di stile di scrittura simile alla Divina Commedia.

Primo step, recuperiamo il testo completo della divina commedia all’indirizzo https://www.retineuraliartificiali.net/keras_tutorial/divina_commedia.txt 

con la funzione “get_file” fornita nel modulo di utility di Keras il file viene in automatico scaricato e salvato in locale (nello spazio locale se usi Google Colab )

Successivamente apriamo e leggiamo il file convertendolo immediatamente in lower case. Ne stampiamo la lunghezza in byte e i primi mille chars.

Per approfondire l’uso e tutte le potenzialità di Keras io ti consiglio di dare un occhiata a questo.

Se siete a digiuno di Python allora vi consiglio di iniziare da questa guida.


import io
import numpy as np
import random
from keras.utils.data_utils import get_file
from keras.callbacks import LambdaCallback
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import CuDNNLSTM
from keras.optimizers import RMSprop

print("Download file...")
path = get_file('divina_commedia.txt', origin='https://www.retineuraliartificiali.net/keras_tutorial/divina_commedia.txt')
print("fatto!")

print("Apertura del file")
with io.open(path, encoding='utf-8') as f:
    text = f.read().lower()


print('Lunghezza del testo:', len(text))
print()
print('\n\n\n\n\n','*****************primi mille caratteri************\n',
      text[0:1000],'***********************************************\n')

'''
Using TensorFlow backend.
Download file...
Downloading data from https://www.retineuraliartificiali.net/keras_tutorial/divina_commedia.txt
565248/558240 [==============================] - 0s 1us/step
fatto!
Apertura del file
Lunghezza del testo: 558240

 *****************primi mille caratteri************
inferno

inferno: canto i


nel mezzo del cammin di nostra vita
  mi ritrovai per una selva oscura
  che' la diritta via era smarrita.

ahi quanto a dir qual era e` cosa dura
  esta selva selvaggia e aspra e forte
  che nel pensier rinova la paura!

tant'e` amara che poco e` piu` morte;
  ma per trattar del ben ch'i' vi trovai,
  diro` de l'altre cose ch'i' v'ho scorte.

io non so ben ridir com'i' v'intrai,
  tant'era pien di sonno a quel punto
  che la verace via abbandonai.

ma poi ch'i' fui al pie` d'un colle giunto,
  la` dove terminava quella valle
  che m'avea di paura il cor compunto,

guardai in alto, e vidi le sue spalle
  vestite gia` de' raggi del pianeta
  che mena dritto altrui per ogne calle.

allor fu la paura un poco queta
  che nel lago del cor m'era durata
  la notte ch'i' passai con tanta pieta.

e come quei che con lena affannata
  uscito fuor del pelago a la riva
  si volge a l'acqua perigliosa e guata,

cosi` l'animo mio, ch'ancor fuggiva,
  si volse a retro a r ***********************************************
'''

Prepariamo i dati per modello Keras LSTM

Creiamo la lista dei caratteri esistenti, comprensiva dei segni di punteggiatura, ritorno a capo ‘\n’ e la ordiniamo.
Successivamente inseriamo i caratteri in apposite mappe per la conversione da carattere-indice e da indice-carattere che utilizzeremo per generare il dato di input per il modello LSTM da addestrare.


chars = sorted(list(set(text)))#lista ordinata di tutto i simboli utilizzati

print('total chars:', len(chars))

char_indices = dict((c, i) for i, c in enumerate(chars))#maooa da simbolo a indice
indices_char = dict((i, c) for i, c in enumerate(chars))

print(char_indices)

Creiamo tante fette lunghe 30 caratteri con una distanza di 3 posizioni una dall’altra.
Presentiamo il dato in modo ridondante in quanto noi vogliamo che il modello LSTM che creeremo con Keras possa ricevere sequenze diverse di 30 caratteri dalle quali intuire/prevedere il carattere successivo.

Cio’ che ci interessa in questo tutorial Keras è vedere praticamente come la rete è in grado di imparare un meccanismo di costruzione di parole anche se spesso non avranno una reale corrispondenza con la realtà.

Data un chunk di informazione di 30 caratteri andremo a far apprendere alla rete quale sarà il carattere successivo. A tutti gli effetti questa è una previsione che la rete farà in base alla sua esperienza.

Se vuoi imparare e capire come funziona l’analisi del linguaggio naturale tramite con python e machine learning qui trovi l’approfondimento tecnico.


maxlen = 30 # lunghezza del chunk
step = 3
sentences = []
next_chars = []
for i in range(0, len(text) - maxlen, step):
    sentences.append(text[i: i + maxlen])
    next_chars.append(text[i + maxlen])
print('nb sequences:', len(sentences))

Instanziamo il vettore x di input che sarà costituito da tutti i chunk di 30 caratteri creati in precedenza e codificati in questo modo: ogni carattere viene codificato con la tecnica one hot vedi l’articolo.

Quindi ogni carattere sarà un vettore di 40 elementi (il numero totale di simboli) che avranno tutti valore 0 tranne per l’elemento corrispondente all’indice del carattere che vogliamo codificare, il quale sarà valorizzato a 1.


print('Generiamo i vettori x di input e y di putput')
x = np.zeros((len(sentences), maxlen, len(chars)), dtype=np.bool)
y = np.zeros((len(sentences), len(chars)), dtype=np.bool)
for i, sentence in enumerate(sentences):
    for t, char in enumerate(sentence):
        x[i, t, char_indices[char]] = 1
    y[i, char_indices[next_chars[i]]] = 1

Creiamo il modello Keras LSTM

Ora costriuiamo il modello Keras LSTM utilizzando il modulo Sequential e, visto che andremo a utilizzare una GPU per velocizzare il processo di apprendimento utilizzeremo il layer CuDNNLSTM fornito da Keras ottimizzato per essere eseguito su runtime GPU.

Per avere informazioni su come disporre gratuitamente di una GPU leggi questo articolo.


print('Compilo il modello...')
model = Sequential()
model.add(CuDNNLSTM(128, input_shape=(maxlen, len(chars))))
model.add(Dense(len(chars), activation='softmax'))

optimizer = RMSprop(lr=0.01)
model.compile(loss='categorical_crossentropy', optimizer=optimizer)
print('Fatto!')

Insegnamo qualcosa al modello LSTM

ed ora, passeremo alla fase di apprendimento.
L’oggetto LambdaCallback è utilissimo per poter creare una funzione di callback richiamabile ad ogni fine epoca di apprendimento.

Alla fine di ogni epoca vogliamo eseguire un test sul grado di apprendimento del modello LSTM e quindi agganceremo il metodo testAfterEpoch. Per velocizzare usiamo batch size 2048.



import sys

print_callback = LambdaCallback(on_epoch_end=testAfterEpoch)

model.fit(x, y,
          batch_size=2048,
          epochs=60,
          callbacks=[print_callback])





def testAfterEpoch(epoch, _):
    
    print()
    print()
    print('**************Epoch: %d**************' % epoch)

    start_index = random.randint(0, len(text) - maxlen - 1)
    
  

    generated = ''
    sentence = text[start_index: start_index + maxlen] #crea un chunk di 30 caratteri a partire da un indice a caso
    generated += sentence
    print('*******************Frase di partenza*****************')
    print(sentence)
    print('*****************************************************')
    sys.stdout.write(generated)

    for i in range(400):
        x_pred = np.zeros((1, maxlen, len(chars)))
        for t, char in enumerate(sentence):
            x_pred[0, t, char_indices[char]] = 1.

        preds = model.predict(x_pred, verbose=0)[0]
        next_index = cattura(preds)#np.argmax(preds)
        next_char = indices_char[next_index]

        sentence = sentence[1:] + next_char

        sys.stdout.write(next_char)
        sys.stdout.flush()
    print()

RISULTATO EPOCA 0

Epoch 1/60 186070/186070 [==============================] –
9s 50us/step – loss: 2.6405
**************Epoch: 0**************
*******************Frase di partenza*****************
ulcro piu` giovani piedi>>, c
*****************************************************
ulcro piu` giovani piedi>>, che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si che si ch

**************Epoch: 59**************
*******************Frase di partenza*****************
piu` da l’uno a l’altro stilo
*****************************************************
piu` da l’uno a l’altro stilo,
che di la` da l’altro solo e di colui
se si spadea, da di la` da la riva
al suo mosse se non si riglia,
e di tanto parte prender tutto crocca
la mente che li occhi miei ch’io nel mio suoni
che si fa che piu` di se’ che si fatto scelse
si` come ‘l fondo che non per lo scoglio
la mente a la sua stazza che son fossi
che si fa che piu` di se’ che si fatto scelse
si` come ‘l

Conclusioni

Il risultato ovviamente non è degno dell’autore del testo originale 🙂 ma ci sono due o tre cose veramente interessanti.

La prima, che si nota immediatamente, è il fatto che la rete neurale ha appreso una sorta di metalinguaggio che da una parvenza di italiano stile divina commedia.

Se notate alcune parole non esistono ma sembrano comunque parole verosimilmente italiane. In più punti sembra ritrovare lo stesso ritmo nei versi e in oltre ha imparato ad andare a capo al momento giusto.


4 pensieri riguardo “Keras LSTM Tutorial

  1. Marco ha detto:

    Ciao, complimenti per il tutorial, molto interessante e chiaro. Potresti gentilmente fornire anche il codice della tua funzione testAfterEpoch? Cosi da poter provare il tuo progetto nella sua interezza? Grazie mille!

    1. RNA ha detto:

      Grazie Marco per la segnalazione. Ho aggiunto la funzione mancante.

  2. Francesco ha detto:

    Ciao Alessandro, ma la funzione cattura(preds) che hai dentro testAfterEpoch, che funzione ha? e dove viene definita?

    http://www.reflexmania.it

    1. fai semplicemente cosi:
      cattura = lambda preds : np.argmax(preds)

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.