Un tutorial di deep learning: dai perceptron alle reti profonde

Pubblicato: 2022-03-11

Negli ultimi anni c'è stata una rinascita nel campo dell'Intelligenza Artificiale. Si è diffuso oltre il mondo accademico con i principali attori come Google, Microsoft e Facebook che creano i propri team di ricerca e realizzano acquisizioni impressionanti.

In parte ciò può essere attribuito all'abbondanza di dati grezzi generati dagli utenti dei social network, molti dei quali devono essere analizzati, all'ascesa di soluzioni avanzate di scienza dei dati, nonché alla potenza di calcolo a basso costo disponibile tramite GPGPU.

Ma al di là di questi fenomeni, questa rinascita è stata alimentata in gran parte da una nuova tendenza nell'IA, in particolare nell'apprendimento automatico, noto come "Apprendimento profondo". In questo tutorial, ti presenterò i concetti chiave e gli algoritmi alla base del deep learning, iniziando con l'unità di composizione più semplice e costruendo i concetti di machine learning in Java.

(Per la divulgazione completa: sono anche l'autore di una libreria di deep learning Java, disponibile qui, e gli esempi in questo articolo sono implementati utilizzando la libreria sopra. Se ti piace, puoi supportarla dandogli una stella su GitHub , per il quale sarei grato. Le istruzioni per l'uso sono disponibili sulla home page.)

Un trentaduesimo tutorial sull'apprendimento automatico

Se non hai familiarità, dai un'occhiata a questa introduzione all'apprendimento automatico:

La procedura generale è la seguente:

  1. Abbiamo un algoritmo che ha fornito una manciata di esempi etichettati, diciamo 10 immagini di cani con l'etichetta 1 ("Cane") e 10 immagini di altre cose con l'etichetta 0 ("Non cane") - nota che stiamo principalmente attaccando alla classificazione binaria supervisionata per questo post.
  2. L'algoritmo "impara" a identificare le immagini dei cani e, quando viene alimentato con una nuova immagine, spera di produrre l'etichetta corretta (1 se è l'immagine di un cane e 0 in caso contrario).

Questa impostazione è incredibilmente generale: i tuoi dati potrebbero essere sintomi e le tue etichette malattie; oppure i tuoi dati potrebbero essere immagini di caratteri scritti a mano e le tue etichette i caratteri effettivi che rappresentano.

Perceptron: primi algoritmi di apprendimento profondo

Uno dei primi algoritmi di addestramento supervisionato è quello del perceptron, un elemento costitutivo della rete neurale di base.

Supponiamo di avere n punti nel piano, etichettati '0' e '1'. Ci viene dato un nuovo punto e vogliamo indovinare la sua etichetta (questo è simile allo scenario "Cane" e "Non cane" sopra). Come lo facciamo?

Un approccio potrebbe essere quello di guardare il vicino più vicino e restituire l'etichetta di quel punto. Ma un modo leggermente più intelligente di procedere sarebbe quello di scegliere una linea che separi al meglio i dati etichettati e usarla come classificatore.

Una rappresentazione dei dati di input in relazione a un classificatore lineare è un approccio di base al deep learning.

In questo caso, ogni dato di input sarebbe rappresentato come un vettore x = ( x_1, x_2 ) e la nostra funzione sarebbe qualcosa come “'0' se sotto la linea, '1' se sopra”.

Per rappresentare questo matematicamente, lascia che il nostro separatore sia definito da un vettore di pesi w e un offset verticale (o bias) b . Quindi, la nostra funzione combinerebbe gli input e i pesi con una funzione di trasferimento della somma ponderata:

funzione di trasferimento della somma ponderata

Il risultato di questa funzione di trasferimento verrebbe quindi inserito in una funzione di attivazione per produrre un'etichettatura. Nell'esempio sopra, la nostra funzione di attivazione era un taglio di soglia (ad esempio, 1 se maggiore di un valore):

risultato di questa funzione di trasferimento

Addestrare il Perceptron

L'addestramento del perceptron consiste nell'alimentarlo con più campioni di addestramento e nel calcolare l'output per ciascuno di essi. Dopo ogni campionamento, i pesi w vengono regolati in modo da minimizzare l' errore di uscita , definito come la differenza tra le uscite desiderate (target) e quelle effettive . Esistono altre funzioni di errore, come l'errore quadratico medio, ma il principio di base dell'allenamento rimane lo stesso.

Inconvenienti del singolo perceptron

L'approccio del singolo percettrone all'apprendimento profondo presenta uno svantaggio principale: può apprendere solo funzioni separabili linearmente. Quanto è grave questo inconveniente? Prendi XOR, una funzione relativamente semplice, e nota che non può essere classificata da un separatore lineare (nota il tentativo fallito, di seguito):

Lo svantaggio di questo approccio di deep learning è che alcune funzioni non possono essere classificate da un separatore lineare.

Per affrontare questo problema, dovremo utilizzare un perceptron multistrato, noto anche come rete neurale feedforward: in effetti, comporremo insieme un gruppo di questi perceptron per creare un meccanismo più potente per l'apprendimento.

Reti neurali feedforward per il deep learning

Una rete neurale è in realtà solo una composizione di percettron, collegati in modi diversi e che operano su diverse funzioni di attivazione.

Il deep learning di rete neutrale feedforward è un approccio più complesso rispetto ai singoli perceptron.

Per cominciare, esamineremo la rete neurale feedforward, che ha le seguenti proprietà:

  • Un input, output e uno o più livelli nascosti . La figura sopra mostra una rete con uno strato di input di 3 unità, uno strato nascosto di 4 unità e uno strato di output con 2 unità (i termini unità e neuroni sono intercambiabili).
  • Ogni unità è un singolo perceptron come quello descritto sopra.
  • Le unità del livello di input servono come input per le unità del livello nascosto, mentre le unità del livello nascosto sono input per il livello di output.
  • Ogni connessione tra due neuroni ha un peso w (simile ai pesi percettron).
  • Ogni unità dello strato t è in genere collegata a ogni unità dello strato precedente t - 1 (sebbene sia possibile disconnetterle impostando il loro peso su 0).
  • Per elaborare i dati di input, "fissa" il vettore di input al livello di input, impostando i valori del vettore come "uscite" per ciascuna delle unità di input. In questo caso particolare, la rete può elaborare un vettore di input tridimensionale (a causa delle 3 unità di input). Ad esempio, se il vettore di input è [7, 1, 2], impostare l'output dell'unità di input superiore su 7, l'unità centrale su 1 e così via. Questi valori vengono quindi propagati alle unità nascoste utilizzando la funzione di trasferimento della somma pesata per ciascuna unità nascosta (da cui il termine propagazione in avanti), che a loro volta ne calcolano le uscite (funzione di attivazione).
  • Il livello di output calcola i suoi output allo stesso modo del livello nascosto. Il risultato del livello di output è l'output della rete.

Oltre la linearità

E se a ciascuno dei nostri perceptron fosse consentito utilizzare solo una funzione di attivazione lineare? Quindi, l'output finale della nostra rete sarà ancora una funzione lineare degli input, appena regolata con una tonnellata di pesi diversi che viene raccolta in tutta la rete. In altre parole, una composizione lineare di un gruppo di funzioni lineari è ancora solo una funzione lineare. Se siamo limitati alle funzioni di attivazione lineare, la rete neurale feedforward non è più potente del perceptron, non importa quanti strati abbia.

Una composizione lineare di un gruppo di funzioni lineari è ancora solo una funzione lineare, quindi la maggior parte delle reti neurali utilizza funzioni di attivazione non lineare.

Per questo motivo, la maggior parte delle reti neurali utilizza funzioni di attivazione non lineare come logistica, tanh, binaria o raddrizzatore. Senza di essi la rete può apprendere solo funzioni che sono combinazioni lineari dei suoi input.

Percettron di addestramento

L'algoritmo di deep learning più comune per l'addestramento supervisionato dei perceptron multistrato è noto come backpropagation. La procedura di base:

  1. Un campione di addestramento viene presentato e propagato attraverso la rete.
  2. Viene calcolato l'errore di uscita, tipicamente l'errore quadratico medio:

    errore quadratico medio

    Dove t è il valore target e y è l'output di rete effettivo. Sono accettabili anche altri calcoli di errore, ma l'MSE è una buona scelta.

  3. L'errore di rete viene ridotto al minimo utilizzando un metodo chiamato discesa del gradiente stocastico.

    Discesa a gradiente

    La discesa del gradiente è universale, ma nel caso delle reti neurali, questo sarebbe un grafico dell'errore di addestramento in funzione dei parametri di input. Il valore ottimale per ogni peso è quello in cui l'errore raggiunge un minimo globale . Durante la fase di addestramento, i pesi vengono aggiornati a piccoli passi (dopo ogni campione di addestramento o un mini-batch di più campioni) in modo tale che cerchino sempre di raggiungere il minimo globale, ma non è un compito facile, poiché tu spesso finiscono in minimi locali, come quello a destra. Ad esempio, se il peso ha un valore di 0,6, è necessario modificarlo verso 0,4.

    Questa figura rappresenta il caso più semplice, quello in cui l'errore dipende da un singolo parametro. Tuttavia, l'errore di rete dipende da ogni peso della rete e la funzione di errore è molto, molto più complessa.

    Per fortuna, la backpropagation fornisce un metodo per aggiornare ogni peso tra due neuroni rispetto all'errore di output. La derivazione stessa è piuttosto complicata, ma l'aggiornamento del peso per un dato nodo ha la seguente (semplice) forma:

    modulo di esempio

    Dove E è l'errore di output e w_i è il peso dell'input i sul neurone.

    In sostanza, l'obiettivo è quello di muoversi nella direzione del gradiente rispetto al peso i . Il termine chiave è, ovviamente, la derivata dell'errore, che non è sempre facile da calcolare: come troveresti questa derivata per un peso casuale di un nodo nascosto casuale nel mezzo di una grande rete?

    La risposta: attraverso la backpropagation. Gli errori vengono prima calcolati nelle unità di output in cui la formula è abbastanza semplice (basata sulla differenza tra i valori target e quelli previsti), e quindi propagati attraverso la rete in modo intelligente, consentendoci di aggiornare in modo efficiente i nostri pesi durante l'allenamento e (si spera) raggiungere un minimo.

Strato nascosto

Lo strato nascosto è di particolare interesse. Con il teorema di approssimazione universale, una singola rete di strati nascosti con un numero finito di neuroni può essere addestrata per approssimare una funzione arbitrariamente casuale. In altre parole, un singolo livello nascosto è abbastanza potente da apprendere qualsiasi funzione. Detto questo, spesso impariamo meglio in pratica con più livelli nascosti (cioè, reti più profonde).

Il livello nascosto è dove la rete memorizza la sua rappresentazione astratta interna dei dati di addestramento.

Lo strato nascosto è il punto in cui la rete memorizza la sua rappresentazione astratta interna dei dati di allenamento, simile al modo in cui un cervello umano (analogia notevolmente semplificata) ha una rappresentazione interna del mondo reale. Andando avanti nel tutorial, esamineremo diversi modi per giocare con il livello nascosto.

Un esempio di rete

Puoi vedere una semplice rete neurale feedforward (4-2-3 strati) che classifica il set di dati IRIS implementato in Java qui attraverso il metodo testMLPSigmoidBP . Il set di dati contiene tre classi di piante di iris con caratteristiche come la lunghezza del sepalo, la lunghezza del petalo, ecc. La rete fornisce 50 campioni per classe. Le caratteristiche sono vincolate alle unità di input, mentre ciascuna unità di output corrisponde a una singola classe del dataset: “1/0/0” indica che l'impianto è di classe Setosa, “0/1/0” indica Versicolour, e “ 0/0/1” indica Virginia). L'errore di classificazione è 2/150 (cioè, classifica erroneamente 2 campioni su 150).

Il problema delle grandi reti

Una rete neurale può avere più di un livello nascosto: in tal caso, i livelli superiori stanno “costruendo” nuove astrazioni sopra i livelli precedenti. E come accennato in precedenza, spesso puoi imparare meglio nella pratica con reti più grandi.

Tuttavia, l'aumento del numero di livelli nascosti porta a due problemi noti:

  1. Gradienti evanescenti: man mano che aggiungiamo sempre più livelli nascosti, la backpropagation diventa sempre meno utile nel passaggio di informazioni ai livelli inferiori. In effetti, man mano che le informazioni vengono restituite, i gradienti iniziano a svanire e diventano piccoli rispetto al peso delle reti.
  2. Overfitting: forse il problema centrale nel machine learning. In breve, overfitting descrive il fenomeno dell'adattamento troppo ravvicinato dei dati di allenamento, magari con ipotesi troppo complesse. In tal caso, il tuo studente finisce per adattare molto bene i dati di allenamento, ma avrà prestazioni molto, molto più scarse su esempi reali.

Diamo un'occhiata ad alcuni algoritmi di deep learning per affrontare questi problemi.

Codificatori automatici

La maggior parte delle lezioni introduttive di machine learning tendono a fermarsi con le reti neurali feedforward. Ma lo spazio delle possibili reti è molto più ricco, quindi continuiamo.

Un autoencoder è in genere una rete neurale feedforward che mira ad apprendere una rappresentazione (codifica) compressa e distribuita di un set di dati.

Un autoencoder è una rete neurale di deep learning che mira ad apprendere una determinata rappresentazione di un set di dati.

Concettualmente, la rete è addestrata a "ricreare" l'input, cioè l'input ei dati di destinazione sono gli stessi. In altre parole: stai cercando di produrre la stessa cosa che avevi immesso, ma compresso in qualche modo. Questo è un approccio confuso, quindi diamo un'occhiata a un esempio.

Compressione dell'input: immagini in scala di grigi

Supponiamo che i dati di addestramento siano costituiti da immagini in scala di grigi 28x28 e che il valore di ciascun pixel sia bloccato su un neurone del livello di input (ovvero, il livello di input avrà 784 neuroni). Quindi, il livello di output avrebbe lo stesso numero di unità (784) del livello di input e il valore target per ciascuna unità di output sarebbe il valore della scala di grigi di un pixel dell'immagine.

L'intuizione alla base di questa architettura è che la rete non imparerà una "mappatura" tra i dati di allenamento e le sue etichette, ma imparerà invece la struttura interna e le caratteristiche dei dati stessi. (Per questo motivo, il livello nascosto è anche chiamato rilevatore di funzionalità .) Di solito, il numero di unità nascoste è inferiore ai livelli di input/output, il che costringe la rete ad apprendere solo le funzionalità più importanti e ottiene una riduzione della dimensionalità.

Vogliamo che alcuni piccoli nodi nel mezzo apprendano i dati a livello concettuale, producendo una rappresentazione compatta.

In effetti, vogliamo che alcuni piccoli nodi nel mezzo apprendano davvero i dati a livello concettuale, producendo una rappresentazione compatta che in qualche modo catturi le caratteristiche principali del nostro input.

Malattia influenzale

Per dimostrare ulteriormente gli autoencoder, diamo un'occhiata a un'altra applicazione.

In questo caso, utilizzeremo un semplice set di dati composto da sintomi influenzali (credito a questo post del blog per l'idea). Se sei interessato, il codice per questo esempio può essere trovato nel metodo testAEBackpropagation .

Ecco come si scompone il set di dati:

  • Sono disponibili sei funzioni di input binario.
  • I primi tre sono sintomi della malattia. Ad esempio, 1 0 0 0 0 0 indica che questo paziente ha la febbre alta, mentre 0 1 0 0 0 0 indica tosse, 1 1 0 0 0 0 indica tosse e febbre alta, ecc.
  • Le ultime tre caratteristiche sono sintomi "contro"; quando un paziente ne ha uno, è meno probabile che sia malato. Ad esempio, 0 0 0 1 0 0 indica che questo paziente ha un vaccino antinfluenzale. È possibile avere combinazioni dei due insiemi di caratteristiche: 0 1 0 1 0 0 indica un paziente vaccinato con tosse e così via.

Consideriamo malato un paziente quando presenta almeno due delle prime tre caratteristiche e sano se presenta almeno due dei secondi tre (con rottura dei legami a favore dei pazienti sani), ad es.:

  • 111000, 101000, 110000, 011000, 011100 = malato
  • 000111, 001110, 000101, 000011, 000110 = sano

Addestreremo un autoencoder (usando la backpropagation) con sei unità di input e sei unità di output, ma solo due unità nascoste .

Dopo diverse centinaia di iterazioni, osserviamo che quando ciascuno dei campioni "malati" viene presentato alla rete di apprendimento automatico, una delle due unità nascoste (la stessa unità per ogni campione "malato") mostra sempre un valore di attivazione superiore al Altro. Al contrario, quando viene presentato un campione "sano", l'altra unità nascosta ha un'attivazione maggiore.

Tornando all'apprendimento automatico

In sostanza, le nostre due unità nascoste hanno appreso una rappresentazione compatta del set di dati sui sintomi dell'influenza. Per vedere come questo si collega all'apprendimento, torniamo al problema dell'overfitting. Allenando la nostra rete per apprendere una rappresentazione compatta dei dati, stiamo favorendo una rappresentazione più semplice piuttosto che un'ipotesi altamente complessa che si adatta ai dati di addestramento.

In un certo senso, favorendo queste rappresentazioni più semplici, stiamo tentando di apprendere i dati in un senso più vero.

Macchine Boltzmann limitate

Il prossimo passo logico è esaminare una macchina di Boltzmann con restrizioni (RBM), una rete neurale stocastica generativa che può apprendere una distribuzione di probabilità sul suo insieme di input .

Nell'apprendimento automatico, le macchine Botlzmann limitate sono composte da unità visibili e nascoste.

Gli RBM sono composti da uno strato nascosto, visibile e bias. A differenza delle reti feedforward, le connessioni tra i livelli visibili e nascosti non sono dirette (i valori possono essere propagati sia nella direzione da visibile a nascosto che da nascosta a visibile) e completamente connesse (ogni unità di un determinato livello è collegata a ogni unità nella successiva: se consentiamo a qualsiasi unità in qualsiasi livello di connettersi a qualsiasi altro livello, avremmo una macchina Boltzmann (piuttosto che una macchina Boltzmann ristretta ).

L'RBM standard ha unità binarie nascoste e visibili: cioè l'attivazione dell'unità è 0 o 1 sotto una distribuzione di Bernoulli, ma ci sono varianti con altre non linearità.

Mentre i ricercatori conoscono da tempo gli RBM, la recente introduzione dell'algoritmo di addestramento senza supervisione della divergenza contrastiva ha rinnovato l'interesse.

Divergenza contrastiva

L'algoritmo di divergenza contrastiva a passo singolo (CD-1) funziona in questo modo:

  1. Fase positiva :
    • Un campione di input v viene bloccato sullo strato di input.
    • v viene propagato al livello nascosto in modo simile alle reti feedforward. Il risultato delle attivazioni del livello nascosto è h .
  2. Fase negativa :
    • Propaga h di nuovo allo strato visibile con il risultato v' (le connessioni tra lo strato visibile e nascosto non sono orientate e quindi consentono il movimento in entrambe le direzioni).
    • Propaga la nuova v' di nuovo al livello nascosto con il risultato delle attivazioni h' .
  3. Aggiornamento del peso :

    aggiornamento del peso

    Dove a è la velocità di apprendimento e v , v' , h , h' e w sono vettori.

L'intuizione alla base dell'algoritmo è che la fase positiva ( h dato v ) riflette la rappresentazione interna della rete dei dati del mondo reale . Nel frattempo, la fase negativa rappresenta un tentativo di ricreare i dati sulla base di questa rappresentazione interna ( v' data h ). L'obiettivo principale è che i dati generati siano il più vicino possibile al mondo reale e ciò si riflette nella formula di aggiornamento del peso.

In altre parole, la rete ha una certa percezione di come possono essere rappresentati i dati di input, quindi cerca di riprodurre i dati sulla base di questa percezione. Se la sua riproduzione non è abbastanza vicina alla realtà, effettua un aggiustamento e riprova.

Tornando all'influenza

Per dimostrare la divergenza contrastiva, utilizzeremo lo stesso set di dati sui sintomi di prima. La rete di test è un RBM con sei unità visibili e due nascoste. Addestreremo la rete usando la divergenza contrastiva con i sintomi v fissati allo strato visibile. Durante il test, i sintomi vengono nuovamente presentati allo strato visibile; quindi, i dati vengono propagati al livello nascosto. Le unità nascoste rappresentano lo stato di malattia/salute, un'architettura molto simile all'autoencoder (che propaga i dati dal livello visibile a quello nascosto).

Dopo diverse centinaia di iterazioni, possiamo osservare lo stesso risultato degli autoencoder: una delle unità nascoste ha un valore di attivazione maggiore quando viene presentato uno qualsiasi dei campioni "malati", mentre l'altra è sempre più attiva per i campioni "sani".

Puoi vedere questo esempio in azione nel metodo testContrastiveDivergence .

Reti profonde

Abbiamo ora dimostrato che i livelli nascosti di autoencoder e RBM agiscono come efficaci rilevatori di funzionalità; ma è raro che possiamo utilizzare queste funzionalità direttamente. In effetti, i dati sopra riportati sono più un'eccezione che una regola. Invece, dobbiamo trovare un modo per utilizzare indirettamente queste funzionalità rilevate.

Fortunatamente, è stato scoperto che queste strutture possono essere impilate per formare reti profonde . Queste reti possono essere addestrate avidamente, uno strato alla volta, per aiutare a superare il gradiente di fuga e i problemi di overfitting associati alla classica backpropagation.

Le strutture risultanti sono spesso piuttosto potenti, producendo risultati impressionanti. Prendi, ad esempio, il famoso documento "gatto" di Google in cui utilizzano un tipo speciale di codificatore automatico profondo per "apprendere" il rilevamento di volti umani e gatti in base a dati senza etichetta .

Diamo un'occhiata più da vicino.

Codificatori automatici impilati

Come suggerisce il nome, questa rete è composta da più codificatori automatici impilati.

Gli autoencoder in pila hanno una serie di input, output e livelli nascosti che contribuiscono ai risultati dell'apprendimento automatico.

Il livello nascosto dell'autoencoder t funge da livello di input per l'autoencoder t+1 . Il livello di input del primo autoencoder è il livello di input per l'intera rete. L'avida procedura di allenamento a strati funziona in questo modo:

  1. Addestra il primo autoencoder ( t=1 o le connessioni rosse nella figura sopra, ma con un livello di output aggiuntivo) individualmente utilizzando il metodo di backpropagation con tutti i dati di training disponibili.
  2. Addestrare il secondo autoencoder t=2 (connessioni verdi). Poiché lo strato di input per t=2 è lo strato nascosto di t=1 non siamo più interessati allo strato di output di t=1 e lo rimuoviamo dalla rete. L'addestramento inizia bloccando un campione di input allo strato di input di t=1 , che viene propagato in avanti allo strato di output di t=2 . Successivamente, i pesi (input-nascosto e nascosto-output) di t=2 vengono aggiornati utilizzando la backpropagation. t=2 utilizza tutti i campioni di addestramento, simili a t=1 .
  3. Ripetere la procedura precedente per tutti i livelli (ad esempio, rimuovere il livello di output dell'autocodificatore precedente, sostituirlo con un altro autocodificatore e allenarsi con la propagazione all'indietro).
  4. I passaggi 1-3 sono chiamati pre-allenamento e lasciano i pesi correttamente inizializzati. Tuttavia, non esiste alcuna mappatura tra i dati di input e le etichette di output. Ad esempio, se la rete è addestrata a riconoscere immagini di cifre scritte a mano, non è ancora possibile mappare le unità dall'ultimo rilevatore di funzionalità (cioè il livello nascosto dell'ultimo autoencoder) al tipo di cifra dell'immagine. In tal caso, la soluzione più comune consiste nell'aggiungere uno o più livelli completamente connessi all'ultimo livello (connessioni blu). L'intera rete può ora essere vista come un perceptron multistrato ed è addestrata utilizzando la backpropagation (questo passaggio è anche chiamato fine-tuning ).

I codificatori automatici impilati, quindi, forniscono un metodo di pre-addestramento efficace per inizializzare i pesi di una rete, lasciandoti con un perceptron complesso e multistrato pronto per l'addestramento (o la messa a punto ).

Reti di credenze profonde

Come con gli autoencoder, possiamo anche impilare macchine Boltzmann per creare una classe nota come Deep Faith Network (DBN) .

Le reti di credenze profonde sono composte da una pila di macchine Boltzmann.

In questo caso, lo strato nascosto di RBM t funge da strato visibile per RBM t+1 . Il livello di input del primo RBM è il livello di input per l'intera rete e il pre-allenamento avido a livello di livello funziona in questo modo:

  1. Addestrare il primo RBM t=1 utilizzando la divergenza contrastiva con tutti i campioni di addestramento.
  2. Addestrare il secondo RBM t=2 . Poiché lo strato visibile per t=2 è lo strato nascosto di t=1 , l'addestramento inizia fissando il campione di input allo strato visibile di t=1 , che viene propagato in avanti allo strato nascosto di t=1 . Questi dati servono quindi per avviare l'allenamento della divergenza contrastiva per t=2 .
  3. Ripetere la procedura precedente per tutti i livelli.
  4. Simile agli autoencoder impilati, dopo il pre-addestramento la rete può essere estesa collegando uno o più livelli completamente connessi al livello nascosto RBM finale. Questo forma un perceptron multistrato che può quindi essere regolato con precisione utilizzando la backpropagation.

Questa procedura è simile a quella degli autoencoder impilati, ma con gli autoencoder sostituiti da RBM e la backpropagation sostituita con l'algoritmo di divergenza contrastiva.

(Nota: per ulteriori informazioni sulla costruzione e l'addestramento di autoencoder impilati o reti di credenze profonde, controlla il codice di esempio qui.)

Reti Convoluzionali

Come architettura finale di deep learning, diamo un'occhiata alle reti convoluzionali, una classe particolarmente interessante e speciale di reti feedforward che sono molto adatte al riconoscimento delle immagini.

Le reti convoluzionali sono una classe speciale di reti feedforward di deep learning.
Immagine tramite DeepLearning.net

Prima di esaminare la struttura effettiva delle reti convoluzionali, definiamo innanzitutto un filtro immagine, o una regione quadrata con pesi associati. Un filtro viene applicato a un'intera immagine di input e spesso applicherai più filtri. Ad esempio, puoi applicare quattro filtri 6x6 a una determinata immagine di input. Quindi, il pixel di output con le coordinate 1,1 è la somma pesata di un quadrato 6x6 di pixel di input con l'angolo in alto a sinistra 1,1 e i pesi del filtro (che è anche 6x6 quadrato). Il pixel di output 2,1 è il risultato del quadrato di input con l'angolo in alto a sinistra 2,1 e così via.

Con quello coperto, queste reti sono definite dalle seguenti proprietà:

  • I livelli convoluzionali applicano una serie di filtri all'input. Ad esempio, il primo livello convoluzionale dell'immagine potrebbe avere quattro filtri 6x6. Il risultato di un filtro applicato sull'immagine è chiamato mappa delle caratteristiche (FM) e il numero delle mappe delle caratteristiche è uguale al numero di filtri. Se anche il livello precedente è convoluzionale, i filtri vengono applicati a tutti i suoi FM con pesi diversi, quindi ogni FM di ingresso è collegato a ogni FM di uscita. L'intuizione alla base dei pesi condivisi nell'immagine è che le caratteristiche verranno rilevate indipendentemente dalla loro posizione, mentre la molteplicità di filtri consente a ciascuna di esse di rilevare diversi insiemi di caratteristiche.
  • I livelli di sottocampionamento riducono le dimensioni dell'input. Ad esempio, se l'input è costituito da un'immagine 32x32 e il livello ha una regione di sottocampionamento di 2x2, il valore di output sarebbe un'immagine 16x16, il che significa che 4 pixel (ogni quadrato 2x2) dell'immagine di input sono combinati in un unico output pixel. Esistono diversi modi per sottocampionare, ma i più popolari sono il pool massimo, il pool medio e il pool stocastico.
  • L'ultimo livello di sottocampionamento (o convoluzionale) è solitamente connesso a uno o più livelli completamente connessi, l'ultimo dei quali rappresenta i dati di destinazione.
  • L'addestramento viene eseguito utilizzando la backpropagation modificata che tiene conto dei livelli di sottocampionamento e aggiorna i pesi del filtro convoluzionale in base a tutti i valori a cui viene applicato quel filtro.

Puoi vedere diversi esempi di reti convoluzionali addestrate (con backpropagation) sul set di dati MNIST (immagini in scala di grigi di lettere scritte a mano) qui, in particolare nei metodi testLeNet* (raccomanderei testLeNetTiny2 in quanto raggiunge un basso tasso di errore di circa il 2% in un arco di tempo relativamente breve). C'è anche una bella visualizzazione JavaScript di una rete simile qui.

Implementazione

Ora che abbiamo trattato le varianti di rete neurale più comuni, ho pensato di scrivere un po' sulle sfide poste durante l'implementazione di queste strutture di deep learning.

In generale, il mio obiettivo nella creazione di una libreria di Deep Learning era (ed è tuttora) quello di costruire un framework basato su rete neurale che soddisfacesse i seguenti criteri:

  • Un'architettura comune in grado di rappresentare diversi modelli (tutte le varianti sulle reti neurali che abbiamo visto sopra, per esempio).
  • La capacità di utilizzare diversi algoritmi di addestramento (backpropagation, divergenza contrastiva, ecc.).
  • Prestazioni decenti.

Per soddisfare questi requisiti, ho adottato un approccio a più livelli (o modulare) alla progettazione del software.

Struttura

Partiamo dalle basi:

  • NeuralNetworkImpl è la classe base per tutti i modelli di rete neurale.
  • Ogni rete contiene un insieme di livelli.
  • Ogni livello ha un elenco di connessioni, in cui una connessione è un collegamento tra due livelli in modo tale che la rete sia un grafo aciclico diretto.

Questa struttura è sufficientemente agile per essere utilizzata per le classiche reti feedforward, nonché per RBM e architetture più complesse come ImageNet.

Consente inoltre a un livello di far parte di più di una rete. Ad esempio, i livelli in una rete Deep Belief sono anche livelli nei corrispondenti RBM.

Inoltre, questa architettura consente di visualizzare un DBN come un elenco di RBM impilati durante la fase di pre-addestramento e una rete feedforward durante la fase di messa a punto, che è sia intuitivamente piacevole che programmaticamente conveniente.

Propagazione dei dati

Il modulo successivo si occupa della propagazione dei dati attraverso la rete, un processo in due fasi:

  1. Determina l'ordine dei livelli. Ad esempio, per ottenere i risultati da un perceptron multistrato, i dati vengono "bloccati" sul livello di input (quindi, questo è il primo livello da calcolare) e propagati fino al livello di output. Per aggiornare i pesi durante la backpropagation, l'errore di output deve essere propagato attraverso ogni livello in ordine di ampiezza, a partire dal livello di output. Ciò si ottiene utilizzando varie implementazioni di LayerOrderStrategy , che sfrutta la struttura del grafico della rete, impiegando diversi metodi di attraversamento del grafico. Alcuni esempi includono la strategia in ampiezza e il targeting di un livello specifico. L'ordine è effettivamente determinato dalle connessioni tra i livelli, quindi le strategie restituiscono un elenco ordinato di connessioni.
  2. Calcola il valore di attivazione. Ogni livello ha un ConnectionCalculator associato che prende la sua lista di connessioni (dal passaggio precedente) e valori di input (da altri livelli) e calcola l'attivazione risultante. Ad esempio, in una semplice rete feedforward sigmoidale, ConnectionCalculator dello strato nascosto prende i valori degli strati di input e di bias (che sono, rispettivamente, i dati di input e un array di 1s ) e i pesi tra le unità (in caso di strati, i pesi sono effettivamente memorizzati in una connessione FullyConnected come Matrix ), calcola la somma ponderata e alimenta il risultato nella funzione sigmoide. I calcolatori di connessione implementano una varietà di funzioni di trasferimento (ad esempio, somma pesata, convoluzionale) e di attivazione (ad esempio, logistica e tanh per perceptron multistrato, binaria per RBM). La maggior parte di essi può essere eseguita su una GPU utilizzando Aparapi e utilizzabile con l'addestramento in mini-batch.

Calcolo GPU con Aparapi

Come accennato in precedenza, uno dei motivi per cui le reti neurali hanno fatto una rinascita negli ultimi anni è che i loro metodi di allenamento sono altamente favorevoli al parallelismo, consentendo di accelerare notevolmente l'allenamento con l'uso di una GPGPU. In questo caso, ho scelto di lavorare con la libreria Aparapi per aggiungere il supporto GPU.

Aparapi impone alcune importanti restrizioni ai calcolatori di connessione:

  • Sono consentiti solo array (e variabili) unidimensionali di tipi di dati primitivi.
  • Solo i metodi-membro della stessa classe Aparapi Kernel possono essere chiamati dal codice eseguibile della GPU.

In quanto tale, la maggior parte dei dati (matrici di pesi, input e output) viene archiviata in istanze Matrix , che utilizzano internamente matrici float unidimensionali. Tutti i calcolatori di connessione Aparapi utilizzano AparapiWeightedSum (per livelli completamente connessi e funzioni di input di somma pesata), AparapiSubsampling2D (per livelli di sottocampionamento) o AparapiConv2D (per livelli convoluzionali). Alcune di queste limitazioni possono essere superate con l'introduzione dell'architettura del sistema eterogeneo. Aparapi consente anche di eseguire lo stesso codice sia su CPU che GPU.

Formazione

The training module implements various training algorithms. It relies on the previous two modules. For example, BackPropagationTrainer (all the trainers are using the Trainer base class) uses feedforward layer calculator for the feedforward phase and a special breadth-first layer calculator for propagating the error and updating the weights.

My latest work is on Java 8 support and some other improvements, will soon be merged into master.

Conclusione

The aim of this Java deep learning tutorial was to give you a brief introduction to the field of deep learning algorithms, beginning with the most basic unit of composition (the perceptron) and progressing through various effective and popular architectures, like that of the restricted Boltzmann machine.

The ideas behind neural networks have been around for a long time; but today, you can't step foot in the machine learning community without hearing about deep networks or some other take on deep learning. Hype shouldn't be mistaken for justification, but with the advances of GPGPU computing and the impressive progress made by researchers like Geoffrey Hinton, Yoshua Bengio, Yann LeCun and Andrew Ng, the field certainly shows a lot of promise. There's no better time to get familiar and get involved like the present.

Appendix: Resources

If you're interested in learning more, I found the following resources quite helpful during my work:

  • DeepLearning.net: a portal for all things deep learning. It has some nice tutorials, software library and a great reading list.
  • An active Google+ community.
  • Two very good courses: Machine Learning and Neural Networks for Machine Learning, both offered on Coursera.
  • The Stanford neural networks tutorial.
Related: Schooling Flappy Bird: A Reinforcement Learning Tutorial