深度学习实战(五):使用自动编码器生成图像
1. 项目简介
如果我们不需要所有这些标记的数据来训练我们的模型。我的意思是标记和分类数据需要太多的工作。不幸的是,大多数现有的模型,从支持向量机到卷积神经网络,都不能在没有它们的情况下进行训练。
除了一小部分的算法可以。这就是所谓的无监督学习(Unsupervised Learning)。无监督学习通过自己的方式从未标记的数据中推断出一个函数。最著名的无监督算法是K-Means,它被广泛用于将数据聚类,而PCA则是降维的首选方案。K-Means和PCA可能是有史以来最好的两种机器学习算法。而让它们更出色的是它们的简单性。如果你掌握了它们,你就会觉得:“为什么我没有早点想到呢?”
2. 自动编码器Autoencoders
为了更好地理解自动编码器,我将提供一些代码以及解释。我们将使用 Pytorch 来构建和训练我们的模型。
import torch
from torch import nn, optim
from torch.autograd import Variable
from torch.nn import functional as F
自动编码器是简单的神经网络,其输出就是其输入。就这么简单。它们的目标是学习如何重构输入数据。但它是如何帮助的呢?诀窍在于它们的结构。网络的第一部分是我们所说的编码器。它接收输入,并将其编码在一个低维的潜在空间中。第二部分(解码器)接收该矢量并对其进行解码,以产生原始输入。
中间的向量(粉红色块)是我们想要的,因为它是输入的压缩表示。此外,我们可以应用它们来重现相同但有点不同甚至更好的数据。这方面的例子:
- 数据去噪,给他们提供一个有噪声的图像,并训练他们输出相同的图像,但没有噪声。
- 训练数据的增强
- 异常检测,在一个单一的类别上训练它们,使每一个异常点都有一个大的重建误差。
然而,自动编码器面临着与大多数神经网络相同的几个问题。它们倾向于过度拟合,并且受到梯度消失问题的影响。那么有什么解决办法吗?
3. 变分自动编码器 (VAE)
VAE是一个相当好的、优雅的努力。它基本上增加了随机性,但不完全是。
让我们进一步解释一下。变异自动编码器被训练来学习模拟输入数据的概率分布,而不是映射输入和输出的函数。然后,它从这个分布中取样,并将其送入解码器以生成新的输入数据样本。但是,等一下。当我听到概率分布时,我只想到了一件事。贝叶斯。是的,贝叶斯法则再一次成为主要原则。顺便说一句,我无意夸大其词,但贝叶斯公式是有史以来唯一最好的方程式。而且我不是在开玩笑。它无处不在。如果你不知道是什么,请查一查。回到变分自动编码器。我想下面的图片会解释清楚了。
你有了它。一个随机的神经网络。在我们建立一个自己的生成新图像的例子之前,我们应该再讨论一些细节。
VAE的一个关键方面是损失函数。最常见的是,它由两部分组成。重建损失衡量重建的数据与原始数据的不同程度(例如二进制交叉熵)。KL-散度 试图使这个过程正规化,并使重建的数据尽可能地多样化。
def loss_function(recon_x, x, mu, logvar) -> Variable:
BCE = F.binary_cross_entropy(recon_x, x.view(-1, 784))
KLD = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
KLD /= BATCH_SIZE * 784
return BCE + KLD
另一个重要方面是如何训练模型。困难的出现是因为变量是注定的,而是随机的,而梯度下降通常不以这种方式工作。
为了解决这个问题,我们使用参数化归一化。潜在向量(z)将等于我们分布的学习平均数(μ)加上学习标准差(σ)乘以(ε),其中ε遵循正态分布。我们对样本进行重新参数化,使随机性与参数无关。
def reparameterize(self, mu: Variable, logvar: Variable) -> Variable:
#mu : mean matrix
#logvar : variance matrix
if self.training:
std = logvar.mul(0.5).exp_() # type: Variable
eps = Variable(std.data.new(std.size()).normal_())
return eps.mul(std).add_(mu)
else:
return mu
4. 使用自动编码器生成图像
在我们的例子中,我们将尝试使用VAE来生成新的图像。我们将使用MNIST数据集,重建的图像将是手写的数字。正如我已经告诉你的,我使用Pytorch作为一个框架,除了熟悉之外,没有什么特别的原因。首先,我们应该定义我们的层。
def __init__(self):
super(VAE, self).__init__()
# ENCODER
self.fc1 = nn.Linear(784, 400)
self.relu = nn.ReLU()
self.fc21 = nn.Linear(400, 20) # mu layer
self.fc22 = nn.Linear(400, 20) # logvariance layer
# DECODER
self.fc3 = nn.Linear(20, 400)
self.fc4 = nn.Linear(400, 784)
self.sigmoid = nn.Sigmoid()
正如你所看到的,我们将使用一个非常简单的网络,只有密集层(在pytorch的情况下为线性)。下一步是建立运行编码器和解码器的函数。
def encode(self, x: Variable) -> (Variable, Variable):
h1 = self.relu(self.fc1(x))
return self.fc21(h1), self.fc22(h1)
def decode(self, z: Variable) -> Variable:
h3 = self.relu(self.fc3(z))
return self.sigmoid(self.fc4(h3))
def forward(self, x: Variable) -> (Variable, Variable, Variable):
mu, logvar = self.encode(x.view(-1, 784))
z = self.reparameterize(mu, logvar)
return self.decode(z), mu, logvar
最后我们可以训练我们的模型并看到我们生成的图像。
optimizer = optim.Adam(model.parameters(), lr=1e-3)
def train(epoch):
model.train()
train_loss = 0
for batch_idx, (data, _) in enumerate(train_loader):
data = Variable(data)
optimizer.zero_grad()
recon_batch, mu, logvar = model(data)
loss = loss_function(recon_batch, data, mu, logvar)
loss.backward()
train_loss += loss.data[0]
optimizer.step()
def test(epoch):
model.eval()
test_loss = 0
for i, (data, _) in enumerate(test_loader):
data = Variable(data, volatile=True)
recon_batch, mu, logvar = model(data)
test_loss += loss_function(recon_batch, data, mu, logvar).data[0]
for epoch in range(1, EPOCHS + 1):
train(epoch)
test(epoch)
当训练完成后,我们执行测试功能来检查模型的工作情况。事实上,它做得很好,构建的图像与原始图像几乎完全一样,我相信没有人能够在不了解整个故事的情况下把它们分开。
下图第一行是原始照片,第二行是制作的照片。
- 点赞
- 收藏
- 关注作者
评论(0)