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

Keras 如何创建一个自动编码器,其中编码器的每一层应与解码器的每一层表示相同,keras,autoencoder,regularized,Keras,Autoencoder,Regularized,我想构建一个自动编码器,其中编码器中的每一层与解码器中的对应层具有相同的含义。因此,如果自动编码器经过完美训练,那么这些层的值应该大致相同 假设自动编码器由e1->e2->e3->d2->d1组成,而e1是输入,d1是输出。一个普通的自动编码器训练在d1和e1中有相同的结果,但我需要额外的约束,即e2和d2是相同的。因此,我需要一个额外的反向传播路径,它从d2到e2,并与从d1到e1的正常路径同时训练。(d代表解码器,e代表编码器) 我尝试使用e2和d2之间的误差作为正则化项,从这个链接的第一个

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

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

我尝试使用e2和d2之间的误差作为正则化项,从这个链接的第一个答案开始使用CustomRegulation层。我还将其用于e1和d1之间的错误,而不是正常路径

编写以下代码时,可以处理1个以上的中间层,还可以使用4个层。 在out注释代码中,是一个普通的自动编码器,它只从开始传播到结束

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为.01。100个具有这种设置的时代产生了非常好的结果

编辑2:我希望反向传播的工作原理与此[图像]()相同。因此,某一层损失的反向传播在相应层停止。我想我以前没有说清楚这一点,我不知道代码目前是否做到了这一点


编辑3:虽然这段代码运行并返回了一个好看的解决方案,但CustomRegulation层没有做我认为它会做的事情,因此它没有做与描述中相同的事情。

似乎主要问题是使用二进制交叉熵来最小化编码器和解码器之间的差异。网络中的内部表示不会像对MNIST数字进行分类时的输出那样是单一类概率。通过这些简单的更改,我可以让您的网络输出一些外观合理的重建:

  • 使用
    目标。均方误差
    代替
    目标。在
    自定义正则化
    类中使用二进制交叉熵

  • 将纪元数更改为5

  • 将学习率更改为0.01

  • 更改2和3只是为了加快测试速度。改变1是这里的关键。交叉熵设计用于存在二元“基本真理”变量和该变量的估计值的问题。但是,在网络中间没有二进制真值,只有在输出层。因此,网络中间的交叉熵损失函数并没有多大意义(至少对我来说)——它将试图测量一个不是二进制的变量的熵。另一方面,均方误差更一般,应该适用于这种情况,因为您只需最小化两个实值之间的差异。本质上,网络的中间部分正在执行回归(两个连续值(即层)中激活的差异),而不是分类,因此需要适合回归的损失函数

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


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

    感谢您的评论,lr为.01和MSE,它工作得很好,尽管也需要lr为.01(10个纪元与.01产生良好结果100个纪元与.001产生不好结果)。你有没有关于在图层上强制执行相同内容的来源,因为我没有发现任何东西,只能通过线性激活函数来理解它。还有,你知道为什么交叉熵不起作用吗?因为我想用交叉熵得到一个解。我还为我的问题添加了一些内容。@FelixUnverzagt我对我的答案进行了一些编辑,试图回答您编辑后的问题。在隐藏层中使用交叉熵有什么特别的原因吗?我不知道有什么方法可以让它工作,但我很好奇你是否试图在隐藏层中获得二进制激活。我不尝试获得二进制表示,而是每个层上的信息浓缩,每个神经元代表一些内容。因此,它类似于分类。它并不是真正的二进制,因为真实值也可能是网络称该图像包含70%的特定内容。比如说一个数字70%看起来像零,20%看起来像八。但是我想,对于使用二进制激活运行这个程序,我将打开另一个问题,因为您的回答涵盖了我问题的标题。