Создание автоэнкодера в Tensorflow, чтобы превзойти PCA

31

Хинтон и Салахутдинов в статье « Сокращение размерности данных с помощью нейронных сетей», Science 2006, предложили нелинейный PCA за счет использования глубокого автоэнкодера. Я несколько раз пытался построить и обучить автоэнкодер PCA с Tensorflow, но мне никогда не удавалось получить лучший результат, чем линейный PCA.

Как эффективно обучить автоэнкодеру?

(Позднее редактирование @amoeba: оригинальная версия этого вопроса содержала код Python Tensorflow, который работал некорректно. Его можно найти в истории редактирования.)

Donbeo
источник
Я обнаружил ошибку в функции активации класса Layer. Я проверяю, работает ли он сейчас
Donbeo
вы исправили ошибку?
Буратино
Привет, Донбео. Я взял на себя смелость удалить код из вашего вопроса (код все еще можно легко найти в истории редактирования). С кодом ваш вопрос выглядел немного как вопрос типа «Помоги мне найти ошибку», который здесь не по теме. В то же время, эта тема имеет 4 тыс. Просмотров, что, вероятно, означает, что многие люди приходят сюда через поиск в Google, поэтому я не хотел закрывать ваш вопрос. Я решил опубликовать ответ с пошаговым описанием автоэнкодера, но из соображений простоты я использовал Keras (работающий поверх Tensorflow) вместо необработанного Tensorflow. Как вы думаете, это отвечает на ваши вопросы?
говорит амеба, восстанови Монику

Ответы:

42

Вот ключевая фигура из научной статьи 2006 года Хинтона и Салахутдинова:

Он показывает уменьшение размерности набора данных MNIST ( черно-белых изображений из одной цифры) с исходных 784 измерений до двух.28×28

Давайте попробуем воспроизвести это. Я не буду использовать Tensorflow напрямую, потому что гораздо проще использовать Keras (высокоуровневую библиотеку, работающую поверх Tensorflow) для простых задач глубокого обучения, подобных этой. H & S использовал архитектуру от с логистическими единицами, предварительно обученными стеку машин с ограниченным числом Больцмана. Десять лет спустя, это звучит очень старой школы. Я буду использовать более простую архитектуру от с экспоненциальными линейными единицами без предварительной подготовки. Я буду использовать оптимизатор Адама (конкретная реализация адаптивного стохастического градиентного спуска с импульсом).784 512 128 2 128 512 784

784100050025022505001000784
7845121282128512784

Код скопирован из блокнота Jupyter. В Python 3.6 вам нужно установить matplotlib (для pylab), NumPy, seaborn, TensorFlow и Keras. При запуске в оболочке Python вам может потребоваться добавить, plt.show()чтобы показать графики.

инициализация

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

Это выводит:

PCA reconstruction error with 2 PCs: 0.056

Обучение автоэнкодеру

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

Это занимает ~ 35 секунд на моем рабочем столе и выводит:

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

так что вы уже можете видеть, что мы превзошли потери PCA только после двух тренировочных эпох.

(Кстати, activation='linear'полезно изменить все функции активации и наблюдать, как потеря сходится точно с потерей PCA. Это потому, что линейный автоэнкодер эквивалентен PCA.)

Построение проекции PCA бок о бок с представлением узкого места

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

введите описание изображения здесь

Перестройки

А теперь давайте посмотрим на реконструкции (первый ряд - исходные изображения, второй ряд - PCA, третий ряд - автоэнкодер):

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

введите описание изображения здесь

Можно получить гораздо лучшие результаты с более глубокой сетью, некоторой регуляризацией и более длительным обучением. Эксперимент. Глубокое обучение легко!

амеба говорит восстановить монику
источник
2
Я удивлен, насколько хорошо PCA работал только с 2 компонентами! спасибо за размещение кода
Аксакал
2
Фантастические! Stupendousness!
Мэтью Друри
2
@shadi Я на самом деле нахожу прямой вызов svd () проще :)
амеба говорит восстановить Monica
1
Разница в производительности еще больше при использовании большего количества компонентов. Я пробовал 10 вместо двух, и автоэнкодер был намного лучше. Недостатком является скорость и потребление памяти
Аксакал
1
для Python 2 вам нужно добавить следующий импортfrom __future__ import absolute_import from __future__ import division from __future__ import print_function
user2589273
7

Огромные реквизиты @amoeba за этот замечательный пример. Я просто хочу показать, что процедура обучения и реконструкции авто-кодировщика, описанная в этом посте, может быть выполнена также в R с такой же легкостью. Приведенный ниже автокодер настроен таким образом, чтобы он подражал примеру amoeba как можно ближе - тот же оптимизатор и общая архитектура. Точные затраты не воспроизводимы из-за того, что серверная часть TensorFlow не засеяна подобным образом.

Инициализация

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

автоассоциатор

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

Построение проекции PCA бок о бок с представлением узкого места

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 )

введите описание изображения здесь

Перестройки

Мы можем сделать восстановление цифр обычным способом. (В верхнем ряду находятся исходные цифры, в среднем ряду реконструкции PCA, а в нижнем ряду реконструкции автоэнкодера.)

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

введите описание изображения здесь

Как уже отмечалось, большее количество эпох и более глубокая и / или более умно обученная сеть дадут гораздо лучшие результаты. Например, ошибка восстановления PCA при = 9 составляет приблизительно , мы можем получить почти такую ​​же ошибку ( ) из автоэнкодера, описанного выше, просто увеличив период обучения с 5 до 25. В этом сценарии использования 2 Компоненты, полученные из автоэнкодера, будут иметь такую ​​же ошибку восстановления, как и 9 основных компонентов. Круто!0,0356 0,0359k0.03560.0359

usεr11852 говорит восстановить Monic
источник
2
+1. Ницца. Приятно видеть, что использовать Keras в R так же просто, как и в Python. Насколько я понимаю, в сообществе глубокого обучения все используют Python в наши дни, поэтому у меня сложилось впечатление, что в других местах это должно быть сложнее.
говорит амеба: восстанови Монику
2

Вот моя записная книжка Jupyter, где я пытаюсь повторить ваш результат со следующими отличиями:

  • вместо того, чтобы напрямую использовать тензорный поток, я использую его
  • рэяйское реле с утечкой вместо рэла, чтобы избежать насыщения (т. е. закодированный вывод равен 0)
    • это может быть причиной плохой работы АЭ
  • ввод автоэнкодера масштабируется до [0,1]
    • Я думаю, что где-то читал, что автоэнкодеры с relu лучше всего работают с данными [0-1]
    • при запуске моего ноутбука со средним значением ввода автоэнкодеров = 0, стандартное значение = 1 давало MSE для AE> 0,7 для всех сокращений размерности, так что, возможно, это одна из ваших проблем
  • Вход PCA сохраняется как данные со средним значением = 0 и стандартным значением = 1
    • Это также может означать, что результат MSE PCA не сопоставим с результатом MSE PCA
    • Может быть, я просто еще раз выполню это позже с данными [0-1] для PCA и AE
  • Вход PCA также масштабируется до [0-1]. PCA также работает с (среднее = 0, стандартное = 1) данными, но MSE будет несопоставимым с AE

Мой MSE приводит к PCA от уменьшения размерности с 1 до 6 (где вход имеет 6 столбцов) и для AE от dim. красный. от 1 до 6 ниже:

С входом PCA (среднее = 0, стандартное = 1), в то время как вход AE находится в диапазоне [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

Линейный PCA без уменьшения размерности может достигать 9e-15, потому что он может просто вытолкнуть все, что ему не удалось вписать в последний компонент.

Шади
источник
Шади, твоя тетрадь импортирует пакет утилит, который, кажется, имеет много нестандартных функций, например, utils.buildNetwork и utils.ae_fit_encode_plot_mse ...
Бероун Хлавати,
Это просто файл в том же хранилище на том же уровне, что и ноутбук.
Шади