如何创建一个自动编码器,其中编码器的每一层都应与解码器的一层表示相同

How to create an autoencoder where each layer of encoder should represent the same as a layer of the decoder

我想构建一个自动编码器,其中编码器中的每一层都与解码器中的相应层具有相同的含义。因此,如果自动编码器经过完美训练,这些层的值应该大致相同。

假设自动编码器由 e1 -> e2 -> e3 -> d2 -> d1 组成,而 e1 是输入,d1 是输出。一个普通的自动编码器训练在 d1 中具有与 e1 相同的结果,但我想要额外的约束,即 e2 和 d2 是相同的。因此,我想要一条从 d2 到 e2 的附加反向传播路径,并与从 d1 到 e1 的正常路径同时训练。 (d代表解码器,e代表编码器)

我尝试将 e2 和 d2 之间的误差用作来自此 link https://github.com/keras-team/keras/issues/5563 的第一个答案的 CustomRegularization 层的正则化项。我也用它来处理 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 而不是交叉熵和 .01 的 lr。使用该设置的 100 个 epoch 会产生非常好的结果。

编辑 2:我希望反向传播像这张 [图片] (https://imgur.com/OOo757x) 中那样工作。所以某一层loss的反向传播在对应层就停止了。我想我之前没有说清楚,我不知道代码目前是否这样做。

编辑 3:虽然这段代码 运行s 和 returns 是一个很好看的解决方案,但 CustomRegularization 层没有按照我的预期去做,因此它做的事情与描述。

似乎主要问题是使用二进制 cross-entropy 来最小化编码器和解码器之间的差异。如果您 class 化 MNIST 数字,网络中的内部表示不会像输出那样是单个 class 概率。通过这些简单的更改,我能够让您的网络输出一些 reasonable-looking 重建:

  1. CustomRegularization class

  2. 中使用 objectives.mean_squared_error 而不是 objectives.binary_crossentropy
  3. 将纪元数更改为 5

  4. 正在将学习率更改为 .01

更改 2 和 3 只是为了加快测试速度。 Change 1 是这里的关键。交叉熵专为存在二进制 "ground truth" 变量和该变量的估计值的问题而设计。但是,您的网络中间没有二进制真值,只有在输出层。因此,网络中间的交叉熵损失函数没有多大意义(至少对我而言)——它将尝试测量非二进制变量的熵。另一方面,均方误差更通用一些,应该适用于这种情况,因为您只是在最小化两个实数值之间的差异。本质上,网络的中间是在进行回归(两个连续值激活之间的差异,即层),而不是class化,所以它需要一个适合回归的损失函数。

我还想建议可能有更好的方法来完成你想要的。如果你真的希望编码器和解码器完全一样,你可以在它们之间共享权重。然后它们将是相同的,而不仅仅是高度相似,并且您的模型将有更少的参数来训练。如果您好奇的话,Keras here 对共享(绑定)权重自动编码器有一个很好的解释。

阅读你的代码,它看起来确实在做你想要的事情,但我不太确定如何验证这一点。