即使是非计算机行业, 大家也知道很多有名的神经网络结构, 比如CNN在处理图像上非常厉害, RNN能够建模序列数据. 然而CNN, RNN之类的神经网络结构本身, 并不能用于执行比如图像的内容和风格分离, 生成一个逼真的图片, 用少量的label信息来分类图像, 或者做数据压缩等任务. 因为上述几个任务, 都需要特殊的网络结构和训练算法 .
有没有一个网络结构, 能够把上述任务全搞定呢? 显然是有的, 那就是对抗自编码器Adversarial Autoencoder(AAE) . 在本文中, 我们将构建一个AAE, 来压缩数据, 分离图像的内容和风格, 用少量样本来分类图像, 然后生成它们。
本系列文章, 专知小组一共分成四篇讲解:
PyTorch实现自编码器
首先我们先回顾一下什么是自编码器 , 然后用PyTorch 进行简单的实现。
1.自编码器
如图所示, 自编码器的输入和输出是一样的, 也就是说, 它不需要监督信息(label), 它主要有两部分构成:
• 编码器(Encoder) : 输入数据
(可以是文本, 图像, 视频, 语音), 输出latent code, 比如上图, 输入数据是
的一张图像, 输出的是
的隐层值h, 或者称之为latent code, 当然h的大小你可以随便设置. 在这种设置下, encoder 起到了压缩图片的作用, 将一个图片从
变化成了
, 就像你用压缩软件( 比如WinRAR)压缩图片一样. 如果我们把Encoder记做函数q, 那么Encoder就是在做:
• 解码器(Decoder) : 输入数据为上一步的输出数据h, 它努力把h重构成x, 上图的例子中, Decoder需要把
的重构回
的
,并使得
和原来的x约相似越好, 就像你用压缩然见解压一个压缩文件一样. 如果我们把Decoder记做函数p, 那么Decoder就是在做:
这个模型似乎是一个天然的降维模型. 但是, 除了降维,Autoencoder还能干什么?
2.PyTorch实现
我们先从简单的全连接网络开始我们的第一部分.
这个Encoder包含
的输入层, 两层隐层, 每层1000个节点, 一个输出层层数为2
import torch
import torch.nn as nn
encoder = nn.Sequential(
nn.Linear(28*28, 1000)
nn.ReLU()
nn.Linear(1000, 1000)
nn.ReLU()
nn.Linear(1000, 2)
)
decoder = nn.Sequential(
nn.Linear(2, 1000)
nn.ReLU()
nn.Linear(2, 1000)
nn.ReLU()
nn.Linear(1000, 28*28)
nn.Sigmoid() #压缩到0-1之间
)
所以, 整个模型是:
import torch
import torch.nn as nn
class AutoEncoder(nn.Module):
def __init__(self):
super(AutoEncoder, self).__init__()
self.encoder = nn.Sequential(
nn.Linear(28 * 28, 1000)
nn.ReLU()
nn.Linear(1000, 1000)
nn.ReLU()
nn.Linear(1000, 2)
)
self.decoder = nn.Sequential(
nn.Linear(2, 1000)
nn.ReLU()
nn.Linear(2, 1000)
nn.ReLU()
nn.Linear(1000, 28 * 28)
nn.Sigmoid()
)
def forward(self, x):
encoded = self.encoder(x)
decoded = self.decoder(encoded)
return encoded, decoded
模型实现完成后, 我们要准备一下数据:
import torchvision
import torch.utils.data as Data
BATCH_SIZE = 64
DOWNLOAD_MNIST = False # 本地没有数据的话, 设成True可以下载
# 从torchvison里加载MNIST数据, 然后转成Tensor
train_data = torchvision.datasets.MNIST(
root='./mnist/',
train=True,
transform=torchvision.transforms.ToTensor(),
download=DOWNLOAD_MNIST,
)
# 加载数据, 维度是(batch_size, n_channel, n_width, n_height )
train_loader = Data.DataLoader(dataset=train_data,
batch_size=BATCH_SIZE,
shuffle=True)
我们选择MSE损失函数来度量重构出来的图像
与原来的图像x的相似程度
loss_func = nn.MSELoss()
LR = 0.005
# 用Adam来优化参数
optimizer = torch.optim.Adam(autoencoder.parameters(), lr=LR)
接下来就可以实现训练步骤了:
EPOCH = 10
autoencoder = AutoEncoder()
for epoch in range(EPOCH):
for step, (x, y) in enumerate(train_loader):
b_x = Variable(x.view(-1, 28*28)) # reshape 成(batch, 28*28)
b_y = Variable(x.view(-1, 28*28))
b_label = Variable(y) # batch label
encoded, decoded = autoencoder(b_x)
loss = loss_func(decoded, b_y) # 算误差
optimizer.zero_grad() # 清空累计导数
loss.backward() # 求导
optimizer.step() # 优化一步
if step % 100 == 0:
print('Epoch: ', epoch, '| train loss: %.4f' % loss.data[0])
可以看一下重建的图像怎么样:
我们可以观察到, 输入的这张3的图片, 一些奇怪的地方呗去掉了(3的左上角).
接下来, 让我们看一下latent code, 它只有2维, 我们可以随便填一个值让Decoder去生成图片, 比如我们认为的令
,让后将它输入到Decoder中:
这好像是个6的图片, 当然也可能是0, 不管怎么说, 这不是一个清晰的数字图片. 这是因为Encoder的输出并不能覆盖整个2维空间(它的输出分布有很多空白)。 因此,如果我们输入一些Decoder没见过的值,我们会看到一下奇怪的输出图像。 这可以通过在生成latent code 时, 将Encoder的输出限制为随机分布(比如,均值为0.0和标准偏差为2.0的正态分布)。 Adversarial Autoencoder就是这么做到的,我们将在第2部分中看看它的实现。