Predicendo il prezzo delle azioni con LSTM

L’apprendimento automatico ha trovato le sue applicazioni in molti campi interessanti in questi anni. Domare il mercato azionario è uno di questi. Era da un po’ di tempo che pensavo di fare un tentativo, soprattutto per consolidare la mia conoscenza pratica delle LSTM. E finalmente ho finito il progetto e sono abbastanza entusiasta di condividere la mia esperienza.

Motivazione e pubblico di riferimento

Scriverò della mia esperienza in una serie di blog. Lo scopo di questa serie non è quello di spiegare le basi dei concetti di LSTM o di Machine Learning. Quindi, assumerò che il lettore abbia iniziato il suo viaggio con il Machine Learning e abbia le basi come Python, familiarità con SkLearn, Keras, LSTM ecc. La ragione è che ci sono già eccellenti articoli su argomenti come “Come funzionano gli LSTM?” da persone che sono molto più qualificate per spiegare la matematica dietro di esso. Ma condividerò i link a tali articoli, ovunque ritenga che la conoscenza di base possa mancare. Mentre ci sono un sacco di articoli là fuori che ti dicono come prevedere i prezzi delle azioni dato un set di dati, la maggior parte degli autori non rivelano/spiegano come hanno raggiunto quella particolare configurazione per una rete neurale o come hanno selezionato quel particolare set di iperparametri. Quindi il vero scopo di questo articolo è quello di condividere tali passi, i miei errori e alcuni passi che ho trovato molto utili. Come tale, questo articolo non si limita al problema della previsione del prezzo delle azioni.

Ecco le cose che vedremo :

  1. Lettura e analisi dei dati. (Pandas)
  2. Normalizzare i dati. (SkLearn)
  3. Conversione dei dati in serie temporali e problema di apprendimento supervisionato.
  4. Creazione del modello (Keras)
  5. Fine tuning del modello (nel prossimo articolo)
  6. Formazione, previsione e visualizzazione del risultato.
  7. Consigli &strumenti che ho trovato molto utili (ultimo articolo della serie)

Si prega di notare che questo primo articolo parla di passi di pre-elaborazione e terminologie di LSTM. Se siete abbastanza sicuri di questi passi, potete passare al prossimo articolo.

Iniziamo!

Lettura e analisi dei dati

Per questo articolo userò i dati storici dei prezzi delle azioni GE. Potete trovare i dati nel mio sito Kaggle qui. Non ricordo la fonte dei dati perché li avevo scaricati molto tempo fa. Possiamo leggere i dati nel frame come mostrato qui sotto :

df_ge = pd.read_csv(os.path.join(INPUT_PATH, "us.ge.txt"), engine='python')
df_ge.tail()

Come potete vedere ci sono circa 14060 elementi, ognuno dei quali rappresenta un giorno di attributi di mercato azionario per la società. Vediamo come appare su un grafico :

from matplotlib import pyplot as pltplt.figure()
plt.plot(df_ge)
plt.plot(df_ge)
plt.plot(df_ge)
plt.plot(df_ge)
plt.title('GE stock price history')
plt.ylabel('Price (USD)')
plt.xlabel('Days')
plt.legend(, loc='upper left')
plt.show()

Sembra che i prezzi – Open, Close, Low, High – non variano troppo l’uno dall’altro eccetto che per occasionali leggeri cali nel prezzo Low.

Ora controlliamo la trama del volume :

plt.figure()
plt.plot(df_ge)
plt.title('GE stock volume history')
plt.ylabel('Volume')
plt.xlabel('Days')
plt.show()

Huh. Avete visto qualcosa di interessante? C’è un’impennata nel numero di transazioni intorno al 12000° giorno sulla linea temporale, che coincide con l’improvviso calo del prezzo delle azioni. Forse possiamo tornare indietro a quella particolare data e scovare vecchi articoli di notizie per trovare la causa.

Ora vediamo se abbiamo qualche valore nullo/Nan di cui preoccuparci. A quanto pare non abbiamo alcun valore nullo. Ottimo!

print("checking if any null values are present\n", df_ge.isna().sum())

Normalizzare i dati

I dati non sono normalizzati e il range per ogni colonna varia, specialmente il Volume. Normalizzare i dati aiuta l’algoritmo a convergere, cioè a trovare il minimo locale/ globale in modo efficiente. Userò MinMaxScaler da Sci-kit Learn. Ma prima di questo dobbiamo dividere il set di dati in set di dati di allenamento e di test. Inoltre convertirò il DataFrame in ndarray nel processo.

Conversione dei dati in serie temporali e problema di apprendimento supervisionato

Questo è abbastanza importante e un po’ difficile. È qui che la conoscenza LSTM è necessaria. Vorrei dare una breve descrizione dei concetti chiave che sono necessari qui, ma consiglio vivamente di leggere il blog di Andre Karpathy qui, che è considerato una delle migliori risorse su LSTM là fuori e questo. Oppure potete guardare anche il video di Andrew Ng (che tra l’altro cita anche il blog di Andre).

Le LSTM consumano input in formato; un array a 3 dimensioni.

  • Batch Size dice quanti campioni di input volete che la rete neurale veda prima di aggiornare i pesi. Quindi diciamo che hai 100 campioni (dataset di input) e vuoi aggiornare i pesi ogni volta che la tua NN ha visto un input. In questo caso la dimensione del batch sarebbe 1 e il numero totale di batch sarebbe 100. Allo stesso modo, se vuoi che la tua rete aggiorni i pesi dopo che ha visto tutti i campioni, la dimensione del batch sarebbe 100 e il numero di batch sarebbe 1. Come risulta, l’uso di batch molto piccoli riduce la velocità di addestramento e d’altra parte l’uso di batch troppo grandi (come l’intero set di dati) riduce la capacità dei modelli di generalizzare a dati diversi e consuma anche più memoria. Ma ci vogliono meno passi per trovare i minimi per la vostra funzione obiettivo. Quindi dovete provare vari valori sui vostri dati e trovare lo sweet spot. È un argomento abbastanza grande. Vedremo come cercare questi valori in modo un po’ più intelligente nel prossimo articolo.
  • I passi temporali definiscono quante unità indietro nel tempo volete che la vostra rete veda. Per esempio, se state lavorando su un problema di predizione dei caratteri dove avete un corpus di testo da addestrare e decidete di dare alla vostra rete 6 caratteri alla volta. Allora il vostro passo temporale è 6. Nel nostro caso useremo 60 come passo temporale, cioè esamineremo 2 mesi di dati per prevedere il prezzo dei prossimi giorni. Più avanti su questo.
  • Caratteristiche è il numero di attributi usati per rappresentare ogni passo temporale. Considerate l’esempio di predizione dei caratteri di cui sopra, e supponiamo che usiate un vettore codificato a un colpo di dimensione 100 per rappresentare ogni carattere. Quindi la dimensione delle caratteristiche qui è 100.

Ora che abbiamo un po’ chiarito le terminologie, convertiamo i nostri dati di stock in un formato adatto. Supponiamo, per semplicità, di aver scelto il 3 come passo temporale (vogliamo che la nostra rete guardi indietro a 3 giorni di dati per prevedere il prezzo del 4° giorno), allora formeremo il nostro set di dati in questo modo:

I campioni da 0 a 2 sarebbero il nostro primo input e il prezzo Close del campione 3 sarebbe il suo corrispondente valore di output; entrambi racchiusi nel rettangolo verde. Allo stesso modo i campioni da 1 a 3 sarebbero il nostro secondo input e il prezzo di chiusura del campione 4 sarebbe il valore di uscita; rappresentato dal rettangolo blu. E così via. Così fino ad ora abbiamo una matrice di forma (3, 5), essendo 3 il passo temporale e 5 il numero di caratteristiche. Ora pensate a quante coppie ingresso-uscita sono possibili nell’immagine sopra? 4.

Insieme a questo, mischiamo anche la dimensione del batch. Supponiamo di scegliere la dimensione del lotto di 2. Allora la coppia input-output 1 (rettangolo verde) e la coppia 2 (rettangolo blu) costituirebbero il lotto uno. E così via. Ecco lo snippet di codice python per fare questo:

‘y_col_index’ è l’indice della vostra colonna di output. Ora supponiamo che dopo aver convertito i dati in formato di apprendimento supervisionato, come mostrato sopra, abbiate 41 campioni nel vostro set di dati di allenamento ma la dimensione del vostro batch sia 20, allora dovrete tagliare il vostro set di allenamento per rimuovere i campioni dispari rimasti fuori. Cercherò un modo migliore per aggirare questo problema, ma per ora questo è quello che ho fatto:

Ora, usando le funzioni di cui sopra, formiamo i nostri set di dati di allenamento, validazione e test

x_t, y_t = build_timeseries(x_train, 3)
x_t = trim_dataset(x_t, BATCH_SIZE)
y_t = trim_dataset(y_t, BATCH_SIZE)
x_temp, y_temp = build_timeseries(x_test, 3)
x_val, x_test_t = np.split(trim_dataset(x_temp, BATCH_SIZE),2)
y_val, y_test_t = np.split(trim_dataset(y_temp, BATCH_SIZE),2)

Ora che i nostri dati sono pronti, possiamo concentrarci sulla costruzione del modello.

Creazione del modello

Per questo compito useremo LSTM, che è una variazione della Rete Neurale Ricorrente. Creare un modello LSTM è semplice come questo:

Ora che hai il tuo modello compilato e pronto per essere allenato, allenalo come mostrato qui sotto. Se vi state chiedendo quali valori usare per parametri come epochs, batch size etc., non preoccupatevi, vedremo come capirli nel prossimo articolo.

Allenare questo modello (con iperparametri messi a punto) ha dato il miglior errore di 3.27e-4 e il miglior errore di validazione di 3.7e-4. Ecco come appare il Training loss vs Validation loss:

Training error vs Validation error

Ecco come appare la predizione con il modello precedente:

predizione vs dati reali

Ho scoperto che questa configurazione per LSTM funziona meglio di tutte le combinazioni che ho provato (per questo dataset), e ne ho provate più di 100! Quindi la domanda è: come si fa a trovare l’architettura perfetta (o in quasi tutti i casi, vicina alla perfezione) per la propria rete neurale? Questo ci porta alla nostra prossima e importante sezione, che continuerà nel prossimo articolo.

Si possono trovare tutti i programmi completi sul mio profilo Github qui.

NOTE: Un’umile richiesta ai lettori – Siete tutti i benvenuti a connettervi con me su LinkedIn o Twitter, ma se avete una domanda riguardante i miei blog per favore postatela nella sezione commenti del rispettivo blog invece che nei messaggi personali, così che se qualcun altro ha la stessa domanda, la troverebbe qui stessa, e non dovrei spiegarla individualmente. Tuttavia, siete ancora i benvenuti ad inviare domande non collegate ai blog o domande tecniche generali, a me personalmente. Grazie 🙂

AGGIORNAMENTO 13/4/19

  1. Sono venuto a conoscenza, da quando ho scritto questo articolo, che il mio modello usato per questo blog potrebbe essere stato sovrastimato. Anche se non l’ho confermato, è probabile. Quindi per favore fate attenzione mentre implementate questo nei vostri progetti. Potreste provare cose come meno epoche, una rete più piccola, più dropout ecc.
  2. Ho usato l’attivazione Sigmoid per l’ultimo strato che può soffrire della limitazione di non essere in grado di predire un prezzo maggiore del prezzo ‘max’ nel set di dati. Si potrebbe provare l’attivazione ‘Linear’ per l’ultimo strato per risolvere questo problema.
  3. Fissato un errore di battitura nella sezione “conversione dei dati in serie temporali”.

Grazie ai lettori per aver portato questi alla mia attenzione.

Aggiornamento 21/1/2020

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.