Negli ultimi dieci anni il tema del deep learning è stato uno dei campi più discussi dell’apprendimento automatico e dell’intelligenza artificiale. Ha prodotto risultati all’avanguardia in aree diverse come la visione artificiale, il riconoscimento delle immagini, l’elaborazione del linguaggio naturale e il riconoscimento vocale. Tuttavia è stato anche ampiamente pubblicizzato – la risposta a tutti i problemi di apprendimento automatico – ed è spesso frainteso a causa della sua ripida curva di apprendimento.
Negli ultimi due anni una delle principali aziende di deep learning, Google DeepMind, è riuscita a utilizzare una tecnica particolare nota come deep reinforcement learning per battere gli umani ai giochi Atari 2600 [7]. In un recente concorso molto pubblicizzato [8], il modello AlphaGo ha battuto il miglior giocatore di Go in una serie di giochi, aumentando ulteriormente il mistero e l’eccitazione che circonda questa tecnica di apprendimento automatico.
Il deep learning non è una materia facile da imparare e per iniziare richiede alcuni concetti matematici a livello universitario. In particolare si dovrebbero conoscere gli elementi di base dell’algebra lineare, del calcolo vettoriale (gradienti, derivate parziali) e della probabilità (stima di massima verosimiglianza). Oltre ai requisiti matematici è necessario una buona comprensione della programmazione orientata agli oggetti e, ai fini dell’efficienza, una conoscenza di base delle operazioni con una GPU. Tuttavia cerchiamo di introdurre molti di questi concetti quando sono necessari. Questa serie di articoli dovrebbe essere relativamente semplice per coloro che hanno un background matematico.
In questi articoli descriviamo cos’è il deep learning, perché è recentemente diventato così popolare, come funziona e come possiamo applicarlo alle aree della finanza quantitativa per migliorare i risultati del nostro modello e la redditività del portafoglio. Vediamo come sfrutture una libreria Python chiamata Theano [5] e le GPU di un computer per aumentare le prestazioni di allenamento dei modelli predittivi.
In particolare, nel corso della serie, consideriamo i seguenti argomenti:
- Regressione logistica a Theano (questo articolo)
- Reti neurali e perceptron multistrato
- Reti neurali di convoluzione (ConvNets)
- Denoising Autoencoder e Stacked Denoising Autoencoder
- Macchine Boltzmann limitate
- Deep Belief Networks
- Reinforcement Learning e Q-Learning
- Deep Learning per l’analisi di serie temporali
Che cos’è l’apprendimento profondo?
Il deep learning è un sottoinsieme del più ampio campo del machine learning che tenta di modellare le astrazioni di alto livello presenti nei dati al fine di migliorare notevolmente le prestazioni nell’apprendimento sia supervisionato che non supervisionato [9].
Raggiunge questo utilizzando più livelli di “processi”, ognuno dei quali contiene un insieme di funzioni di trasformazione non lineari che apprendono le rappresentazioni all’interno dei dati.
Questo approccio è basato direttamente dai processi che regolano le funzioni del cervello umano. Nel corso della nostra vita impariamo idee semplici e poi usiamo queste idee semplici per formare gerarchie di idee più complesse. Il deep learning offre un’applicazione di questo approccio alle attività di machine learning.
Motivazioni per l’uso del deep learning
Uno dei principali problemi del machine learning statistico è il tempo richiesto per creare manualmente le feature, o predittori, al fine di generare efficaci algoritmi di classificazione o regressione. Ad esempio, quando si tenta di prevedere i valori futuri di un indice del mercato azionario, potrebbe essere pertinente includere i tassi di interesse, i prezzi delle materie prime o i dati fondamentali delle azioni. Oppure per i dati che inizialmente non sono in una comoda rappresentazione numerica come video, audio o testo è necessario trovare efficienti algoritmi di trasformazione per ricavare feature predittive.
La scelta delle feature ottimali è difficile e richiede tempo. Le feature devono essere spesso realizzate a mano tramite la riduzione o la trasformazione della dimensione dei dati per migliorare le prestazioni predittive. Esempi di tali trasformazioni includono il Modello della borsa di parole e i metodi di vettorizzazione della term frequency–inverse document frequency per l’elaborazione del linguaggio naturale.
Il deep learning promette di sostituire l’attività manuale di ingegneria delle feature, molto dispendiosa in termini di tempo, tramite l’introduzione di efficienti architetture di rete per l’ apprendimento non supervisionato delle feature. La chiave di questo processo risiede nella rappresentazione gerarchica delle feature all’interno della struttura della rete.
L’attuale ricerca sugli algoritmi di deep learning tenta di creare modelli che riescano ad apprendere le rappresentazioni astratte a partire da grandi quantità di dati senza etichette. Tale ricerca è importante perché i dati non etichettati sono spesso estremamente abbondanti e facilmente disponibili rispetto al più costoso processo per ottenere dei dati etichettati, che viene utilizzato negli approcci di apprendimento supervisionato.
Il deep learning è diventato popolare negli ultimi dieci anni grazie a tre documenti “rivoluzionari” di Geoff Hinton , Yoshua Bengio , Yann LeCun e altri [2]:
- Hinton, G. E., Osindero, S. and Teh, Y., A fast learning algorithm for deep belief nets Neural Computation 18:1527-1554, 2006
- Yoshua Bengio, Pascal Lamblin, Dan Popovici and Hugo Larochelle, Greedy Layer-Wise Training of Deep Networks, in J. Platt et al. (Eds), Advances in Neural Information Processing Systems 19 (NIPS 2006), pp. 153-160, MIT Press, 2007
- Marc’Aurelio Ranzato, Christopher Poultney, Sumit Chopra and Yann LeCun Efficient Learning of Sparse Representations with an Energy-Based Model, in J. Platt et al. (Eds), Advances in Neural Information Processing Systems (NIPS 2006), MIT Press, 2007
Oltre a tutta la vasta letteratura sull’argomento prodotta negli ultimi anni.
Inoltre, grazie all’aumento della potenza di calcolo, in particolare per quanto riguarda i costi ridotti e la maggiore disponibilità di unità di elaborazione grafica (GPU), il deep learning è diventato accessibile ad un’ampia comunità non accademica.
Alcune tecniche di deep learning forniscono risultati all’avanguardia in campi come la visione artificiale, l’elaborazione delle immagini, il riconoscimento vocale automatico e l’elaborazione del linguaggio naturale. Quindi è interessante considerare come potrebbero essere applicata ad altri settori come la finanza quantitativa.
Deep Learning per la finanza quantitativa
Nonostante il deep learning sia chiaramente molto utile e performante in alcuni campi, offre le stesse prestazioni nella finanza quantitativa? Secondo me sì. Il deep learning è stato applicato con successo ai dati delle serie temporali [11] nonostante è necessario prendere in considerazione la natura temporale dei dati durante la realizzazione degli algoritmi di deep learning. Questo non è un problema banale e richiede molte ricerche.
Inoltre il deep learning ha mostrato risultati promettenti nel rilevamento delle anomalie temporali, almeno nei campi dell’ingegneria, ed è competitivo ad altri strumenti di rilevamento delle anomalie come le reti bayesiane applicate ai dati di serie temporali multivariate. Alcuni algoritmi di finanza quantitativa necessitano di determinare i “cambiamenti di regime” e il deep learning è probabilmente molto utile in questo caso.
Dove il Deep Learning aggiunge davvero valore al trader quantitativo è la possibilità di analizzare e dare facilmente un significato a grandi set di dati di immagini e produrre segnali basati su queste immagini. Come possiamo applicarlo in strategie quantitativi? Siamo interessati solo ai dati storici dei prezzi o anche ai dati fondamentali?
Per rispondere a queste domande dobbiamo considerare che oggi esiste una vasta gamma di fornitori di dati geospaziali, generati da una combinazione di costellazioni di satelliti in orbita terrestre bassa (LEO) e di droni appositamente costruiti per catturare immagini. Questi dati satellitari possono fornire una vasta gamma di informazioni su parametri precedentemente molto difficili da ottenere. Più passaggi al mese su una particolare regione forniscono l’elemento temporale alle immagini satellitari.
Ecco alcuni esempi di come una attività di trading quantitativo (un fondo o un trader retail) potrebbe utilizzare i dati satellitari e tecniche di deep learning per produrre segnali di trading:
- Parcheggi auto dei negozi al dettaglio – L’analisi delle immagini satellitari dei parcheggi dei negozi al dettaglio per un determinato periodo, ad esempio tre mesi, con uno strumento di classificazione del deep learning, in grado di contare le auto, consente un’indicazione approssimativa dei dati di vendita. Questa indicazione può essere utilizzata, oltre ai dati fondamentali più tradizionali, per determinare la probabilità che un’azienda soddisfi le previsioni di vendita per il prossimo trimestre.
- Altezza dei serbatoi di stoccaggio delle raffinerie di petrolio – Il deep learning può essere applicato ai dati satellitari per classificare i serbatoi di stoccaggio del petrolio situati nelle raffinerie di tutto il mondo. Analizzando le ombre presenti nelle immagini è possibile determinare la quantità di petrolio stoccato in vari momenti. Questo può essere utilizzato per formare un “indice” di restrizione dell’offerta di petrolio, insieme a dati petroliferi più tradizionali come i futures sul petrolio greggio, per creare un modello fondamentale più accurato nel determinare la domanda e l’offerta.
- Rendimenti delle colture – Tecniche simili a quelle sopra possono essere utilizzate per accertare le rese delle colture (in molti settori), che forniranno informazioni su come il mercato dei futures per quella particolare commodity potrebbe comportarsi al momento del raccolto.
Se tutto può sembrare piuttosto inverosimile, suggeriamo di dare un’occhiata ad alcune aziende che forniscono questo tipo di servizi al settore finanziario come Orbital Insight e Satellite Imaging Corporation.
Gli hedge fund quantistici applicano queste tecniche già da tempo. La sempre maggiore disponibilità di immagini satellitari, librerie di deep learning e archiviazione ed elaborazione basate su cloud, permette anche al trader quantitativo retail di poter accedere facilmente a queste tecniche, se è ben determinato a fare qualcosa di simile.
Nei successi articoli descriviamo come eseguire tale analisi, sperando che possa consentire di sviluppare e aggiungere alcune strategie non correlate al proprio portafoglio.
Librerie di software per il deep learning
La popolarità del deep learning significa che non mancano le librerie di software open source disponibili. Un elenco completo può essere trovato qui su Wikipedia. Le librerie più performanti sono Tensorflow, Torch, Theano e Caffe.
Non ci soffermiamo sui pro e dei contro tra queste librerie. In definitiva, tutte sono utili per creare modelli di deep learning, in diversi linguaggi/ambienti di programmazione, con caratteristiche di prestazioni e diverse API per farlo. Se desideri indagare su quale potrebbe essere preferibile per i tuoi progetti, è meglio seguire il link Wikipedia sopra e confrontare.
Abbiamo scelto Theano per questi tutorial perchè è basato su Python, ha accesso sia alla CPU che alla GPU ed è un prerequisito per PyMC3, che abbiamo già usato nell’articolo sull’MCMC bayesiano.
In questo articolo introduciamo Theano e lo applichiamo ad un semplice modello di regressione logistica.
Introduzione a Theano
Per questa serie di articoli utilizzeremo la libreria Theano di Python.
Cos’è Theano?
Theano è una libreria di calcolo numerico che consente di definire, ottimizzare e valutare espressioni matematiche che coinvolgono array multidimensionali, in modo efficiente attraverso la CPU o la GPU [5].
Cosa significa questo? Ebbene, molti modelli di machine learning vengono creati utilizzando grandi array multidimensionali, che vengono spesso utilizzati per memorizzare i valori o i pesi dei parametri. Inoltre, questi modelli analizzano i dati che sono anche archiviati in grandi array multidimensionali. Pertanto ha senso utilizzare una libreria in grado di gestire in modo efficiente i dati multidimensionali.
Questo rende Theano particolarmente attraente dal punto di vista del deep learning perchè utilizza espressioni simboliche. In altre parole, invece di scrivere direttamente il codice Python per formule matematiche, si usa oggetti per rappresentare l’equazione. Theano prende questa equazione e trova il miglior modo per eseguirla ,in modo completamente trasparente per il programmatore. Di estrema importanza per le applicazioni di deep learning (soprattutto quelle che utilizzano la discesa stocastica del gradiente) è la capacità delle espressioni di essere simbolicamente differenziate. Descriviamo di seguito questo concetto in modo più dettagliato.
E’ fondamentare evidenziare che Theano consente di scrivere le specifiche del modello anziché le implementazioni del modello. Questo particolarmente utile perchè Theano è molto ben integrato nella GPU, che fornisce sostanziali accelerazioni per l’allenamento di deep learning.
Quindi Theano si trova da qualche parte tra NumPy e la libreria matematica simbolica SymPy di Python
Dobbiamo sottolineare che Theano non viene usano esclusivamente per la ricerca sul deep learning. ad esempio, PyMC3, la libreria bayesiana di programmazione probabilistica, è parzialmente scritta anche in Theano.
Installazione di Theano
È consigliabile seguire le istruzioni di installazione per Theano direttamente dal sito ufficiale dato che è disponbile per molte piattaforme e diverse versioni di Python.
Questo articolo è sicuramente più facile da seguire se si dispone di un sistema basato su Unix, come Linux/Ubuntu, o Mac OS X. Credo che semplifichi anche l’interazione con una GPU perché è molto difficile poter accedere a una GPU tramite Anaconda su sistemi Windows.
Descriviamo ora la regressione logistica e alla sua implementazione a Theano.
Regressione logistica con Theano
Abbiamo già descritto le motivazioni per cui il deep learning è un approccio da considerare e merita un’approfondimento. In questo paragrafo descriviamo come creare il nostro primo modello statistico – una regressione logistica multiclasse – utilizzando il framework Theano per capirne il funzionamento. Sebbene questa non sia una vera e propria tecnica di deep learning, è un primo passo fondamentale verso la comprensione di architetture più complicate come i perceptron multistrato e le reti neurali convoluzionali.
Applichiamo la regressione logistica a un famoso database noto come MNIST (database di cifre scritte a mano). Vediamo come possiamo ottenere ragionevoli prestazioni di classificazione con questo metodo. Tuttavia, l’aspetto più importante è descrivere come costruire un modello non banale in Theano e usarlo come base per modelli di deep learning più complessi nei prossimi articoli.
Iniziamo descrivendo il database MNIST e quindi ripassiamo brevemente la regressione logistica. Esploriamo poi la discesa stocastica del gradiente, un algoritmo di ottimizzazione utilizzato per addestrare modelli, compresi quelli del deep learning. Descriviamo quindi le funzioni di verosimiglianza al fine di fornire una “funzione obiettivo” per l’addestramento. Infine, implementiamo tutte queste tecniche in Theano e quindi addestriamo/testiamo il modello di regressione logistica con le CPU e le GPU.
Set di dati MNIST
Il database Mixed National Institute of Standards and Technology (MNIST) [16] è un insieme di immagini di cifre scritte a mano, che viene spesso utilizzato per la formazione e il test nel campo del machine learning. Contiene 60.000 immagini di allenamento e 10.000 immagini di test, tutte di dimensioni 28×28 pixel.
Gli algoritmi di deep learning sono attualmente lo stato dell’arte per le prestazioni di classificazione sul set di dati MNIST. In particolare, un approccio con reti neurali a convoluzione gerarchica ha raggiunto un tasso di errore di appena lo 0,23% nel 2012.
In questa serie di articoli usiamo il set di dati MNIST poiché è facile da lavorare e ci consente di verificare come le prestazioni di classificazione migliorano con la crescente sofisticazione delle architetture che introduciamo.
Il set di dati MNIST, in una semplice forma per essere usata in questi tutorial, può essere trovata qui: http://www.iro.umontreal.ca/~lisa/deep/data/mnist/mnist.pkl.gz.
Affinché questo tutorial sia eseguito correttamente, questo file deve essere posizionato nella stessa directory del codice Python, che abbiamo chiamato deep_logistic_regression_theano.py
. Non ha bisogno di essere decompresso. La decompressione verrà gestita all’interno di Python tramite il modulo gzip
.
Nei sistemi Linux/Ubuntu possiamo direttamente scaricare il file con la seguente riga di comando:
$ wget http://www.iro.umontreal.ca/~lisa/deep/data/mnist/mnist.pkl.gz
Modello di regressione logistica
In questo tutorial usiamo il modello probabilistico di regressione logistica multiclasse per classificare le cifre scritte a mano di MNIST. Nonostante il nome, la regressione logistica è in realtà una tecnica di classificazione.
Una discussione dettagliata sulla regressione logistica non rientra nello scopo di questo articolo. Un’introduzione all’argomento con un semplice livello matematico può essere trovata in James et al [13], mentre una discussione più avanzata a livello accademico può essere trovata in Hastie et al [12].
La regressione logistica ha lo scopo di assegnare probabilisticamente un’etichetta di classe all’immagine di una cifra (“classificazione”) dopo aver opportunamente “addestrato” il modello di regressione logistica su precedenti coppie immagine-etichetta. Quindi siamo in un regime di apprendimento supervisionato.
Consideriamo \(Y\) la risposta della classe etichettata (la cifra) e \(x\) il nostro vettore di feature in ingresso (l’immagine 28×28 pixel) allora la probabilità di classificazione in una determinata cifra \(K\) a partire da una specifica immagine \(x\) è data da [12]:
\(\begin{eqnarray}P(Y=k \mid x) = \frac{\text{exp}(\beta_{k0} + \beta^{T}_k x)}{1 + \sum^{K-1}_{l=1} \text{exp}(\beta_{l0} + \beta^{T}_{l} x)}\end{eqnarray}\)
Dove \(k \in \{1,\ldots,K-1 \}\).
Per semplificare l’elenco dei parametri \(\beta_j\) possiamo riscrivere la formula usando le matrici. Se consideriamo \(W\) la matrice dei pesi e \(b\) il vettore dei bias, allora la probabilità diventa:
\(\begin{eqnarray}P(Y=k \mid x; W,b) = \frac{\text{exp}(W_k x + b_k}{\sum^{K}_{l=1} \text{exp}(W_l x + b_l)}\end{eqnarray}\)
A questo punto, supponendo di avere un meccanismo per trovare la matrice dei pesi \(W\) e il vettore dei bias \(b\), possiamo dire che la cifra più probabile per un particolare vettore di feature dell’immagine \(x\) è data da \(P(Y=k \mid x; W,b)\).
Quindi classifichiamo l’immagine in una specifica cifra prendendo la cifra con la probabilità più alta tra tutte le cifre 0…9. Matematicamente questo viene scritto usando la funzione \(\text{argmax}\) per la previsione del modello, \(y_{\text{pred}}\):
\(\begin{eqnarray}y_{\text{pred}} = \text{argmax}_k P(Y=k \mid x; W,b)\end{eqnarray}\)
Quindi non ci resta che determinare (in qualche modo) \(W\) e \(b\). È qui che entrano in gioco i concetti di funzioni obiettivo, verosimiglianza e addestramento. Prima di una discussione sulla probabilità, dobbiamo descrivere un algoritmo di ottimizzazione chiamato discesa stocastica del gradiente che costituisce la base per molti futuri algoritmi di deep learning.
Discesa stocastica del gradiente
Una parte fondamentale degli algoritmi di deep learning è l’ottimizzazione. Molte architetture di deep learning richiedono di ridurre al minimo una qualche forma di funzione obiettivo (o funzione di costo) affinché una rete di deep learning possa essere “addestrata” o “istruita”.
Un esempio più familiare è tratto dalla statistica classica. Per la regressione lineare potremmo usare i minimi quadrati per trovare la “linea di miglior adattamento”. Un altro esempio è dato dalla regressione logistica utilizzata in questo articolo. Abbiamo bisogno di ottimizzare il negative log-likelihood (descritta più avanti) per verificare i parametri della regressione logistica.
Un particolare metodo di ottimizzazione usato frequentemente nel deep learning è la discesa stocastica del gradiente. Per descrivere come funziona la discesa stocastica del gradiente dobbiamo prima introdurre la discesa normale del gradiente.
La discesa del gradiente si basa sull’idea che se consideriamo una funzione obiettivo multivariabile o una “superficie di ottimizzazione”, data da \(f(x)\), dove \(f\) è differenziabile e \(x \in \mathbb{R}^n\), quindi in una regione locale intorno a un punto a, \(f\) diminuisce più rapidamente se ci si sposta nella direzione del gradiente negativo di \(f\) in a. In altre parole, viaggiamo nella direzione data da \(- \nabla f(a)\). In pratica questo vuol dire che il modo più veloce per scendere da una collina è viaggiare lungo il pendio che è il più ripido localmente.
Con questo algoritmo il successivo punto raggiunto, \(x_{n+1}\), è uguale al punto precedente, \(x_{n}\), meno la distanza percorsa in direzione della pendenza più ripida:
\(\begin{eqnarray}x_{n+1} = x_n – \gamma_n \nabla f(x_n)\end{eqnarray}\)
Dove \(\gamma_n\) è un parametro dipendente dal passo della distanza da percorrere, noto come step size o learning rate.
Tale sequenza di passaggi dovrebbe convergere al minimo locale desiderato. Nel caso di una rete di deep learning, questo dovrebbe portare a un set di parametri “formazione” o “apprendimento” ottimizzato localmente.
Nel machine learning e in altri esempi di stima statistica, le funzioni obiettivo hanno spesso la forma:
\(\begin{eqnarray}f(x) = \sum_{i=1}^n f_i (x)\end{eqnarray}\)
Cioè, la funzione obiettivo è una somma di funzioni \(f_i\), che sono spesso associati alla i-esima osservazione del set di dati di feature/formazione [14] .
La discesa stocastica del gradiente è simile alla discesa normale del gradiente. In questo caso, invece di valutare ad ogni step tutte le derivate parziali degli addendi \(f_i\), \(\frac{\partial f_i}{\partial x_j}\) (che è computazionalmente costoso), ad ogni passo si valuta solo un sottoinsieme casuale di derivate parziali. Questo porta ad enormi risparmi sui costi computazionali, in particolare per le reti di deep learning ad alta dimensione che dobbiamo prendere in considerazione.
Usiamo la discesa stocastica del gradiente per valutare la nostra funzione obiettivo, descritta di seguito, così come per tutti i restanti articoli sulle architetture di deep learning.
La particolare forma di discesa stocastica del gradiente usata in questo esempio è chiamata discesa stocastica del gradiente con la tecnica del mini-batch (MSGD) che prevede il calcolo della funzione obiettivo su più istanze selezionate anziché su una sola istanza. Questo ha l’effetto di ridurre la varianza nella stima del gradiente, poiché non siamo fortemente dipendenti da una singola istanza di allenamento per il nostro calcolo del gradiente. Introduce anche un parametro aggiuntivo, denominato \(B\), che è la dimensione del minibatch. Descriviamo la dimensione di \(B\) per particolari problemi che si possono presentano, poiché ha un impatto sulla velocità di convergenza.
Funzione obiettivo per il Negative Log-Likelihood
Come i modelli avanzati di deep learning, i modelli statistici classici richiedono un meccanismo in grado di generare i parametri ottimali per la previsione e l’inferenza. Come accennato in precedenza, un esempio familiare è l’approccio dei minimi quadrati ordinario per la regressione lineare.
Per la regressione logistica dobbiamo utilizzare un concetto noto come metodo della massima verosimiglianza (MLE). L’idea alla base della MLE è stimare i parametri di un modello statistico (come la pendenza e l’intercetta su una regressione lineare univariata) che “meglio” si adattano ai dati. Nel nostro caso siamo interessati a selezionare la migliore matrice W dei pesi e il vettore b dei bias che si adattano ai nostri dati di allenamento.
Il MLE prevede di selezionare i parametri che massimizzano la funzione di verosimiglianza. In questo modo si massimizza il “fitting” del modello con i dati di training.
Molte funzioni di verosimiglianza sono infatti prodotti di probabilità condizionata. Dato che stiamo cercando di massimizzare queste funzioni di verosimiglianza, dobbiamo considerare le loro derivate parziali rispetto ai valori dei parametri e impostarle a zero, come è consuetudine per trovare un punto massimo, minimo o stazionario.
L’assunzione di derivate parziali di prodotti di probabilità porta a equazioni complesse che diventano computazionalmente costose da valutare su larga scala. Quindi è spesso più facile lavorare con la verosimiglianza logaritmica (“log-likelihood”). Il logaritmo naturale di un prodotto corrisponde a una somma di logaritmi rendendo la differenziazione molto più semplice. Questo è possibile perché lo stesso logaritmo è una funzione monotona, cioè il massimo della log-likelihood è anche il massimo della verosimiglianza. Quindi possiamo usare la log-likelihood al posto della funzione di verosimiglianza.
Nella regressione logistica la negative log-likelihood per N osservazioni dei dati di allenamento è data da [12]:
\(\begin{eqnarray}\ell (\theta = \{ W, d \} \mid \mathcal{D}) = \sum_{i=1}^N \log (P(Y=k \mid x_i; \theta))\end{eqnarray}\)
Cioè, considerando i dati \(\mathcal{D}\) la negative log-likelihood dei parametri \(\theta\) (che sono la matrice W dei pesi e il vettore b dei bias) è uguale alla somma, attraverso tutti gli set di training in \(\mathcal{D}\) ,del logaritmo delle rispettive probabilità che una specifica cifra si verifichi dato la specifica i-esima immagine del vettore delle feature, \(x_i\), con i parametri scelti.
Questa è la funzione obiettivo da valutare usando Theano. Di solito dobbiamo differenziare questa funzione, che potrebbe produrre alcune espressioni complesse per le sue derivate parziali rispetto ai parametri. Inoltre dobbiamo considerare problemi associati alla stabilità numerica [15] . Tuttavia, possiamo usare l’operatore di gradiente grad
di Theano per raggiungere questo obiettivo.
Implementazione della Regressione Logistica con Theano
Finalmente siamo arrivati al- punto di iniziare a scrivere del codice con Theano. Descriviamo ora i singoli frammenti di codice mentre procediamo con l’implementazione-
Prima di iniziare dobbiamo installare Theano:
$ pip install theano
Come già detto, nel sito ufficiale sono disponibili le istruzioni di installazione di Theano per varie piattaforme.
Iniziamo con l’importazione delle librerie necessarie:
import gzip
import six.moves.cPickle as pickle
import timeit
import numpy as np
import theano
import theano.tensor as T
Importiamo gzip
e una versione più veloce di pickle
per decomprimere e deserializzare il database MNIST. Inoltre importiamo timeit
per calcolare il valore di elapsed real time per il confronto CPU/GPU. Numpy è necessario per la creazione delle matrici e degli array, mentre importiamo la libreria tensor di Theano come T
per comodità.
La prima cosa da fare è creare una classe che implementa il nostro modello di regressione logistica. Questa classe contiene le variabili per memorizzare la matrice W dei pesi e il vettore b dei bias polarizzazione. Conterrà anche un meccanismo per calcolare la probabilità di appartenenza alla classe \(P(Y=k \mid x; W,b)\) e conseguentemente, la previsione alla nuova classe \(y_{\text{pred}}\).
Diamo un’occhiata al codice che implementa la classe LogisticRegression
.
Il nostro primo codice definisce la matrice dei pesi:
self.W = theano.shared(
value=np.zeros(
(num_in, num_out), dtype=theano.config.floatX
),
name='W', borrow=True
)
Questo codice usa utilizza l’oggetto theano.shared
, che consente di condividere una variabile simbolica tra le funzioni. Richiede una matrice di zeri di Numpy di un tipo di dati fisso floatX
(che può essere a 32 o 64 bit). Dobbiamo dargli un nome, in questo caso ‘W’ e infine usiamo il parametro borrow=True
per evitare il deepcopying dei dati in memoria. Questo è simile al passaggio di puntatori in C++. Tuttavia questo parametro non avrà alcun effetto su una GPU, come approfondito nel tutorial di Theano. In questa fase W NON è stata “addestrata” ma solo inizializzata con una matrice di zeri.
Ora creiamo uno codice simile per il vettore dei bias. È simile con l’eccezione che creiamo un vettore di zeri invece di una matrice di zeri:
self.b = theano.shared(
value=np.zeros(
(num_out,), dtype=theano.config.floatX
),
name='b', borrow=True
)
abbiamo anche bisogno di un’espressione simbolica da memorizzare \(P(Y=k \mid x; W,B)\). Il codice è riportato di seguito:
self.p_y_x = T.nnet.softmax(T.dot(x, self.W) + self.b)
Sembra piuttosto complicato! Cosa si sta facendo in questo frammento di codice? In primo luogo, notiamo l’uso della funzione softmax
. Questa è una particolare funzione matematica (che si può approfondire qui) data da:
\(\begin{eqnarray}\sigma({\bf z}) = \frac{\text{exp}(z_k)}{\sum^{K}_{l=1} \text{exp}(z_l)}\end{eqnarray}\)
Dove \(k \in \{ 1, \ldots, K \}\). Questa formula è nella stessa forma della probabilità della classe etichetta Y, dato un vettore di feature x. Nella nostra formula possiamo quindi utilizzare la funzione softmax implementata nella libreria Theano. Da notare che usiamo l’operatore dot
tra il vettore x e la matrice dei pesi W, sommato al vettore dei bias b. Abbiamo quindi una rappresentazione simbolica per il calcolo della probabilità di Y.
L’ultima variabile da inizializzare è il meccanismo per calcolare l’effettiva classe della cifra, data la probabilità. Come descritto nei paragrafi precedenti, questo è caratterizzato dalla funzione \(\text{argmax}\). Quindi il seguente frammento di codice è relativamente facile da interpretare poiché utilizza semplicemente la funzione argmax
di Theano:
self.y_pred = T.argmax(self.p_y_x, axis=1)
Questo completa l’inizializzazione della classe. Da notare ancora una volta che in questa fase W e b non sono stati addestrati e sono semplicemente impostati come matrici di zeri, cioè matrici con tutti gli elementi uguali a zero.
A questo punto possiamo usare gli operatori di Theano per definire la funzione obiettivo della negative log-likelihood con una espressione simbolica. In questo modo possiamo definire un meccanismo per valutare la verosimiglianza per qualsiasi specifico insieme di parametri W e b, che sarebbe molto complicato da implementare in un codice senza l’aiuto degli operatori forniti da Theano:
def negative_log_likelihood(self, y):
return -T.mean(T.log(self.p_y_x)[T.arange(y.shape[0]), y])
Sebbene il codice sia conciso, effettua molte operazioni e quindi è opportuno descriverlo in modo approfondito. y.shape[0]
è il numero di esempi nel minibatch di dimensione N, che usato come parametro di T.arange(..)
otteniamo il vettore simbolico contenente l’elenco di numeri interi da 0 a N-1.
L’operatore log
di Theano agisce sulla probabilità della classe \(y\), dato vettore delle feature \(x\), per produrre una matrice di probabilità logaritmiche. Questa matrice ha tante righe quanti sono gli esempi di addestramento nel minibatch (ad es N) e tante colonne quante sono le classi/cifre, K. Nel nostro caso abbiamo K=10 dato stiamo considerando le cifre \(0..9\). Quindi abbiamo una matrice di dimensioni \(N \times K\) = \(N \times 10\).
Combinando queste funzioni nel comando T.log(self.p_y_x)[T.arange(y.shape[0]), y]
otteniamo un vettore che contiene le probabilità logaritmiche per ogni coppia di esempio di addestramento / risposta della classe della cifra. Questo vettore ha lunghezza N. Calcoliamo la media (T.mean
) di questo vettore per ottenere la verosimiglianza logaritmica media di tutti gli esempi di addestramento nel minibatch. Infine, prendiamo il negativo per ottenere la negative log likelihood media degli esempi di addestramento.
Notiamo quindi che effettuiamo molti calcoli con una sola riga di comando. Questo è uno dei principali vantaggi quando usiamo Theano. Ci permette di definire le modalità di calcolo di un’espressione piuttosto che obbligarci a implementare il calcolo. Possiamo lasciare a Theano l’arduo compito di trovare un’implementazione ottimale.
Il passaggio finale è definire un meccanismo per calcolare il tasso di errore per un particolare batch di previsioni delle cifre. Per questo scopo definiamo il metodo errors
per la classe LogisticRegression
, che accetta un vettore di cifre y
e lo confronta con il vettore delle previsioni self.y_pred
:
def errors(self, y):
# Per prima cosa controlliamo se il vettore y ha
# la stessa dimensione del vettore di previsione y
if y.ndim != self.y_pred.ndim:
raise TypeError(
"y should have the same shape as self.y_pred",
("y", y.type, "y_pred", self.y_pred.type)
)
# Controlla se y contiene i tipi (interi) corretti
if y.dtype.startswith('int'):
# Possiamo usare l'operatore Theano neq per restituire
# il vettore di 1s o 0s, dove 1 rappresenta un
# errore di classificazione
return T.mean(T.neq(self.y_pred, y))
else:
raise NotImplementedError()
Il metodo utilizza l’operatore neg
di Theano, che restituisce un vettore di 1 o 0, dove 1 rappresenta un errore di classificazione. Questo ci permette di stimare l’accuratezza della previsione nell’addestramento del modello.
Addestramento del modello
Prima di poter addestrare il modello è necessario decomprimere il file gzip MNIST che abbiamo scaricato in precedenza, all’interno dello script Python.
Per restituire i corretti set di training, validazione e test delle coppie feature-risposta (cioè coppie immagine-cifra) abbiamo bisogno di un meccanismo per creare variabili condivise di Theano, in modo da copiare i dati nella memoria della GPU. Se dovessimo copiare un minibatch in sequenza, le prestazioni si degraderebbero in modo significativo poiché il trasferimento della memoria della GPU è lento. Di seguito il metodo shared_dataset
che implementa quanto sopra:
def shared_dataset(data_xy, borrow=True):
"""
Crea variabili condivise Theano per consentire la
copia dei dati sulla GPU per evitare il degrado
delle performance per i dati in minibatch
"""
data_x, data_y = data_xy
shared_x = theano.shared(
np.asarray(
data_x, dtype=theano.config.floatX
), borrow=borrow
)
shared_y = theano.shared(
np.asarray(
data_y, dtype=theano.config.floatX
), borrow=borrow
)
return shared_x, T.cast(shared_y, 'int32')
def load_mnist_data(filename):
"""
Carico il MNIST compresso con gzip nei
dataset di test, convalida e training
tramite la libreria pickle Python
"""
# Uso la compressione gzip e le librerie di
# deserializzazione pickle per aprire le immagini
# MNIST nei dataset di addestramento, convalida e test
with gzip.open(filename, 'rb') as gzf:
try:
train_set, valid_set, test_set = pickle.load(
gzf, encoding='latin1'
)
except:
train_set, valid_set, test_set = pickle.load(gzf)
# Uso la funzione shared_dataset per creare le
# variabili condivise di Theano da copiare nella GPU
test_set_x, test_set_y = shared_dataset(test_set)
valid_set_x, valid_set_y = shared_dataset(valid_set)
train_set_x, train_set_y = shared_dataset(train_set)
# Creo la lista di tuple che contiene le coppie
# feature-risposta per i dataset di addestramento,
# di validazione e di testing
rval = [
(train_set_x, train_set_y),
(valid_set_x, valid_set_y),
(test_set_x, test_set_y)
]
return rval
Questo codice è relativamente semplice. Usiamo un contesto with
per aprire il file tramite la libreria gzip
, quindi deserializziamo i dati tramite la libreria pickle
di Python, preoccupandi di specificare la corretta codifica.
Quindi utilizziamo la funzioneshared_dataset
, definita in precedenza, per creare le variabili condivise di Theano per i dati (per essere copiati sulla GPU in modo da ottimizzare le prestazioni). Infine, restituiamo un insieme di coppie feature-risposta per i rispettive dataset di addestramento, convalida e testing.
Dopo aver descritto il codice per caricare il set di dati, dobbiamo implementare il metodo di addestramento della discesa stocastica del gradiente. Questa è una funzione complessa e contiene molto codice. Proviamo quindi a descrivere i singoli blocchi che la compongono. In questa serie di articolo descriviamo molti esempi di addestramenti SGD, quindi ci sono altre occasioni dove approfondire questi concetti.
Il primo passo è definire la funzione e i suoi parametri:
def stoch_grad_desc_train_model(
filename, gamma=0.13, epochs=1000, B=600
):
"""
Addestramento del modello di regrassione logistica
usando la discesa stocastica del gradiente.
filename - il percorso del file del dataset MNIST
gamma - "learning rate" per la discesa del gradiente
epochs - numero massimo di epoche per la SGD
B - la dimensione per ogni minibatch
"""
# Ottengo la corrette partizioni dei dataset
datasets = load_mnist_data(filename)
train_set_x, train_set_y = datasets[0]
valid_set_x, valid_set_y = datasets[1]
test_set_x, test_set_y = datasets[2]
# Calcolo il numero di minibatc per ogni partizioni dei
# dataset di addestramento, validazione e testting
# Nota: Uso l'operatore // per restituisce la parte intera
# del quoziente della divisione, ad es.
# 1.0//2 is equal to 0.0
# 1//2 is equal to 0
n_train_batches = train_set_x.get_value(borrow=True).shape[0] // B
n_valid_batches = valid_set_x.get_value(borrow=True).shape[0] // B
n_test_batches = test_set_x.get_value(borrow=True).shape[0] // B
filename,
la dimensione dello step (o velocità di apprendimento) gamma
, il numero massimo di epochs
per eseguire l’algoritmo SGD e la dimensione del minibatch B
. I valori predefiniti sono stati scelti in base a quelli suggeriti dal tutorial originale di regressione logistica di Theano.
Prima di tutto la funzione acquisisce i dataset MNIST e lo suddivide nei set di addestramento, convalida e test. Successivamente si calcola il numero di minibatch per ciascuna partizione dividendo le dimensioni di ciascun dataset per la dimensione del batch, B.
Il passaggio successivo è costruire il modello di regressione logistica:
# COSTRUZIONE DEL MODELLO
# ===============
print("Building the logistic regression model...")
# Creo le variabili simboliche per i dati del minibatch
index = T.lscalar() # valori scalari interi
# Vettore delle feature, ad es. le immagini
x = T.matrix('x')
# Vettore degli interi che rappresentano le cifre
y = T.ivector('y')
# Inizializzazione del modello di regressione
# logisita ed assegnazione del costo
logreg = LogisticRegression(x=x, num_in=28 ** 2, num_out=10)
cost = logreg.negative_log_likelihood(y) # Questo deve essere minimizzato con la SGD
# Creazione di un insieme di funzioni di Theano per i dataser di
# testing e validazione che calcola gli errori del modello
# per uno specifico minibatch
test_model = theano.function(
inputs=[index],
outputs=logreg.errors(y),
givens={
x: test_set_x[index * B: (index + 1) * B],
y: test_set_y[index * B: (index + 1) * B]
}
)
validate_model = theano.function(
inputs=[index],
outputs=logreg.errors(y),
givens={
x: valid_set_x[index * B: (index + 1) * B],
y: valid_set_y[index * B: (index + 1) * B]
}
)
Le variabili simboliche vengono create per memorizzare i dati del minibatch prima dell’istanziazione della clase LogisticRegression
. Da notare la dimensione degli input num_in
, che è pari a 28×28 pixel per le immagini della calligrafia MNIST. La dimensione dell’output è 10, che rappresenta ciascuna delle cifre da 0 a 9.
La sezione successiva crea un set di funzioni Theano, che calcola gli errori associati a uno specifico minibatch di dati di test e addestramento. Fanno uso del metodo errors
della classe LogisticRegression
. Entrambe queste funzioni sono usate di seguito.
L’operatore simbolico grad
di Theano è usato per calcolare il gradiente della log-verosimiglianza negativa rispetto alle variazioni dei sottostanti parametri W e b. A questo punto codifichiamo l’intervallo di discesa stocastica del stocastico nella lista updates
. Infine usiamo un’ulteriore function
di Theano, insieme al parametro updates=updates
per valutare gli errori sul minibatch di addestramento e simultaneamente eseguire lo step di aggiornamento SGD sui parametri:
# Uso di Theano per calcolare i gradienti simbolici della
# funzione di costo (negative log likelihood) per i
# sottostanti parametri W e b
grad_W = T.grad(cost=cost, wrt=logreg.W)
grad_b = T.grad(cost=cost, wrt=logreg.b)
# Questo è la fase della discesa del gradiente.
# Si specifica una lista di coppie, ognuna contiente una variabile
# Theano e una espresione su come aggiornarla ad ogni step.
updates = [
(logreg.W, logreg.W - gamma * grad_W),
(logreg.b, logreg.b - gamma * grad_b)
]
# Simile al precedente insieme di funzioni di Theano, ma in questo
# caso viene eseguita sui dati di allenamento e aggiorna i parametri
# W, b in quanto valuta il costo per uno specifico minibatch
train_model = theano.function(
inputs=[index],
outputs=cost,
updates=updates,
givens={
x: train_set_x[index * B: (index + 1) * B],
y: train_set_y[index * B: (index + 1) * B]
}
)
# ADDESTRAMENTO DEL MODELLO
# ===============
print("Training the logistic regression model...")
# Imposto i parametri per fermare anticipatamente il
# minibatch se le performance sono abbastanza buone
patience = 5000 # Numero minimo di esempi da valutare
patience_increase = 2 # Aumento per il nuovo miglior punteggio
improvement_threshold = 0.995 # Soglia di miglioramento relativo
# Addrestramento su questo numero di minibatch prima di
# controllare le prestazioni sul set di convalida
validation_frequency = min(n_train_batches, patience // 2)
# Tengo traccia della funzione obiettivo e dei punteggi del test
best_validation_loss = np.inf
test_score = 0.
start_time = timeit.default_timer()
Definiamo una variabile chiamata patience
, che usiamo per determinare il numero minimo iniziale di esempi da esaminare in ogni minibatch. Il valore di questo parametro aumenta all’aumentare delle prestazioni (cioè al diminuire dell’errore di classificazione), dato che sono necessari più campioni per minibatch per aumentare continuamente le prestazioni relative per minibatch.
Inoltre calcoliamo la variabile validation_frequency
per determinare la frequenza con cui valutare la performance della classificazione sul set di validazione. Infine iniziamo a cronometrare la durata della procedura di training tramite il modulo timeit
di Python.
La seguente sezione del codice riguarda il ciclo di addestramento principale. È piuttosto lunga. Riportiamo il codice completo qui e lo descriviamo in dettaglio successivamente:
# Inizio del ciclo di addestramento
# Il ciclo while esterno sul numero di epoche
# Il ciclo for interno sui minibatch
finished = False
cur_epoch = 0
while (cur_epoch < epochs) and (not finished):
cur_epoch = cur_epoch + 1
# Ciclo sui minibatch
for minibatch_index in range(n_train_batches):
# Calcolo della verosimiglianza media per i minibatch
minibatch_avg_cost = train_model(minibatch_index)
iter = (cur_epoch - 1) * n_train_batches + minibatch_index
if (iter + 1) % validation_frequency == 0:
# Se l'itezione corrente ha raggiunto la frequenza di validazione
# allora si calcola la funzione obiettivo dei batch validati
validation_losses = [
validate_model(i)
for i in range(n_valid_batches)
]
this_validation_loss = np.mean(validation_losses)
# Stampo i risultati della validazione corrente
print(
"Epoch %i, Minibatch %i/%i, Validation Error %f %%" % (
cur_epoch,
minibatch_index + 1,
n_train_batches,
this_validation_loss * 100.
)
)
# Se il valore dell'ultima funzione obiettio è il migliore
if this_validation_loss < best_validation_loss:
# Se la funzione obiettivo è all'interno della soglia
# di miglioramento, allora si incrementa il numero
# di iterazione ("patience") fino al prossimo controllo
if this_validation_loss < best_validation_loss * \
improvement_threshold:
patience = max(patience, iter * patience_increase)
# Importo la validazione corrente come la migliore
# validazione della perdita
best_validation_loss = this_validation_loss
# Calcolo le funzioni obiettivo sui minibatch nel
# dataset di testing. Il "test_score" è la media
# delle funzioni obiettivo
test_losses = [
test_model(i)
for i in range(n_test_batches)
]
test_score = np.mean(test_losses)
# Stampa dei risultati del test corrente
print(
(
" Epoch %i, Minibatch %i/%i, Test error of"
" best model %f %%"
) % (
cur_epoch,
minibatch_index + 1,
n_train_batches,
test_score * 100.
)
)
# Salvataggio del modello su disco usando pickle
with open('best_model.pkl', 'wb') as f:
pickle.dump(logreg, f)
# Se l'iterazione eccede l'attuale "patience"
# allora fermo il loop oer questo minibatch
if iter > patience:
done_looping = True
break
end_time = timeit.default_timer()
print(
(
"Optimization complete with "
"best validation score of %f %%,"
"with test performance %f %%"
) % (best_validation_loss * 100., test_score * 100.)
)
print(
'The code run for %d epochs, with %f epochs/sec' % (
cur_epoch,
1. * cur_epoch / (end_time - start_time)
)
)
print("The code ran for %.1fs" % (end_time - start_time))
Il ciclo esterno è un ciclo while
sul numero di epoche. Il ciclo interno è un ciclo for
sul numero di minibatch di addestramento. Per ogni iterazione del ciclo interno, calcoliamo la negative log-likelihood media del minibatch tramite il metodo train_model
, definito come una function
di Theano.
Se il numero di iterazioni raggiunto è un multiplo della frequenza di convalida, calcoliamo la funzione obiettivo della validazione e stampiamo i risultati sulla console. A questo punto effettuiamo un controllo per verificare se abbiamo ottenuto la miglior funzione obiettivo della validazione, finora calcolata. In questo caso aumentiamo la variabile “patience” in modo da richiedere più iterazioni per il successivo miglioramento della validazione. A questo punto calcoliamo la negative log-likelihood sul dataset di testing e la stampiamo sulla console.
Quindi salviamo su disco (tramite pickle
) l’attuale “migliore” modello di training. Se per questo minibatch superiamo il valore di “patience” allora passiamo al prossimo minibatch. Infine calcoliamo l’ora di fine e quindi stampiamo su console la migliore funzione obiettivo della validazione e tempo totale di esecuzione.
Questo completa la discesa stocastica del gradiente per il modello di regressione logistica con i parametri W e b.
Testare il modello
Rispetto all’addestramento del modello, la previsione delle cifre per la fase di test con nuovi dati mai visti è relativamente semplice. Il seguente frammento mostra come eseguire una previsione:
def test_model(filename, num_preds):
"""
Verifica del modello con un dataset MNIST di testing
che il modello non ha mai visto
"""
# Carichiamo il migliore modello salvato
classifier = pickle.load(open('best_model.pkl'))
# Uso di Theano per creare una funzione di previsione
predict_model = theano.function(
inputs=[classifier.x],
outputs=classifier.y_pred
)
# Caricamento del dal dataset MNIST da "filename"
# e isolamente dei dati di testing
datasets = load_mnist_data(filename)
test_set_x, test_set_y = datasets[2]
test_set_x = test_set_x.get_value()
# Predizione delle cifre per il numero 'num_preds' di immagini
preds = predict_model(test_set_x[:num_preds])
print(
"Predicted digits for the first %s " \
"images in test set:" % num_preds
)
print(preds)
Se hai utilizzato la libreria Scikit-Learn per eseguire qualsiasi lavoro di apprendimento automatico supervisionato, noterai che l’API non è troppo dissimile.
Il primo passaggio consiste nel caricare il modello best_model.pkl
in pickled e inserirlo nella variabile classifier
. Successivamente creiamo una funzione theano.function
, che prende il vettore delle feature x
in input e restituisce i valori previsti per le cifre y_pred
. Quindi carichiamo il dataset effettivo e isoliamo il set di test. Infine creiamo un vettore di previsioni, preds
, che contiene le previsioni di cifre per le prime num_preds
immagini nel dataset di test.
Script principale
Di seguito il codice per unire tutti i pezzi e lanciare lo script:
if __name__ == "__main__":
# Impostazione del dataset e del numero di
# predizioni da fare sui dati di testing
dataset_filename = "mnist.pkl.gz"
num_preds = 20
# Addestramento del modello tramite la
# discesa stocastica del gradiente
stoch_grad_desc_train_model(dataset_filename)
# Verifica delle regressione logistica su un
# dataset di test mai visto
test_model(dataset_filename, num_preds)
Esecuzione del modello sulla CPU o sulla GPU
Per eseguire il codice sulla CPU possiamo usare il seguente comando da terminale. Usiamo il simbolo del dollaro per indicare che si tratta di un comando da terminale, quindi assicurati di non includerlo quando copi e incolli il codice nel terminale:
$ THEANO_FLAGS=mode=FAST_RUN,device=cpu,floatX=float32 python deep_logistic_regression_theano.py
Building the logistic regression model...
Training the logistic regression model...
Epoch 1, Minibatch 83/83, Validation Error 12.458333 %
Epoch 1, Minibatch 83/83, Test error of best model 12.375000 %
Epoch 2, Minibatch 83/83, Validation Error 11.010417 %
Epoch 2, Minibatch 83/83, Test error of best model 10.958333 %
Epoch 3, Minibatch 83/83, Validation Error 10.312500 %
Epoch 3, Minibatch 83/83, Test error of best model 10.312500 %
Epoch 4, Minibatch 83/83, Validation Error 9.875000 %
Epoch 4, Minibatch 83/83, Test error of best model 9.833333 %
Epoch 5, Minibatch 83/83, Validation Error 9.562500 %
Epoch 5, Minibatch 83/83, Test error of best model 9.479167 %
Epoch 6, Minibatch 83/83, Validation Error 9.322917 %
Epoch 6, Minibatch 83/83, Test error of best model 9.291667 %
..
.. --TRUNCATED--
..
Epoch 71, Minibatch 83/83, Validation Error 7.520833 %
Epoch 72, Minibatch 83/83, Validation Error 7.510417 %
Epoch 73, Minibatch 83/83, Validation Error 7.500000 %
Epoch 73, Minibatch 83/83, Test error of best model 7.489583 %
Epoch 74, Minibatch 83/83, Validation Error 7.479167 %
Epoch 74, Minibatch 83/83, Test error of best model 7.489583 %
Optimization complete with best validation score of 7.479167 %,with test performance 7.489583 %
The code run for 1000 epochs, with 37.882747 epochs/sec
The code ran for 26.4s
Predicted digits for the first 20 images in test set:
[7 2 1 0 4 1 4 9 6 9 0 6 9 0 1 5 9 7 3 4]
Sul mio desktop con un Intel Core i7-4770K, overcloccato a 3,50 Ghz, ci sono voluti 26,4 secondi.
In alternativa, per eseguire lo script sulla GPU dobbiamo inserire nel terminale il seguente comando:
$ THEANO_FLAGS=mode=FAST_RUN,device=gpu,floatX=float32 python deep_logistic_regression_theano.py
Using gpu device 0: GeForce GTX 780 Ti (CNMeM is disabled, cuDNN not available)
Building the logistic regression model...
Training the logistic regression model...
Epoch 1, Minibatch 83/83, Validation Error 12.458333 %
Epoch 1, Minibatch 83/83, Test error of best model 12.375000 %
Epoch 2, Minibatch 83/83, Validation Error 11.010417 %
Epoch 2, Minibatch 83/83, Test error of best model 10.958333 %
Epoch 3, Minibatch 83/83, Validation Error 10.312500 %
Epoch 3, Minibatch 83/83, Test error of best model 10.312500 %
Epoch 4, Minibatch 83/83, Validation Error 9.875000 %
Epoch 4, Minibatch 83/83, Test error of best model 9.833333 %
Epoch 5, Minibatch 83/83, Validation Error 9.562500 %
Epoch 5, Minibatch 83/83, Test error of best model 9.479167 %
Epoch 6, Minibatch 83/83, Validation Error 9.322917 %
Epoch 6, Minibatch 83/83, Test error of best model 9.291667 %
..
.. --TRUNCATED--
..
Epoch 71, Minibatch 83/83, Validation Error 7.520833 %
Epoch 72, Minibatch 83/83, Validation Error 7.510417 %
Epoch 73, Minibatch 83/83, Validation Error 7.500000 %
Epoch 73, Minibatch 83/83, Test error of best model 7.489583 %
Epoch 74, Minibatch 83/83, Validation Error 7.479167 %
Epoch 74, Minibatch 83/83, Test error of best model 7.489583 %
Optimization complete with best validation score of 7.479167 %,with test performance 7.489583 %
The code run for 1000 epochs, with 251.317223 epochs/sec
The code ran for 4.0s
Predicted digits for the first 20 images in test set:
[7 2 1 0 4 1 4 9 6 9 0 6 9 0 1 5 9 7 3 4]
Sulla stessa macchina desktop con una GeForce GTX 780 Ti abilitata per NVidia CUDA sono stati necessari 4,0 secondi, cioè un fattore di accelerazione di 6,6x per questo script. In prospettiva, un’attività di allenamento che richiede una settimana su una CPU richiederà solo poco più di un giorno sulla GPU. Quindi, se sei seriamente intenzionato a costruire una macchina o un cluster per la ricerca di deep learning, vale la pena considerare l’uso delle GPU. Sono il modo più semplice per aumentare le prestazioni per la maggior parte delle architetture di deep learning.
Prossimi passi
Sebbene la regressione logistica non sia certo una tecnica all’avanguardia ai fini della classificazione, ci consente di esplorare l’API di Theano, costruire un modello non banale su un set di dati di grandi dimensioni, addestrare il modello sia sulla CPU che sulla GPU e prevedere nuove classificazioni da questo modello.
Nel prossimo articolo descriviamo il nostro primo modello di rete neurale, ovvero il perceptron multistrato (MLP). Costruiamo un MLP con Theano e lo usiamo per lo stesso compito di classificazione supervisionato per predire le cifre MNIST. Quindi verifichiamo se le prestazioni sono migliori rispetto al modello di regressione logistica.
Risorse aggiuntive per Machine Learning, Deep Learning e GPU
- Coursera: Machine Learning by Andrew Ng
- NVIDIA Self-Study Courses for Deep Learning
- Udacity: Intro to Parallel Programming – Using CUDA to Harness the Power of GPUs
- Tim Dettmers Blog – A Full Hardware Guide to Deep Learning
Riferimenti
- [1] Very Brief Introduction to Machine Learning for AI, IFT6266 Winter 2010
- [2] Introduction to Deep Learning Algorithms, IFT6266 Winter 2010
- [3] Bengio, Y. (2009). Learning Deep Architectures for AI. Foundations and Trends in Machine Learning 2 (1), pg1-127
- [4] Deep Learning Tutorials, deeplearning.net
- [5] Bergstra, J. et al. (2010) “Theano: A CPU and GPU Math Expression Compiler”. Proceedings of the Python for Scientific Computing Conference (SciPy) 2010. June 30 – July 3, Austin, TX
- [6] Nielson, M. (2015). “Neural Networks and Deep Learning”, Determination Press
- [7] Playing Atari with Deep Reinforcement Learning, DeepMind Technologies
- [8] Silver, D. et al. (2016). Mastering the Game of Go with Deep Neural Networks and Tree Search, Nature 529, p484-489
- [9] Wikipedia: Deep Learning
- [10] Beissinger, M. (2013). “Deep Learning 101”
- [11] Längkvist, M.J. (2014). “A Review of Unsupervised Feature Learning and Deep Learning for Time-Series Modeling”. Pattern Recognition Letters 42 (1)
- [12] Hastie, T., Tibshirani, R., Friedman, J. (2009) The Elements of Statistical Learning, Springer
- [13] James, G., Witten, D., Hastie, T., Tibshirani, R. (2013) An Introduction to Statistical Learning, Springer
- [14] Wikipedia: Stochastic Gradient Descent
- [15] Classifying MNIST digits using Logistic Regression, deeplearning.net
- [16] LeCun, Y., Cortes, C., Burges, C.J.C., (2012). MNIST Database of Handwritten Digits