从零开始实施GANs
当识别现有数据中的模式时,机器学习算法的效果非常好。然后,这种洞察力可以被用于分类和回归的目的。然而,当被要求生成新的数据时,机器一直在挣扎;2014年,当伊恩-古德费罗(Ian Goodfellow)将生成式对抗网络(GANs)的想法引入机器学习的世界时,这一切都发生了改变。
生成式建模的力量使得GANs扩展了神经网络的能力。这种技术使机器能够生成真实的数据,这些数据似是而非地来自于现有的样本分布。
这篇博客将介绍GANs的基础知识,并使用PyTorch库实现一个GANs。
前提条件
尽管我已经尽量使本文自成一体,但仍然需要有机器学习的基本知识。此外,请读者参考PyTorch库的文档。PyTorch是基于Python的开源机器学习库,我们将用它来实现GAN。
主要收获
在本博客结束时,你将理解以下概念。
- 生成式建模的介绍。
- 什么是GANs?
- GANs是如何工作的?
- GAN架构。
- 如何训练一个GAN。
- 使用PyTorch从头开始实现一个GAN。
简介
"生成对抗网络是过去10年中ML领域最有趣的想法"
Yann LeCun,Facebook的首席人工智能科学家
GAN中的 "生成 "一词表明其唯一目的,即生成新数据。GANs生成的数据取决于我们在哪个数据集上训练了我们的GAN。
另一方面,对抗性这个词指的是生成器和判别器之间类似游戏的竞争,我们将在博客的后半部分了解到这一点。
在开始讨论GANs之前,了解生成模型是很有必要的,因为它是GAN概念的基础。
什么是生成式建模
为了解释生成式建模,我将以一个场景为例,我们已经得到了一个机器学习模型,该模型将给定的图像分类为猫或狗的图像。
现在,如果我们想把这个过程倒过来,也就是说,我们已经得到了关于狗的特征,我们想让我们的模型通过使用给定的特征来生成一个狗的图像,会怎么样?这就是生成式模型的最简单的形式。
为了从数学上解释这个问题,让我们假设我们有一个数据集,其中有实例和相应的标签。机器学习中主要有两种类型的建模。
- 判别性建模。该模型试图捕捉条件概率,。
- 生成式建模。如果是监督学习,该模型试图捕捉联合概率,否则。
在生成模型中,分布被学习并表示在一个潜在空间中。潜伏空间是的隐藏表示。此外,生成模型使用这个潜伏空间作为灵感来源来生成。为了解释为什么被称为灵感之源,我将举一个人类思维的例子。
当我们把人的思想作为生成模型时,人的思想也做了类似的事情。例如,让我们考虑这样一个场景:我们试图向一个一生中从未见过狗的人解释狗是什么。我们将通过给出狗的所有必要特征来描述狗在人眼中的样子。然后,一个人的大脑会把这些信息作为编码信息储存在他们的头脑中。
现在,使用作为灵感的来源,人的大脑将试图重新创造一只狗会是什么样子。注意,这个人的大脑不需要在他们的大脑中生成一个狗的确切形象。这将完全取决于你给的信息和它的建模。
GANs简介
GANs是一类ML技术,由两个同时训练的神经网络组成。一个是用于生成假数据的生成器,另一个是鉴别器,用于对给它的输入是真的还是假的进行分类。
想象一下这样一个场景:一个小偷想从博物馆偷一幅画,并用一幅假画代替,而有一个馆长的工作是检测这幅画是真的还是假的。小偷和馆长之间会有一场竞争,他们两个人都会有一种敌对关系。在我们的案例中,小偷是发电机,而馆长是鉴别者。
GAN架构
GAN的架构由以下部分组成。
- 训练数据集。我们训练鉴别器网络的真实数据样本,以区分真实和虚假数据。你想产生的数据类型取决于你的训练数据集。
- 随机噪声:它是我们生成器网络的一个起点(原始输入)。这个噪声在生成器的帮助下被转化为假数据。
- 生成器网络。它是一个将随机噪声作为输入并产生假数据作为输出的神经网络。它的目标是生成不能被鉴别器网络检测为假数据的数据。
- 鉴别器网络。它也是一个神经网络,将训练数据集中的数据和假数据作为输入,并将它们分类为真实或假的。
- 迭代训练。由于GAN由两个神经网络组成,训练阶段同时训练发生器和鉴别器。我们将在下一部分中详细介绍这一点。
训练一个GAN
如前所述,GAN由两个不同的神经网络组成,即判别器和生成器,它们同时被训练。鉴别器是直接在真实和生成的数据上训练的,而生成器是间接训练的。它是通过鉴别器网络训练的。让我们看看这是如何发生的。
训练生成器
- 从我们的训练数据集中取一个随机数据样本,标记为真实数据。
- 同样,取一个由生成器网络生成的假数据样本,标记为假数据。
这两个输入都通过鉴别器神经网络,计算出分类误差。接下来,反向传播总误差以更新判别器网络的权重和偏置,寻求分类误差最小化。
训练鉴别器
- 以随机噪声向量作为输入,用生成器网络生成一个假的样本$x'。
- 将作为输入传给鉴别器网络以计算分类误差。
- 通过反推这个分类误差来更新生成器网络的权重和偏置,以寻求判别器的误差最大化。
训练何时收敛?
为了解释GAN是如何训练的,首先,我将讨论博弈论中的一个设置,即零和博弈,这是一个双人游戏,其中一个玩家提高了一定的数量,另一个玩家恶化了同样的数量。所有的零和博弈都有一个平衡点,在这个平衡点上,双方都不能改善自己的状况。
这个点被称为纳什均衡。我们在GAN的案例中也有类似的情况。在GAN中,一个判别器与一个生成器竞争,与零和游戏的两个玩家一样。同样地,GAN的训练在达到纳什均衡时也会收敛。
在GAN中实现纳什均衡的时间是:
- 鉴别器以0.5美元的概率对某一特定数据进行真假分类。
- 生成器产生的假数据与真实数据没有区别。
在实践中,由于GAN的训练复杂性,几乎不可能在训练时实现纳什均衡,这意味着GAN的训练实际上永远不会收敛。然而,如果仔细训练,甚至不收敛,GAN也能产生令人满意的结果。
实施
为了实现GAN,我们将使用标准的MNIST数据集。在这个数据集上训练完GAN后,我们将尝试在MNIST数据集的基础上生成新数据。
首先,如果你在你的本地机器上工作,请安装PyTorch库。如果你在Google Colab中工作,你不需要担心这个问题,因为PyTorch已经安装了。
#importing essential libraries
import torch
import torch.optim as optim
import torch.nn as nn
from torch.utils import data
import torchvision
import matplotlib.pyplot as plt
import numpy as np
如果你的机器上有GPU,设备将自动选择为Cuda;否则,将是CPU。我们还在下面的单元格中设置了训练所需的超参数。
#setting up the hyperparameters
learning_rate = 2e-4
noise_dim = 32
image_dim = 28 * 28 * 1
batch_size = 32
num_epochs = 25
生成器网络
在下面的单元格中,我们定义了Generator类,它继承自nn.Module类。我们将发电机模型定义为一个仅由两个隐藏层组成的神经网络。
随机噪声和图像的尺寸被作为参数传递给我们类的构造函数。然后,它将随机噪声作为网络的输入。
class Generator(nn.Module):
def __init__(self, noise_dim, image_dim):
super(Generator,self).__init__()
self.linear1 = nn.Linear(noise_dim, 128)
self.relu = nn.LeakyReLU(0.01)
self.linear2 = nn.Linear(128, image_dim)
self.tanh = nn.Tanh()
def forward(self, x):
out = self.linear1(x)
out = self.relu(out)
out = self.linear2(out)
out = self.tanh(out)
return out
鉴别器网络
在下面的单元格中,我们将判别器网络定义为一个继承自PyTorch的nn.Module class 。它又是一个简单的两层网络,就像发电机一样。首先,它将图像作为输入。
注意,最后一层的激活函数是sigmoid,这意味着网络的输出在0和1之间。1代表MNIST数据集中的真实图像,0代表Generator网络生成的假图像。
class Discriminator(nn.Module):
def __init__(self, in_image):
super(Discriminator,self).__init__()
self.linear1 = nn.Linear(in_image, 64)
self.relu = nn.LeakyReLU(0.01)
self.linear2 = nn.Linear(64, 1)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
out = self.linear1(x)
out = self.relu(out)
out = self.linear2(out)
out = self.sigmoid(out)
return out
加载数据
我们不需要明确地下载MNIST数据集,PyTorch库的数据集类将为我们下载和转换MNIST数据集。
在转换中,我们需要在将图像送入神经网络之前对其进行归一化处理,并将其转换为张量。现在我们将使用DataLoader类来加载数据,以馈送给神经网络。
discriminator = Discriminator(image_dim)
generator = Generator(noise_dim, image_dim)
noise = torch.randn((batch_size, noise_dim))
tf = torchvision.transforms.Compose(
[torchvision.transforms.ToTensor(),torchvision.transforms.Normalize((0.5,), (0.5,)),]
)
ds = torchvision.datasets.MNIST(root="dataset/", transform=tf, download=True)
loader = data.DataLoader(ds, batch_size=batch_size, shuffle=True)
#Visualizing real data
real_sample = iter(loader).next()[0]
img_grid_real = torchvision.utils.make_grid(real_sample, normalize=True)
npgrid = img_grid_real.cpu().numpy()
plt.imshow(np.transpose(npgrid, (1, 2, 0)), interpolation='nearest')
plt.axis('off')

#setting up the optimizers
opt_discriminator = optim.Adam(discriminator.parameters(), lr=learning_rate)
opt_generator = optim.Adam(generator.parameters(), lr=learning_rate)
criterion = nn.BCELoss()
训练阶段
正如我们之前在训练GANs时讨论的那样,同样的原理在PyTorch库的帮助下以编程方式实现。
注意,我们正在反向传播损失D,这是损失D_real和损失D_fake的平均值,以更新判别器网络的权重和偏差。
for epoch in range(num_epochs):
for id, (training_sample, _) in enumerate(loader):
training_sample = training_sample.view(-1, 784)
batch_size = training_sample.shape[0]
### Training the Discriminator
noise = torch.randn(batch_size, noise_dim)
fake_sample = generator(noise)
disc_realSample = discriminator(training_sample).view(-1)
lossD_realSample = criterion(disc_realSample, torch.ones_like(disc_realSample))
disc_fakeSample = discriminator(fake_sample).view(-1)
lossD_fakeSample = criterion(disc_fakeSample, torch.zeros_like(disc_fakeSample))
lossD = (lossD_realSample + lossD_fakeSample) / 2
discriminator.zero_grad()
#we are trying to minimize the total classification error
lossD.backward(retain_graph=True)
opt_discriminator.step()
### Training the Generator
lossD_fakeSample = discriminator(fake_sample).view(-1)
lossG = criterion(lossD_fakeSample, torch.ones_like(lossD_fakeSample))#we are trying to maximize the error of classification of fake image by the discriminator
generator.zero_grad()
lossG.backward()
opt_generator.step()
if id == 0:
print( "Epoch: {epoch} \t Discriminator Loss: {lossD} Generator Loss: {lossG}".format( epoch=epoch, lossD=lossD, lossG=lossG))
生成假数据
现在我们的训练已经完成,让我们试着生成一些新的数据。我们使用fixed_noise来生成新数据,从而将其传递给我们的生成器网络。
由于我们的批处理量是32美元,所以生成器网络将返回32美元的图像。我们正在使用torchvision.utils的make_grid来显示所有的图像。
fake = gen(fixed_noise).reshape(-1, 1, 28, 28)
img_grid_fake = torchvision.utils.make_grid(fake, normalize=True)
npgrid = img_grid_fake.cpu().numpy()
lt.imshow(np.transpose(npgrid, (1, 2, 0)), interpolation='nearest')
plt.axis('off')

总结
我们在这个博客中实现了一个GAN,基于MNIST数据集使用随机噪声生成了新的数据。需要注意的一点是,我们不得不处理MNIST数据。
在这里,分布是相当简单的,并由生成器建模,但如果我们的数据是非常复杂的,并且有多模型分布,那该怎么办。这仍然是GANs的一个问题,它限制了GANs产生多样化结果的能力。