稀疏自动编码器
另一种会导致良好特征提取的约束是稀疏性:通过在成本函数中添加适当的函数项,强迫自动编码器减少编码层中活动神经元的数量。例如,可以强迫其在编码层中平均仅有5%的显著活动神经元。这迫使自动编码器将每个输入表示为少量活动神经元的组合。结果,编码层中的每个神经元最终会代表一个有用的特征
一种简单的方式是在编码层中使用sigmoid激活函数(将编码限制为0到1之间的值),使用较大的编码层(例如有300个神经元),并向编码层的激活函数添加一些\(\ell_1\)正则化:
from tensorflow import keras
sparse_l1_encoder = keras.models.Sequential([
keras.layers.Flatten(input_shape=[28, 28]),
keras.layers.Dense(100, activation='gelu'),
keras.layers.Dense(300, activation='sigmoid'),
keras.layers.ActivityRegularization(l1=1e-3)
])
sparse_l1_decoder = keras.models.Sequential([
keras.layers.Dense(100, activation='gelu', input_shape=[300]),
keras.layers.Dense(28 * 28, activation='sigmoid'),
keras.layers.Reshape([28, 28])
])
sparse_l1_ae = keras.models.Sequential([sparse_l1_encoder, sparse_l1_decoder])
此ActivityRegularization层只返回其输入,但作为副作用,它添加了等于其输入的绝对值之和的训练损失(该层仅在训练期间起作用)。同样,可以删除ActivityRegularization层,并在上一层中设置activity_regularizer=keras.regularizers.l1(1e-3)。这个惩罚会鼓励神经网络产生接近于0的编码,但是如果它不能正常地重构输入,由于也会收到惩罚,因此它不得不输出至少一些非零值。使用\(\ell_1\)而不是\(\ell_2\)规范会迫使神经网络保留最重要的编码,同时消除输入图像不需要的编码(而不仅仅是减少所有编码)
经常会产生更好结果的另一种方法是在每次训练迭代时测量编码层的实际稀疏度,并在测量的稀疏度与目标稀疏度不同时对模型进行惩罚。通过在整个训练批次中计算编码层中每个神经元的平均激活来实现。批次大小不能太小,否则平均会不准确
一旦获得了每个神经元的平均激活,便希望通过向成本函数添加稀疏惩罚来惩罚过于活跃或不够活跃的神经元。例如,如果测量一个神经元的平均激活为0.3,但目标稀疏度为0.1,则必须对其进行惩罚来降低激活。一种方法是简单地将平方误差\((0.3-0.1)^2\)加到成本函数中,但在实践中,更好的方法是使用Kullback-Leibler(KL)散度,它的梯度要比均方误差大得多
给定两个离散的概率分布\(P\)和\(Q\),可以使用下面公式计算这些分布之间的\(KL\)散度,记为\(D_{KL}(P||Q)\)
\[D_{KL}(P||Q)=\sum_iP(i)\log\frac{P(i)}{Q(i)} \]
要测量编码层中一个神经元要激活的目标概率\(p\)和实际概率\(q\)(即训练批次的平均激活)之间的散度。可以简化为
\[D_{KL}(p||q)=p\log\frac pq+(1-p)\log\frac{1-p}{1-q} \]
一旦计算了编码层中每个神经元的稀疏损失,就把这些损失相加并将结果加到成本函数中。为了控制稀疏损失和重建损失的相对重要性,可以将稀疏损失乘以稀疏权重超参数。如果此权重过高,则模型会接近目标稀疏度,但可能无法正确重构输入,不会学习任何特征
创建一个自定义正则化来应用\(KL\)散度正则化:
K = keras.backend
k1_divergence = keras.losses.kullback_leibler_divergence
class KLDivergenceRegularizer(keras.regularizers.Regularizer):
def __init__(self, weight, target=0.1):
self.weight = weight
self.target = target
def __call__(self, inputs):
mean_activities = K.mean(inputs, axis=0)
return self.weight * (
k1_divergence(self.target, mean_activities) + k1_divergence(1. - self.target, 1. - mean_activities)
)
现在可以构建这个稀疏自动编码器,使用KLDivergenceRegularizer来进行编码层的激活
fashion_mnist = keras.datasets.fashion_mnist
(X_train_all, y_train_all), (X_test, y_test) = fashion_mnist.load_data()
X_valid, X_train = X_train_all[:5000] / 255., X_train_all[5000:] / 255.
y_valid, y_train = y_train_all[:5000], y_train_all[5000:]
kld_reg = KLDivergenceRegularizer(weight=0.05, target=0.1)
sparse_kl_encoder = keras.models.Sequential([
keras.layers.Flatten(input_shape=[28, 28]),
keras.layers.Dense(100, activation='gelu'),
keras.layers.Dense(300, activation='sigmoid', activity_regularizer=kld_reg)
])
sparse_kl_decoder = keras.models.Sequential([
keras.layers.Dense(100, activation='gelu', input_shape=[300]),
keras.layers.Dense(28 * 28, activation='sigmoid'),
keras.layers.Reshape([28, 28])
])
sparse_kl_ae = keras.models.Sequential([sparse_kl_encoder, sparse_kl_decoder])
sparse_kl_ae.compile(loss='binary_crossentropy', optimizer=keras.optimizers.Adam())
history = sparse_kl_ae.fit(X_train, X_train, validation_data=(X_valid, X_valid), batch_size=32, epochs=10)
Epoch 1/10
1719/1719 [==============================] - 7s 4ms/step - loss: 0.3357 - val_loss: 0.3010
Epoch 2/10
1719/1719 [==============================] - 6s 3ms/step - loss: 0.2970 - val_loss: 0.2893
Epoch 3/10
1719/1719 [==============================] - 6s 3ms/step - loss: 0.2879 - val_loss: 0.2818
Epoch 4/10
1719/1719 [==============================] - 6s 4ms/step - loss: 0.2827 - val_loss: 0.2779
Epoch 5/10
1719/1719 [==============================] - 6s 4ms/step - loss: 0.2794 - val_loss: 0.2755
Epoch 6/10
1719/1719 [==============================] - 6s 4ms/step - loss: 0.2772 - val_loss: 0.2736
Epoch 7/10
1719/1719 [==============================] - 6s 3ms/step - loss: 0.2755 - val_loss: 0.2721
Epoch 8/10
1719/1719 [==============================] - 6s 4ms/step - loss: 0.2741 - val_loss: 0.2710
Epoch 9/10
1719/1719 [==============================] - 6s 4ms/step - loss: 0.2731 - val_loss: 0.2702
Epoch 10/10
1719/1719 [==============================] - 6s 4ms/step - loss: 0.2721 - val_loss: 0.2691
可视化重构
import matplotlib.pyplot as plt
def plot_image(image):
plt.imshow(image, cmap='binary')
plt.axis('off')
def show_reconstructions(model, n_images=5):
reconstructions = model.predict(X_valid[:n_images])
fig = plt.figure(figsize=(n_images * 1.5, 3))
for image_index in range(n_images):
plt.subplot(2, n_images, 1 + image_index)
plot_image(X_valid[image_index])
plt.subplot(2, n_images, 1 + n_images + image_index)
plot_image(reconstructions[image_index])
show_reconstructions(sparse_kl_ae)