Construire un autoencodeur dans Tensorflow pour dépasser PCA


31

Hinton et Salakhutdinov, dans Reducing the Dimensionality of Data with Neural Networks, Science 2006, ont proposé une ACP non linéaire grâce à l'utilisation d'un auto-encodeur profond. J'ai essayé de construire et d'entraîner plusieurs fois un autoencodeur PCA avec Tensorflow mais je n'ai jamais pu obtenir de meilleurs résultats qu'un PCA linéaire.

Comment puis-je former efficacement un autoencodeur?

(Édition ultérieure par @amoeba: la version originale de cette question contenait du code Python Tensorflow qui ne fonctionnait pas correctement. On peut le trouver dans l'historique des éditions.)


J'ai trouvé une erreur dans la fonction d'activation de la classe Layer. Je teste si maintenant ça marche
Donbeo

avez-vous corrigé votre erreur?
Pinocchio

Salut Donbeo. J'ai pris la liberté de supprimer le code de votre question (le code peut toujours être facilement trouvé dans l'historique des modifications). Avec le code, votre question ressemblait un peu au type de question "Aidez-moi à trouver un bug" qui est hors sujet ici. En même temps, ce fil a 4k vues, ce qui signifie probablement que beaucoup de gens viennent ici via les recherches Google, donc je ne voulais pas fermer votre question. J'ai décidé de poster une réponse avec une procédure pas à pas pour l'encodeur automatique, mais pour des raisons de simplicité, j'ai utilisé Keras (fonctionnant au-dessus de Tensorflow) au lieu de Tensorflow brut. Pensez-vous que cela répond à votre Q?
amoeba dit Reinstate Monica

Réponses:


42

Voici le chiffre clé de l'article scientifique de 2006 de Hinton et Salakhutdinov:

Il montre la réduction de la dimensionnalité de l'ensemble de données MNIST ( images en noir et blanc de chiffres uniques) des 784 dimensions d'origine à deux.28×28

Essayons de le reproduire. Je n'utiliserai pas Tensorflow directement, car il est beaucoup plus facile d'utiliser Keras (une bibliothèque de niveau supérieur fonctionnant au-dessus de Tensorflow) pour de simples tâches d'apprentissage en profondeur comme celle-ci. H&S a utilisé une architecture avec des unités logistiques, pré-entraînée avec la pile de machines Boltzmann restreintes. Dix ans plus tard, cela semble très old-school. J'utiliserai une architecture plus simple avec des unités linéaires exponentielles sans aucune pré-formation. J'utiliserai l'optimiseur Adam (une implémentation particulière de la descente de gradient stochastique adaptatif avec momentum).784 512 128 2 128 512 784

784100050025022505001000784
7845121282128512784

Le code est copié-collé à partir d'un ordinateur portable Jupyter. Dans Python 3.6, vous devez installer matplotlib (pour pylab), NumPy, seaborn, TensorFlow et Keras. Lors de l'exécution dans le shell Python, vous devrez peut-être ajouter plt.show()pour afficher les tracés.

Initialisation

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

Cela produit:

PCA reconstruction error with 2 PCs: 0.056

Formation de l'autoencodeur

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

Cela prend environ 35 secondes sur mon bureau de travail et génère:

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

vous pouvez donc déjà voir que nous avons dépassé la perte de PCA après seulement deux périodes d'entraînement.

(Soit dit en passant, il est instructif de modifier toutes les fonctions d'activation activation='linear'et d'observer comment la perte converge précisément vers la perte PCA. En effet, l'autoencodeur linéaire est équivalent à PCA.)

Tracer la projection PCA côte à côte avec la représentation du goulot d'étranglement

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

entrez la description de l'image ici

Reconstructions

Et maintenant, regardons les reconstructions (première rangée - images originales, deuxième rangée - PCA, troisième rangée - encodeur automatique):

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

entrez la description de l'image ici

On peut obtenir de bien meilleurs résultats avec un réseau plus profond, une certaine régularisation et une formation plus longue. Expérience. L'apprentissage en profondeur est facile!


2
Je suis surpris de voir à quel point PCA a bien fonctionné avec seulement 2 composants! merci pour l'affichage du code
Aksakal

2
Fantastique! Superbe!
Matthew Drury

2
@shadi Je trouve en fait un appel direct à svd () plus simple :)
amoeba dit Reinstate Monica

1
La différence de performances est encore plus grande lorsque vous utilisez plus de composants. J'ai essayé 10 au lieu de deux et l'encodeur automatique était bien meilleur. L'inconvénient est la vitesse et la consommation de mémoire
Aksakal

1
pour python 2, vous devez ajouter les importations suivantesfrom __future__ import absolute_import from __future__ import division from __future__ import print_function
user2589273

7

D'énormes accessoires à @amoeba pour avoir fait ce grand exemple. Je veux juste montrer que la procédure de formation et de reconstruction de l'encodeur automatique décrite dans ce post peut également être effectuée en R avec une facilité similaire. L'auto-encodeur ci-dessous est configuré pour émuler l'exemple d'amibe aussi près que possible - même optimiseur et architecture globale. Les coûts exacts ne sont pas reproductibles car le backend TensorFlow n'est pas semé de la même manière.

Initialisation

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

Encodeur automatique

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

Tracer la projection PCA côte à côte avec la représentation du goulot d'étranglement

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 )

entrez la description de l'image ici

Reconstructions

On peut faire la reconstruction des chiffres de la manière habituelle. (La rangée du haut représente les chiffres d'origine, la rangée du milieu les reconstructions PCA et la rangée du bas les reconstructions de l'encodeur automatique.)

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

entrez la description de l'image ici

Comme indiqué, plus d'époques et un réseau plus profond et / ou mieux formé donneront de bien meilleurs résultats. Par exemple, l'erreur de reconstruction PCA de = 9 est d'environ , nous pouvons obtenir presque la même erreur ( ) de l'autoencodeur décrit ci-dessus, simplement en augmentant les époques d'apprentissage de 5 à 25. Dans ce cas d'utilisation, le 2 Les composants dérivés de l'autoencodeur fourniront une erreur de reconstruction similaire à 9 composants principaux. Cool!0,0356 0,0359k0.03560.0359


2
+1. Agréable. Il est bon de voir qu'il est aussi simple d'utiliser Keras en R qu'en Python. Pour autant que je sache, dans la communauté d'apprentissage en profondeur, tout le monde utilise Python ces jours-ci, alors j'avais l'impression que cela devrait être plus difficile ailleurs.
amibe dit Réintégrer Monica

2

Voici mon cahier jupyter où j'essaie de reproduire votre résultat, avec les différences suivantes:

  • au lieu d'utiliser tensorflow directement, je l'utilise voir les keras
  • relu qui fuit au lieu de relu pour éviter la saturation (c'est-à-dire que la sortie codée est 0)
    • cela pourrait être une raison de la mauvaise performance de AE
  • l'entrée de l'encodeur automatique est mise à l'échelle des données à [0,1]
    • Je pense avoir lu quelque part que les encodeurs automatiques avec relu fonctionnent mieux avec les données [0-1]
    • l'exécution de mon ordinateur portable avec l'entrée des encodeurs automatiques étant la moyenne = 0, std = 1 a donné MSE pour AE> 0,7 pour toutes les réductions de dimensionnalité, donc c'est peut-être l'un de vos problèmes
  • L'entrée PCA est toujours constituée de données avec une moyenne = 0 et std = 1
    • Cela peut également signifier que le résultat MSE de l'APC n'est pas comparable au résultat MSE de l'APC
    • Peut-être que je vais juste relancer cela plus tard avec des données [0-1] pour PCA et AE
  • L'entrée PCA est également mise à l'échelle sur [0-1]. L'ACP fonctionne également avec les données (moyenne = 0, std = 1), mais le MSE serait incomparable avec AE

Mon MSE résulte pour PCA de la réduction de dimensionnalité de 1 à 6 (où l'entrée a 6 colonnes) et pour AE de dim. rouge. de 1 à 6 sont ci-dessous:

Avec l'entrée PCA étant (moyenne = 0, std = 1) tandis que l'entrée AE est dans la plage [0-1] - 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

Le PCA linéaire sans réduction de dimensionnalité peut atteindre 9e-15 car il peut simplement pousser tout ce qu'il n'a pas pu intégrer dans le dernier composant.


shadi, votre ordinateur portable importe un paquet utils qui semble avoir beaucoup de fonctions non standard utils.buildNetwork et utils.ae_fit_encode_plot_mse par exemple ...
Berowne Hlavaty

C'est juste un fichier dans le même référentiel au même niveau que le bloc-notes.
shadi
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.