深度学习算法(第30期)----降噪自编码器和稀疏自编码器及其实现

2019-10-14 15:29:16 浏览数 (1)

上期我们一起学习了深度学习中的可视化自编码器和无监督预训练的相关知识,

深度学习算法(第29期)----可视化自编码器和无监督预训练 今天我们一起学一下降噪自编码器和稀疏自编码器方面的知识。

降噪自编码器

一般情况下,为了强制自编码器去学习一些有用的特征,往往会对输入数据进行增加一些噪声,然后训练它去恢复原始没有噪声的数据。这就防止了自编码器耍小聪明,去复制输入到输出。因此最终会寻找到输入数据的特征模式。

自20世纪80年代以来,使用自编码器消除噪声的想法已经出现(例如,在 Yann LeCun的1987年硕士论文中提到过)。在2008年的一篇论文中,Pascal Vincent 表明自编码器也可用于特征提取。在2010年的一篇文章中Vincent等人引入栈式降噪自编码器。

噪声可以是纯粹的高斯噪声添加到输入,或者它可以随机关闭输入,就像之前学的dropout, 深度学习算法(第7期)----深度学习之避免过拟合(正则化)

如下图:

其中左侧的是对原始数据增加高斯噪声,右边的是利用Dropout方法实现的降噪自编码器。

降噪自编码器的TensorFlow实现

在tensorflow中实现降噪自编码器并不难,首先加入高斯噪声,其他的就像训练一个常规的自编码器一样,而且重构损失是基于原始输入上的,代码如下:

代码语言:javascript复制
X = tf.placeholder(tf.float32, shape=[None, n_inputs])
X_noisy = X   tf.random_normal(tf.shape(X))
[...]
hidden1 = activation(tf.matmul(X_noisy, weights1)   biases1)
[...]
reconstruction_loss = tf.reduce_mean(tf.square(outputs - X)) # MSE
[...]

而Dropout版本,更为简单,不多歪歪,代码如下:

代码语言:javascript复制
from tensorflow.contrib.layers import dropout

keep_prob = 0.7

is_training = tf.placeholder_with_default(False, shape=(), name='is_training')
X = tf.placeholder(tf.float32, shape=[None, n_inputs])
X_drop = dropout(X, keep_prob, is_training=is_training)
[...]
hidden1 = activation(tf.matmul(X_drop, weights1)   biases1)
[...]
reconstruction_loss = tf.reduce_mean(tf.square(outputs - X)) # MSE
[...]

需要注意的是,正如之前学过的,在训练的时候,需要设置is_training为True.

代码语言:javascript复制
sess.run(training_op, feed_dict={X: X_batch, is_training: True})
稀疏自编码器

往往提取好的特征的另外一种约束就是稀疏性,通过在损失函数中添加一个合适的项,使得自编码器努力去减少编码层中活跃的神经元。例如,它可以使得编码层平均只有5%的活跃神经元,这就迫使自编码器去将每个输入表示为少量激活的组合。结果,编码层中的每个神经元通常都会代表一个有用的特征(如果您每个月只能说几个字,您肯定会字字千金)。

为了支持稀疏模型,我们首先必须在每次训练迭代中计算编码层的实际稀疏度。 我们通过计算整个训练batch中,编码层中的每个神经元的平均激活情况来实现。 这里的训练batch不能太小,否则平均数不准确。

一旦我们有了每个神经元的平均激活情况,我们希望通过向损失函数添加稀疏损失来惩罚太活跃的神经元。例如,如果我们计算一个神经元的平均激活值为 0.3,但目标稀疏度为0.1,那么它必须受到惩罚才能降低神经元的活跃度。一种方法可以简单地将平方误差(0.3-0.1)^2添加到损失函数中,但实际上更好的方法是使用Kullback-Leibler散度,其具有比均方误差更强的梯度,如下图所示:

假如给定两个离散概率分布P和Q,那么这两个分布之间的KL散度计算如下:

在我们的这个情况下,我们想要计算编码层中的神经元将要激活的目标概率p与实际概率q(即训练batch上的平均激活)之间的散度,那么上面公式则简化为:

一旦我们计算了编码层中每一个神经元的稀疏损失,我们就可以把它们累加起来添加到损失函数中了。为了控制稀疏损失和重构损失的相对重要性,我们可以用稀疏权重这个超参数乘以稀疏损失。如果这个权重太高,模型会紧贴目标稀疏度,但它可能无法正确重建输入,导致模型无用。相反,如果它太低,模型将大多忽略稀疏目标,进而不会学习任何有趣的功能。

稀疏自编码器的TensorFlow实现

介绍完了稀疏自编码器,我们一起看一下,在tensorflow中,它是如何实现的:

代码语言:javascript复制
def kl_divergence(p, q):
    return p * tf.log(p / q)   (1 - p) * tf.log((1 - p) / (1 - q))

learning_rate = 0.01
sparsity_target = 0.1
sparsity_weight = 0.2

[...] # Build a normal autoencoder (in this example the coding layer is hidden1)

optimizer = tf.train.AdamOptimizer(learning_rate)

hidden1_mean = tf.reduce_mean(hidden1, axis=0) # batch mean
sparsity_loss = tf.reduce_sum(kl_divergence(sparsity_target, hidden1_mean))
reconstruction_loss = tf.reduce_mean(tf.square(outputs - X)) # MSE
loss = reconstruction_loss   sparsity_weight * sparsity_loss
training_op = optimizer.minimize(loss)

需要注意的是,编码层的活跃度必须在0-1之间(不能为0或者1),否则的话,KL散度将为NaN(一个非数字值)。一个简单的解决方法就是在编码层的激活函数用sigmoid函数,如下:

代码语言:javascript复制
hidden1 = tf.nn.sigmoid(tf.matmul(X, weights1)   biases1)

这里我们可以用一个小技巧来加快模型的收敛,可以将重构损失设置为具有较大梯度的交叉熵来代替MSE。用交叉熵的话,就需要将输入归一化到0到1之间,并在输出层用sigmoid函数作为激活函数,这样就能保证在输出值也是在0到1之间。tensorflow中sigmoid_cross_entropy_with_logits()函数在输出层应用了sigmoid激活函数:

代码语言:javascript复制
[...]
logits = tf.matmul(hidden1, weights2)   biases2)
    outputs = tf.nn.sigmoid(logits)

reconstruction_loss = tf.reduce_sum(
    tf.nn.sigmoid_cross_entropy_with_logits(labels=X, logits=logits))

需要注意的是,outputs运算操作在实际训练中并不需要,这里我们只是想看一下重构过程。

0 人点赞