Essentials of Deep Learning : Introduction to Long Short Term Memory

Introduzione

I problemi di previsione delle sequenze esistono da molto tempo. Sono considerati come uno dei problemi più difficili da risolvere nel settore della scienza dei dati. Questi includono una vasta gamma di problemi; dalla previsione delle vendite alla ricerca di modelli nei dati dei mercati azionari, dalla comprensione delle trame dei film al riconoscimento del modo di parlare, dalle traduzioni linguistiche alla previsione della prossima parola sulla tastiera del vostro iPhone.

Con le recenti scoperte che stanno avvenendo nella scienza dei dati, si è scoperto che per quasi tutti questi problemi di predizione di sequenze, le reti di memoria a breve termine, a.k.a LSTMs sono state osservate come la soluzione più efficace.

LSTMs hanno un vantaggio rispetto alle tradizionali reti neurali feed-forward e RNN in molti modi. Questo è dovuto alla loro proprietà di ricordare selettivamente i modelli per lunghi periodi di tempo. Lo scopo di questo articolo è spiegare LSTM e permettervi di usarlo in problemi di vita reale. Diamo un’occhiata!

Nota: Per passare attraverso l’articolo, è necessario avere una conoscenza di base delle reti neurali e di come funziona Keras (una libreria di deep learning). Puoi fare riferimento agli articoli menzionati per capire questi concetti:

  • Capire le reti neurali da zero
  • Fondamenti di apprendimento profondo – Introduzione alle reti neurali ricorrenti
  • Tutorial: Ottimizzazione delle Reti Neurali usando Keras (con caso di studio di riconoscimento immagini)

Tabella dei Contenuti

  1. Flashback: Uno sguardo alle Reti Neurali Ricorrenti (RNN)
  2. Limitazioni delle RNN
  3. Miglioramento rispetto alle RNN: Long Short Term Memory (LSTM)
  4. Architettura di LSTM
    1. Forget Gate
    2. Input Gate
    3. Output Gate
  5. Generazione di testo usando LSTM.

Flashback: Uno sguardo alle Reti Neurali Ricorrenti (RNN)

Prendiamo un esempio di dati sequenziali, che possono essere i dati del mercato azionario per un particolare titolo. Un semplice modello di apprendimento automatico o una rete neurale artificiale può imparare a prevedere i prezzi delle azioni sulla base di un certo numero di caratteristiche: il volume delle azioni, il valore di apertura ecc. Mentre il prezzo dell’azione dipende da queste caratteristiche, è anche in gran parte dipendente dai valori delle azioni nei giorni precedenti. In effetti, per un trader, questi valori nei giorni precedenti (o la tendenza) è un fattore decisivo per le previsioni.

Nelle reti neurali feed-forward convenzionali, tutti i casi di test sono considerati indipendenti. Cioè quando si adatta il modello per un giorno particolare, non si tiene conto dei prezzi delle azioni nei giorni precedenti.

Questa dipendenza dal tempo è ottenuta tramite le Reti Neurali Ricorrenti. Una tipica RNN assomiglia a:

Questo può essere intimidatorio a prima vista, ma una volta spiegato, sembra molto più semplice:

Ora è più facile per noi visualizzare come queste reti stanno considerando la tendenza dei prezzi delle azioni, prima di prevedere i prezzi delle azioni per oggi. Qui ogni previsione al tempo t (h_t) dipende da tutte le previsioni precedenti e dalle informazioni apprese da esse.

Le RNN possono risolvere il nostro scopo di gestione delle sequenze in larga misura, ma non completamente. Vogliamo che i nostri computer siano abbastanza bravi da scrivere sonetti shakespeariani. Ora le RNN sono ottime quando si tratta di contesti brevi, ma per essere in grado di costruire una storia e ricordarla, abbiamo bisogno che i nostri modelli siano in grado di capire e ricordare il contesto dietro le sequenze, proprio come un cervello umano. Questo non è possibile con una semplice RNN.

Perché? Diamo un’occhiata.

Limitazioni delle RNN

Le reti neurali ricorrenti funzionano bene quando si tratta di dipendenze a breve termine. Cioè quando applicate a problemi come:

Le RNN risultano essere abbastanza efficaci. Questo perché questo problema non ha nulla a che fare con il contesto della dichiarazione. Le RNN non hanno bisogno di ricordare cosa è stato detto prima di questo, o qual era il suo significato, tutto quello che devono sapere è che nella maggior parte dei casi il cielo è blu. Così la predizione sarebbe:

Tuttavia, le RNN vaniglia non riescono a capire il contesto dietro un input. Qualcosa che è stato detto molto tempo prima, non può essere ricordato quando si fanno previsioni nel presente. Comprendiamo questo come un esempio:

Qui, possiamo capire che poiché l’autore ha lavorato in Spagna per 20 anni, è molto probabile che possa possedere una buona padronanza dello spagnolo. Ma, per fare una previsione corretta, la RNN ha bisogno di ricordare questo contesto. Le informazioni rilevanti possono essere separate dal punto in cui sono necessarie, da un enorme carico di dati irrilevanti. È qui che una Rete Neurale Ricorrente fallisce!

La ragione dietro questo è il problema del Vanishing Gradient. Per capire questo, è necessario avere qualche conoscenza su come una rete neurale feed-forward impara. Sappiamo che per una rete neurale feed-forward convenzionale, l’aggiornamento del peso che viene applicato su un particolare strato è un multiplo del tasso di apprendimento, il termine di errore dallo strato precedente e l’ingresso a quello strato. Così, il termine di errore per un particolare strato è da qualche parte un prodotto di tutti gli errori degli strati precedenti. Quando si tratta di funzioni di attivazione come la funzione sigmoide, i piccoli valori delle sue derivate (che si verificano nella funzione di errore) vengono moltiplicati più volte man mano che ci si sposta verso gli strati iniziali. Come risultato di questo, il gradiente quasi svanisce man mano che ci spostiamo verso gli strati iniziali, e diventa difficile addestrare questi strati.

Un caso simile si osserva nelle Reti Neurali Ricorrenti. Le RNN ricordano le cose solo per piccole durate di tempo, cioè se abbiamo bisogno dell’informazione dopo un piccolo lasso di tempo può essere riproducibile, ma una volta che vengono inserite molte parole, questa informazione si perde da qualche parte. Questo problema può essere risolto applicando una versione leggermente modificata delle RNN – le reti a memoria lunga a breve termine.

Miglioramento rispetto alle RNN: Reti LSTM (Long Short-Term Memory)

Quando organizziamo il nostro calendario per il giorno, diamo priorità ai nostri appuntamenti, giusto? Se nel caso in cui abbiamo bisogno di fare spazio per qualcosa di importante, sappiamo quale incontro potrebbe essere cancellato per ospitare una possibile riunione.

Si scopre che una RNN non fa così. Per aggiungere una nuova informazione, trasforma completamente l’informazione esistente applicando una funzione. A causa di questo, l’intera informazione viene modificata, nel suo complesso, cioè non c’è considerazione per le informazioni “importanti” e quelle “non così importanti”.

Le LSTM, d’altra parte, apportano piccole modifiche alle informazioni tramite moltiplicazioni e aggiunte. Con le LSTM, l’informazione scorre attraverso un meccanismo noto come stati delle cellule. In questo modo, le LSTM possono ricordare o dimenticare selettivamente le cose. L’informazione in un particolare stato di cella ha tre diverse dipendenze.

Visualizziamo questo con un esempio. Prendiamo l’esempio di prevedere il prezzo delle azioni di un particolare titolo. Il prezzo delle azioni di oggi dipenderà da:

  1. La tendenza che lo stock ha seguito nei giorni precedenti, forse una tendenza al ribasso o una tendenza al rialzo.
  2. Il prezzo dello stock il giorno precedente, perché molti commercianti confrontano il prezzo del giorno precedente dello stock prima di acquistarlo.
  3. I fattori che possono influenzare il prezzo dello stock per oggi. Questo può essere una nuova politica aziendale che viene ampiamente criticata, o un calo dei profitti dell’azienda, o forse un cambiamento inaspettato nella leadership senior dell’azienda.

Queste dipendenze possono essere generalizzate a qualsiasi problema come:

  1. Lo stato precedente della cella (cioè l’informazione che era presente nella memoria dopo il passo temporale precedente)
  2. Lo stato nascosto precedente (cioè questo è lo stesso dell’output della cella precedente)
  3. L’input al passo temporale corrente (cioè la nuova informazione che viene immessa in quel momento)

Un’altra importante caratteristica di LSTM è la sua analogia con i nastri trasportatori!

Esattamente!

Le industrie li usano per spostare i prodotti per diversi processi. Le LSTM usano questo meccanismo per spostare le informazioni.

Possiamo avere qualche aggiunta, modifica o rimozione di informazioni mentre scorre attraverso i diversi strati, proprio come un prodotto può essere modellato, dipinto o confezionato mentre è su un nastro trasportatore.

Il seguente diagramma spiega la stretta relazione tra gli LSTM e i nastri trasportatori.

Fonte

Anche se questo diagramma non è nemmeno vicino all’architettura reale di un LSTM, risolve il nostro scopo per ora.

Solo grazie a questa proprietà degli LSTM, dove non manipolano l’intera informazione ma piuttosto la modificano leggermente, sono in grado di dimenticare e ricordare cose selettivamente. Come lo fanno, è quello che impareremo nella prossima sezione?

Architettura delle LSTM

Il funzionamento delle LSTM può essere visualizzato comprendendo il funzionamento della squadra di un canale di notizie che copre una storia di omicidio. Ora, una storia di notizie è costruita intorno a fatti, prove e dichiarazioni di molte persone. Ogni volta che si verifica un nuovo evento si fa uno dei tre passi.

Diciamo che stavamo assumendo che l’omicidio è stato fatto ‘avvelenando’ la vittima, ma il rapporto dell’autopsia che è appena arrivato dice che la causa della morte è stata ‘un impatto sulla testa’. Facendo parte di questa squadra di giornalisti, cosa si fa? Si dimentica immediatamente la precedente causa della morte e tutte le storie che sono state tessute intorno a questo fatto.

Che cosa, se un sospetto completamente nuovo viene introdotto nel quadro. Una persona che aveva dei rancori con la vittima e che potrebbe essere l’assassino? Tu inserisci queste informazioni nel tuo news feed, giusto?

Ora tutti questi pezzi di informazione spezzati non possono essere serviti dai media tradizionali. Quindi, dopo un certo intervallo di tempo, è necessario riassumere queste informazioni e trasmettere le cose rilevanti al tuo pubblico. Forse sotto forma di “XYZ risulta essere il principale sospettato”.

Ora entriamo nei dettagli dell’architettura della rete LSTM:

Source

Ora, questo non è affatto vicino alla versione semplificata che abbiamo visto prima, ma lascia che ti accompagni. Una tipica rete LSTM è composta da diversi blocchi di memoria chiamati celle
(i rettangoli che vediamo nell’immagine). Ci sono due stati che vengono trasferiti alla cella successiva: lo stato della cella e lo stato nascosto. I blocchi di memoria sono responsabili di ricordare le cose e le manipolazioni a questa memoria sono fatte attraverso tre meccanismi principali, chiamati gates. Ognuno di essi viene discusso di seguito.

4.1 Forget Gate

Prendendo l’esempio di un problema di previsione del testo. Supponiamo che un LSTM sia alimentato con la seguente frase:

Appena si incontra il primo punto dopo “persona”, il forget gate si rende conto che potrebbe esserci un cambiamento di contesto nella frase successiva. Come risultato di ciò, il soggetto della frase viene dimenticato e il posto per il soggetto viene lasciato libero. E quando cominciamo a parlare di “Dan” questa posizione del soggetto viene assegnata a “Dan”. Questo processo di dimenticanza del soggetto è portato dal forget gate.

Un forget gate è responsabile della rimozione delle informazioni dallo stato delle cellule. Le informazioni che non sono più necessarie alla LSTM per capire le cose o le informazioni che sono di minore importanza vengono rimosse tramite la moltiplicazione di un filtro. Questo è necessario per ottimizzare le prestazioni della rete LSTM.

Questa porta prende due input; h_t-1 e x_t.

h_t-1 è lo stato nascosto dalla cella precedente o l’output della cella precedente e x_t è l’input in quel particolare passo temporale. Gli input dati sono moltiplicati per le matrici di peso e viene aggiunto un bias. In seguito, la funzione sigmoide viene applicata a questo valore. La funzione sigmoide produce un vettore, con valori che vanno da 0 a 1, corrispondente ad ogni numero nello stato della cella. Fondamentalmente, la funzione sigmoide è responsabile di decidere quali valori tenere e quali scartare. Se viene emesso uno ‘0’ per un particolare valore nello stato della cella, significa che il forget gate vuole che lo stato della cella dimentichi completamente quell’informazione. Allo stesso modo, un ‘1’ significa che il forget gate vuole ricordare quell’intera informazione. Questo vettore in uscita dalla funzione sigmoide è moltiplicato per lo stato della cella.

4.2 Input Gate

Ok, prendiamo un altro esempio in cui la LSTM sta analizzando una frase:

Ora l’informazione importante qui è che “Bob” sa nuotare e che ha servito la Marina per quattro anni. Questo può essere aggiunto allo stato della cella, tuttavia, il fatto che ha detto tutto questo per telefono è un fatto meno importante e può essere ignorato. Questo processo di aggiunta di nuove informazioni può essere fatto tramite il gate di ingresso.

Ecco la sua struttura:

Il gate di ingresso è responsabile dell’aggiunta di informazioni allo stato della cella. Questa aggiunta di informazioni è fondamentalmente un processo in tre fasi, come si vede dal diagramma sopra.

  1. Regola quali valori devono essere aggiunti allo stato della cella coinvolgendo una funzione sigmoide. Questo è fondamentalmente molto simile al forget gate e agisce come un filtro per tutte le informazioni da h_t-1 e x_t.
  2. Creare un vettore contenente tutti i possibili valori che possono essere aggiunti (come percepito da h_t-1 e x_t) allo stato della cella. Questo viene fatto usando la funzione tanh, che produce valori da -1 a +1.
  3. Moltiplicando il valore del filtro regolatore (la porta sigmoide) al vettore creato (la funzione tanh) e poi aggiungendo queste informazioni utili allo stato della cellula tramite l’operazione di addizione.

Una volta fatto questo processo in tre fasi, ci assicuriamo che vengano aggiunte allo stato della cellula solo le informazioni che sono importanti e non sono ridondanti.

4.3 Output Gate

Non tutte le informazioni che corrono lungo lo stato della cella, sono adatte per essere emesse in un certo momento. Visualizzeremo questo con un esempio:

In questa frase, ci potrebbero essere diverse opzioni per lo spazio vuoto. Ma sappiamo che l’attuale input di ‘brave’, è un aggettivo che viene usato per descrivere un sostantivo. Quindi, qualsiasi parola segua, ha una forte tendenza ad essere un sostantivo. E quindi, Bob potrebbe essere un’uscita appropriata.

Questo lavoro di selezionare le informazioni utili dallo stato corrente della cellula e mostrarle come un’uscita è fatto attraverso il gate di uscita. Ecco la sua struttura:

Il funzionamento di un gate di uscita può ancora una volta essere suddiviso in tre fasi:

  1. Creare un vettore dopo aver applicato la funzione tanh allo stato della cella, scalando così i valori nell’intervallo da -1 a +1.
  2. Fare un filtro usando i valori di h_t-1 e x_t, in modo che possa regolare i valori che devono essere emessi dal vettore creato sopra. Questo filtro impiega di nuovo una funzione sigmoide.
  3. Moltiplicando il valore di questo filtro regolatore al vettore creato nel passo 1, e mandandolo in uscita e anche allo stato nascosto della cella successiva.

Il filtro nell’esempio precedente farà in modo di diminuire tutti gli altri valori tranne ‘Bob’. Quindi il filtro deve essere costruito sui valori di input e di stato nascosto ed essere applicato sul vettore di stato della cella.

Generazione di testo usando LSTMs

Abbiamo avuto abbastanza concetti teorici e funzionamento di LSTMs. Ora cercheremmo di costruire un modello che possa prevedere un certo numero n di caratteri dopo il testo originale di Macbeth. La maggior parte dei testi classici non sono più protetti da copyright e possono essere trovati qui. Una versione aggiornata del file .txt può essere trovata qui.

Utilizzeremo la libreria Keras, che è un’API di alto livello per reti neurali e funziona sopra TensorFlow o Theano. Quindi assicuratevi che prima di tuffarvi in questo codice abbiate Keras installato e funzionante.

Ok, quindi generiamo un po’ di testo!

  • Importazione delle dipendenze

# Importing dependencies numpy and kerasimport numpyfrom keras.models import Sequentialfrom keras.layers import Densefrom keras.layers import Dropoutfrom keras.layers import LSTMfrom keras.utils import np_utils

Importiamo tutte le dipendenze richieste e questo è praticamente autoesplicativo.

  • Caricamento del file di testo e creazione di mappature da carattere a intero

# load textfilename = "/macbeth.txt"text = (open(filename).read()).lower()# mapping characters with integersunique_chars = sorted(list(set(text)))char_to_int = {}int_to_char = {}for i, c in enumerate (unique_chars): char_to_int.update({c: i}) int_to_char.update({i: c})

Il file di testo è aperto, e tutti i caratteri sono convertiti in lettere minuscole. Al fine di facilitare i passi successivi, si mapperà ogni carattere ad un rispettivo numero. Questo viene fatto per rendere più facile la parte di calcolo del LSTM.

  • Preparazione del dataset

# preparing input and output datasetX = Y = for i in range(0, len(text) - 50, 1): sequence = text label =text X.append( for char in sequence]) Y.append(char_to_int)

I dati vengono preparati in un formato tale che se vogliamo che il LSTM predica la ‘O’ in ‘CIAO’, dobbiamo inserire come input e come output previsto. Allo stesso modo, qui fissiamo la lunghezza della sequenza che vogliamo (impostata a 50 nell’esempio) e poi salviamo le codifiche dei primi 49 caratteri in X e l’output previsto cioè il 50° carattere in Y.

  • Reshaping di X

# reshaping, normalizing and one hot encodingX_modified = numpy.reshape(X, (len(X), 50, 1))X_modified = X_modified / float(len(unique_chars))Y_modified = np_utils.to_categorical(Y)

Una rete LSTM si aspetta che l’input sia nella forma dove samples è il numero di punti dati che abbiamo, time steps è il numero di passi dipendenti dal tempo che ci sono in un singolo punto dati, features si riferisce al numero di variabili che abbiamo per il corrispondente valore vero in Y. Poi scaliamo i valori in X_modified tra 0 e 1 e uno caldo codifichiamo i nostri veri valori in Y_modified.

  • Definizione del modello LSTM

# defining the LSTM modelmodel = Sequential()model.add(LSTM(300, input_shape=(X_modified.shape, X_modified.shape), return_sequences=True))model.add(Dropout(0.2))model.add(LSTM(300))model.add(Dropout(0.2))model.add(Dense(Y_modified.shape, activation='softmax'))model.compile(loss='categorical_crossentropy', optimizer='adam')

Si usa un modello sequenziale che è una pila lineare di strati. Il primo strato è uno strato LSTM con 300 unità di memoria e restituisce sequenze. Questo è fatto per assicurare che lo strato LSTM successivo riceva sequenze e non solo dati sparsi a caso. Uno strato di dropout è applicato dopo ogni strato LSTM per evitare l’overfitting del modello. Infine, abbiamo l’ultimo strato come uno strato completamente connesso con un’attivazione ‘softmax’ e neuroni uguali al numero di caratteri unici, perché abbiamo bisogno di emettere un risultato codificato a caldo.

  • Adattamento del modello e generazione di caratteri

# fitting the modelmodel.fit(X_modified, Y_modified, epochs=1, batch_size=30)# picking a random seedstart_index = numpy.random.randint(0, len(X)-1)new_string = X# generating charactersfor i in range(50): x = numpy.reshape(new_string, (1, len(new_string), 1)) x = x / float(len(unique_chars)) #predicting pred_index = numpy.argmax(model.predict(x, verbose=0)) char_out = int_to_char seq_in = for value in new_string] print(char_out) new_string.append(pred_index) new_string = new_string

Il modello viene adattato su 100 epoche, con una dimensione del batch di 30. Poi fissiamo un seme casuale (per una facile riproducibilità) e iniziamo a generare caratteri. La predizione del modello fornisce la codifica del carattere predetto, che viene poi decodificata nel valore del carattere e aggiunta al modello.

Ecco come sarebbe l’output della rete

Eventualmente, dopo abbastanza epoche di allenamento, darà risultati sempre migliori nel tempo. Questo è il modo in cui usereste LSTM per risolvere un compito di predizione di sequenza.

Note finali

Le LSTM sono una soluzione molto promettente per problemi relativi a sequenze e serie temporali. Tuttavia, l’unico svantaggio che trovo su di loro, è la difficoltà di addestramento. Un sacco di tempo e risorse di sistema vanno nella formazione anche di un modello semplice. Ma questo è solo un vincolo hardware! Spero di essere riuscito a darvi una comprensione di base di queste reti. Per qualsiasi problema o questione relativa al blog, non esitate a commentare qui sotto.

Imparate, impegnatevi, hackerate e fatevi assumere!

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.