Erstellen eines Autoencoders in Tensorflow, um PCA zu übertreffen

31

Hinton und Salakhutdinov schlugen bei der Reduzierung der Dimensionalität von Daten mit neuronalen Netzen in Science 2006 eine nichtlineare PCA durch die Verwendung eines tiefen Autoencoders vor. Ich habe mehrmals versucht, einen PCA-Autoencoder mit Tensorflow zu bauen und zu trainieren, aber ich konnte nie ein besseres Ergebnis erzielen als mit linearem PCA.

Wie kann ich einen Autoencoder effizient trainieren?

(Später bearbeitet von @amoeba: Die Originalversion dieser Frage enthielt Python Tensorflow-Code, der nicht richtig funktionierte. Man kann ihn im Bearbeitungsverlauf finden.)

Donbeo
quelle
Ich habe einen Fehler in der Aktivierungsfunktion der Layer-Klasse gefunden. Ich
teste,
Hast du deinen Fehler behoben?
Pinocchio
Hallo Donbeo. Ich habe mir erlaubt, den Code aus Ihrer Frage zu entfernen (der Code ist immer noch leicht im Bearbeitungsverlauf zu finden). Mit dem Code sah Ihre Frage ein bisschen so aus wie die Frage "Hilf mir, einen Fehler zu finden", die hier nicht zum Thema gehört. Gleichzeitig hat dieser Thread 4 KB-Aufrufe, was wahrscheinlich bedeutet, dass viele Leute über die Google-Suche hierher kommen, sodass ich Ihre Frage nicht schließen wollte. Ich habe mich entschieden, eine Antwort mit einem Autoencoder-Durchgang zu veröffentlichen, aber aus Gründen der Einfachheit habe ich Keras (läuft auf Tensorflow) anstelle von Tensorflow-Rohdaten verwendet. Glaubst du, das beantwortet deine Frage?
Amöbe sagt Reinstate Monica

Antworten:

42

Hier ist die Schlüsselfigur aus dem Science Paper von 2006 von Hinton und Salakhutdinov:

Es zeigt die Dimensionsreduktion des MNIST-Datensatzes ( Schwarzweißbilder mit einzelnen Ziffern) von den ursprünglichen 784-Dimensionen auf zwei.28×28

Versuchen wir es zu reproduzieren. Ich werde Tensorflow nicht direkt verwenden, da es viel einfacher ist, Keras (eine übergeordnete Bibliothek, die auf Tensorflow läuft) für einfache vertiefende Lernaufgaben wie diese zu verwenden. H & S verwendete eine Architektur von mit logistischen Einheiten, die mit dem Stapel der eingeschränkten Boltzmann-Maschinen vorab trainiert wurden. Zehn Jahre später klingt das sehr altmodisch. Ich werde eine einfachere Architektur mit exponentiellen Lineareinheiten ohne Vorkenntnisse verwenden. Ich werde den Adam-Optimierer verwenden (eine spezielle Implementierung des adaptiven stochastischen Gradientenabfalls mit Impuls).

784100050025022505001000784
7845121282128512784

Der Code wird aus einem Jupyter-Notizbuch kopiert. In Python 3.6 müssen Sie matplotlib (für pylab), NumPy, seaborn, TensorFlow und Keras installieren. Wenn Sie in der Python-Shell arbeiten, müssen Sie möglicherweise hinzufügen plt.show(), um die Diagramme anzuzeigen.

Initialisierung

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

Dies gibt aus:

PCA reconstruction error with 2 PCs: 0.056

Training des Autoencoders

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

Dies dauert ca. 35 Sekunden auf meinem Arbeits-Desktop und gibt Folgendes aus:

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

Sie sehen also bereits, dass wir den PCA-Verlust nach nur zwei Trainingsepochen überschritten haben.

(Übrigens ist es activation='linear'aufschlussreich , alle Aktivierungsfunktionen zu ändern und zu beobachten, wie der Verlust genau zum PCA-Verlust konvergiert. Dies liegt daran, dass der lineare Autoencoder dem PCA äquivalent ist.)

Nebeneinanderzeichnen der PCA-Projektion mit der Flaschenhalsdarstellung

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

Bildbeschreibung hier eingeben

Rekonstruktionen

Und jetzt schauen wir uns die Rekonstruktionen an (erste Reihe - Originalbilder, zweite Reihe - PCA, dritte Reihe - 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()

Bildbeschreibung hier eingeben

Mit einem tieferen Netzwerk, etwas Regularisierung und längerem Training kann man viel bessere Ergebnisse erzielen. Experiment. Tiefes Lernen ist einfach!

Amöbe sagt Reinstate Monica
quelle
2
Ich bin überrascht, wie gut PCA mit nur 2 Komponenten funktioniert hat!
Vielen
2
Fantastisch! Wahnsinn!
Matthew Drury
2
@shadi Ich finde tatsächlich einen direkten Anruf bei svd () einfacher :)
Amöbe sagt Reinstate Monica
1
Der Leistungsunterschied ist noch größer, wenn mehr Komponenten verwendet werden. Ich habe 10 statt zwei probiert und der Autoencoder war viel besser. Der Nachteil ist die Geschwindigkeit und der Speicherverbrauch
Aksakal
1
Für Python 2 müssen Sie die folgenden Importe hinzufügenfrom __future__ import absolute_import from __future__ import division from __future__ import print_function
user2589273
7

Riesige Requisiten an @amoeba für dieses großartige Beispiel. Ich möchte nur zeigen, dass das in diesem Beitrag beschriebene Auto-Encoder-Training und -Rekonstruktionsverfahren auch in R mit ähnlicher Leichtigkeit durchgeführt werden kann. Der folgende Auto-Encoder ist so eingerichtet, dass er das Beispiel von Amoeba so genau wie möglich nachahmt - derselbe Optimierer und die gleiche Gesamtarchitektur. Die genauen Kosten sind nicht reproduzierbar, da das TensorFlow-Backend nicht auf ähnliche Weise gesät wird.

Initialisierung

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

Nebeneinanderzeichnen der PCA-Projektion mit der Flaschenhalsdarstellung

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 )

Bildbeschreibung hier eingeben

Rekonstruktionen

Wir können die Rekonstruktion der Ziffern auf die übliche Weise durchführen. (Obere Reihe sind die Originalziffern, mittlere Reihe die PCA-Rekonstruktionen und untere Reihe die Autoencoder-Rekonstruktionen.)

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

Bildbeschreibung hier eingeben

k0,03560,0359

usεr11852 sagt Reinstate Monic
quelle
2
+1. Nett. Es ist schön zu sehen, dass die Verwendung von Keras in R genauso einfach ist wie in Python. Soweit ich sehen kann, verwenden in der Deep-Learning-Community derzeit alle Python. Daher hatte ich den Eindruck, dass es anderswo schwieriger sein sollte.
Amöbe sagt Reinstate Monica
2

Hier ist mein Jupyter-Notizbuch, in dem ich versuche, Ihr Ergebnis mit den folgenden Unterschieden zu replizieren:

  • Anstatt Tensorflow direkt zu verwenden, verwende ich es, um Keras anzuzeigen
  • Leaky Relu anstelle von Relu, um eine Sättigung zu vermeiden (dh die codierte Ausgabe ist 0)
    • Dies könnte ein Grund für die schlechte Leistung von AE sein
  • Autoencoder-Eingang ist Daten skaliert auf [0,1]
    • Ich glaube, ich habe irgendwo gelesen, dass Autoencoder mit relu am besten mit [0-1] -Daten funktionieren
    • Wenn ich mein Notebook mit einer Eingabe von Autoencodern mit dem Mittelwert = 0 und std = 1 laufen lasse, erhält ich MSE für AE> 0,7 für alle Dimensionsreduzierungen. Vielleicht ist dies also eines Ihrer Probleme
  • PCA-Eingabe wird als Daten mit Mittelwert = 0 und Standard = 1 beibehalten
    • Dies kann auch bedeuten, dass das MSE-Ergebnis von PCA nicht mit dem MSE-Ergebnis von PCA vergleichbar ist
    • Vielleicht führe ich das später noch einmal mit [0-1] Daten für PCA und AE aus
  • Der PCA-Eingang ist ebenfalls auf [0-1] skaliert. PCA funktioniert auch mit Daten (Mittelwert = 0, Standard = 1), aber die MSE wäre mit AE nicht zu vergleichen

Meine MSE ergibt sich für PCA aus der Dimensionsreduktion von 1 auf 6 (wobei die Eingabe 6 Spalten hat) und für AE aus dim. rot. von 1 bis 6 sind unten:

Bei PCA-Eingang (Mittelwert = 0, Standard = 1) und AE-Eingang [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

Lineares PCA ohne Dimensionsreduzierung kann 9e-15 erreichen, da es nur das drücken kann, was nicht in die letzte Komponente passen konnte.

shadi
quelle
shadi, ihr notebook importiert ein utils-paket, das viele nicht standardmäßige funktionen zu haben scheint utils.buildNetwork und utils.ae_fit_encode_plot_mse zum beispiel ...
Berowne Hlavaty
Dies ist nur eine Datei im selben Repository auf derselben Ebene wie das Notebook.
Shadi