Как создать автоэнкодер, где каждый слой кодера должен представлять то же самое, что и слой декодера - PullRequest
2 голосов
/ 10 мая 2019

Я хочу построить автоэнкодер, в котором каждый слой в кодере имеет то же значение, что и соответствующий слой в декодере. Поэтому, если автоэнкодер отлично обучен, значения этих слоев должны быть примерно одинаковыми.

Итак, предположим, что автоэнкодер состоит из e1 -> e2 -> e3 -> d2 -> d1, тогда как e1 - вход, а d1 - выход. Нормальный автоэнкодер тренируется, чтобы иметь тот же результат в d1, что и e1, но я хочу дополнительное ограничение, чтобы e2 и d2 были одинаковыми. Поэтому я хочу дополнительный путь обратного распространения, который ведет от d2 до e2 и проходит в то же время, что и обычный путь от d1 до e1. (d обозначает декодер, e обозначает кодер).

Я пытался использовать ошибку между e2 и d2 в качестве условия регуляризации со слоем CustomRegularization из первого ответа по этой ссылке https://github.com/keras-team/keras/issues/5563. Я также использую это для ошибки между e1 и d1 вместо обычного пути .

Следующий код написан так, что можно обрабатывать более 1 промежуточного уровня, а также использовать 4 слоя. В прокомментированном коде есть обычный автоэнкодер, который распространяется только от начала до конца.

from keras.layers import Dense
import numpy as np
from keras.datasets import mnist
from keras.models import Model
from keras.engine.topology import Layer
from keras import objectives
from keras.layers import Input
import keras
import matplotlib.pyplot as plt


#A layer which can be given as an output to force a regularization term between two layers
class CustomRegularization(Layer):
    def __init__(self, **kwargs):
        super(CustomRegularization, self).__init__(**kwargs)

    def call(self, x, mask=None):
        ld=x[0]
        rd=x[1]
        bce = objectives.binary_crossentropy(ld, rd)
        loss2 = keras.backend.sum(bce)
        self.add_loss(loss2, x)
        return bce

    def get_output_shape_for(self, input_shape):
        return (input_shape[0][0],1)


def zero_loss(y_true, y_pred):
    return keras.backend.zeros_like(y_pred)

#Create regularization layer between two corresponding layers of encoder and decoder
def buildUpDownRegularization(layerNo, input, up_layers, down_layers):
    for i in range(0, layerNo):
        input = up_layers[i](input)
    start = input
    for i in range(layerNo, len(up_layers)):
        input = up_layers[i](input)

    for j in range(0, len(down_layers) - layerNo):
        input = down_layers[j](input)
    end = input
    cr = CustomRegularization()([start, end])
    return cr


# Define shape of the network, layers, some hyperparameters and training data
sizes = [784, 400, 200, 100, 50]
up_layers = []
down_layers = []
for i in range(1, len(sizes)):
    layer = Dense(units=sizes[i], activation='sigmoid', input_dim=sizes[i-1])
    up_layers.append(layer)
for i in range(len(sizes)-2, -1, -1):
    layer = Dense(units=sizes[i], activation='sigmoid', input_dim=sizes[i+1])
    down_layers.append(layer)

batch_size = 128
num_classes = 10
epochs = 100
(x_train, y_train), (x_test, y_test) = mnist.load_data()

x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
x_train = x_train.reshape([x_train.shape[0], 28*28])
x_test = x_test.reshape([x_test.shape[0], 28*28])


y_train = x_train
y_test = x_test

optimizer = keras.optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=None, decay=0.0, amsgrad=False)



"""
### Normal autoencoder like in base mnist example
model = keras.models.Sequential()
for layer in up_layers:
    model.add(layer)
for layer in down_layers:
    model.add(layer)

model.compile(optimizer=optimizer, loss=keras.backend.binary_crossentropy)
model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs)

score = model.evaluate(x_test, y_test, verbose=0)
#print('Test loss:', score[0])
#print('Test accuracy:', score[1])


decoded_imgs = model.predict(x_test)


n = 10  # how many digits we will display
plt.figure(figsize=(20, 4))
for i in range(n):
    # display original
    ax = plt.subplot(2, n, i + 1)
    plt.imshow(x_test[i].reshape(28, 28))
    plt.gray()
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)

    # display reconstruction
    ax = plt.subplot(2, n, i + 1 + n)
    plt.imshow(decoded_imgs[i].reshape(28, 28))
    plt.gray()
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
plt.show()

"""

### My autoencoder where each subpart is also an autoencoder

#This part is only because the model needs a path from start to end, contentwise this should do nothing
output = input = Input(shape=(sizes[0],))
for i in range(0, len(up_layers)):
    output = up_layers[i](output)
for i in range(0, len(down_layers)):
    output = down_layers[i](output)
crs = [output]
losses = [zero_loss]

#Build the regularization layer
for i in range(len(up_layers)):
    crs.append(buildUpDownRegularization(i, input, up_layers, down_layers))
    losses.append(zero_loss)


#Create and train model with adapted training data
network = Model([input], crs)
optimizer = keras.optimizers.Adam(lr=0.0001, beta_1=0.9, beta_2=0.999, epsilon=None, decay=0.0, amsgrad=False)
network.compile(loss=losses, optimizer=optimizer)

dummy_train = np.zeros([y_train.shape[0], 1])
dummy_test = np.zeros([y_test.shape[0], 1])

training_data = [y_train]
test_data = [y_test]

for i in range(len(network.outputs)-1):
    training_data.append(dummy_train)
    test_data.append(dummy_test)


network.fit(x_train, training_data, batch_size=batch_size, epochs=epochs,verbose=1, validation_data=(x_test, test_data))
score = network.evaluate(x_test, test_data, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])

decoded_imgs = network.predict(x_test)


n = 10  # how many digits we will display
plt.figure(figsize=(20, 4))
for i in range(n):
    # display original
    ax = plt.subplot(2, n, i + 1)
    plt.imshow(x_test[i].reshape(28, 28))
    plt.gray()
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)

    # display reconstruction
    ax = plt.subplot(2, n, i + 1 + n)
    plt.imshow(decoded_imgs[0][i].reshape(28, 28))
    plt.gray()
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
plt.show()

Если вы запустите код как есть, это покажет, что в моем коде больше нет возможности воспроизведения. Я ожидаю, что поведение будет похоже на незакомментированный код, который показывает обычный автоэнкодер.

Редактировать: Как уже упоминалось в ответах, это хорошо работает с MSE вместо кроссентропии и lr 0,01. 100 эпох с такими настройками дают действительно хорошие результаты.

Редактировать 2: Мне бы хотелось, чтобы обратное распространение работало так, как на этом [изображении] (https://imgur.com/OOo757x).). Поэтому обратное распространение потери определенного слоя останавливается на соответствующем слое. Я думаю, что я этого не сделал очистить раньше, и я не знаю, если код в настоящее время делает это.

Редактировать 3: Хотя этот код запускается и возвращает хорошее решение, слой CustomRegularization не выполняет то, что я думал, что он будет делать, поэтому он не выполняет те же действия, что и в описании.

1 Ответ

0 голосов
/ 10 мая 2019

Кажется, что основной проблемой является использование двоичной кросс-энтропии, чтобы минимизировать разницу между кодером и декодером.Внутреннее представление в сети не будет единственной вероятностью класса, как могло бы быть на выходе, если бы вы классифицировали цифры MNIST.Я смог заставить вашу сеть выводить некоторые разумные на вид реконструкции с такими простыми изменениями:

  1. Использование objectives.mean_squared_error вместо objectives.binary_crossentropy в CustomRegularization классе

  2. Изменение количества эпох на 5

  3. Изменение скорости обучения на 0,01

Изменения 2 и 3 были простосделано для ускорения тестирования.Изменение 1 является ключом здесь.Кросс-энтропия предназначена для задач, в которых есть бинарная переменная «истинности основания» и оценка этой переменной.Однако у вас нет двоичного значения истинности в середине вашей сети, только на выходном уровне.Таким образом, функция кросс-энтропийной потери в середине сети не имеет большого смысла (по крайней мере, для меня) - она ​​будет пытаться измерить энтропию для переменной, которая не является двоичной.Среднеквадратическая ошибка, с другой стороны, немного более общая и должна работать в этом случае, поскольку вы просто минимизируете разницу между двумя действительными значениями.По сути, середина сети выполняет регрессию (разницу между активациями в двух непрерывных значениях, то есть слоях), а не классификацию, поэтому ей нужна функция потерь, которая подходит для регрессии.

Я также хочу предложитьчто может быть лучший подход для достижения того, что вы хотите.Если вы действительно хотите, чтобы кодер и декодер были в точности одинаковыми, вы можете разделить веса между ними.Тогда они будут идентичны, а не просто очень похожи, и ваша модель будет иметь меньше параметров для обучения.Существует неплохое объяснение общих (связанных) весовых автоэнкодеров с Keras здесь , если вам интересно.

Читая ваш код, кажется, что он делает то, что вы хотите на своей иллюстрации,но я не совсем уверен, как это проверить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...