赞
踩
作者提出了一个通过对抗性过程估计生成模型的新框架,在该框架中,将同时训练两个模型:
生成模型G:捕捉数据分布。在统计学中,确定数据的分布就可以生成数据。
判别模型D:估计一个样本的概率。分辨这个样本是来自训练数据,还是来自于G生成的。
对于G的训练过程是最大化D犯错的概率。这个框架对应于一个极小极大的两人博弈。
这在GAN的目标函数中可以明确体现,D会希望这个目标函数越大越好,G则希望该目标函数越来越小。G和D的对抗就体现在这个目标函数中。
在任意函数G和D的空间中,存在一个唯一的解,其中G恢复了训练数据分布,而D在任何地方都等于1/2。
人们最终希望G取得胜利,G能够生成(恢复)数据分布,这也代表着D将无法分辨这个样本是来自训练数据,还是来自于G生成的。
在G和D由多层感知机定义的情况下,整个系统可以使用反向传播进行训练。
原始的GAN重在提出了生成对抗的思想,两个模型用的都是简单的多层感知机(MLP)。后期的很多GAN的改进版本会解决原始GAN的诸多问题,如模式崩溃、梯度爆炸等。
在训练或生成样本期间,不需要任何马尔可夫链或展开的近似推理网络。实验证明了该框架通过对生成的样本的定性和定量评估的潜力。
在所提出的对抗性网络框架中,生成模型与对手对抗:一个判别模型D,学习确定样本是来自模型分布还是来自数据分布。生成模型G可以被认为类似于一个伪造者团队,试图生产假货币并在没有检测到的情况下使用,而判别模型类似于警察,试图检测假货币。这场比赛中的竞争促使两支球队改进他们的方法,直到赝品与真品分不开。
这个故事有两个主角,一个造假者和一个警察。造假者试图制造假币并在未被发现的情况下使用它,警察则试图检测假币。见图2-1(因人民币图片违规用彩纸代替)。
图2-1 故事背景
表2-1 前三轮较量情况
第一轮较量 | 造假者用一张白纸企图糊弄过去。 |
警察直接没收了纸币,毕竟很容易发现百元大钞是红色的而不是白色的。 | |
第二轮较量 | 造假者又拿着一张红纸打算蒙混过关。 |
警察又很快识破了这拙劣的把戏,毕竟上面连数字都没有。 | |
第三轮较量 | 造假者通过前两次的较量提高了造假技术。他在红色的纸上添加了数字100,并画了个人像。 |
警察这次有些迟疑,但仔细观察还是发现了问题,并没收了纸币。警察的检测技术得到了提高。 |
图2-2 前三轮较量情况
经历了n轮较量,警察和造假者互有胜负。同时他们的造假水平和鉴别水平都有了显著提高。
此时,假币上有数字、人头、纹路、凸点等关键信息,已经十分接近真钞了。见图2-3。
图2-3 多轮较量情况
最终,我们希望造假者更胜一筹,造出高质量的假钞。获得胜利。也就是说,警察将无法分辨原始的真钞和生成的假钞。
该框架可以为许多类型的模型和优化算法生成特定的训练算法。
在本文中,生成模型G通过多层感知器传递随机噪声来生成样本的特殊情况,并且判别模型D也是多层感知机。作者将这种特殊情况称为对抗性网络(adversarial nets)。
生成模型G是一个MLP,输入的是一个随机的噪声。这个MLP能够把产生随机噪声的数据分布(通常是高斯分布)映射到任何一个我们想去拟合的分布。同样,判别模型也是一个MLP。
在这种情况下,可以只使用非常成功的反向传播和丢弃算法来训练这两个模型,并只使用正向传播从生成模型中进行采样。不需要近似推理或马尔可夫链。(在计算上更具有优势)
表3-1 原文第三章第一段的翻译
The adversarial modeling framework is most straightforward to apply when the models are both multilayer perceptrons. 对抗性建模框架最直接的应用是在模型都是多层感知机的情况下。 |
To learn the generator’s distribution pg over data x, we define a prior on input noise variables Pz(z), then represent a mapping to data space as G(z; θg), where G is a differentiable function represented by a multilayer perceptron with parameters θg. 为了学习生成器对数据x的分布Pg,我们定义了输入噪声变量Pz(z)的先验分布,然后将映射到数据空间的函数表示为G(z; θg),其中G是一个可微分的函数,由多层感知机参数θg表示。 |
We also define a second multilayer perceptron D(x; θd) that outputs a single scalar.D(x) represents the probability that x came from the data rather than Pg. 我们还定义了一个第二个多层感知机D(x; θd),它输出一个单个标量。D(x)表示x来自(真实)数据而不是来自Pg的概率。 |
We train D to maximize the probability of assigning the correct label to both training examples and samples from G. 我们训练D以最大化分配正确标签给训练样本和从G生成的样本的概率。 |
We simultaneously train G to minimize log(1 − D(G(z))).In other words, D and G play the following two-player minimax game with value function V (G, D): 我们训练D以最大化分配正确标签给训练样本和从G生成的样本的概率。我们同时训练G以最小化log(1 − D(G(z)))。换句话说,D和G进行以下两人极小极大博弈,其价值函数为V(G, D): |
表3-2 符号说明
下面是生成对抗网络的目标函数。
生成器G的目标:让 V(D,G) 尽可能的变小。
辨别器D的目标:让 V(D,G) 尽可能的变大。
目标函数=两项期望的值的和。V(D,G) 的值域是 (-∞,0]。
我们对该公式进行分析可以发现:
当辨别器D表现良好时,D(x) 会较大,D(G(z)) 会较小。
在最好情况下,辨别器D能完全区分真假,此时:
D(x)max=1 → log(D(x))max=0
D(G(z))min=0 → log(1-D(G(z)))max=0
所以 V(D,G)max=0+0=0
当辨别器D表现不好时,D(x) 会较小,D(G(z)) 会较大。
在最坏情况下,辨别器D不能区分真假,此时:
D(x)min=0 → log(D(x))min=
D(G(z))max=1 → log(1-D(G(z)))min=
所以 V(D,G)min=+=
需要注意的是,当辨别器D表现不好时,并不代表生成器G就表现很好。
最终的理想结果也不是D(x)=0或D(G(z))=1,而是达到一种纳什均衡。
表3-3 原文第三章第三段的翻译
In practice, equation 1 may not provide sufficient gradient for G to learn well. 在实践中,方程1(目标函数)可能无法为G提供足够的梯度以进行良好的学习。 |
Early in learning, when G is poor, D can reject samples with high confidence because they are clearly different from the training data. In this case, log(1 − D(G(z))) saturates. 在学习早期,当G表现较差时,D可以以高置信度拒绝样本,因为它们明显与训练数据不同。在这种情况下,log(1 − D(G(z)))会饱和。 |
Rather than training G to minimize log(1 − D(G(z))) we can train G to maximize log D(G(z)). 我们可以训练G以最大化log D(G(z)),而不是将G的训练目标设为最小化log(1 − D(G(z)))。 |
This objective function results in the same fixed point of the dynamics of G and D but provides much stronger gradients early in learning. 这个目标函数会导致G和D动态的相同固定点,但在学习早期提供更强的梯度。 |
如图3-1所示,黑色虚线为真实数据分布(Px),绿色实线来自生成分布(Pg(G)),蓝色虚线时判别分布(D)。我们同时更新判别分布(D)来训练生成对抗性网络,使得其在来自数据生成分布(Px)的样本与来自生成分布(Pg(G))的样本之间进行区分。
通俗的理解:
生成分布(Pg(G))和真实数据分布(Px)的峰值越接近,则越相似。
在判别分布(D)中,认为该处存在真实数据→值为1,认为该处存在生成(假)数据→值为0,完全无法分辨→值为1/2。
图3-1(a) :一开始,Pg(G) 与 Px 有明显差别,峰值不在一个位置,这表明生成器G的效果并不好 。同时,D也不具备很好的分辨能力,在 Px 的非峰值区域也会输出较高的值。
图3-2(b):辨别器D经过更新后,学会了 Px 和 Pg(G) 的分布,在 Px 的峰值位置置值为1,在 Pg(G) 的峰值位置置值为0。
图3-2(c):生成器经过更新后,Pg(G) 逐渐向 Px 靠拢,D还是可以区分两种分布。
图3-2(d):多次训练更新后,Pg(G) 与 Px 的分布完全重合,这表明生成器G的效果非常好,这也是我们希望看到的。辨别器D则完全无法分辨两个分布,此时D(x)=1/2。
图3-1 模型训练演示
图3-2 GAN的伪代码
输入:一个小批量(m个)的噪声样本z,一个小批量(m个)的真实样本x。
更新D:首先更新D中的θd,更新k次,需要调用目标函数中的两项,因为两项中都包含D。
更新G:然后更新G中的θg,更新1次,只需要调用目标函数中的后一项,只有后一项包含G。
收敛:很不稳定,因为存在两个更新。两个都收敛,或是一个波动一个收敛等等情况?无法确定收敛。后续工作会优化这个问题。
作者对理论结果进行了说明,主要包括两个部分:
目标函数存在一个全局最优解:当且仅当是生成器G学到的分布和真实数据的分布是相等的情况。
伪代码所提出的算法确实可以求解目标函数。
以下是一个简单的基于PyTorch的生成对抗网络(GAN)的代码示例:
- import torch
- import torch.nn as nn
- import torch.optim as optim
- from torchvision import datasets, transforms
- from torch.utils.data import DataLoader
- from torch.autograd import Variable
-
- # 定义生成器
- class Generator(nn.Module):
- def __init__(self):
- super(Generator, self).__init__()
- self.fc1 = nn.Linear(100, 128)
- self.fc2 = nn.Linear(128, 392)
- self.fc3 = nn.Linear(392, 784)
- self.relu = nn.ReLU()
- self.tanh = nn.Tanh()
-
- def forward(self, x):
- x = self.relu(self.fc1(x))
- x = self.relu(self.fc2(x))
- x = self.tanh(self.fc3(x))
- return x
-
- # 定义判别器
- class Discriminator(nn.Module):
- def __init__(self):
- super(Discriminator, self).__init__()
- self.fc1 = nn.Linear(784, 392)
- self.fc2 = nn.Linear(392, 128)
- self.fc3 = nn.Linear(128, 1)
- self.relu = nn.ReLU()
- self.sigmoid = nn.Sigmoid()
-
- def forward(self, x):
- x = self.relu(self.fc1(x))
- x = self.relu(self.fc2(x))
- x = self.sigmoid(self.fc3(x))
- return x
-
- # 定义训练参数
- device = "cuda" if torch.cuda.is_available() else "cpu"
- batch_size = 100
- num_epochs = 200
- learning_rate = 0.0002
-
- # 定义数据集
- transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize(mean=[0.5], std=[0.5])])
- mnist_data = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
- data_loader = DataLoader(dataset=mnist_data, batch_size=batch_size, shuffle=True)
-
- # 初始化网络
- G = Generator().to(device)
- D = Discriminator().to(device)
-
- # 定义损失函数和优化器
- criterion = nn.BCELoss().to(device)
- optimizer_G = optim.Adam(G.parameters(), lr=learning_rate)
- optimizer_D = optim.Adam(D.parameters(), lr=learning_rate)
-
- # 训练网络
- for epoch in range(num_epochs):
- for i, (images, _) in enumerate(data_loader):
- # 定义真实数据和生成的数据
- real_images = Variable(images.view(batch_size, -1)).to(device)
- fake_images = Variable(torch.randn(batch_size, 100)).to(device)
-
- # 训练判别器
- optimizer_D.zero_grad()
- real_labels = Variable(torch.ones(batch_size, 1)).to(device)
- fake_labels = Variable(torch.zeros(batch_size, 1)).to(device)
- real_outputs = D(real_images)
- fake_outputs = D(G(fake_images))
- d_loss = criterion(real_outputs, real_labels) + criterion(fake_outputs, fake_labels)
- d_loss.backward()
- optimizer_D.step()
-
- # 训练生成器
- optimizer_G.zero_grad()
- fake_images = Variable(torch.randn(batch_size, 100)).to(device)
- fake_outputs = D(G(fake_images))
- g_loss = criterion(fake_outputs, real_labels)
- g_loss.backward()
- optimizer_G.step()
-
- # 输出训练过程
- print("Epoch [%d/%d], d_loss: %.4f, g_loss: %.4f" % (epoch+1, num_epochs, d_loss.item(), g_loss.item()))
-
- # 保存模型
- torch.save(G.state_dict(), 'generator.pth')
- torch.save(D.state_dict(), 'discriminator.pth')
在这个代码示例中,我们首先定义了生成器和判别器两个神经网络。生成器将一个100维的噪声向量映射成784维的向量,代表了一张28x28的灰度图像。判别器则将这个784维的向量映射为一个实数,表示该图像是否为真实的MNIST数据集中的图像。
在训练过程中,我们首先训练判别器,将真实的MNIST图像标记为1,生成的图像标记为0,并计算判别器的损失函数。然后我们训练生成器,将生成的图像标记为1,并计算生成器的损失函数。最后,我们输出训练过程中的判别器损失和生成器损失,并保存训练好的生成器和判别器模型。
在使用这个生成器模型生成新的图像时,我们可以使用以下代码:
- import torch
- import matplotlib.pyplot as plt
- import torchvision.utils
- from torch.autograd import Variable
- import torch.nn as nn
- import numpy as np
-
- # 定义生成器
- class Generator(nn.Module):
- def __init__(self):
- super(Generator, self).__init__()
- self.fc1 = nn.Linear(100, 128)
- self.fc2 = nn.Linear(128, 392)
- self.fc3 = nn.Linear(392, 784)
- self.relu = nn.ReLU()
- self.tanh = nn.Tanh()
-
- def forward(self, x):
- x = self.relu(self.fc1(x))
- x = self.relu(self.fc2(x))
- x = self.tanh(self.fc3(x))
- return x
-
- # 加载模型
- device = "cuda" if torch.cuda.is_available() else "cpu"
- G = Generator().to(device)
-
- # 尝试加载模型的权重参数
- try:
- G.load_state_dict(torch.load('generator.pth'))
- print('模型参数加载成功!')
- except:
- print('模型参数加载失败,请检查模型的结构是否一致,或者权重参数是否被正确保存。')
-
- # 切换到评估模式
- G.eval()
-
- # 打印出生成器的输入和输出
- z = Variable(torch.randn(1, 100)).to(device)
- print('生成器的输入:', z)
- image = G(z)
- image = image.view(28, 28) # Reshape to image format
- print('生成器的输出:', image)
-
- # 生成图像
- img_grid = torchvision.utils.make_grid(image, nrow=8, normalize=True)
-
- # 将网格状图像转换为numpy数组并交换通道维度
- img_grid = img_grid.cpu().numpy().transpose((1, 2, 0))
-
- # 将像素值缩放到[0, 255]范围内,并转换为整数类型
- img_grid = (img_grid * 255).astype(np.uint8)
-
- # 调整通道顺序为RGB
- img_grid = img_grid[:, :, [2, 1, 0]]
-
- # 显示图像
- plt.imshow(img_grid, interpolation='nearest')
- plt.axis("off")
- plt.show()
这里我们首先加载训练好的生成器模型,然后生成一个100维的噪声向量,并将其输入到生成器中生成一张新的图像。由于我们在训练时对生成的噪声向量进行了归一化处理,因此在生成新的图像时也需要对噪声向量进行归一化处理,否则生成的图像可能会很模糊或失真。
图3-3 输出图片示例
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。