Costruire un autoencoder a Tensorflow per superare la PCA


31

Hinton e Salakhutdinov nel ridurre la dimensionalità dei dati con le reti neurali, Science 2006 ha proposto un PCA non lineare attraverso l'uso di un autoencoder profondo. Ho provato a costruire e addestrare un autoencoder PCA con Tensorflow diverse volte, ma non sono mai stato in grado di ottenere risultati migliori rispetto al PCA lineare.

Come posso addestrare in modo efficiente un autoencoder?

(Modifica successiva di @amoeba: la versione originale di questa domanda conteneva il codice Python Tensorflow che non funzionava correttamente. Si può trovare nella cronologia delle modifiche.)


Ho trovato un errore nella funzione activaction della classe Layer. Sto testando se ora funziona
Donbeo l'

hai corretto il tuo errore?
Pinocchio,

Ciao Donbeo. Mi sono preso la libertà di rimuovere il codice dalla tua domanda (il codice può ancora essere facilmente trovato nella cronologia delle modifiche). Con il codice, la tua domanda assomigliava un po 'al tipo di domanda "Aiutami a trovare un bug" che è fuori tema qui. Allo stesso tempo, questa discussione ha visualizzazioni 4K, probabilmente significa che molte persone vengono qui tramite ricerche su Google, quindi non volevo chiudere la tua domanda. Ho deciso di pubblicare una risposta con un walker-through con un autoencoder, ma per ragioni di semplicità ho usato Keras (in esecuzione su Tensorflow) anziché Tensorflow grezzo. Pensi che questo risponda alla tua Q?
ameba dice di reintegrare Monica il

Risposte:


42

Ecco la figura chiave del documento scientifico 2006 di Hinton e Salakhutdinov:

Mostra la riduzione della dimensionalità del set di dati MNIST (immagini bianco e nero di singole cifre) dalle dimensioni originali 784 a due.28×28

Proviamo a riprodurlo. Non userò direttamente Tensorflow, perché è molto più facile usare Keras (una libreria di livello superiore in esecuzione su Tensorflow) per semplici compiti di deep learning come questo. H&S ha usato l' architettura con unità logistiche, pre-addestrata con la pila di macchine Boltzmann limitate. Dieci anni dopo, sembra molto vecchio stile. Userò un più semplice 784 512 128 2 128 512

784100050025022505001000784
architettura con unità lineari esponenziali senza alcun pre-allenamento. Userò Adam Optimizer (una particolare implementazione della discesa adattiva del gradiente stocastico con slancio).
7845121282128512784

Il codice viene copiato e incollato da un notebook Jupyter. In Python 3.6 devi installare matplotlib (per pylab), NumPy, seaborn, TensorFlow e Keras. Durante l'esecuzione nella shell Python, potrebbe essere necessario aggiungere plt.show()per mostrare i grafici.

Inizializzazione

%matplotlib notebook

import pylab as plt
import numpy as np
import seaborn as sns; sns.set()

import keras
from keras.datasets import mnist
from keras.models import Sequential, Model
from keras.layers import Dense
from keras.optimizers import Adam

(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train = x_train.reshape(60000, 784) / 255
x_test = x_test.reshape(10000, 784) / 255

PCA

mu = x_train.mean(axis=0)
U,s,V = np.linalg.svd(x_train - mu, full_matrices=False)
Zpca = np.dot(x_train - mu, V.transpose())

Rpca = np.dot(Zpca[:,:2], V[:2,:]) + mu    # reconstruction
err = np.sum((x_train-Rpca)**2)/Rpca.shape[0]/Rpca.shape[1]
print('PCA reconstruction error with 2 PCs: ' + str(round(err,3)));

Questo produce:

PCA reconstruction error with 2 PCs: 0.056

Addestramento del codificatore automatico

m = Sequential()
m.add(Dense(512,  activation='elu', input_shape=(784,)))
m.add(Dense(128,  activation='elu'))
m.add(Dense(2,    activation='linear', name="bottleneck"))
m.add(Dense(128,  activation='elu'))
m.add(Dense(512,  activation='elu'))
m.add(Dense(784,  activation='sigmoid'))
m.compile(loss='mean_squared_error', optimizer = Adam())
history = m.fit(x_train, x_train, batch_size=128, epochs=5, verbose=1, 
                validation_data=(x_test, x_test))

encoder = Model(m.input, m.get_layer('bottleneck').output)
Zenc = encoder.predict(x_train)  # bottleneck representation
Renc = m.predict(x_train)        # reconstruction

Questo richiede circa 35 secondi sul mio desktop di lavoro e genera:

Train on 60000 samples, validate on 10000 samples
Epoch 1/5
60000/60000 [==============================] - 7s - loss: 0.0577 - val_loss: 0.0482
Epoch 2/5
60000/60000 [==============================] - 7s - loss: 0.0464 - val_loss: 0.0448
Epoch 3/5
60000/60000 [==============================] - 7s - loss: 0.0438 - val_loss: 0.0430
Epoch 4/5
60000/60000 [==============================] - 7s - loss: 0.0423 - val_loss: 0.0416
Epoch 5/5
60000/60000 [==============================] - 7s - loss: 0.0412 - val_loss: 0.0407

quindi puoi già vedere che abbiamo superato la perdita di PCA dopo solo due epoche di allenamento.

(A proposito, è istruttivo cambiare tutte le funzioni di attivazione in activation='linear'e osservare come la perdita converge esattamente alla perdita di PCA. Questo perché l'autoencoder lineare è equivalente a PCA.)

Tracciamento della proiezione PCA affiancata alla rappresentazione del collo di bottiglia

plt.figure(figsize=(8,4))
plt.subplot(121)
plt.title('PCA')
plt.scatter(Zpca[:5000,0], Zpca[:5000,1], c=y_train[:5000], s=8, cmap='tab10')
plt.gca().get_xaxis().set_ticklabels([])
plt.gca().get_yaxis().set_ticklabels([])

plt.subplot(122)
plt.title('Autoencoder')
plt.scatter(Zenc[:5000,0], Zenc[:5000,1], c=y_train[:5000], s=8, cmap='tab10')
plt.gca().get_xaxis().set_ticklabels([])
plt.gca().get_yaxis().set_ticklabels([])

plt.tight_layout()

inserisci qui la descrizione dell'immagine

ricostruzioni

E ora diamo un'occhiata alle ricostruzioni (prima fila - immagini originali, seconda fila - PCA, terza fila - autoencoder):

plt.figure(figsize=(9,3))
toPlot = (x_train, Rpca, Renc)
for i in range(10):
    for j in range(3):
        ax = plt.subplot(3, 10, 10*j+i+1)
        plt.imshow(toPlot[j][i,:].reshape(28,28), interpolation="nearest", 
                   vmin=0, vmax=1)
        plt.gray()
        ax.get_xaxis().set_visible(False)
        ax.get_yaxis().set_visible(False)

plt.tight_layout()

inserisci qui la descrizione dell'immagine

Si possono ottenere risultati molto migliori con una rete più profonda, una certa regolarizzazione e una formazione più lunga. Sperimentare. L'apprendimento profondo è facile!


2
Sono sorpreso di come PCA abbia funzionato con solo 2 componenti! grazie per aver pubblicato il codice
Aksakal,

2
Fantastico! Stupendousness!
Matthew Drury,

2
@shadi In realtà trovo una chiamata diretta a svd () più semplice :)
amoeba dice Reinstate Monica

1
La differenza di prestazioni è ancora maggiore quando si utilizzano più componenti. Ho provato 10 invece di due e l'autoencoder era molto meglio. Lo svantaggio è la velocità e il consumo di memoria
Aksakal,

1
per Python 2 è necessario aggiungere le seguenti importazionifrom __future__ import absolute_import from __future__ import division from __future__ import print_function
user2589273

7

Enormi oggetti di scena per @amoeba per aver reso questo grande esempio. Voglio solo mostrare che la procedura di addestramento e ricostruzione dell'encoder automatico descritta in quel post può essere eseguita anche in R con facilità simile. L'encoder automatico di seguito è configurato in modo da emulare l'esempio di amoeba il più vicino possibile: stesso ottimizzatore e architettura generale. I costi esatti non sono riproducibili a causa del back-end TensorFlow non seminati in modo simile.

Inizializzazione

library(keras)
library(rARPACK) # to use SVDS
rm(list=ls())
mnist   = dataset_mnist()
x_train = mnist$train$x
y_train = mnist$train$y
x_test  = mnist$test$x
y_test  = mnist$test$y

# reshape & rescale
dim(x_train) = c(nrow(x_train), 784)
dim(x_test)  = c(nrow(x_test), 784)
x_train = x_train / 255
x_test = x_test / 255

PCA

mus = colMeans(x_train)
x_train_c =  sweep(x_train, 2, mus)
x_test_c =  sweep(x_test, 2, mus)
digitSVDS = svds(x_train_c, k = 2)

ZpcaTEST = x_test_c %*% digitSVDS$v # PCA projection of test data

Autoencoder

model = keras_model_sequential() 
model %>%
  layer_dense(units = 512, activation = 'elu', input_shape = c(784)) %>%  
  layer_dense(units = 128, activation = 'elu') %>%
  layer_dense(units = 2,   activation = 'linear', name = "bottleneck") %>%
  layer_dense(units = 128, activation = 'elu') %>% 
  layer_dense(units = 512, activation = 'elu') %>% 
  layer_dense(units = 784, activation='sigmoid')

model %>% compile(
  loss = loss_mean_squared_error, optimizer = optimizer_adam())

history = model %>% fit(verbose = 2, validation_data = list(x_test, x_test),
                         x_train, x_train, epochs = 5, batch_size = 128)

# Unsurprisingly a 3-year old laptop is slower than a desktop
# Train on 60000 samples, validate on 10000 samples
# Epoch 1/5
#  - 14s - loss: 0.0570 - val_loss: 0.0488
# Epoch 2/5
#  - 15s - loss: 0.0470 - val_loss: 0.0449
# Epoch 3/5
#  - 15s - loss: 0.0439 - val_loss: 0.0426
# Epoch 4/5
#  - 15s - loss: 0.0421 - val_loss: 0.0413
# Epoch 5/5
#  - 14s - loss: 0.0408 - val_loss: 0.0403

# Set the auto-encoder
autoencoder = keras_model(model$input, model$get_layer('bottleneck')$output)
ZencTEST = autoencoder$predict(x_test)  # bottleneck representation  of test data

Tracciamento della proiezione PCA affiancata alla rappresentazione del collo di bottiglia

par(mfrow=c(1,2))
myCols = colorRampPalette(c('green',     'red',  'blue',  'orange', 'steelblue2',
                            'darkgreen', 'cyan', 'black', 'grey',   'magenta') )
plot(ZpcaTEST[1:5000,], col= myCols(10)[(y_test+1)], 
     pch=16, xlab = 'Score 1', ylab = 'Score 2', main = 'PCA' ) 
legend( 'bottomright', col= myCols(10), legend = seq(0,9, by=1), pch = 16 )

plot(ZencTEST[1:5000,], col= myCols(10)[(y_test+1)], 
     pch=16, xlab = 'Score 1', ylab = 'Score 2', main = 'Autoencoder' ) 
legend( 'bottomleft', col= myCols(10), legend = seq(0,9, by=1), pch = 16 )

inserisci qui la descrizione dell'immagine

ricostruzioni

Possiamo fare la ricostruzione delle cifre con la solita maniera. (La riga superiore indica le cifre originali, la riga centrale le ricostruzioni PCA e la riga inferiore le ricostruzioni del codificatore automatico.)

Renc = predict(model, x_test)        # autoencoder reconstruction
Rpca = sweep( ZpcaTEST %*% t(digitSVDS$v), 2, -mus) # PCA reconstruction

dev.off()
par(mfcol=c(3,9), mar = c(1, 1, 0, 0))
myGrays = gray(1:256 / 256)
for(u in seq_len(9) ){
  image( matrix( x_test[u,], 28,28, byrow = TRUE)[,28:1], col = myGrays, 
         xaxt='n', yaxt='n')
  image( matrix( Rpca[u,], 28,28, byrow = TRUE)[,28:1], col = myGrays , 
         xaxt='n', yaxt='n')
  image( matrix( Renc[u,], 28,28, byrow = TRUE)[,28:1], col = myGrays, 
         xaxt='n', yaxt='n')
}

inserisci qui la descrizione dell'immagine

K0,03560,0359


2
+1. Bello. È bello vedere che è semplice usare Keras in R come in Python. Per quanto posso vedere, nella comunità del deep learning tutti usano Python in questi giorni, quindi avevo l'impressione che dovesse essere più difficile altrove.
ameba dice Ripristina Monica il

2

Ecco il mio taccuino jupyter in cui provo a replicare il tuo risultato, con le seguenti differenze:

  • invece di usare direttamente tensorflow, lo uso view keras
  • relu che perde invece di relu per evitare la saturazione (ovvero l'output codificato è 0)
    • questo potrebbe essere un motivo per scarse prestazioni di AE
  • l'ingresso del codificatore automatico viene ridimensionato in [0,1]
    • Penso di aver letto da qualche parte che i codificatori automatici con relu funzionano meglio con i dati [0-1]
    • far funzionare il mio notebook con input degli autoencoder come media = 0, std = 1 ha dato MSE per AE> 0,7 per tutte le riduzioni di dimensionalità, quindi forse questo è uno dei tuoi problemi
  • L'input PCA viene mantenuto come dato con media = 0 e std = 1
    • Ciò può anche significare che il risultato MSE di PCA non è paragonabile al risultato MSE di PCA
    • Forse lo eseguirò più tardi con [0-1] dati sia per PCA che per AE
  • Anche l'ingresso PCA è ridimensionato su [0-1]. PCA funziona anche con (media = 0, std = 1) dati, ma MSE sarebbe incomparabile con AE

Il mio MSE risulta per PCA dalla riduzione dimensionale da 1 a 6 (dove l'ingresso ha 6 colonne) e per AE da dim. rosso. da 1 a 6 sono di seguito:

Con l'ingresso PCA essere (media = 0, std = 1) mentre l'ingresso AE è [0-1] intervallo - 4e-15: PCA6 - .015: PCA5 - .0502: AE5 - .0508: AE6 - .051: AE4 - .053: AE3 - .157: PCA4 - .258: AE2 - .259: PCA3 - .377: AE1 - .483: PCA2 - .682: PCA1

  • 9e-15: PCA6
  • .0094: PCA5
  • .0502: AE5
  • .0507: AE6
  • .0514: AE4
  • .0532: AE3
  • .0772: PCA4
  • .1231: PCA3
  • .2588: AE2
  • .2831: PCA2
  • .3773: AE1
  • .3885: PCA1

La PCA lineare senza riduzione della dimensionalità può raggiungere 9e-15 perché può semplicemente spingere qualunque cosa non fosse in grado di adattarsi all'ultimo componente.


shadi, il tuo notebook importa un pacchetto utils che sembra avere molte funzioni non standard utils.buildNetwork e utils.ae_fit_encode_plot_mse per esempio ...
Berowne Hlavaty

Questo è solo un file nello stesso repository allo stesso livello del notebook.
Shadi,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.