Budowanie autokodera w Tensorflow, aby przewyższyć PCA

31

Hinton i Salakhutdinov w zmniejszaniu wymiarów danych za pomocą sieci neuronowych Science 2006 zaproponowali nieliniowe PCA poprzez zastosowanie głębokiego autoencodera. Kilka razy próbowałem zbudować i wyszkolić autoencoder PCA z Tensorflow, ale nigdy nie byłem w stanie uzyskać lepszego wyniku niż liniowy PCA.

Jak mogę skutecznie trenować autoencoder?

(Późniejsza edycja przez @amoeba: oryginalna wersja tego pytania zawierała kod Pythona Tensorflow, który nie działał poprawnie. Można go znaleźć w historii edycji.)

Donbeo
źródło
Znalazłem błąd w funkcji aktywacji klasy Layer.
Testuję,
naprawiłeś błąd?
Pinokio
Cześć Donbeo. Pozwoliłem sobie na usunięcie kodu z twojego pytania (kod nadal można łatwo znaleźć w historii edycji). Z kodem twoje pytanie wyglądało trochę jak pytanie „Pomóż mi znaleźć błąd”, które jest tutaj nie na temat. Jednocześnie ten wątek ma 4k wyświetleń, co prawdopodobnie oznacza, że ​​wiele osób przyjeżdża tutaj przez wyszukiwarki Google, więc nie chciałem, aby twoje pytanie zostało zamknięte. Postanowiłem opublikować odpowiedź z instrukcją obsługi autokodera, ale dla uproszczenia użyłem Keras (działającego na Tensorflow) zamiast surowego Tensorflow. Czy uważasz, że to odpowiada twojemu Q?
ameba mówi Przywróć Monikę

Odpowiedzi:

42

Oto kluczowa postać z dokumentu naukowego z 2006 r. Autorstwa Hintona i Salachutdinowa:

Pokazuje zmniejszenie wymiarów zestawu danych MNIST ( czarno-białych obrazów pojedynczych cyfr) z oryginalnych 784 wymiarów do dwóch.28×28

Spróbujmy to odtworzyć. Nie będę używać Tensorflow bezpośrednio, ponieważ o wiele łatwiej jest używać Keras (biblioteka wyższego poziomu działająca na Tensorflow) do prostych zadań głębokiego uczenia się, takich jak to. H&S użył architektury architektury z jednostkami logistycznymi, wstępnie przeszkolonymi ze stosu Ograniczonych Maszyn Boltzmanna. Dziesięć lat później to brzmi bardzo oldschoolowo. prostszej architektury z wykładniczymi jednostkami liniowymi bez żadnego wstępnego treningu. Użyję optymalizatora Adama (szczególna implementacja adaptacyjnego stochastycznego spadku gradientu z pędem).784 512 128 2 128 512 784

784100050025022505001000784
7845121282128512784

Kod jest wklejany z notesu Jupyter. W Pythonie 3.6 musisz zainstalować matplotlib (dla pylab), NumPy, seaborn, TensorFlow i Keras. Podczas działania w powłoce Pythona może być konieczne dodanie, plt.show()aby pokazać wykresy.

Inicjalizacja

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

To daje:

PCA reconstruction error with 2 PCs: 0.056

Szkolenie autokodera

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

Zajmuje to ~ 35 sekund na moim pulpicie roboczym i wyświetla:

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

więc już widać, że pokonaliśmy straty PCA już po dwóch epokach treningowych.

(Nawiasem mówiąc, pouczające jest, aby zmienić wszystkie funkcje aktywacyjne activation='linear'i obserwować, w jaki sposób strata dokładnie zbiega się ze stratą PCA. Jest tak, ponieważ liniowy autoencoder jest równoważny PCA.)

Rysowanie projekcji PCA obok siebie z reprezentacją wąskiego gardła

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

wprowadź opis zdjęcia tutaj

Rekonstrukcje

A teraz spójrzmy na rekonstrukcje (pierwszy rząd - oryginalne obrazy, drugi rząd - PCA, trzeci rząd - 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()

wprowadź opis zdjęcia tutaj

Można uzyskać znacznie lepsze wyniki dzięki głębszej sieci, pewnej regularyzacji i dłuższemu szkoleniu. Eksperyment. Głębokie uczenie się jest łatwe!

ameba mówi Przywróć Monikę
źródło
2
Jestem zaskoczony, jak dobrze PCA działa tylko z 2 komponentami! dzięki za opublikowanie kodu
Aksakal
2
Fantastyczny! Głupota!
Matthew Drury
2
@shadi Właściwie znajduję bezpośrednie połączenie z svd () prostsze :)
amoeba mówi Reinstate Monica
1
Różnica wydajności jest jeszcze większa, gdy używa się większej liczby komponentów. Próbowałem 10 zamiast dwóch, a autoencoder był znacznie lepszy. Wadą jest szybkość i zużycie pamięci
Aksakal
1
dla Pythona 2 musisz dodać następujące importyfrom __future__ import absolute_import from __future__ import division from __future__ import print_function
użytkownik2589273
7

Ogromne rekwizyty dla @amoeba za zrobienie tego wspaniałego przykładu. Chcę tylko pokazać, że procedura szkolenia i rekonstrukcji automatycznego kodera opisana w tym poście może być wykonana również w R z podobną łatwością. Automatyczny koder poniżej jest skonfigurowany, więc emuluje przykład ameby tak blisko, jak to możliwe - ten sam optymalizator i ogólna architektura. Dokładne koszty nie są odtwarzalne, ponieważ zaplecze TensorFlow nie jest rozstawione w podobny sposób.

Inicjalizacja

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

Rysowanie projekcji PCA obok siebie z reprezentacją wąskiego gardła

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 )

wprowadź opis zdjęcia tutaj

Rekonstrukcje

Możemy odtworzyć cyfry w zwykły sposób. (Górny rząd to oryginalne cyfry, środkowy rząd to rekonstrukcje PCA, a dolny rząd to rekonstrukcje autoencodera.)

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

wprowadź opis zdjęcia tutaj

Jak wspomniano, więcej epok i głębsza i / lub bardziej inteligentnie wyszkolona sieć da znacznie lepsze wyniki. Na przykład błąd rekonstrukcji PCA wynoszący = 9 wynosi około , możemy uzyskać prawie taki sam błąd ( ) z opisanego powyżej autoencodera, po prostu poprzez zwiększenie epok treningowych z 5 do 25. W tym przypadku użycia 2 komponenty pochodzące z autoencodera zapewnią podobny błąd rekonstrukcji jak 9 głównych komponentów. Fajne!0.0356 0.0359k0.03560.0359

usεr11852 mówi Reinstate Monic
źródło
2
+1. Miły. Dobrze jest zauważyć, że korzystanie z Keras w R jest tak proste, jak w Pythonie. O ile widzę, w społeczności zajmującej się głębokim uczeniem się wszyscy obecnie używają Pythona, więc miałem wrażenie, że gdzie indziej powinno być trudniej.
ameba mówi Przywróć Monikę
2

Oto mój notatnik jupyter, w którym próbuję powtórzyć twój wynik, z następującymi różnicami:

  • zamiast bezpośrednio korzystać z tensorflow, używam go do przeglądania kamer
  • nieszczelny relu zamiast relu, aby uniknąć nasycenia (tzn. zakodowane wyjście wynosi 0)
    • może to być przyczyną słabej wydajności AE
  • dane wejściowe autoencodera są skalowane do [0,1]
    • Myślę, że gdzieś czytałem, że autoencodery z relu najlepiej działają z danymi [0-1]
    • uruchomienie mojego notebooka z wejściem autoencoderów będącym średnią = 0, std = 1 dało MSE dla AE> 0,7 dla wszystkich redukcji wymiarów, więc może to jest jeden z twoich problemów
  • Dane wejściowe PCA są nadal przechowywane jako dane ze średnią = 0 i std = 1
    • Może to również oznaczać, że wynik MSE PCA nie jest porównywalny z wynikiem MSE PCA
    • Może po prostu ponownie uruchomię to później z danymi [0-1] zarówno dla PCA, jak i AE
  • Wejście PCA jest również skalowane do [0-1]. PCA działa również z danymi (średnia = 0, std = 1), ale MSE byłoby nieporównywalne z AE

Moje wyniki MSE dla PCA ze zmniejszenia wymiarów od 1 do 6 (gdzie wejście ma 6 kolumn) i dla AE z dim. czerwony. od 1 do 6 są poniżej:

Przy wejściu PCA będącym (średnia = 0, std = 1), podczas gdy wejście AE jest w zakresie [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

Liniowy PCA bez redukcji wymiarów może osiągnąć 9e-15, ponieważ może po prostu wepchnąć wszystko, czego nie był w stanie zmieścić w ostatnim elemencie.

Shadi
źródło
shadi, twój notebook importuje pakiet utils, który wydaje się mieć wiele niestandardowych funkcji, np. utils.buildNetwork i utils.ae_fit_encode_plot_mse na przykład ...
Berowne Hlavaty
To tylko plik w tym samym repozytorium na tym samym poziomie co notatnik.
shadi