Benvenuti nella Parte 3 della serie Applied Deep Learning. La parte 1 era un’introduzione pratica alle reti neurali artificiali, che copriva sia la teoria che l’applicazione con molti esempi di codice e visualizzazione. Nella Parte 2 abbiamo applicato il deep learning ai set di dati del mondo reale, coprendo i 3 problemi più comunemente incontrati come casi di studio: classificazione binaria, classificazione multiclasse e regressione.
Ora inizieremo a immergerci in specifiche architetture di deep learning, iniziando con le più semplici: Autoencoders.
- Introduzione
- Architettura
- Realizzazione
- Denoising Autoencoders
- Sparse Autoencoders
- Casi di Utilizzo
- Conclusione
Il codice per questo articolo è disponibile qui come Jupyter notebook, sentitevi liberi di scaricare e provare voi stessi.
Introduzione
Gli autoencoder sono un tipo specifico di reti neurali feedforward in cui l’input è lo stesso dell’output. Comprimono l’input in un codice dimensionale inferiore e quindi ricostruiscono l’output da questa rappresentazione. Il codice è un “riepilogo” compatto o” compressione ” dell’input, chiamato anche rappresentazione dello spazio latente.
Un autoencoder è costituito da 3 componenti: encoder, codice e decoder. L’encoder comprime l’input e produce il codice, il decodificatore quindi ricostruisce l’input solo usando questo codice.
Per costruire un autoencoder abbiamo bisogno di 3 cose: un metodo di codifica, un metodo di decodifica e una funzione di perdita per confrontare l’output con il target. Esploreremo questi nella prossima sezione.
Gli autoencoder sono principalmente un algoritmo di riduzione della dimensionalità (o compressione) con un paio di proprietà importanti:
- Dati specifici: gli autoencoder sono solo in grado di comprimere in modo significativo i dati in modo simile a quello su cui sono stati addestrati. Dal momento che imparano caratteristiche specifiche per i dati di allenamento dati, sono diversi da un algoritmo di compressione dei dati standard come gzip. Quindi non possiamo aspettarci un autoencoder addestrato su cifre scritte a mano per comprimere le foto di paesaggio.
- Perdita: L’output dell’autoencoder non sarà esattamente lo stesso dell’input, sarà una rappresentazione vicina ma degradata. Se vuoi la compressione senza perdita, non sono la strada da percorrere.
- Non supervisionato: per addestrare un autoencoder non abbiamo bisogno di fare nulla di speciale, basta lanciare i dati di input grezzi. Gli autoencoders sono considerati una tecnica di apprendimento non supervisionata poiché non hanno bisogno di etichette esplicite su cui allenarsi. Ma per essere più precisi sono auto-supervisionati perché generano le proprie etichette dai dati di formazione.
Architettura
Esploriamo i dettagli del codificatore, del codice e del decodificatore. Sia l’encoder che il decodificatore sono reti neurali feedforward completamente collegate, essenzialmente le ANN che abbiamo trattato nella Parte 1. Il codice è un singolo strato di un’ANN con la dimensionalità della nostra scelta. Il numero di nodi nel livello di codice (dimensione del codice) è un iperparametro che abbiamo impostato prima di allenare l’autoencoder.
Questa è una visualizzazione più dettagliata di un autoencoder. In primo luogo l’ingresso passa attraverso l’encoder, che è un ANN completamente collegato, per produrre il codice. Il decodificatore, che ha la struttura ANN simile, produce quindi l’output solo utilizzando il codice. L’obiettivo è ottenere un output identico all’input. Si noti che l’architettura del decoder è l’immagine speculare dell’encoder. Questo non è un requisito, ma è in genere il caso. L’unico requisito è che la dimensionalità dell’input e dell’output deve essere la stessa. Qualsiasi cosa nel mezzo può essere giocato con.
Ci sono 4 iperparametri che dobbiamo impostare prima di allenare un autoencoder:
- Dimensione del codice: numero di nodi nel livello intermedio. Dimensioni più piccole si traduce in una maggiore compressione.
- Numero di livelli: l’autoencoder può essere profondo come vogliamo. Nella figura sopra abbiamo 2 strati sia nell’encoder che nel decoder, senza considerare l’ingresso e l’uscita.
- Numero di nodi per livello: l’architettura di autoencoder su cui stiamo lavorando è chiamata autoencoder in pila poiché i livelli sono impilati uno dopo l’altro. Di solito gli autoencoders impilati sembrano un “sandwitch”. Il numero di nodi per livello diminuisce con ogni livello successivo dell’encoder e aumenta di nuovo nel decodificatore. Anche il decodificatore è simmetrico all’encoder in termini di struttura dei livelli. Come notato sopra questo non è necessario e abbiamo il controllo totale su questi parametri.
- Funzione di perdita: usiamo l’errore quadrato medio (mse) o la crossentropia binaria. Se i valori di input sono nell’intervallo, in genere usiamo crossentropy, altrimenti usiamo l’errore quadrato medio. Per maggiori dettagli guarda questo video.
Gli autoencoders sono addestrati allo stesso modo degli ANN tramite backpropagation. Controlla l’introduzione della Parte 1 per maggiori dettagli su come vengono addestrate le reti neurali, si applica direttamente agli autoencoders.
Implementazione
Ora implementiamo un autoencoder per la seguente architettura, 1 livello nascosto nel codificatore e nel decodificatore.
Useremo il set di dati MNIST estremamente popolare come input. Contiene immagini in bianco e nero di cifre scritte a mano.
Sono di dimensioni 28×28 e li usiamo come un vettore di 784 numeri tra . Controllare il notebook jupyter per i dettagli.
Implementeremo ora l’autoencoder con Keras. Gli iperparametri sono: 128 nodi nel livello nascosto, la dimensione del codice è 32 e la crossentropia binaria è la funzione di perdita.
Questo è molto simile alle ANN su cui abbiamo lavorato, ma ora stiamo usando l’API funzionale Keras. Fare riferimento a questa guida per i dettagli, ma ecco un rapido confronto. Prima abbiamo usato per aggiungere livelli utilizzando l’API sequenziale come segue:
model.add(Dense(16, activation='relu'))
model.add(Dense(8, activation='relu'))
Con l’API funzionale lo facciamo:
layer_1 = Dense(16, activation='relu')(input)
layer_2 = Dense(8, activation='relu')(layer_1)
È più dettagliato ma un modo più flessibile per definire modelli complessi. Possiamo facilmente afferrare parti del nostro modello, ad esempio solo il decoder, e lavorare con quello. L’output del metodo Denso è un livello chiamabile, utilizzando l’API funzionale forniamo l’input e memorizziamo l’output. L’output di un livello diventa l’input del livello successivo. Con l’API sequenziale il metodo add ha gestito implicitamente questo per noi.
Si noti che tutti i livelli utilizzano la funzione di attivazione relu, in quanto è lo standard con reti neurali profonde. L’ultimo livello utilizza l’attivazione sigmoid perché abbiamo bisogno che le uscite siano tra . Anche l’input è nello stesso intervallo.
Si noti inoltre la chiamata alla funzione fit, prima con ANNS abbiamo usato per fare:
model.fit(x_train, y_train)
Ma ora lo facciamo:
model.fit(x_train, x_train)
Ricorda che gli obiettivi dell’autoencoder sono gli stessi dell’input. Ecco perché forniamo i dati di allenamento come obiettivo.
Visualizzazione
Ora visualizziamo quanto bene il nostro autoencoder ricostruisce il suo input.
Eseguiamo l’autoencoder sul set di test semplicemente usando la funzione predict di Keras. Per ogni immagine nel set di test, otteniamo l’output dell’autoencoder. Ci aspettiamo che l’output sia molto simile all’input.
Sono davvero abbastanza simili, ma non esattamente lo stesso. Possiamo notarlo più chiaramente nell’ultima cifra “4”. Poiché questo era un compito semplice, il nostro autoencoder si è comportato abbastanza bene.
Consigli
Abbiamo il controllo totale sull’architettura dell’autoencoder. Possiamo renderlo molto potente aumentando il numero di livelli, nodi per livello e, soprattutto, la dimensione del codice. L’aumento di questi iperparametri consentirà all’autoencoder di apprendere codifiche più complesse. Ma dobbiamo stare attenti a non renderlo troppo potente. Altrimenti l’autoencoder imparerà semplicemente a copiare i suoi input nell’output, senza apprendere alcuna rappresentazione significativa. Sarà solo imitare la funzione di identità. L’autoencoder ricostruirà perfettamente i dati di allenamento, ma sarà overfitting senza essere in grado di generalizzare a nuove istanze, che non è quello che vogliamo.
Questo è il motivo per cui preferiamo un’architettura “sandwitch” e manteniamo deliberatamente le dimensioni del codice ridotte. Poiché il livello di codifica ha una dimensionalità inferiore rispetto ai dati di input, si dice che l’autoencoder sia undercomplete. Non sarà in grado di copiare direttamente i suoi input nell’output e sarà costretto a imparare funzioni intelligenti. Se i dati di input hanno un modello, ad esempio la cifra “1” di solito contiene una linea un po ‘ retta e la cifra “0” è circolare, imparerà questo fatto e lo codificherà in una forma più compatta. Se i dati di input erano completamente casuali senza alcuna correlazione o dipendenza interna, un autoencoder undercomplete non sarà in grado di recuperarlo perfettamente. Ma fortunatamente nel mondo reale c’è molta dipendenza.
Denoising Autoencoders
Mantenere il livello di codice piccolo ha costretto il nostro autoencoder a imparare una rappresentazione intelligente dei dati. C’è un altro modo per forzare l’autoencoder per imparare funzioni utili, che sta aggiungendo rumore casuale ai suoi ingressi e facendolo recuperare i dati originali senza rumore. In questo modo l’autoencoder non può semplicemente copiare l’input nel suo output perché l’input contiene anche rumore casuale. Stiamo chiedendo di sottrarre il rumore e produrre i dati significativi sottostanti. Questo è chiamato un autoencoder denoising.
La riga superiore contiene le immagini originali. Aggiungiamo rumore gaussiano casuale a loro e i dati rumorosi diventano l’input per l’autoencoder. L’autoencoder non vede affatto l’immagine originale. Ma poi ci aspettiamo che l’autoencoder rigeneri l’immagine originale senza rumore.
C’è solo una piccola differenza tra l’implementazione di denoising autoencoder e quella regolare. L’architettura non cambia affatto, solo la funzione fit. Abbiamo addestrato il normale autoencoder come segue:
autoencoder.fit(x_train, x_train)
Denoising autoencoder è addestrato come:
autoencoder.fit(x_train_noisy, x_train)
Semplice come quello, tutto il resto è esattamente lo stesso. L’input per l’autoencoder è l’immagine rumorosa e la destinazione prevista è quella originale senza rumore.
Visualizzazione
Ora vediamo se siamo in grado di recuperare le immagini senza rumore.
Sembra abbastanza buono. La riga inferiore è l’uscita autoencoder. Possiamo fare meglio usando un’architettura autoencoder più complessa, come gli autoencoder convoluzionali. Tratteremo le circonvoluzioni nel prossimo articolo.
Autoencoder sparse
Abbiamo introdotto due modi per forzare l’autoencoder per imparare funzioni utili: mantenere la dimensione del codice piccola e denoising autoencoder. Il terzo metodo sta usando la regolarizzazione. Possiamo regolarizzare l’autoencoder usando un vincolo di sparsità tale che solo una frazione dei nodi avrebbe valori diversi da zero, chiamati nodi attivi.
In particolare, aggiungiamo un termine di penalità alla funzione di perdita in modo tale che solo una frazione dei nodi diventi attiva. Questo costringe l’autoencoder a rappresentare ogni input come una combinazione di un piccolo numero di nodi e richiede che scopra una struttura interessante nei dati. Questo metodo funziona anche se la dimensione del codice è grande, poiché solo un piccolo sottoinsieme dei nodi sarà attivo in qualsiasi momento.
È abbastanza facile farlo in Keras con un solo parametro. Come promemoria, in precedenza abbiamo creato il livello di codice come segue:
code = Dense(code_size, activation='relu')(input_img)
Ora aggiungiamo un altro parametro chiamato activity_regularizer specificando la forza di regolarizzazione. Questo è in genere un valore nell’intervallo . Qui abbiamo scelto 10e-6.
code = Dense(code_size, activation='relu', activity_regularizer=l1(10e-6))(input_img)
La perdita finale del modello sparse è 0,01 superiore a quella standard, a causa del termine di regolarizzazione aggiunto.
Dimostriamo che le codifiche generate dal modello regolarizzato sono davvero scarse. Se guardiamo l’istogramma dei valori del codice per le immagini nel set di test, la distribuzione è la seguente:
La media per il modello standard è 6.6 ma per il modello regolarizzato è 0.8, una riduzione piuttosto grande. Possiamo vedere che una grande porzione di valori di codice nel modello regolarizzato è effettivamente 0, che è quello che volevamo. Anche la varianza del modello regolarizzato è piuttosto bassa.
Casi d’uso
Ora potremmo porre le seguenti domande. Quanto sono bravi gli autoencoders a comprimere l’input? E sono una tecnica di apprendimento profondo comunemente usata?
Purtroppo autoencoders non sono ampiamente utilizzati nelle applicazioni del mondo reale. Come metodo di compressione, non funzionano meglio delle sue alternative, ad esempio jpeg esegue la compressione delle foto meglio di un autoencoder. E il fatto che gli autoencoders siano specifici per i dati li rende poco pratici come tecnica generale. Hanno 3 casi d’uso comune però:
- Dati denoising: abbiamo visto un esempio di questo sulle immagini.
- Riduzione della dimensionalità: la visualizzazione di dati ad alta dimensione è impegnativa. t-SNE è il metodo più comunemente usato, ma lotta con un gran numero di dimensioni (in genere superiore a 32). Quindi gli autoencoder vengono utilizzati come fase di pre-elaborazione per ridurre la dimensionalità e questa rappresentazione compressa viene utilizzata da t-SNE per visualizzare i dati nello spazio 2D. Per grandi articoli su t-SNE fare riferimento qui e qui.
- Variational Autoencoders (VAE): questo è un caso d’uso più moderno e complesso degli autoencoders e li tratteremo in un altro articolo. Ma come un breve riassunto, VAE impara i parametri della distribuzione di probabilità modellando i dati di input, invece di imparare una funzione arbitraria nel caso di autoencoders vaniglia. Campionando i punti da questa distribuzione possiamo anche usare il VAE come modello generativo. Ecco un buon riferimento.
Conclusione
Gli autoencoder sono una tecnica di riduzione della dimensionalità molto utile. Sono molto popolari come materiale didattico nei corsi introduttivi di deep learning, molto probabilmente a causa della loro semplicità. In questo articolo li abbiamo trattati in dettaglio e spero vi sia piaciuto.
L’intero codice per questo articolo è disponibile qui se si desidera incidere su di esso da soli. Se avete commenti non esitate a entrare in contatto con me su Twitter.