TensorFlow 中的自动编码器(附示例)

⚡ 智能摘要

自编码器将输入压缩成更小的内部表示,然后重建它,从而无需标签即可学习最重要的特征。本教程使用 TensorFlow 堆叠四个密集层,并重建 CIFAR-10 马匹图像。

  • 🔘 编码器和解码器: 两个对称的模块在一个狭窄的编码层相遇,迫使网络只保留必要的信号。
  • ☑️ 数据集准备: 将 CIFAR-10 批次图像加载、转换为灰度图像、堆叠图像,并筛选出 5,000 张马匹图像。
  • 输送管道: 数据集估计器,带有 from_tensor_slices、repeat 和 batch 功能,通过可初始化的迭代器提供图像。
  • 🧪 网络形状: 层宽分别为 1024、300、150、300、1024,采用 ELU 激活函数、Xavier 初始化和 L2 正则化。
  • 🛠️ 训练设置: 输出与输入之间的均方误差,Adam 优化器,批大小为 150,每个 epoch 迭代 33 次。
  • 📉 测量结果: 在绘制重建图之前,经过 100 个 epoch 的训练,损失从 2,934 下降到大约 1,454。

TensorFlow 中的自编码器

深度学习中的自动编码器是什么?

An 自动编码器 是一个用于高效学习数据编码的工具 无监督 方式。这是一种 人工神经网络 它通过训练神经网络忽略信号噪声,帮助你学习数据集的表示形式,从而实现降维。它是重现输入数据的绝佳工具。

简单来说,这台机器接收一张图片,就能生成一张与其高度相似的图像。这种神经网络的输入是无标签的,这意味着网络无需监督即可学习。更准确地说,网络会对输入进行编码,使其只关注最关键的特征。这也是自编码器在降维领域广受欢迎的原因之一。此外,自编码器还可以用于构建生成式学​​习模型。例如,可以用一组人脸图像训练神经网络,然后生成新的人脸图像。

TensorFlow Autoencoder 如何工作?

自动编码器的目的是通过仅关注基本特征来产生输入的近似值。您可能会想,为什么不只是学习如何复制和粘贴输入来产生输出呢?事实上,自动编码器是一组约束,它迫使网络学习新的方式来表示数据,而不仅仅是复制输出。

典型的自编码器由输入、内部表示和输出(输入的近似值)组成。学习过程发生在与内部表示相连的层中。实际上,它包含两个主要的层模块,类似于传统的神经网络。略有不同的是,包含输出的层必须与输入相同。在下图所示的结构中,原始输入进入第一个模块,称为编码器。该内部表示对输入进行压缩(减小)处理。在第二个模块中,对输入进行重构,即解码阶段。

自编码器的编码器和解码器模块,中间部分是压缩后的内部表示。
自动编码器的工作原理

模型将通过最小化损失函数来更新权重。如果重建输出与输入不同,则模型会受到惩罚。

具体来说,想象一张 50×50 像素(即 2,500 像素)的图片,以及一个只有一个隐藏层、包含一百个神经元的神经网络。学习过程在一个比输入图像小二十五倍的特征图上进行。这意味着网络需要找到一种方法,仅用一个包含 100 个神经元的向量来重建这 2,500 像素的图像。

堆叠自动编码器示例

在本自编码器教程中,您将学习如何使用堆叠式自编码器。其架构类似于传统的神经网络。输入首先进入隐藏层进行压缩(即降低其维度),然后到达重建层。目标是生成尽可能接近原始图像的输出图像。该模型必须学习在特定约束条件下(即在较低维度下)完成任务的方法。

如今,深度学习中的自编码器主要用于图像去噪。想象一下,即使图像上有划痕,人仍然能够识别出图像内容。去噪自编码器的思想是向图像中添加噪声,迫使网络学习数据背后的模式。

另一类有用的自编码器 深度学习 是变分自编码器。这种类型的网络可以生成新的图像。想象一下,你用一张人脸图像训练一个网络;这样的网络可以生成新的面孔。

下表将本教程中构建的堆叠式自编码器与您可能遇到的其他自编码器系列并列,以便于比较每个自编码器所添加的约束。

自编码器类型 新增约束 典型用途
未完成/堆叠 编码层比输入层窄 降维,特征提取tracTION
去噪 输入信号中添加噪声,目标干净 图像和信号清理
对活跃单元数量的惩罚 可解释的、基于部件的特征
变分 编码层学习概率分布 生成新样本

如何使用 TensorFlow 构建自动编码器

在本教程中,您将学习如何构建堆叠自动编码器来重建图像。

您将使用 CIFAR-10 数据集,其中包含 60000 张 32×32 像素的彩色图像。自编码器数据集已划分为 50000 张用于训练,10000 张用于测试。数据集最多包含十个类别:

  • 飞机
  • 汽车
  • 鹿
  • 青蛙
  • 卡车

你需要从以下位置下载图片: CIFAR-10 数据集页面 解压缩该压缩包。文件夹 cifar-10-batches-py 包含五批数据,每批数据包含 10000 张图像,顺序随机。

在构建和训练模型之前,您需要进行一些数据处理。您将按以下步骤操作:

  1. 导入数据
  2. 将数据转换为黑白格式
  3. 附加所有批次
  4. 构建训练数据集
  5. 构建图像可视化工具

版本说明: 本教程中的代码针对 TensorFlow 1.x。在 TensorFlow 2 中,tf.contrib 模块已不存在,占位符、会话和可初始化迭代器位于 tf.compat.v1 下。导入后调用 tf.compat.v1.disable_v2_behavior() 是使代码列表保持不变的最快方法。

图像预处理

步骤1)导入数据

根据官网说明,您可以使用以下代码上传数据。自编码器代码会将数据及其标签加载到一个字典中。请注意,该代码是一个函数。

import numpy as np
import tensorflow as tf
import pickle
def unpickle(file):
    import pickle
    with open(file, 'rb') as fo:
        dict = pickle.load(fo, encoding='latin1')
    return dict

步骤2)将数据转换为黑白格式

为简单起见,您将数据转换为灰度。也就是说,只有一个维度,而彩色图像有三个维度。大多数神经网络只适用于一维输入。

def grayscale(im):
    return im.reshape(im.shape[0], 3, 32, 32).mean(1).reshape(im.shape[0], -1)

步骤 3)附加所有批次

现在两个函数都已创建,数据集也已加载,您可以编写一个循环来将数据追加到内存中。仔细检查后,您会发现解压后的数据文件名为 data_batch_,后面跟着 1 到 5 之间的数字。您可以遍历这些文件并将其追加到 data 中。

完成此步骤后,您将颜色数据转换为灰度格式。如图所示,数据的形状为 50000 和 1024。32*32 像素现在被展平为 1024 像素。

# Load the data into memory
data, labels = [], []
## Loop over the b
for i in range(1, 6):
    filename = './cifar-10-batches-py/data_batch_' + str(i)
    open_data = unpickle(filename)
    if len(data) > 0:
        data = np.vstack((data, open_data['data']))
        labels = np.hstack((labels, open_data['labels']))
    else:
        data = open_data['data']
        labels = open_data['labels']

data = grayscale(data)
x = np.matrix(data)
y = np.array(labels)
print(x.shape)
(50000, 1024)

注意: 将“./cifar-10-batches-py/data_batch_”替换为您的文件的实际位置。例如,对于 Windows 机器,路径可以是 filename = 'E:\cifar-10-batches-py\data_batch_' + str(i)

步骤4)构建训练数据集

为了加快训练速度并简化操作,您将仅使用马的图像来训练模型。马的图像在标签数据中标记为 7。正如 CIFAR-10 数据集的文档中所述,每个类别包含 5000 张图像。您可以打印数据形状以确认确实有 5,000 张图像和 1024 列,如下面的 TensorFlow 自编码器示例步骤所示。

horse_i = np.where(y == 7)[0]
horse_x = x[horse_i]
print(np.shape(horse_x)) 
(5000, 1024)

步骤 5)构建图像可视化工具

最后,你构建一个函数来绘制图像。你将需要这个函数来打印来自自动编码器的重建图像。

打印图像的一个简单方法是使用 Matplotlib 库中的 imshow() 函数。请注意,您需要将数据的形状从 1024 转换为 32*32(即图像格式)。

# To plot pretty figures
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
def plot_image(image, shape=[32, 32], cmap = "Greys_r"):
    plt.imshow(image.reshape(shape), cmap=cmap,interpolation="nearest")
    plt.axis("off")   

该函数接受 3 个参数:

  • 图片:输入
  • 形状:列表,图像尺寸
  • Cmap:选择颜色映射。默认值为灰色。

您可以尝试绘制数据集中的第一幅图像。您应该看到一个骑在马上的男人。

plot_image(horse_x[1], shape=[32, 32], cmap = "Greys_r")

该调用返回下面的灰度缩略图,并确认将 1024 个值重新塑形为 32×32 图像是正确的。

使用 plot_image() 函数绘制的骑马者灰度 32x32 CIFAR-10 缩略图

设置数据集估计器

好了,现在数据集已经准备就绪,可以开始使用TensorFlow了。在构建模型之前,请使用TensorFlow的数据集估计器来为网络提供数据。

您将使用 TensorFlow 估算器构建数据集。为了刷新您的思维,您需要使用:

  • from_tensor_slices
  • 重复
  • 批量

构建数据集的完整代码是:

dataset = tf.data.Dataset.from_tensor_slices(x).repeat().batch(batch_size)

请注意,x 是一个形状为 [None, n_inputs] 的占位符。第一个维度设置为 None,因为输入到网络的图像数量等于批次大小,而批次大小仅在运行时确定。更多详情,请参阅相关教程。 使用 TensorFlow 进行线性回归.

之后,您需要创建迭代器。如果没有这行代码,则不会有数据通过管道。

iter = dataset.make_initializable_iterator() # create the iteratorfeatures = iter.get_next()

现在管道已经准备就绪,您可以检查第一张图像是否与之前相同(即,马背上的一个人)。

您将批处理大小设置为 1,是因为您只想用一张图像来填充数据集。您可以使用 `print(sess.run(features).shape)` 查看数据的维度,其值为 (1, 1024)。其中 1 表示每次只填充一张包含 1024 个值的图像。如果将批处理大小设置为 2,则会有两张图像进入处理流程。请勿更改批处理大小,否则会报错,因为每次只能有一张图像进入 `plot_image()` 函数。

## Parameters
n_inputs = 32 * 32
BATCH_SIZE = 1
batch_size = tf.placeholder(tf.int64)

# using a placeholder
x = tf.placeholder(tf.float32, shape=[None,n_inputs])
## Dataset
dataset = tf.data.Dataset.from_tensor_slices(x).repeat().batch(batch_size)
iter = dataset.make_initializable_iterator() # create the iterator
features = iter.get_next()

## Print the image
with tf.Session() as sess:
    # feed the placeholder with data
    sess.run(iter.initializer, feed_dict={x: horse_x,
                                         batch_size: BATCH_SIZE}) 
    print(sess.run(features).shape) 
    plot_image(sess.run(features), shape=[32, 32], cmap = "Greys_r")
(1, 1024)

该管道返回与直接从 NumPy 矩阵绘制的相同的骑手,这证明占位符、迭代器和批大小连接正确。

图像经过 TensorFlow 数据集迭代器处理后,打印出的仍然是同一匹马的缩略图。

建立网络

现在该构建网络了。您将训练一个堆叠式自动编码器,即具有多个隐藏层的网络。

你的网络将有一个输入层,有 1024 个点,即 32×32,图像的形状。

编码器模块包含一个顶部隐藏层(300个神经元)和一个中心层(150个神经元)。解码器模块与编码器模块对称。您可以在下图看到该网络的结构。请注意,您可以更改隐藏层和中心层的神经元数量。

具有 1024 个输入、300 层和 150 层单元编码器层以及镜像解码器的对称自编码器架构

构建自动编码器网络

构建自动编码器与任何其他深度学习模型非常相似。

您将按照以下步骤构建模型:

  1. 定义参数
  2. 定义图层
  3. 定义架构
  4. 定义优化
  5. 运行模型
  6. 评估模型

在上一节中,您学习了如何创建模型输入管道,因此无需再次创建数据集。您将构建一个四层的自编码器。您将使用 Xavier 初始化。这是一种根据输入和输出的方差设置初始权重的技术。最后,您将使用 ELU 激活函数。您将使用 L2 正则化项对损失函数进行正则化。

步骤 1)定义参数

第一步是定义每层的神经元数量、学习率和正则器的超参数。

在此之前,您需要导入部分函数。这是一种更好的方法来定义全连接层的参数。以下代码定义了自编码器架构的值。如前所述,编码器有两层,第一层有 300 个神经元,第二层有 150 个神经元。它们的值分别存储在 n_hidden_​​1 和 n_hidden_​​2 中。

您需要定义学习率和L2超参数。这些值分别存储在learning_rate和l2_reg中。

from functools import partial

## Encoder
n_hidden_1 = 300
n_hidden_2 = 150  # codings

## Decoder
n_hidden_3 = n_hidden_1
n_outputs = n_inputs

learning_rate = 0.01
l2_reg = 0.0001

Xavier 初始化技术通过估计器 contrib 中的对象 xavier_initializer 调用。在同一个估计器中,您可以使用 l2_regularizer 添加正则化器。

## Define the Xavier initialization
xav_init =  tf.contrib.layers.xavier_initializer()
## Define the L2 regularizer
l2_regularizer = tf.contrib.layers.l2_regularizer(l2_reg)

版本说明: TensorFlow 2 中移除了 tf.contrib。直接的替代品是 tf.keras.initializers.GlorotUniform() 替代 xavier_initializer(),以及 tf.keras.regularizers.l2() 替代 l2_regularizer()。

步骤 2)定义图层

所有密集层的参数都已设置完毕;您可以使用对象 partial 将所有内容打包到变量 dense_layer 中。dense_layer 会在每次调用时使用 ELU 激活函数、Xavier 初始化和 L2 正则化。

## Create the dense layer
dense_layer = partial(tf.layers.dense,
                         activation=tf.nn.elu,
                         kernel_initializer=xav_init,
                         kernel_regularizer=l2_regularizer)

步骤 3)定义架构

如果你查看架构图,你会发现该网络由三层和一个输出层组成。在下面的代码中,你需要连接相应的层。例如,第一层计算输入矩阵特征与包含 300 个权重的矩阵的点积。点积计算完成后,输出被传递给 ELU 激活函数。该输出成为下一层的输入,这就是为什么你需要用它来计算 hidden_​​2 等等。由于使用了相同的激活函数,所以每一层的矩阵乘法都相同。需要注意的是,最后一层(输出层)没有应用激活函数。这是合理的,因为这是重构后的输入。

## Make the mat mul
hidden_1 = dense_layer(features, n_hidden_1)
hidden_2 = dense_layer(hidden_1, n_hidden_2)
hidden_3 = dense_layer(hidden_2, n_hidden_3)
outputs = dense_layer(hidden_3, n_outputs, activation=None)

步骤 4)定义优化

最后一步是构建优化器。损失函数采用均方误差 (MSE)。如果您还记得线性回归教程,就会知道 MSE 是通过预测输出与真实标签之间的差异来计算的。这里,标签是特征,因为模型试图重构输入。因此,您需要的是预测输出与输入之间平方差之和的均值。使用 TensorFlow,您可以按如下方式编写损失函数:

loss = tf.reduce_mean(tf.square(outputs - features))

接下来,你需要优化损失函数。使用 Adam 优化器来计算梯度。目标函数是最小化损失。

## Optimize
loss = tf.reduce_mean(tf.square(outputs - features))
optimizer = tf.train.AdamOptimizer(learning_rate)
train  = optimizer.minimize(loss)

在训练模型之前,还有一个设置。您希望使用 150 的批处理大小,即每次迭代向管道提供 150 张图像。您需要手动计算迭代次数。这很容易做到:

如果你想每次处理 150 张图像,并且你知道数据集中有 5000 张图像,那么迭代次数等于 5000 除以 150。 Python 您可以运行以下代码,并确保输出结果为 33:

BATCH_SIZE = 150
### Number of batches :  length dataset / batch size
n_batches = horse_x.shape[0] // BATCH_SIZE
print(n_batches)
33

步骤 5)运行模型

最后,训练模型。训练模型需要 100 个 epoch。也就是说,模型在优化权重的过程中会查看图像 100 次。

您应该已经熟悉在 TensorFlow 中训练模型的代码。略有不同的是,需要在运行训练之前先将数据通过管道传输。这样可以加快模型训练速度。

您希望打印十个 epoch 后的损失,以查看模型是否正在学习某些东西(即损失是否在减少)。训练需要 2 到 5 分钟,具体取决于您的机器硬件。

## Set params
n_epochs = 100

## Call Saver to save the model and re-use it later during evaluation
saver = tf.train.Saver()

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    # initialise iterator with train data
    sess.run(iter.initializer, feed_dict={x: horse_x,
                                          batch_size: BATCH_SIZE})
    print('Training...')
    print(sess.run(features).shape) 
    for epoch in range(n_epochs):       
        for iteration in range(n_batches):
            sess.run(train)
        if epoch % 10 == 0:
            loss_train = loss.eval()   # not shown
            print("\r{}".format(epoch), "Train MSE:", loss_train) 
        #saver.save(sess, "./my_model_all_layers.ckpt") 
    save_path = saver.save(sess, "./model.ckpt")    
    print("Model saved in path: %s" % save_path)  
Training...
(150, 1024)
0 Train MSE: 2934.455
10 Train MSE: 1672.676
20 Train MSE: 1514.709
30 Train MSE: 1404.3118
40 Train MSE: 1425.058
50 Train MSE: 1479.0631
60 Train MSE: 1609.5259
70 Train MSE: 1482.3223
80 Train MSE: 1445.7035
90 Train MSE: 1453.8597
Model saved in path: ./model.ckpt

步骤6)评估模型

现在你的模型已经训练好了,是时候对其进行评估了。你需要从文件 /cifar-10-batches-py/ 中导入测试集。

test_data = unpickle('./cifar-10-batches-py/test_batch')
test_x = grayscale(test_data['data'])
#test_labels = np.array(test_data['labels'])

注意: 对于 Windows 机器,代码变成 test_data = unpickle(r”E:\cifar-10-batches-py\test_batch”)

您可以尝试打印图片 13,那是一匹马。

plot_image(test_x[13], shape=[32, 32], cmap = "Greys_r")

该图证实测试批次已正确加载,并且图像 13 属于模型训练所用的类别。

CIFAR-10 测试批次中的第 13 号测试图像,显示了一匹灰度马。

为了评估模型,您将使用此图像的像素值,并查看编码器是否能在将图像缩小到 1024 像素后重建出相同的图像。请注意,您需要定义一个函数来评估模型在不同图像上的表现。该模型应该只对马的图像效果更好。

该函数接受两个参数:

  • df:导入测试数据
  • 图像编号:指示要导入的图像。

该函数分为三部分:

  1. 将图像重塑为正确的尺寸,即 1
  2. 将未见过的图像输入到模型中,对图像进行编码/解码
  3. 打印真实图像和重建图像
def reconstruct_image(df, image_number = 1):
    ## Part 1: Reshape the image to the correct dimension i.e 1, 1024
    x_test = df[image_number]
    x_test_1 = x_test.reshape((1, 32*32))
    
    ## Part 2: Feed the model with the unseen image, encode/decode the image
    with tf.Session() as sess:     
        sess.run(tf.global_variables_initializer()) 
        sess.run(iter.initializer, feed_dict={x: x_test_1,
                                      batch_size: 1})
    ## Part 3:  Print the real and reconstructed image
      # Restore variables from disk.
        saver.restore(sess, "./model.ckpt")  
        print("Model restored.")
      # Reconstruct image
        outputs_val = outputs.eval()
        print(outputs_val.shape)
        fig = plt.figure()
      # Plot real
        ax1 = fig.add_subplot(121)
        plot_image(x_test_1, shape=[32, 32], cmap = "Greys_r")
      # Plot estimated
        ax2 = fig.add_subplot(122)
        plot_image(outputs_val, shape=[32, 32], cmap = "Greys_r")
        plt.tight_layout()
        fig = plt.gcf()

现在评估函数已经定义好了,你可以看一下重建图像的第十三张。

reconstruct_image(df =test_x, image_number = 13)
INFO:tensorflow:Restoring parameters from ./model.ckpt
Model restored.
(1, 1024)

该图将原始测试图像放在左边,将自编码器输出放在右边,因此很容易判断从 1024 压缩到 150 后细节的损失。

原始马匹图像与模糊的自编码器重建图像的并排比较

常见问题

两者都能降低维度,但主成分分析 (PCA) 仅限于线性投影。自编码器堆叠非线性激活函数(例如 ELU),因此能够捕捉 PCA 无法捕捉的曲线结构。仅需一个线性层和平方误差,自编码器就能几乎完全复现 PCA 的结果。

未作更改。tf.contrib 模块已被移除,占位符和会话已迁移至 tf.compat.v1。添加 disable_v2_behavior() 可快速移植列表,同时原生 TensorFlow 2 重写使用 tf.keras.layers.Dense 和 GlorotUniform 初始化。

自动化搜索工具尝试的编码层宽度、激活值和正则化强度组合远比手动调优快,然后根据重建误差对结果进行排序。虽然它们缩短了搜索时间,但仍需人工检查重建结果是否符合当前数据。

是的。 GitHub 副驾驶 它通过简短的注释草拟编码器和解码器的对称性、训练循环以及绘图辅助函数,从而去除大部分样板代码。由于生成的代码经常混合使用 TensorFlow 1 和 2 的语法,因此请自行验证张量形状和损失定义。

初始层数应接近输入宽度的 10% 到 20%,就像这里 150 个单元的层处理 1024 像素的图像一样。单元数太少会导致细节模糊,而单元数太多则会让网络直接复制输入,而不是学习紧凑的代码。

灰度图像将三个颜色通道合并为一个,使每个输入值从 3072 个减少到 1024 个。训练速度更快,密集层更小,并且重建任务仍然能够清晰地展示自编码器所学习到的内容。

到第 30 个 epoch 时,打印量从 2,934 下降到大约 1,404,然后在 1,425 和 1,609 之间波动。此时,0.01 的固定学习率超过了最小值——降低学习率或添加衰减计划通常会恢复进一步的进展。

生产应用包括服务器日志和事务的异常检测(其中高重构误差会标记异常值)、推荐嵌入、传感器去噪和预训练特征提取。trac为下游分类器提供数据的 tors。

总结一下这篇文章: