跳至主要内容

自动编码器入门:从基础到 PyTorch 中的高级应用

带您了解自动编码器、其多种变体,以及在现实世界中的潜在应用。
· 10分钟 阅读

使用 AI 探索

传统前馈神经网络在执行分类和回归等任务时表现出色,但如果我们想实现信号去噪或异常检测等方案呢?一种方法是使用自动编码器(Autoencoder)。

本教程将对自动编码器进行实践性介绍,包括一个基于 PyTorch 的动手示例以及一些潜在用例。

您可以在这本包含教程全部代码的 DataLab 工作簿中跟随学习。

什么是自动编码器?

自动编码器是一类特殊的无监督前馈神经网络(无需标签!)。其主要应用是精准捕捉所给数据的关键特征,从而提供输入数据的压缩版本、生成逼真的合成数据或标记异常。

自动编码器由两个关键的全连接前馈神经网络组成(图 1):

  • 编码器(Encoder):对输入数据进行压缩以去除各种形式的噪声,并生成潜在空间/瓶颈。因此,其输出维度小于输入维度,并可作为超参数进行调整,以决定压缩的有损程度。
  • 解码器(Decoder):仅利用来自潜在空间的压缩表示,尽可能高保真地重构原始输入数据(因此该网络的结构通常与编码器镜像对称)。随后可通过使用损失函数计算输入与输出数据之间的重构误差来衡量预测的“好坏”。

通过不断迭代地将数据传经编码器和解码器、测量误差并通过反向传播微调参数,自动编码器最终能够正确处理极其复杂的数据形式。

Figure 1: Autoencoder Architecture (Image by Author).

图 1:自动编码器架构(作者绘制)。

如果自动编码器接收到的一组输入特征彼此完全独立,那么模型要在不丢失大量信息的情况下找到良好的低维表示将会非常困难(即有损压缩)。

因此,自动编码器也可被视为一种降维技术。与传统技术如主成分分析(PCA)相比,它可以利用非线性变换将数据投影到低维空间。如果您想进一步了解其他特征提取技术,可参考这篇特征提取教程。

此外,与gzpi等标准数据压缩算法不同,自动编码器不能用作通用压缩算法,而是针对与其训练数据分布相似的数据进行手工定制,以发挥最佳效果。

在优化自动编码器时,一些最常见可调的超参数包括:

  • 编码器与解码器的层数
  • 每一层的节点数
  • 用于优化过程的损失函数(例如二元交叉熵或均方误差)
  • 潜在空间的大小(越小,压缩率越高,因此也起到正则化作用)

最后,自动编码器可以面向不同类型的数据(如表格、时间序列或图像)进行设计,因此也可以采用多种层类型,例如用于图像分析的卷积层。

理想情况下,一个训练良好的自动编码器应能对输入数据做出足够灵活的响应,从而给出量身定制的结果,但又不会过度拟合以至于只是在机械地模仿输入数据,无法对未见数据泛化。

自动编码器的类型

多年来,研究者提出了多种自动编码器类型:

  • 欠完备自动编码器(Undercomplete Autoencoder)
  • 稀疏自动编码器(Sparse Autoencoder)
  • 收缩自动编码器(Contractive Autoencoder)
  • 去噪自动编码器(Denoising Autoencoder)
  • 卷积自动编码器(Convolutional Autoencoder)
  • 变分自动编码器(Variational Autoencoder)

下面逐一展开。

欠完备自动编码器

这是最简单的自动编码器版本。在这种情况下,我们没有显式的正则化机制,但通过确保瓶颈的大小始终小于原始输入大小来避免过拟合。这类配置通常用作降维技术(比 PCA 更强,因为它还能捕捉数据中的非线性)。

稀疏自动编码器

稀疏自动编码器与欠完备自动编码器非常相似,但主要区别在于正则化的方式。事实上,使用稀疏自动编码器时,我们不一定要缩小瓶颈维度,而是使用一种损失函数来惩罚模型在各个隐藏层中“动用所有神经元”(图 2)。

这种惩罚通常称为稀疏性函数,它与传统正则化不同,因为它关注的不是惩罚权重大小,而是惩罚被激活的节点数量。

Figure 2: Sparse Autoencoder Architecture (Image by Author).

通过这种方式,不同节点可以针对不同的输入类型进行“专门化”,并根据输入数据的具体情况被激活/抑制。该稀疏性约束可通过 L1 正则化和 KL 散度来引入,从而有效防止模型过拟合。

收缩自动编码器

收缩自动编码器背后的主要思想是:对于一些相似的输入,其压缩表示也应当相似(输入的邻域应被“收缩”为输出空间中的小邻域)。在数学上,这可通过在输入相似时保持输入到隐藏层的激活对输入的导数较小来实现。

去噪自动编码器

在去噪自动编码器中,模型的输入与输出不再相同。例如,模型可以接收低分辨率、被破坏的图像,并以提升图像质量为目标生成输出。为了评估并提升模型性能,我们需要一些带标签的干净图像与模型预测进行对比。

卷积自动编码器

为处理图像数据,卷积自动编码器在编码器与解码器两步中都用卷积神经网络取代传统前馈神经网络。通过调整损失函数类型等设置,这类自动编码器也可根据用例需求,被设计为例如稀疏或去噪等变体。

变分自动编码器

在前述各类自动编码器中,编码器对每个维度输出单一数值。而在变分自动编码器(VAE)中,我们将这一过程改为概率式,为每个维度构建一个概率分布。随后解码器可从描述各维度的分布中采样数值并构建输入向量,用以重构原始输入数据。

变分自动编码器的主要应用之一是生成任务。事实上,从分布中对潜在表示进行采样,可使解码器生成以确定性方法此前无法产生的新型输出。

如果您有兴趣在线体验一个在 MNIST 数据集上训练的变分自动编码器,可以查看此在线示例。

自动编码器实战示例

现在我们来进行一个使用自动编码器进行降维的实践演示。本次练习将采用 PyTorch 作为深度学习框架。

本示例将使用Kaggle 澳大利亚降雨数据集。本文全部代码可在这本 DataLab 工作簿中获取。

首先,我们导入所有必要的库,并移除缺失值与非数值列(图 3)。

import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler, LabelEncoder
from mpl_toolkits.mplot3d import Axes3D

df = pd.read_csv("../input/weather-dataset-rattle-package/weatherAUS.csv")
df = df.drop(['Date', 'Location', 'WindDir9am',
             'WindGustDir', 'WindDir3pm'], axis=1)
df = df.dropna(how = 'any')
df.loc[df['RainToday'] == 'No', 'RainToday'] = 0
df.loc[df['RainToday'] == 'Yes', 'RainToday'] = 1
df.head()

Figure 3: Sample of Dataset Columns (Image by Author).

图 3:数据集列示例(作者绘制)。

接下来,我们将数据划分为特征与标签,对特征进行归一化,并将标签转换为数值格式。

在本例中,初始特征集由 17 列组成。分析的总体目标是正确预测明天是否会下雨。

X, Y = df.drop('RainTomorrow', axis=1, inplace=False), df[['RainTomorrow']]
# Normalizing Data
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# Converting to PyTorch tensor
X_tensor = torch.FloatTensor(X_scaled)

# Converting string labels to numerical labels
label_encoder = LabelEncoder()
Y_numerical = label_encoder.fit_transform(Y.values.ravel())

在 PyTorch 中,我们可以将自动编码器模型定义为一个类,并用两个线性层来指定编码器与解码器。

# Defining Autoencoder model
class Autoencoder(nn.Module):
   def __init__(self, input_size, encoding_dim):
       super(Autoencoder, self).__init__()
       self.encoder = nn.Sequential(
           nn.Linear(input_size, 16),
           nn.ReLU(),
           nn.Linear(16, encoding_dim),
           nn.ReLU()
       )
       self.decoder = nn.Sequential(
           nn.Linear(encoding_dim, 16),
           nn.ReLU(),
           nn.Linear(16, input_size),
           nn.Sigmoid()
       )

   def forward(self, x):
       x = self.encoder(x)
       x = self.decoder(x)
       return x

现在模型已搭建完成,我们将编码维度设为 3(便于后续可视化),并开始训练。

# Setting random seed for reproducibility
torch.manual_seed(42)

input_size = X.shape[1]  # Number of input features
encoding_dim = 3  # Desired number of output dimensions
model = Autoencoder(input_size, encoding_dim)

# Loss function and optimizer
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.003)

# Training the autoencoder
num_epochs = 20
for epoch in range(num_epochs):
   # Forward pass
   outputs = model(X_tensor)
   loss = criterion(outputs, X_tensor)

   # Backward pass and optimization
   optimizer.zero_grad()
   loss.backward()
   optimizer.step()

   # Loss for each epoch
   print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')

# Encoding the data using the trained autoencoder
encoded_data = model.encoder(X_tensor).detach().numpy()
Epoch [1/20], Loss: 1.2443
Epoch [2/20], Loss: 1.2410
Epoch [3/20], Loss: 1.2376
Epoch [4/20], Loss: 1.2342
Epoch [5/20], Loss: 1.2307
Epoch [6/20], Loss: 1.2271
Epoch [7/20], Loss: 1.2234
Epoch [8/20], Loss: 1.2196
Epoch [9/20], Loss: 1.2156
Epoch [10/20], Loss: 1.2114
Epoch [11/20], Loss: 1.2070
Epoch [12/20], Loss: 1.2023
Epoch [13/20], Loss: 1.1974
Epoch [14/20], Loss: 1.1923
Epoch [15/20], Loss: 1.1868
Epoch [16/20], Loss: 1.1811
Epoch [17/20], Loss: 1.1751
Epoch [18/20], Loss: 1.1688
Epoch [19/20], Loss: 1.1622
Epoch [20/20], Loss: 1.1554

最后,我们可以绘制得到的嵌入维度(图 4)。如下面所示,我们成功将特征维度从 17 降至 3,同时仍能在三维空间中较好地区分不同类别的样本。

# Plotting the encoded data in 3D space
fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(111, projection='3d')
scatter = ax.scatter(encoded_data[:, 0], encoded_data[:, 1],
                    encoded_data[:, 2], c=Y_numerical, cmap='viridis')

# Mapping numerical labels back to original string labels for the legend
labels = label_encoder.inverse_transform(np.unique(Y_numerical))
legend_labels = {num: label for num, label in zip(np.unique(Y_numerical),
                                                 labels)}

# Creating a custom legend with original string labels
handles = [plt.Line2D([0], [0], marker='o', color='w',
                    markerfacecolor=scatter.to_rgba(num),
                    markersize=10,
                    label=legend_labels[num]) for num in np.unique(Y_numerical)]
ax.legend(handles=handles, title="Raining Tomorrow?")

# Adjusting the layout to provide more space for labels
ax.xaxis.labelpad = 20
ax.yaxis.labelpad = 20

ax.set_xticks([])
ax.set_yticks([])
ax.set_zticks([])

# Manually adding z-axis label for better visibility
ax.text2D(0.05, 0.95, 'Encoded Dimension 3', transform=ax.transAxes,
         fontsize=12, color='black')

ax.set_xlabel('Encoded Dimension 1')
ax.set_ylabel('Encoded Dimension 2')
ax.set_title('Autoencoder Dimensionality Reduction')

plt.tight_layout()

plt.savefig('Rain_Prediction_Autoencoder.png')

plt.show()

Figure 4: Resulting Encoded Dimensions (Image by Author).

图 4:编码结果维度(作者绘制)。

自动编码器的真实应用

图像压缩/去噪

自动编码器的一项主要应用是压缩图像以减少文件大小,同时尽可能保留有价值的信息,或对随时间退化的图像进行修复。

异常检测

由于自动编码器善于区分数据的关键信息与噪声,它可用于检测异常(例如图像是否被 PS 过、网络中是否存在异常活动等)。

数据生成

变分自动编码器和生成式对抗网络(GAN)常用于生成合成数据(例如逼真的人物图像)。

结语

总之,自动编码器是一种非常灵活的工具,可支撑多种用例。尤其是变分自动编码器与 GAN 的出现,打开了通往生成式 AI 的大门,让我们初见 AI 生成全新内容的可能性。

本教程对自动编码器领域进行了入门介绍,但还有很多内容值得深入!DataCamp 提供了丰富资源,例如如何在 Keras 中实现自动编码器,以及将其用作分类器!

Pier Paolo Ippolito

作者

Pier Paolo Ippolito

瑞再(Swiss RE)数据与人工智能顾问,人工智能硕士,研究兴趣涵盖数据科学、机器学习与云端开发。除本职工作外,我还经营兼职咨询业务,并拥有 Azure 四项、GCP、AWS 以及 Databricks 等云平台认证。

主题