Allenare il Perceptron con Scikit-Learn e TensorFlow

Allenare il Perceptron con Scikit-Learn e TensorFlow

Sommario

SCRIVIMI SU TELEGRAM

Per informazioni, suggerimenti, collaborazioni...

Se è la prima volta che atterri su DataTrading, BENVENUTO!

Lascia che mi presenti. Sono Gianluca, ingegnere, trekker e appassionato di coding, finanza e trading. Leggi la mia storia.

Ho creato DataTrading per aiutare le altre persone ad utilizzare nuovi approcci e nuovi strumenti, ed applicarli correttamente al mondo del trading.

DataTrading vuole essere un punto di ritrovo per scambiare esperienze, opinioni ed idee.

TUTORIAL

Nel precedente articolo sul tema delle reti neurali artificiali abbiamo introdotto il concetto di perceptron. Abbiamo dimostrato che il perceptron è in grado di classificare i dati di input tramite un confine decisionale lineare.

Tuttavia abbiamo rinviato una discussione su come calcolare i parametri che governano questo confine di decisione lineare. In questo articolo vediamo come determinare questi parametri mediante ‘allenamento’ del perceptron.

Iniziamo descrivendo la procedura di addestramento. Verifichiamo poi la sua somiglianza con un  approccio di ottimizzazione  molto popolare nell’apprendimento profondo noto come discesa stocastica del gradiente. Quindi forniamo il codice Python che dimostra il meccanismo di addestramento, implementato tramite la libreria Scikit-Learn. Infine esaminiamo il codice corrispondente nella libreria TensorFlow e vediamo le differenze.

Addestrare il Perceptron

Come descritto nell’articolo precedente, una volta che adeguati pesi e valori di bias sono disponibili, è semplice classificare i nuovi dati di input tramite il prodotto scalare tra i pesi e i componenti di input, nonché la funzione a gradino di attivazione.

Finora abbiamo tralasciato di descrivere come si determinano i pesi e i valori di bias prima di eseguire qualsiasi classificazione con il percettrone. È qui che entra in gioco una procedura di addestramento nota come regola di apprendimento del perceptron .

La regola di apprendimento del perceptron funziona considerando l’errore di previsione generato quando il perceptron tenta di classificare una particolare istanza di dati di input etichettati. In particolare la regola amplifica i pesi (connessioni) che portano ad una minimizzazione dell’errore.

Quando vengono fornite singole istanze di addestramento al perceptron, viene effettuata una previsione. Se viene generata una classificazione errata, rispetto all’etichetta corretta di ‘verità di base’, i pesi che avrebbero portato a una previsione corretta vengono rafforzati[3].

In questo modo i pesi sono modificati in modo iterativo man mano che nuovi campioni di addestramento sono inseriti nel perceptron, fino a quando non viene trovata una soluzione ottimale.

Matematicamente questa procedura è data dal seguente algoritmo di aggiornamento:

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

Dove:

  • \(w_i^{n}\) è l’i-esimo peso al passo n
  • \(x_i\) è  l’i-esimo componente dell’istanza di dati di input di addestramento corrente
  • \(y\) è l’etichetta di classificazione corretta per la “verità di base” per questi dati di input
  • \(\hat{y}\) è l’etichetta di classificazione prevista per questi dati di input
  • \(\nu\) è il tasso di apprendimento

Scomponiamo questa formula in termini separati per ricavare qualche intuizione su come funziona. Si afferma che i nuovi pesi \(w_i^{n+1}\), al passo \(n+1\),
sono dati dalla somma dei vecchi pesi \(w_i^{n}\), al passo n, e di un termine aggiuntivo \(\nu (y – \hat{y}) x_i\).

Dato che il termine aggiuntivo include la differenza tra il valore previsto del risultato
e la verità di base, questo termine aumenterà se questa differenza è più elevata. Cioè, i pesi sono modificati dal vecchio valore tanto quanto maggiore diventa questa differenza. Ciò ha senso poiché se la previsione è lontana dal valore etichettato corretto sarà necessario spostare ulteriormente il peso per migliorare la precisione della previsione successiva.

L’altro fattore in questo termine è il tasso di apprendimento \(\nu\). Questo coefficiente scala il movimento dei pesi, in modo che possa essere notevolmente ridotto o amplificato. Un \(\nu\) piccolo significa che anche per una grande differenza di previsione, i pesi non si sposteranno molto. Di conseguenza, un \(\nu\) grande causa uno spostamento significativo dei pesi anche per una piccola differenza predittiva.

Il tasso di apprendimento è un esempio di iperparametro per il modello. Inoltre è necessario determinare il suo valore ottimale. Descriviamo l’ottimizzazione degli iperparametri in articoli successivi, dove vediamo architetture di reti neurali più complesse.

Infine il termine viene anche moltiplicato per \(x_i\). Cioè, se l’i-esimo componente dell’input è grande, lo è anche lo modifica del peso, a parità di tutti gli altri fattori.

Vediamo ora questa procedura di addestramento del perceptron con due  separate librerie Python, ovvero Scikit-Learn e TensorFlow.

Di recente abbiamo pubblicato un articolo su come installare TensorFlow su Ubuntu contro una GPU , che aiuterà nell’esecuzione del seguente codice TensorFlow.

Implementazione del codice

In questa sezione usiamo un set di dati sul diabete del National Institute of Diabetes and Digestive and Kidney Diseases[4] per testare la capacità di classificazione del perceptron.

Il set di dati contiene 768 record con otto misurazioni diagnostiche e un risultato sul fatto che un paziente abbia il diabete. Nel dataset tutti i pazienti sono donne, almeno 21 anni di età, e di origine Pima.

Il file CSV del set di dati può essere ottenuto qui dal sito Kaggle. Per caricare i dati correttamente questo file dovrà essere posizionato nella stessa directory del seguente frammento di codice.

Non ci soffermiamo sulle specifiche del set di dati. Piuttosto, lo usiamo esclusivamente come mezzo per spiegare l’algoritmo di addestramento. Se desideri saperne di più sulle misurazioni diagnostiche e su come sono stati ottenuti i dati, vedi [4] per maggiori dettagli.

Scikit-Learn

Nel seguente script perc_diabetes_sklearn.py usiamo Pandas e Scikit-Learn per caricare i dati sul diabete e adattare un modello di classificazione binaria del perceptron.

Il primo compito è chiamare il metodo read_csv di Pandas per caricare il file CSV del set di dati in un DataFrame, concatenando il metodo values per convertire l’oggetto DataFrame in una matrice NumPy, adatta per l’estrazione del valore in Scikit-Learn.

La matrice delle features X è definita come le prime otto colonne di questa matrice (ha la forma (768, 8)). Il vettore dei risultati y è la colonna finale, composta da 0 per nessun diabete e 1 per il diabete.

Il modello perceptron è quindi inizializzato con un particolare seme casuale per garantire risultati riproducibili. Il modello è addestrato con la regola di apprendimento del perceptron tramite il metodo fit.

Infine è emesso il punteggio di precisione medio sugli stessi dati 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
				
			

Da notare che il punteggio di classificazione è di circa il 53%.

C’era da aspettarselo. Stiamo essenzialmente cercando di chiedere a una singola unità lineare di soglia di adattarsi a un iperpiano decisionale lineare tramite dati complessi a otto dimensioni. È improbabile che tali dati presentino un preciso confine di decisione lineare tra “nessun diabete” e “diabete”.

TensorFlow & Keras

Vediamo come implementare il perceptron con l’API Keras utilizzando la libreria TensorFlow. Il codice è leggermente più complesso rispetto alla versione Scikit-Learn. Tuttavia, la complessità aggiunta nell’API si rivele vantaggiosa negli articoli successivi dove descriviamo  come modellare architetture di reti neurali profonde.

Funzione sigmoidea di attivazione rigida

Prima di dimostrare e spiegare il codice corrispondente TensorFlow/Keras per l’addestramento di un singolo perceptron, dobbiamo evidenziare la difficoltà nel riprodurre completamente il perceptron come descritto nell’articolo precedenteVedere [6] per una discussione dettagliata  sulle motivazioni.

In sintesi questo è causato dalla natura dell’API Keras, progettata principalmente per architetture di reti neurali profonde con funzioni di attivazione differenziabili che producono gradienti diversi da zero. La funzione di attivazione utilizzata nel perceptron originale è una funzione a gradini, che non è continua (e quindi non differenziabile) a zero.

Poiché Keras utilizza la discesa stocastica del gradiente come procedura di ottimizzazione principale, è necessario coinvolgere gradienti diversi da zero se i pesi devono essere modificati durante l’allenamento.

Per evitare questo problema è possibile sostituire la funzione a gradini con una funzione strettamente correlata chiamata hard sigmoid, come funzione di attivazione. Il hard sigmoid  è un’approssimazione lineare a tratti della funzione sigmoidea originale (una “curva a s”), che è differenziabile ovunque tranne che in due punti. Possiede ancora zero gradienti per alcune parti del dominio ma ammette gradienti diversi da zero nella sezione centrale lineare a tratti.

Questo è sufficiente per produrre in Keras e TensorFlow un’implementazione “simile al perceptron”.

Implementazione TensorFlow

Tramite la descrizione del dimostrare il codice TensorFlow/Keras corrispondente, l’obiettivo di questo articolo è iniziare a familiarizzare con l’API utilizzata per le reti neurali profonde. Molti dei parametri forniti per la creazione del modello richiedono spiegazioni più approfondite di quelle possibili in questo post. Descriviamo brevemente ogni parametro, ma rinviamo le  spiegazioni più complete fino a quando non vedremo le architetture di reti neurali profonde negli articoli successivi.

Nel seguente script ( perc_diabetes_tensorflow.py) consideriamo lo stesso set di dati sul diabete usato per Scikit-Learn,. Nello stesso modo si carica i dati da CSV e si costruisce la matrice delle features X e il vettore dei risultati y.

La differenza tra le due implementazioni inizia quando definiamo il modello perceptron tramite l’API Keras.

Per prima cosa creiamo il modello usando una chiamata a Sequential, che permette di raggruppare una pila lineare di livelli 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.

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 (per ulteriori dettagli si rimanda ad un precedente articolo), 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.

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.

Prossimi passi

Ora abbiamo implementato e addestrato 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.

Nel prossimo articolo descriviamo il perceptron multistrato come primo passo per aggiungere più complessità e quindi una potenziale accuratezza predittiva.

Riferimenti

Torna su