分类 TensorFlow & Keras 下的文章

循环自动编码器

如果要为序列构建自动编码器,例如时间序列和文本(例如,用于无监督学习或降维),那么递归神经元可能比密集网络更合适。构建循环自动编码器非常简单直接:编码器通常是序列到向量的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

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

一次训练一个自动编码器

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

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

使用不完整的线性自动编码器执行PCA

如果自动编码器仅使用线性激活,并且成本函数是均方误差MSE,则最后执行的是主成分分析(PCA)

以下代码构建了一个简单的线性自动编码器,来对3D数据集执行PCA,将其投影到2D:

from tensorflow import keras

encoder = keras.models.Sequential([
    keras.layers.Dense(2, input_shape=[3])
])
decoder = keras.models.Sequential([
    keras.layers.Dense(3, input_shape=[2])
])
autoencoder = keras.models.Sequential([encoder, decoder])
autoencoder.compile(loss='mse', optimizer=keras.optimizers.SGD(lr=.1))
  • 将自动编码器分为两个子主件:编码器和解码器。两者都具有单一Dense层的常规Sequential模型,而自动编码器是包含编码器和解码器的Sequential模型(一个模型可以用作另一个模型中的层)
  • 自动编码器的输出数量等于输入的数量
  • 为了执行简单的PCA,不使用任何激活函数(即所有神经元都是线性的),成本函数为MSE
import tensorflow as tf

X_train = tf.random.normal([1000, 3], 0, 1)
print(X_train.shape)
history = autoencoder.fit(X_train, X_train, epochs=20)
# X_train=X_train[np.newaxis,...]
codings = encoder.predict(X_train)
print(codings.shape)
(1000, 3)
Epoch 1/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3010
Epoch 2/20
32/32 [==============================] - 0s 2ms/step - loss: 0.3008
Epoch 3/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3005
Epoch 4/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3007
Epoch 5/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3009
Epoch 6/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3004
Epoch 7/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3004
Epoch 8/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3011
Epoch 9/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3001
Epoch 10/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3007
Epoch 11/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3010
Epoch 12/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3004
Epoch 13/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3004
Epoch 14/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3003
Epoch 15/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3007
Epoch 16/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3004
Epoch 17/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3005
Epoch 18/20
32/32 [==============================] - 0s 1ms/step - loss: 0.2999
Epoch 19/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3003
Epoch 20/20
32/32 [==============================] - 0s 1ms/step - loss: 0.3005
(1000, 2)
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure()
ax = fig.add_subplot(121, projection='3d')
ax.plot(X_train[:, 0], X_train[:, 1], X_train[:, 2])
ax = fig.add_subplot(122)
ax.plot(codings[:, 0], codings[:, 1])


有效的数据表示

你发现以下哪个数字顺序最容易记忆?

  • 40,27,25,36,81,57,10,73,19,68
  • 50,48,46,44,42,40,38,36,34,32,30,28,26,24,22,20,18,16,14

乍一看,第一个序列似乎应该更容易些,因为它要短得多。但是,如果仔细看第二个序列,会发现它只是从50到14的偶数列表。一旦注意到这个模型,第二个序列就比第一个序列更容易记忆,因为只需要记住模式(偶数递减)以及开始和结束的数字(即50和14)。如果可以快速轻松地记住很长的序列们就不会关心第二个序列中是否存在模式。会认真得记忆每个数字,就是这样。很难记住长序列这一事实使得模式识别变得很有用。这能澄清为什么在训练过程中约束自动编码器会促使其发现和利用数据中的模式

记忆、感知和模式匹配之间的关系在1970年代初期由Willian Chase和Herbert Simon进行了著名的研究。他们观察到,国际象棋专家可以通过观察棋盘上的位置只需要5秒钟就能记住所有棋子的位置,这是大多数人无法实现的任务。但是,只有把棋子放在真实位置(根据具体棋局)时才是这种情况,而不是将棋子随机放置。国际象棋专家的记忆力并不比你我的好。多亏了他们在象棋中的经验,他们才更容易看到国际象棋的模式。注意到模式可以帮助他们有效地存储信息。

就像这个记忆实验中的国际棋手一样,自动编码器会查看输入,将其转换为有效的潜在特征,然后输出一些看起来非常接近输出的东西。自动编码器通常由两部分组成:将输入转换为潜在特征的编码器(或识别网络),然后是将内部表征转化为输出的解码器(或生成网络)

自动编码器通常具有多层感知器(MLP)相同的架构,除了输出层中的神经元数量必须等于输入的数量。输出通常称为重构,因为自动编码器会试图重构输入,并且成本函数包含一个重构损失,当重构与输入不同时会惩罚这个模型

因为内部表征的维度比输入数据的维度低,所以自动编码器被认为是不完整的。不完整的自动编码器无法将其输入简单地复制到编码中,必须找到一种输出其输入副本的方法。它被迫学习输入数据中最重要的特征(并删除不重要的特征)