cienanos 发布的文章

稀疏自动编码器

另一种会导致良好特征提取的约束是稀疏性:通过在成本函数中添加适当的函数项,强迫自动编码器减少编码层中活动神经元的数量。例如,可以强迫其在编码层中平均仅有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)

去噪自动编码器

强制自动编码器学习有用特征的另一种方法是向其输入中添加噪声,训练它来恢复原始的无噪声输入。这个想法自1980年代开始就存在(在Yann LeCun 1987年的硕士论文中提到过)。在2008年的论文中,Pascal Vincent等人表明自动编码器也可以用于特征提取。在2010年的论文中,Vincent等人提出了堆叠式去噪自动编码器

噪声可以是添加到输入的纯高斯噪声,也可以是随机关闭的输入

实现很简单,在编码器的输入中附加一个Dropout层(或者使用GaussianNoise层)。Dropout层和GasussianNoise层仅在训练期间处于激活状态

from tensorflow import keras

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:]
dropout_encoder = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28, 28]),
    keras.layers.Dropout(.5),
    keras.layers.Dense(100, activation='gelu'),
    keras.layers.Dense(30, activation='gelu')
])
dropout_decoder = keras.models.Sequential([
    keras.layers.Dense(100, activation='gelu', input_shape=[30]),
    keras.layers.Dense(28 * 28, activation='sigmoid'),
    keras.layers.Reshape([28, 28])
])
dropout_ae = keras.models.Sequential([dropout_encoder, dropout_decoder])
dropout_ae.compile(loss='binary_crossentropy', optimizer=keras.optimizers.Adam())
history = dropout_ae.fit(X_train, X_train, validation_data=(X_valid, X_valid), batch_size=32, epochs=10)
Epoch 1/10
1719/1719 [==============================] - 7s 3ms/step - loss: 0.3260 - val_loss: 0.2988
Epoch 2/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.3027 - val_loss: 0.2921
Epoch 3/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.2982 - val_loss: 0.2891
Epoch 4/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.2957 - val_loss: 0.2867
Epoch 5/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.2940 - val_loss: 0.2854
Epoch 6/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.2928 - val_loss: 0.2842
Epoch 7/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.2918 - val_loss: 0.2831
Epoch 8/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.2911 - val_loss: 0.2825
Epoch 9/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.2905 - val_loss: 0.2818
Epoch 10/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.2899 - val_loss: 0.2815

可视化重构

去噪自动编码器不仅可以用于数据可视化或无监督学习,而且还可以非常简单有效地用于图像中的噪声去除

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(dropout_ae)

循环自动编码器

如果要为序列构建自动编码器,例如时间序列和文本(例如,用于无监督学习或降维),那么递归神经元可能比密集网络更合适。构建循环自动编码器非常简单直接:编码器通常是序列到向量的RNN,它将输入序列压缩为单个向量。解码器是向量到序列RNN,做相反的处理:

from tensorflow import keras

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:]
recurrent_encoder = keras.models.Sequential([
    keras.layers.LSTM(100, return_sequences=True, input_shape=[None, 28]),
    keras.layers.LSTM(30)
])
recurrent_decoder = keras.models.Sequential([
    keras.layers.RepeatVector(28, input_shape=[30]),
    keras.layers.LSTM(100, return_sequences=True),
    keras.layers.TimeDistributed(keras.layers.Dense(28, activation='sigmoid'))
])
recurrent_ae = keras.models.Sequential([recurrent_encoder, recurrent_decoder])
recurrent_ae.compile(loss='binary_crossentropy', optimizer=keras.optimizers.Adam())
history = recurrent_ae.fit(X_train, X_train, epochs=10, validation_data=(X_valid, X_valid), batch_size=32)
Epoch 1/10
1719/1719 [==============================] - 23s 11ms/step - loss: 0.3433 - val_loss: 0.3127
Epoch 2/10
1719/1719 [==============================] - 19s 11ms/step - loss: 0.3047 - val_loss: 0.2962
Epoch 3/10
1719/1719 [==============================] - 19s 11ms/step - loss: 0.2943 - val_loss: 0.2896
Epoch 4/10
1719/1719 [==============================] - 18s 11ms/step - loss: 0.2881 - val_loss: 0.2832
Epoch 5/10
1719/1719 [==============================] - 19s 11ms/step - loss: 0.2841 - val_loss: 0.2795
Epoch 6/10
1719/1719 [==============================] - 19s 11ms/step - loss: 0.2812 - val_loss: 0.2769
Epoch 7/10
1719/1719 [==============================] - 19s 11ms/step - loss: 0.2790 - val_loss: 0.2747
Epoch 8/10
1719/1719 [==============================] - 19s 11ms/step - loss: 0.2773 - val_loss: 0.2733
Epoch 9/10
1719/1719 [==============================] - 19s 11ms/step - loss: 0.2757 - val_loss: 0.2723
Epoch 10/10
1719/1719 [==============================] - 18s 10ms/step - loss: 0.2745 - val_loss: 0.2711

这种循环自动编码器可以处理任何长度的序列,每个时间步长都具有28个维度,这意味着它可以通过把每个图像视为一系列的行来处理Fashion MNIST图像:在每个时间步长,RNN都将处理一个28像素的行,显然可以对任何类型的序列使用循环自动编码器。在这里使用RepeatVector层作为编码器的第一层,以确保其输入向量在每个时间步长都馈送到解码器

可视化重构

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(recurrent_ae)

为了强制自动编码器学习有趣的特征,我们限制了编码层的大小,使其成为不完整自动编码器,实际上,还有许多其它类型的约束可以使用,包括使编码层与输入层一样大甚至更大,从而成为一个完整自动编码器

卷积自动编码器

如果要处理图像,目前为止的自动编码器都无法很好的工作(除非图像非常小),卷积神经网络比密集网络更适合处理图像。如果要为图像构建自动编码器(例如,用于无监督预训练或降维),则需要构建卷积自动编码器。编码器是由卷积层和池化层组成的常规CNN。它通常会减小输入的空间尺寸(即高度和宽度),同时会增加深度(即特征图的数量)。解码器必须进行相反的操作(放大图像并减少其深度到原始尺寸),为此可以使用转置卷积层(或者可以将上采样层与卷积层组合在一起)

以下构建适用于Fashion MNIST的简单卷积自动编码器:

from tensorflow import keras
import tensorflow as tf

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:]
conv_encoder = keras.models.Sequential([
    keras.layers.Reshape([28, 28, 1], input_shape=[28, 28]),
    keras.layers.Conv2D(16, kernel_size=3, padding='same', activation='gelu'),
    keras.layers.MaxPool2D(pool_size=2),
    keras.layers.Conv2D(32, kernel_size=3, padding='same', activation='gelu'),
    keras.layers.MaxPool2D(pool_size=2),
    keras.layers.Conv2D(64, kernel_size=3, padding='same', activation='gelu'),
    keras.layers.MaxPool2D(pool_size=2)
])
conv_decoder = keras.models.Sequential([
    keras.layers.Conv2DTranspose(32, kernel_size=3, strides=2, padding='valid', activation='gelu'),
    keras.layers.Conv2DTranspose(16, kernel_size=3, strides=2, padding='same', activation='gelu'),
    keras.layers.Conv2DTranspose(1, kernel_size=3, strides=2, padding='same', activation='sigmoid'),
    keras.layers.Reshape([28, 28])
])
conv_ae = keras.models.Sequential([conv_encoder, conv_decoder])
conv_ae.compile(loss='binary_crossentropy', optimizer=keras.optimizers.Adam())
history = conv_ae.fit(X_train, X_train, epochs=10, validation_data=(X_valid, X_valid), batch_size=32)
Epoch 1/10
1719/1719 [==============================] - 12s 7ms/step - loss: 0.3013 - val_loss: 0.2745
Epoch 2/10
1719/1719 [==============================] - 11s 7ms/step - loss: 0.2734 - val_loss: 0.2672
Epoch 3/10
1719/1719 [==============================] - 11s 6ms/step - loss: 0.2684 - val_loss: 0.2637
Epoch 4/10
1719/1719 [==============================] - 11s 6ms/step - loss: 0.2655 - val_loss: 0.2614
Epoch 5/10
1719/1719 [==============================] - 11s 6ms/step - loss: 0.2636 - val_loss: 0.2597
Epoch 6/10
1719/1719 [==============================] - 11s 6ms/step - loss: 0.2623 - val_loss: 0.2588
Epoch 7/10
1719/1719 [==============================] - 11s 7ms/step - loss: 0.2613 - val_loss: 0.2577
Epoch 8/10
1719/1719 [==============================] - 12s 7ms/step - loss: 0.2605 - val_loss: 0.2572
Epoch 9/10
1719/1719 [==============================] - 12s 7ms/step - loss: 0.2599 - val_loss: 0.2567
Epoch 10/10
1719/1719 [==============================] - 11s 7ms/step - loss: 0.2593 - val_loss: 0.2563

可视化重构

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(conv_ae)

堆叠式自动编码器

自动编码器可以具有多个隐藏层。在这种情况下,它们被称为堆叠式自动编码器(或深度自动编码器)。添加更多的层有助于自动编码器学习更多的复杂的编码。就是说,要注意不要使自动编码器过于强大。想象一个强大的编码器,它只是学会了把每个输入映射到单个任意数字(而解码器则学习反向映射)。显然这样的自动编码器可以完美地重建训练数据,但是它不会学到任何有用的数据表征(并且不太可能将其很好地泛化到新实例中)

堆叠式自动编码器的架构典型地相对于中间隐藏层(编码层)堆成。

使用Keras实现堆叠式自动编码器

可以实现常规的深度MLP那样来实现堆叠式编码器。特别地,可以使用用于训练深度网络相同的技术。

# 使用SELU激活函数为Fashion MNIST构建了一个堆叠式自动编码器
from tensorflow import keras

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:]
print(X_train_all.shape)
stacked_encoder = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28, 28]),
    keras.layers.Dense(100, activation='gelu'),
    keras.layers.Dense(30, activation='gelu')
])
stacked_decoder = keras.models.Sequential([
    keras.layers.Dense(100, activation='gelu', input_shape=[30]),
    keras.layers.Dense(28 * 28, activation='sigmoid'),
    keras.layers.Reshape([28, 28])
])
stacked_ae = keras.models.Sequential([stacked_encoder, stacked_decoder])
stacked_ae.compile(loss='binary_crossentropy', optimizer=keras.optimizers.Adam())
history = stacked_ae.fit(X_train, X_train, epochs=10, validation_data=(X_valid, X_valid))
(60000, 28, 28)
Epoch 1/10
1719/1719 [==============================] - 7s 3ms/step - loss: 0.3156 - val_loss: 0.2933
Epoch 2/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.2916 - val_loss: 0.2852
Epoch 3/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.2861 - val_loss: 0.2817
Epoch 4/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.2833 - val_loss: 0.2796
Epoch 5/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.2817 - val_loss: 0.2783
Epoch 6/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.2806 - val_loss: 0.2774
Epoch 7/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.2797 - val_loss: 0.2766
Epoch 8/10
1719/1719 [==============================] - 4s 3ms/step - loss: 0.2789 - val_loss: 0.2758
Epoch 9/10
1719/1719 [==============================] - 4s 3ms/step - loss: 0.2783 - val_loss: 0.2753
Epoch 10/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.2777 - val_loss: 0.2746
  • 将自动编码器模型分为两个字模型:编码器和解码器
  • 编码器使用$28\times28$像素的灰度图像,先将它们展平,以便每个图像表示为大小784的向量,然后通过两个尺寸递减的Dense层(100个神经元然后是30个)来处理这些向量,两个都使用GELU激活函数。对于每个输入图像,编码器输出大小为30的向量
  • 解码器使用大小为30的编码(由编码器输出),并通过两个大小递增的Dense层(100个神经元然后784个)来处理它们,并将最终向量重构为$28\times28$的数组,因此解码器的输出具有与编码器输入相同的形状
  • 在编译堆叠式自动编码器时,使用二元交叉熵损失代替均方误差。将重建任务视为多标签二元分类问题:每个像素强度代表像素应为黑色的概率,以这种方法(而不是回归问题)往往会使得模型收敛得更快
  • 最后,使用X_train作为输入和目标来训练函数(类似地,使用X_valid作为验证输入和输出目标)

    可视化重构

    确保对自动编码器进行了恰当训练的一种方法是比较输入和输出:差异不应该很明显。

    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(stacked_ae)

    重构是可识别的,但损失有点大,可能需要训练模型更持久一点,或者使编码器和解码器更深,或者使编码更大。但是,如果使网络过于强大,它就能够在没有学习到数据中任何有用模式的情况下,进行完美的重构

    可视化Fashion MNIST数据集

    现在已经训练了一个堆叠式自动编码器,可以使用它来减少数据集的维度。对于可视化而言,与其他降维算法相比,它并没有给出很好的结果,但是自动编码器的一大优势是它们可以处理具有许多实例和许多特征的大型数据集。因此,一种策略是使用自动编码器将维度降低到合理水平,然后使用另一维降维算法进行可视化。以下使用这种策略来可视化Fashion MNIST
    首先,使用堆叠式自动编码器中的编码器将维度缩小到30,然后使用Scikit-Learn中的t-SNE算法的实现将维度减小到2来进行可视化:

    from sklearn.manifold import TSNE
    
    X_valid_compressed = stacked_encoder.predict(X_valid)
    tsne = TSNE()
    X_valid_2D = tsne.fit_transform(X_valid_compressed)
    plt.scatter(X_valid_2D[:, 0], X_valid_2D[:, 1], c=y_valid, s=10, cmap='tab10')

    t-SNE算法识别出几个与类合理匹配的集群(每个类用不同颜色表示)
    自动编码器可用于降维。另一个应用是无监督学习

    使用堆叠式自动编码器的无监督预训练

    如果要处理一个复杂的有监督任务,但是没有很多标记好的数据,一种解决方案是找到执行类似任务的神经网络并重用其较低层网络。这样就可以使用很少的训练数据来训练一个高性能的模型,因此神经网络不必学习所有的底层特征,它只会重用现有网络学到的特征检测器。
    同样,如果有一个大型数据集,但大多数未被标记,可以先使用所有数据训练一个堆叠的自动编码器,然后重用较低层为实际任务创建神经网络,并使用标记的数据对其进行训练。

实现:适用所有训练数据(标记的和未标记的)来训练自动编码器,然后重用其编码器层来创建一个新的神经网络

绑定权重

当一个自动编码器整齐对称时,一种常见的技术是将解码器层的权重与编码器层的权重绑定起来。这样可以将模型中权重的数量减少一半,从而加快训练速度并降低过拟合的风险。具体来说,如果自动编码器有N层(不算输入层),并且$W_L$表示第$L$层的连接权重,解码器层可以简单定义为:
$$W_{N-L+1} = W_L^T$$
其中,$L=1, 2, \dots, N/2$

# 为了使用Keras绑定各层之间的权重,需要定义一个自定义层
import tensorflow as tf


class DenseTranspose(keras.layers.Layer):
    def __init__(self, dense, activation=None, **kwargs):
        self.dense = dense
        self.activation = keras.activations.get(activation)
        super().__init__(**kwargs)

    def build(self, batch_input_shape):
        self.biases = self.add_weight(name='bias', initializer='zeros', shape=[self.dense.input_shape[-1]])
        super().build(batch_input_shape)

    def call(self, inputs):
        z = tf.matmul(inputs, self.dense.weights[0], transpose_b=True)
        return self.activation(z + self.biases)

这个自定义层的作用类似于常规的Dense层,但是它使用了另一个Dense层的权重,并对它进行了转置(设置transpose_b=True等效于转置第二个参数,但是它的效率更高,因为它可以在matmul()操作中即时执行转置)。但是,它使用自己的偏置向量,接下来,可以创建一个新的堆叠式自动编码器,和前一个非常相似,但是将解码器的Dense绑定到了编码器的密集层

dense_1 = keras.layers.Dense(100, activation='gelu')
dense_2 = keras.layers.Dense(30, activation='gelu')
tied_encoder = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28, 28]),
    dense_1,
    dense_2
])
tied_decoder = keras.models.Sequential([
    DenseTranspose(dense_2, activation='gelu'),
    DenseTranspose(dense_1, activation='gelu'),
    keras.layers.Reshape([28, 28])
])
tied_ae = keras.models.Sequential([tied_encoder, tied_decoder])
tied_ae.compile(loss='binary_crossentropy', optimizer=keras.optimizers.Adam())
history = tied_ae.fit(X_train, X_train, epochs=10, validation_data=(X_valid, X_valid))
Epoch 1/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.6947 - val_loss: 0.4871
Epoch 2/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.4224 - val_loss: 0.3859
Epoch 3/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.3779 - val_loss: 0.3609
Epoch 4/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.3565 - val_loss: 0.3362
Epoch 5/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.3400 - val_loss: 0.3309
Epoch 6/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.3319 - val_loss: 0.3201
Epoch 7/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.3360 - val_loss: 0.3591
Epoch 8/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.3279 - val_loss: 0.3173
Epoch 9/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.3215 - val_loss: 0.3300
Epoch 10/10
1719/1719 [==============================] - 5s 3ms/step - loss: 0.3327 - val_loss: 0.3436

与前一个模型相比,该模型的参数数量减少了几乎一半

一次训练一个自动编码器

与其像一次训练整个堆叠式自动编码器那样,不如一次训练一个浅层自动编码器,然后将它们全部堆叠成一个堆叠式自动编码器

在训练的第一阶段,第一个自动编码器学习重建输入。然后,使用第一个自动编码器对整个训练集进行编码,这提供了一个新的(压缩)训练集。然后,在此新的数据集上训练第二个自动编码器,这是训练的第二阶段。最后,使用所有这些自动编码器来构建堆叠式自动编码器