深度学习算法(第27期)----栈式自编码器
上期我们一起学习了深度学习中的自编码器的相关知识,
深度学习算法(第26期)----深度网络中的自编码器
今天我们更进一步一起学一下栈式自编码器。
栈式自编码器
跟之前我们学过的其他神经网络一样,自编码器也可以有多个隐藏层。这种有多个隐藏层的自编码器被称为栈式自编码器(类似内存中的堆栈),也称为深度自编码器。增加更多的层,有助于自编码器学到更为复杂的编码,然而,需要注意的是不能够让自编码器太过强大,假设一个自编码器太过强大,而学到了将每一个输入映射为一个任意值,而解码的时候反向映射。显然,这样的自编码器能够很完美的重构训练数据,但是并没有学到任何有效的表现数据的方法,泛化能力必然一塌糊涂。
栈式自编码器一般是一个关于中心隐藏层(编码层)对称的结构,或者说像一个三明治。例如对于我们很熟悉的MNIST数据集的一个自编码器应该是什么样子的呢?如下图:
在这个例子中,有784个输入,接着是一个300个神经元的隐藏层,然后中心隐藏层(编码层)是由150个神经元组成,再接着就又是300个神经元,最终输出为784个神经元(跟输入一致)。
tensorflow 实现
我们可以用常规的深度MLP来实现一个栈式自编码器,如之前所学的一样:
深度学习三人行(第3期)----TensorFlow从DNN入手
如下代码创建了一个MNIST的栈式自编码器,其中用He初始化,ELU激活函数等。
n_inputs = 28 * 28 # for MNIST
n_hidden1 = 300
n_hidden2 = 150 # codings
n_hidden3 = n_hidden1
n_outputs = n_inputs
learning_rate = 0.01
l2_reg = 0.001
X = tf.placeholder(tf.float32, shape=[None, n_inputs])
with tf.contrib.framework.arg_scope(
[fully_connected],
activation_fn=tf.nn.elu,
weights_initializer=tf.contrib.layers.variance_scaling_initializer(),
weights_regularizer=tf.contrib.layers.l2_regularizer(l2_reg)):
hidden1 = fully_connected(X, n_hidden1)
hidden2 = fully_connected(hidden1, n_hidden2) # codings
hidden3 = fully_connected(hidden2, n_hidden3)
outputs = fully_connected(hidden3, n_outputs, activation_fn=None)
reconstruction_loss = tf.reduce_mean(tf.square(outputs - X)) # MSE
reg_losses = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
loss = tf.add_n([reconstruction_loss] + reg_losses)
optimizer = tf.train.AdamOptimizer(learning_rate)
training_op = optimizer.minimize(loss)
init = tf.global_variables_initializer()
我们可以按照一般流程训练模型,注意这里并没有用到标签。
n_epochs = 5
batch_size = 150
with tf.Session() as sess:
init.run()
for epoch in range(n_epochs):
n_batches = mnist.train.num_examples // batch_size
for iteration in range(n_batches):
X_batch, y_batch = mnist.train.next_batch(batch_size)
sess.run(training_op, feed_dict={X: X_batch})
权重关联
像我们刚才创建的一样,当一个自编码器是完全对称的时候,为了加速训练和放置过拟合,一个通常的做法就是将编码器的权重和解码器的权重捆绑在一起共用,这样权重的数量就能减半。其实在tensorflow中,用fully_connected()函数实现这个权重捆绑还是有一些麻烦的,一般手动定义这些层还是比较简单的,虽然下面代码看起来有些冗余:
activation = tf.nn.elu
regularizer = tf.contrib.layers.l2_regularizer(l2_reg)
initializer = tf.contrib.layers.variance_scaling_initializer()
X = tf.placeholder(tf.float32, shape=[None, n_inputs])
weights1_init = initializer([n_inputs, n_hidden1])
weights2_init = initializer([n_hidden1, n_hidden2])
weights1 = tf.Variable(weights1_init, dtype=tf.float32, name="weights1")
weights2 = tf.Variable(weights2_init, dtype=tf.float32, name="weights2")
weights3 = tf.transpose(weights2, name="weights3") # tied weights
weights4 = tf.transpose(weights1, name="weights4") # tied weights
biases1 = tf.Variable(tf.zeros(n_hidden1), name="biases1")
biases2 = tf.Variable(tf.zeros(n_hidden2), name="biases2")
biases3 = tf.Variable(tf.zeros(n_hidden3), name="biases3")
biases4 = tf.Variable(tf.zeros(n_outputs), name="biases4")
hidden1 = activation(tf.matmul(X, weights1) + biases1)
hidden2 = activation(tf.matmul(hidden1, weights2) + biases2)
hidden3 = activation(tf.matmul(hidden2, weights3) + biases3)
outputs = tf.matmul(hidden3, weights4) + biases4
reconstruction_loss = tf.reduce_mean(tf.square(outputs - X))
reg_loss = regularizer(weights1) + regularizer(weights2)
loss = reconstruction_loss + reg_loss
optimizer = tf.train.AdamOptimizer(learning_rate)
training_op = optimizer.minimize(loss)
init = tf.global_variables_initializer()
这段代码是比较直接的实现了权重捆绑,但是有几点还是需要注意的:
权重3和权重4并不是新的变量,而是权重2和权重1的转置。
因为不是新的变量,所以,没办法对他们进行正则化,所以这里只对权重1和2进行正则化。
偏置没有捆绑,也不需要正则化。
好了,至此,今天我们简单学习了自编码器中栈式自编码器的相关知识,希望有些收获,下期我们将更深一步的学习如何训练一个深度自编码器的相关知识,欢迎留言或进社区共同交流,喜欢的话,就点个“在看”吧,您也可以置顶公众号,第一时间接收最新内容。