Processing math: 100%

36 votos

Construyendo un autoencoder en Tensorflow para superar el PCA

Hinton y Salakhutdinov en Reducción de la dimensionalidad de los datos con redes neuronales, Ciencia 2006 propuso un PCA no lineal mediante el uso de un autocodificador profundo. He intentado construir y entrenar un autoencoder PCA con Tensorflow varias veces pero nunca he podido obtener un resultado mejor que el PCA lineal.

¿Cómo puedo entrenar eficazmente un autoencoder?

(Edición posterior por @amoeba: la versión original de esta pregunta contenía código Python Tensorflow que no funcionaba correctamente. Se puede encontrar en el historial de ediciones).

50voto

zowens Puntos 1417

Aquí está la cifra clave del artículo de Science de 2006 de Hinton y Salakhutdinov:

Muestra la reducción de la dimensionalidad del conjunto de datos MNIST ( 28×28 imágenes en blanco y negro de un solo dígito) de las 784 dimensiones originales a dos.

Intentemos reproducirlo. No usaré Tensorflow directamente, porque es mucho más fácil usar Keras (una librería de alto nivel que se ejecuta sobre Tensorflow) para tareas sencillas de aprendizaje profundo como esta. H&S utilizado 784100050025022505001000784 arquitectura con unidades logísticas, preentrenadas con la pila de Máquinas de Boltzmann Restringidas. Diez años después, esto suena muy de la vieja escuela. Utilizaré un método más sencillo 7845121282128512784 arquitectura con unidades lineales exponenciales sin ningún tipo de entrenamiento previo. Utilizaré el optimizador Adam (una implementación particular del descenso de gradiente estocástico adaptativo con impulso).


El código está copiado de un cuaderno Jupyter. En Python 3.6 necesitas instalar matplotlib (para pylab), NumPy, seaborn, TensorFlow y Keras. Cuando se ejecuta en el shell de Python, es posible que tenga que añadir plt.show() para mostrar los gráficos.

Inicialización

%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)));

Estas salidas:

PCA reconstruction error with 2 PCs: 0.056

Entrenamiento del autoencoder

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

Esto toma ~35 segundos en mi escritorio de trabajo y salidas:

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

por lo que ya se puede ver que superamos la pérdida de PCA después de sólo dos épocas de entrenamiento.

(Por cierto, es instructivo cambiar todas las funciones de activación a activation='linear' y observar cómo la pérdida converge precisamente a la pérdida PCA. Esto se debe a que el autoencoder lineal es equivalente al PCA).

Trazado de la proyección PCA junto a la representación del cuello de botella

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()

enter image description here

Reconstrucciones

Y ahora veamos las reconstrucciones (primera fila - imágenes originales, segunda fila - PCA, tercera 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()

enter image description here

Se pueden obtener resultados mucho mejores con una red más profunda, cierta regularización y un entrenamiento más largo. Experimento. El aprendizaje profundo es fácil.

7voto

usεr11852 Puntos 5514

Enorme apoyo a @amoeba por hacer este gran ejemplo. Sólo quiero mostrar que el procedimiento de entrenamiento y reconstrucción del autocodificador descrito en ese post se puede hacer también en R con una facilidad similar. El autocodificador de abajo está configurado para emular el ejemplo de amoeba tan cerca como sea posible - el mismo optimizador y la arquitectura general. Los costes exactos no son reproducibles debido a que el back-end de TensorFlow no está sembrado de forma similar.

Inicialización

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

Trazado de la proyección PCA junto a la representación del cuello de botella

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 )

enter image description here

Reconstrucciones

Podemos hacer la reconstrucción de los dígitos con la forma habitual. (La fila superior son los dígitos originales, la fila central las reconstrucciones PCA y la fila inferior las reconstrucciones del autocodificador).

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')
}

enter image description here

Como se ha señalado, un mayor número de épocas y una red más profunda y/o más inteligentemente entrenada darán resultados mucho mejores. Por ejemplo, el error de reconstrucción PCA de k = 9 es aproximadamente 0.0356 podemos obtener casi el mismo error ( 0.0359 ) del autoencoder descrito anteriormente, simplemente aumentando las épocas de entrenamiento de 5 a 25. En este caso, los 2 componentes derivados del autocodificador proporcionarán un error de reconstrucción similar al de 9 componentes principales. ¡Genial!

2voto

user169306 Puntos 26

Aquí es mi cuaderno de jupyter donde intento replicar tu resultado, con las siguientes diferencias:

  • en lugar de usar tensorflow directamente, lo uso vista keras

  • relu con fugas en lugar de relu para evitar la saturación (es decir, que la salida codificada sea 0)

    • esto podría ser una razón para el bajo rendimiento de AE
  • la entrada del autocodificador son datos escalados a [0,1]

    • Creo haber leído en alguna parte que los autocodificadores con relu funcionan mejor con datos [0-1]
    • al ejecutar mi cuaderno con la entrada de los autoencoders siendo la media=0, std=1 dio MSE para AE > 0.7 para todas las reducciones de dimensionalidad, así que tal vez este sea uno de sus problemas
  • La entrada del PCA se mantiene siendo datos con media=0 y std=1 * Esto también puede significar que el resultado MSE de PCA no es comparable al resultado MSE de PCA* Tal vez vuelva a ejecutar esto más tarde con datos [0-1] tanto para PCA como para AE

  • La entrada del PCA también está escalada a [0-1]. PCA también funciona con datos (media=0,std=1), pero el MSE sería incomparable con AE

Mis resultados de MSE para PCA a partir de la reducción de la dimensionalidad de 1 a 6 (donde la entrada tiene 6 columnas) y para AE de reducción de dimensionalidad de 1 a 6 están abajo:

Con la entrada PCA siendo (media=0,std=1) mientras que la entrada AE siendo [0-1] rango - 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

El PCA lineal sin reducción de la dimensionalidad puede alcanzar 9e-15 porque puede simplemente empujar lo que no pudo encajar en el último componente.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X