Addestrare le reti neurali con Scikit-Learn e TensorFlow

Addestrare le reti neurali con Scikit-Learn e TensorFlow

In questa lezione spieghiamo come addestrare le reti neurali artificiali e i perceptron, introdotti nel corso base sul deep learning. Dimostriamo che il perceptron classifica i dati di input con un confine decisionale lineare.

Finora abbiamo rimandato la spiegazione su come calcoliamo i parametri che determinano questo confine. In questa lezione impariamo a stimare tali parametri attraverso l’allenamento del perceptron.

Descriviamo la procedura per addestrare le reti neurali i perceptron e analizziamo la somiglianza con un approccio molto usato nel deep learning: la discesa stocastica del gradiente. Presentiamo poi il codice Python che esegue l’addestramento con la libreria Scikit-Learn. Infine confrontiamo il codice equivalente in TensorFlow per evidenziare le differenze.

Addestrare le reti neurali

Dopo aver ricavato i pesi e bias, classifichiamo facilmente nuovi dati di input calcolando il prodotto scalare tra i pesi e i componenti dell’input, seguito dalla funzione di attivazione a gradino.

Non abbiamo ancora definito come impostiamo pesi e bias prima di classificare. In questo interviene la regola di apprendimento del perceptron, una procedura di addestramento efficace.

Applichiamo la regola considerando l’errore di previsione che si verifica quando il perceptron elabora un dato etichettato. In particolare, rafforziamo i pesi che riducono tale errore.

Ogni volta che forniamo un’istanza al perceptron, otteniamo una previsione. Se la classificazione non coincide con l’etichetta corretta, modifichiamo i pesi per favorire una previsione corretta.

Procediamo aggiornando i pesi in modo iterativo ogni volta che introduciamo nuovi dati, fino a raggiungere una soluzione ottimale.

\(\begin{eqnarray}
w_i^{n+1} = w_i^n + \nu (y – \hat{y}) x_i
\end{eqnarray}\)

Dove:

  • \(w_i^{n}\) indica l’i-esimo peso al passo n
  • \(x_i\) rappresenta l’i-esimo componente dell’input corrente
  • \(y\) è l’etichetta corretta
  • \(\hat{y}\) è l’etichetta prevista
  • \(\nu\) è il tasso di apprendimento

Analizziamo ora la formula per comprenderne il funzionamento. Calcoliamo i nuovi pesi \(w_i^{n+1}\) aggiungendo ai pesi precedenti \(w_i^{n}\) un termine \(\nu (y – \hat{y}) x_i\).

Poiché questo termine contiene la differenza tra valore previsto e valore reale, aumenta se cresce tale differenza. In pratica, modifichiamo i pesi in proporzione all’errore: più la previsione è errata, maggiore è la correzione.

Usiamo il tasso di apprendimento \(\nu\) per regolare l’entità della correzione. Se impostiamo un \(\nu\) basso, i pesi variano poco anche con errori grandi. Se invece scegliamo un \(\nu\) alto, otteniamo modifiche più marcate anche con errori minimi.

Il tasso di apprendimento rappresenta un iperparametro, perciò dobbiamo selezionarlo con cura. Approfondiamo l’ottimizzazione degli iperparametri in lezioni successive, dove esploriamo architetture neurali più complesse.

Infine, il termine di aggiornamento è moltiplicato per \(x_i\). Se l’input ha un valore elevato, anche la variazione del peso sarà maggiore, a parità degli altri fattori.

Procediamo ad addestrare le reti neurali e i perceptron usando due librerie Python: Scikit-Learn e TensorFlow.

Nella lezione precedente abbiamo descritto come installare TensorFlow su Ubuntu con GPU: ci sarà utile per eseguire il codice che segue.

Implementazione del codice

Per addestrare le reti neurali utilizziamo un dataset sul diabete fornito dal National Institute of Diabetes and Digestive and Kidney Diseases[4] per valutare l’efficacia del perceptron nella classificazione.

Il dataset contiene 768 record, ciascuno con otto misurazioni diagnostiche e un’etichetta che indica se il paziente presenta diabete. Tutti i pazienti sono donne di almeno 21 anni appartenenti alla popolazione Pima.

Scarichiamo il file CSV da questo link su Kaggle. Per utilizzarlo correttamente, salviamo il file nella stessa cartella del codice che mostriamo di seguito.

Non analizziamo nel dettaglio le caratteristiche del dataset. Lo impieghiamo soltanto per illustrare l’algoritmo di addestramento. Se desideri approfondire le misurazioni diagnostiche e i metodi di raccolta, consulta [4] per ulteriori informazioni.

Scikit-Learn

Nel file perc_diabetes_sklearn.py impieghiamo Pandas e Scikit-Learn per caricare il dataset e costruire un modello di classificazione binaria con il perceptron.

Come primo passo, usiamo read_csv di Pandas per caricare il file CSV in un DataFrame. Convertiamo poi il DataFrame in una matrice NumPy tramite values, rendendolo compatibile con Scikit-Learn.

Definiamo la matrice delle feature X come le prime otto colonne (forma (768, 8)). Il vettore degli output y è costituito dall’ultima colonna: 0 indica assenza di diabete, 1 la presenza.

Inizializziamo il modello perceptron con un seme casuale per ottenere risultati riproducibili. Alleniamo poi il modello con la regola di apprendimento del perceptron usando il metodo fit.

Infine calcoliamo la precisione media sulle stesse istanze del campione.

				
					# perc_diabetes_sklearn.py

import pandas as pd
from sklearn.linear_model import Perceptron


if __name__ == "__main__":
    # Carica il set di dati sul diabete dal CSV e lo converte 
    # in una matrice NumPy adatta per l'estrazione nel formato 
    # X, y,  necessario per Scikit-Learn
    diabetes = pd.read_csv('diabetes.csv').values

    # Estrarre le colonne delle features e della risposta 
    # del risultato nelle variabili appropriate
    X = diabetes[:, 0:8]
    y = diabetes[:, 8]

    # Crea e addestra un modello perceptron model (con un seed
    # random e riproducibile)
    model = Perceptron(random_state=1)
    model.fit(X, y)

    # Output il punteggio medio di precisione
    # della classificazione
    print("%0.3f" % model.score(X, y))
				
			

L’uscita è la seguente:

				
					0.531
				
			

Notiamo che il punteggio di classificazione si aggira intorno al 53%.

Non ci sorprende questo risultato. Stiamo infatti cercando di far apprendere a una singola unità lineare di soglia un iperpiano decisionale lineare basandoci su dati complessi a otto dimensioni. È difficile che questi dati presentino un confine decisionale netto tra “nessun diabete” e “diabete”.

TensorFlow & Keras

Vediamo ora come addestrare le reti neurali con l’API Keras utilizzando la libreria TensorFlow. Il codice risulta leggermente più complesso rispetto alla versione con Scikit-Learn. Tuttavia, questa maggiore complessità dell’API ci torna utile nelle lezioni successive, dove modelliamo architetture di reti neurali profonde.

Funzione sigmoidea di attivazione rigida

Prima di mostrare e spiegare il codice corrispondente TensorFlow/Keras per addestrare un singolo perceptron, evidenziamo le difficoltà nel riprodurre fedelmente il perceptron così come lo abbiamo descritto nella lezione precedente. Approfondiamo le motivazioni in [6].

Riassumiamo il problema nella natura dell’API Keras, che progettiamo principalmente per reti neurali profonde con funzioni di attivazione differenziabili e capaci di produrre gradienti diversi da zero. La funzione di attivazione del perceptron originale è una funzione a gradino, che non risulta continua (quindi nemmeno differenziabile) nel punto zero.

Poiché adottiamo la discesa stocastica del gradiente come procedura principale di ottimizzazione in Keras, dobbiamo ottenere gradienti diversi da zero per aggiornare i pesi durante l’addestramento.

Per aggirare questa limitazione, utilizziamo come funzione di attivazione un’alternativa simile: la hard sigmoid. Si tratta di un’approssimazione lineare a tratti della funzione sigmoidea classica (la “curva a S”), differenziabile ovunque tranne in due punti. Questa funzione presenta gradienti nulli in alcune porzioni del dominio, ma consente gradienti diversi da zero nella parte centrale lineare.

Questa caratteristica ci basta per ottenere in Keras e TensorFlow un comportamento “simile al perceptron”.

Implementazione TensorFlow

Mostrando il codice TensorFlow/Keras, vogliamo iniziare a prendere confidenza con l’API che useremo nelle reti neurali profonde. Molti parametri richiesti nella definizione del modello meritano spiegazioni più approfondite, che rimandiamo alle prossime lezioni. Intanto descriviamo brevemente ogni parametro.

Nello script (perc_diabetes_tensorflow.py) lavoriamo con lo stesso dataset sul diabete che abbiamo usato con Scikit-Learn. Carichiamo i dati da CSV nello stesso modo e costruiamo la matrice delle feature X e il vettore dei target y.

Le differenze tra le due implementazioni emergono quando definiamo il modello perceptron con l’API Keras.

Cominciamo creando il modello con una chiamata a Sequential, che ci consente di raggruppare una pila lineare di layer di rete neurale in un unico modello.

				
					# Crea il 'Perceptron' tramite le API Keras
model = Sequential()
				
			

Dal momento che abbiamo solo un singolo “strato” nel perceptron, questa chiamata potrebbe sembrare superflua. Tuttavia, implementandolo in questo modo, stiamo mostrando una caratteristica comune dell’API Keras e acquisendo familiarità, che può essere fruttata per futuri modelli di deep learning negli articoli successivi.

Usiamo il metodo add per aggiungere uno strato di nodi al modello sequenziale. In particolare stiamo aggiungendo un livello Dense, il che significa che tutti i nodi nel livello sono collegati a tutti gli ingressi e le uscite. Gli strati densi sono anche chiamati strati completamente connessi . Discuteremo a lungo dei livelli di rete neurale densi nel successivo articolo sui perceptron multistrato.

				
					model.add(Dense(1, input_shape=(8,), activation=hard_sigmoid, kernel_initializer='glorot_uniform'))
				
			

Nella chiamata alla funzione Dense il primo argomento 1 è la dimensione dell’output. Dal momento che stiamo tentando di determinare se un paziente ha il diabete o meno, abbiamo bisogno solo di una singola dimensione. Tuttavia il secondo parametro determina il numero di ingressi. Per il set di dati sul diabete questo è otto, uno per ciascuna delle colonne del features nel file CSV.

Specifichiamo quindi la funzione di attivazione per il livello come hard sigmoid.

Addestramento del modello

Al parametro kernel_initializer assegniamo il valore 'glorot_uniform'. Dato che stiamo allenando il perceptron con la discesa del gradiente stocastico (piuttosto che con la regola di apprendimento del perceptron), è necessario inizializzare i pesi con valori casuali diversi da zero invece di impostarli inizialmente a zero. Questo aspetto sarà approfondito nei successivi articoli.

Impostiamo la funzione obiettivo in modo da usate l’entropia incrociata binaria, che corrisponde alla funzione obiettivo standard per i problemi di classificazione binaria.

				
					model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
				
			

Il parametro optimizer   è impostato  ad 'adam'. Adam è una particolare  variante della discesa stocastica del gradiente. In questo articolo non descriviamo il funzionamento di Adam. Per gli obiettivi del nostro codice possiamo considerare Adam come una variante computazionalmente più efficiente della discesa stocastica del gradiente.

Addestriamo il modello tramite l’algoritmo di discesa stocastica del gradiente di Adam. Usiamo il concetto di mini-batch, passando in 25 campioni di addestramento alla volta. Puoi leggere di più sui mini-lotti qui

				
					# Addestramento del perceptron tramite la discesa stocastica del gradiente
# con un campione di validazione del 20%
model.fit(X, y, epochs=225, batch_size=25, verbose=1, validation_split=0.2)
				
			

Il parametro  epochs  determina quante volte ripetiamo l’intero training set. In questo esempio abbiamo 225 epoche. È necessario iterare più volte il set di dati in modo da mitigare il problema di otterenre un minimo locale del set di valori per i pesi. Più epoche offrono una migliore possibilità di raggiungere il massimo globale o un minimo locale potenzialmente migliorato.

Codice completo

In questo caso usiamo il 20% dei dati di addestramento come set di “convalida”, che è “tenuto fuori” (cioè non addestrato) e utilizzato esclusivamente per valutare l’accuratezza delle previsioni. Non l’abbiamo fatto per l’implementazione di Scikit-Learn, dove abbiamo invece verificato l’accuratezza in sample.

				
					# perc_diabetes_tensorflow.py

import pandas as pd
from tensorflow.keras.layers import Dense
from tensorflow.keras.models import Sequential
from tensorflow.keras.activations import hard_sigmoid


if __name__ == "__main__":
    # Carica il set di dati sul diabete da CSV
    # e converte in una matrice NumPy adatta per
    # l'estrazione nel formato X, y necessaria per TensorFlow
    diabetes = pd.read_csv('diabetes.csv').values

    # Estrae la matrice delle features e il vettore della risposta
    # in specifiche variabili
    X = diabetes[:, 0:8]
    y = diabetes[:, 8]

    # Crea il 'Perceptron' tramite le API Keras
    model = Sequential()
    model.add(Dense(1, input_shape=(8,), activation=hard_sigmoid, kernel_initializer='glorot_uniform'))
    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

    # Addestrare il  perceptron tramite la discesa stocastica del gradiente
    # con un set di validazione del 20%
    model.fit(X, y, epochs=225, batch_size=25, verbose=1, validation_split=0.2)

    # Valutazione dell'accuratezza del modello
    _, accuracy = model.evaluate(X, y)
    print("%0.3f" % accuracy)
				
			

L’output (estratto) è simile al seguente:

				
					..
..
Epoch 214/225
25/25 [==============================] - 0s 2ms/step - loss: 5.3510 - accuracy: 0.6531 - val_loss: 5.5089 - val_accuracy: 0.6429
Epoch 215/225
25/25 [==============================] - 0s 2ms/step - loss: 5.3510 - accuracy: 0.6531 - val_loss: 5.5089 - val_accuracy: 0.6429
Epoch 216/225
25/25 [==============================] - 0s 2ms/step - loss: 5.3510 - accuracy: 0.6531 - val_loss: 5.5089 - val_accuracy: 0.6429
Epoch 217/225
25/25 [==============================] - 0s 2ms/step - loss: 5.3510 - accuracy: 0.6531 - val_loss: 5.5089 - val_accuracy: 0.6429
Epoch 218/225
25/25 [==============================] - 0s 2ms/step - loss: 5.3510 - accuracy: 0.6531 - val_loss: 5.5089 - val_accuracy: 0.6429
Epoch 219/225
25/25 [==============================] - 0s 2ms/step - loss: 5.3510 - accuracy: 0.6531 - val_loss: 5.5089 - val_accuracy: 0.6429
Epoch 220/225
25/25 [==============================] - 0s 2ms/step - loss: 5.3510 - accuracy: 0.6531 - val_loss: 5.5089 - val_accuracy: 0.6429
Epoch 221/225
25/25 [==============================] - 0s 2ms/step - loss: 5.3510 - accuracy: 0.6531 - val_loss: 5.5089 - val_accuracy: 0.6429
Epoch 222/225
25/25 [==============================] - 0s 2ms/step - loss: 5.3510 - accuracy: 0.6531 - val_loss: 5.5089 - val_accuracy: 0.6429
Epoch 223/225
25/25 [==============================] - 0s 2ms/step - loss: 5.3510 - accuracy: 0.6531 - val_loss: 5.5089 - val_accuracy: 0.6429
Epoch 224/225
25/25 [==============================] - 0s 2ms/step - loss: 5.3510 - accuracy: 0.6531 - val_loss: 5.5089 - val_accuracy: 0.6429
Epoch 225/225
25/25 [==============================] - 0s 2ms/step - loss: 5.3510 - accuracy: 0.6531 - val_loss: 5.5089 - val_accuracy: 0.6429
24/24 [==============================] - 0s 793us/step - loss: 5.3827 - accuracy: 0.6510
0.651
				
			

Da notare che il punteggio finale è di circa il 65%.

Tuttavia, dobbiamo considerare questa cifra con cautela. Non abbiamo completamente implementato il perceptron allo stesso modo in cui è stato fatto con Scikit-Learn, e non abbiamo valutato l’accuratezza allo stesso modo a causa  dato che abbiamo usato un set di convalida.

In sintesi abbiamo eseguito la regola di apprendimento del perceptron, usando la funzione a gradino come funzione di attivazione per Scikit-Learn. Nell’implementazione di TensorFlow/Keras abbiamo effettuato la discesa stocastica del gradiente, utilizzando il hard sigmoide come funzione di attivazione (per lo più) differenziabile. Quindi i risultati dell’accuratezza della classificazione differiranno.

Nonostante queste differenze, l’obiettivo di questo articolo è quello di fornire alcune informazioni sulle API di ciascuna libreria. Usiamo ampiamente TensorFlow e l’API Keras negli articoli successivi.

Conclusione

Abbiamo descritto come addestrare le reti neurali e i perceptron. Abbiamo implementato il nostro primo modello di rete neurale in TensorFlow con l’API Keras. Tuttavia, è improbabile che un modello così semplicistico produca un’accuratezza di previsione efficace su dati più complessi, in particolare quelli utilizzati nella finanza quantitativa.

Il perceptron multistrato è il primo passo per aggiungere più complessità e quindi una potenziale accuratezza predittiva.

Riferimenti

Il codice completo presentato in questa lezione è disponibile nel seguente repository GitHub: “https://github.com/tradingquant-it/TQResearch

Torna in alto