目录
三、Generative Adversarial Network (GAN)
二、JS divergence is not suitable
摘要
本周学习了关于生成式对抗网络的内容,GAN由一个生成网络和一个判别网络组成,生成网络从随机噪声中生成新的数据实例,判别网络尝试区分生成的数据实例和真实数据实例。生成网络和判别网络之间的竞争促进了生成网络的能力,使其能够制造出越来越逼真的数据。GAN的主要优点在于能够生成高质量、逼真的数据,这使得GAN在许多领域得到广泛应用,包括图像生成、超分辨率、风格迁移和数据增强等。然而,GAN也存在一些局限性,例如训练的稳定性问题,模式崩溃等。尽管有这些挑战,GAN的研究仍在不断发展,新的改进和变体持续出现,为各种应用领域提供了强大的工具。
ABSTRACT
This week, I studied about Generative Adversarial Networks (GAN). A GAN is composed of a generator network and a discriminator network. The generator network creates new data instances from random noise, while the discriminator network attempts to distinguish between these generated data instances and real ones. The competition between the generator and the discriminator network enhances the ability of the generator network, enabling it to produce increasingly realistic data. A key advantage of GAN is their ability to generate high-quality, realistic data, leading to their widespread application in various fields including image generation, super-resolution, style transfer, and data augmentation. However, GAN also have some limitations, such as issues with training stability and mode collapse. Despite these challenges, research on GAN continues to develop, with new improvements and variants emerging to provide powerful tools for various application domains.
一、生成器(generator)
区别于给定x输出y的神经网络,现在给神经网络的输入添加一个从简单分布(Simple Distribution)中随机采样的变量,生成一个复杂的满足特定分布规律的输出(Complex Distribution)
分布:高斯分布、均一分布
z所选择的不同的distribution之间的差异并没有真的非常大,generator会想办法把这个简单的distribution对应到一个复杂的distribution。所以可以简单地选择“正态分布”
方法:
X,Z两个向量直接接起来,变成一个比较长的向量,作为network的input
X跟Z正好长度一样,相加以后当做network的input
一、何为高斯分布
正态分布的性质:
- 概率密度曲线在均值
处达到最大,并且左右对称;
- 一旦均值和标准差确定,那么正态分布的曲线也就确定了;
- 当X的取值向横轴左右两个方向无限延伸时,曲线的两个尾端也无限渐近横轴,但理论上永远不会与之相交;
- 正态随机变量在特定区间上的取值概率由正态曲线下的面积给出,而且其曲线下的总面积等于1;
- 均值
可取实数轴上的任意数值,它决定了正态概率密度曲线的具体位置;
- 标准差决定了概率密度曲线的“陡峭”或“扁平”程度,标准差越大,曲线越扁平;标准差越小,曲线越陡峭。这是因为,标准差越小,意味着大多数变量距离均值的距离越短,也就是说大多数变量都紧密地聚集在均值周围,图形所能覆盖的变量值就少些,图形上呈现瘦高型。相反,标准差越大,数据跨度就越大,分散程度越大,所覆盖的变量就越多,图形呈现“矮胖型”。
二、为什么distribution
当我们的任务需要一点创造力的时候,一个输入有多种可能的输出,这些不同的输出都是对的,但又只需要输出一个对象,这时就需要使用generator,在网络中加入随机输入,让网络在复杂分布中随机选择一个对象输出(输出有几率的分布)。
三、Generative Adversarial Network (GAN)
GAN是众多generator中比较有代表性的一个,而GAN的种类也非常多,常常会出现重名的GAN和奇奇怪怪的GAN名字。
以unconditional generation(不考虑x,只考虑z)为例,假设Z是从一个normal distribution里采样出来的向量,通常会是一个low dimensional的向量,维度是你自己决定的,丢到generator裡面,输出一个非常高维的向量,希望是一个二次元人物的脸
Discriminator也是一个神经网络(可以考虑使用CNN、transformer等),可以将generator输出的图像转换成数字,越接近于1,说明图像越真实,品质越高。
四、Basic Idea of GAN
在最开始GAN的训练方式是:
1、有一个Generator和一个Discriminator,那么一开始生成器的参数基本都是随机化的,那么它所产生的图像也很难接近真实的动漫人脸,而辨别器的主要任务就是找出生成器生成的图片与真实的动漫图片之间的不同,例如在下面的图片中它第一次辨认的依据是眼睛
2、那么第二轮呢生成器就学习到应该要产生出眼睛来骗过辨别器,那么其参数调整后就生成出有眼睛的动漫人脸,那么辨别器就需要找出更多的特别来进行辨认,例如嘴巴、头发
3、第三轮呢生成器就再次调整,生成出嘴巴、头发等,那么这时候辨别器就需要再次调整寻找新的特征
4、因此生成器和辨别器就是在这个对抗的过程中不断进步
五、训练方法
生成器和辨别器具体的训练步骤如下所示:
Step1:随机初始化生成器和辨别器的参数,并固定住生成器的参数,让生成器接受向量并产生一些图像的输出;另外在真实动漫人脸数据库中采样一些样本出来标识为1,而生成器生成的假图标识为0,然后用这些样本去训练辨别器,让它输出一个0到1之间的数值,1代表越接近于真实的图片。如下所示:
Step2:固定住辨别器的参数,让生成器生成一张图片并传给辨别器得到一个输出,代表该图片为真实图片的可能性,然后调整生成器的参数使辨别器的输出越高越好,那么这里调整的方法跟普通的神经网络类似, 可以把生成器和辨别器连在一起看成一个大的网络,是接受一个向量的输入然后输出一个数值,那么就同样可以采用梯度下降等的方式来调整生成器的参数。这个步骤也可以看成是生成器在学习如何欺骗辨别器。
Step3:不断重复Step1和Step2的训练,直到生成器输出的图片能够满足要求
更有趣的应用,如果我们用来训练产生真实人脸,可以实现两张人脸之间的过度,具体我们可以看下图,就是在两张人脸对应的向量之间做插值,我相信这个效果也有很多小伙伴在网络上看过,我也是此刻才明白具体的原理,也就是用各式各样的GAN来实现。
六、GAN的理论
一、基础理论介绍
在GAN中,我们可以把我们的目标进行简化,就比如下图,我们希望能够找到一组G的参数,它能够对分布z的输入产生对应的分布,而假设我们真实的分布为
,我们希望它们能够越接近越好,即:
其中Div可以用来衡量两个分布之间的距离,例如KL散度等等。但是目前的问题是这个Div很可能写出来是一个非常复杂的积分等等,因为我们根本不知道两个分布是什么,我们根据就不知道怎么表示出来或者说怎么进行最小化,因此这也是GAN在训练的时候会遇到的常见问题。而GAN告诉我们的解决方案就是:不需要知道两个分布的具体函数,只需要有办法能够从分布中进行采样即可,即 和
只需要知道怎么采样即可,如下图:
具体的实现还是有辨别器来做到的。见下图:
在训练辨别器的时候,像我们之前说到的,使用了从真实数据中采样的数据和生成的假的数据来分别加上标签进行训练,然后重点就在于损失函数的确定,从图中可以看到损失函数的具体是式子为:
那么实际上V ( G , D )就是加了负号的交叉熵,那我们希望最大化V ( G , D )就相当于最小化交叉熵,也就相当于将辨别器看成一个二分类的贝叶斯分类器来训练。而另外一个需要注意的点是当你最大化V ( G , D )的时候,解出来的这个V ( G , D ) 的值实际上和JS divergence是有关的。这个观点可以直观地进行理解:
1、当两个分布很接近的时候,即它们之间的divergence很小的时候,辨别器很难完全地将它们分开,因此实际上它训练参数之后得到的最大化的V ( G , D ) 还是比较小的,那么跟divergence比较小是对应的
2、当两个分布不接近,即它们之间的divergence很大的时候,辨别器就能够轻易地将它们分开,因此实际上它训练参数之后得到的最大化的V ( G , D ) 就会比较大的,那么跟divergence比较大是对应的
因此,divergence的值和V ( G , D ) 的值之间可以认为存在一定的正比例关系,那么我们在一开始中用到Div的目标函数就可以用V ( G , D ) 进行替换,即:
而我们之前说到了G和D之间对抗不断调整的过程实际上就是这个新的目标函数的求解过程。
二、JS divergence is not suitable
我们需要先了解一下为什么JS divergence存在问题,之后再来了解著名的WGAN。
首先,我们要明确和
它们之间相交的部分实在是太少了,具体的理由有两个:
1、它们都是高维空间中的能够表示为图片(或者说我们想要的动漫人脸)的向量,但是在高维空间中满足条件的向量只占非常小的一部分,例如可以认为它们分别只占二维空间中的一条直线或者曲线,那么它们之间相交之处只能是几个点而已(除非它们重合),那么就可以认为它们之间相交的部分实在特别少
2、我们是对真实的两个分布之间进行采样的,就算原始的真正的分布它们之间存在重叠的部分,但如果我们采样的不是特别多,不能够完全地描述出两个原来的分布,那还是可以找到一个分界将这两类采样出来的点完全分开,那么也可以认为它们是没有相交的部分的。
而JS divergence的特性在于如果两个分布没有交叠,计算出来永远时log2,可以看下图:
从图中可以看到,第二个情况明明比第一个情况更加接近,但是实际上JS计算出来的值还是一直都是log2,除非它们真的出现了交叠,才会计算出新的值,这样就导致假设我们在分布中采样的样本数不是非常非常多,那我们用之前类似于贝叶斯的思想来训练分类器的时候可以发现我们总是100%的正确率,因为根据这个JS就无法提供指导性的作用,它无法告诉机器说让两个分布越来越接近可以让损失函数越来越小,因此无法训练成功。
三、Wasserstein distance
Wasserstein distance是另一种衡量两个分布之间的距离,可以通俗的想象成两个分布分别是两堆土,如下图:
那么两个分布之间的距离就是用推土机将分布P推到分布Q的位置时经过的距离。但实际上的分布可能更复杂一点:
例如上图,那么从分布P经过推土机的操作得到分布Q可以有很多种方式,可以认为每一种方式的d都不一样,那么Wasserstein distance的定义就是穷举所有的d,选取里面最小的d来作为真正的Wasserstein distance。那么也就是说我们还需要解这个Wasserstein distance的优化问题。
那么将计算距离更换为Wasserstein distance,便可以让我们发现在两个分布越来越接近的时候计算出来的距离越来越小,这样就可以指导我们的网络往这个方向去调整。
四、WGAN
当用Wasserstein distance取代JS divergence的时候,此时的GAN就称为WGAN。那么现在的问题就在于Wasserstein distance这个距离应该怎么距离计算呢?推导过于复杂,结论就是解下面这个函数,最终得到的值(目标函数的值)就是我们要计算的两个分布之前的Wasserstein distance:
这跟前面那个将目标函数Div更换成贝叶斯那个是同理的。
但是此处对于评估函数D还是有限制的,要求它是足够平滑的,不能够是具有剧烈变化的,否则例如下图:
只要这两堆没有重叠,就会将取值推向两个无穷的极端
七、生成器效能评估与条件式生成
一、训练的问题
虽然已经将评估分布的距离更换成Wasserstein distance,但实际上GAN还是很难训练的,主要原因是生成器和辨别器它们彼此之间是相互砥砺、相互进步的,只要其中有一个训练发生了差错,那么另外一个肯定也无法继续提升,即只要其中一个在某次更新过程中没有更新,那么可能整个训练过程就坏掉了,无法再继续提升下去了。
特别是在将GAN用于生成文字的时候更难训练,例如在下图的模型中,我们产生了一段文字然后让辨别器查看文字是否是机器生成的并且打分,那么如果采用梯度下降的方法我们给生成器的参数带来了一点微小的变动,但由于各个输出向量都是采用取那个概率最大的文字作为输出的方式,因此微小的变化计算能够改变各个概率的值,但一般不会使得概率最大的文字改变,也就是输出没有发生改变,那么也就没有办法进行微分。
另外一个需要注意的点是应该如何评估GAN这种模型所生成结果的好坏呢。
二、评估生成器的好坏
这个问题没有一个标注性的答案,在GAN刚出现的时候,对于生成结果都是由人们自己来判断效果,这样主观性太强而且不够稳定。
现在对于生成图像的系统,可以再另外训练一个影像辨识系统来进行验证,例如生成的都是狗的图片,那么在这个影像辨识系统中接受输入,并且输出是概率分布,那我们就希望这个概率分布能够有一个分类,其概率能够越接近于1越好,就说明大部分图片我们将其归为一类,这样就说明可能生成效果还是不错的;而如果分成了很多类而且概率都差不多,那么说明生成效果就不好了。但是在这个评估策略中可能会遇到一个问题,称为Mode Collapse,可以通过下图直观理解,这种问题就是说虽然能够产生出效果比较好的结果,但可能那些结果具有很高的相似性,例如左下方的红色星星都集中的同一个点,很难像真实的分布能够较为广阔;在右边的例子中很可能产生的图像越来越相似,例如我指出来的那几张基本上都一样了,这种情况可能训练到最后只能够输出这一张图片而已。
产生这个现象的原因可以直观理解为:例如左下方的例子中,聚集的地方可以称为辨别器的盲点,只要产生在这附近的结果那么辨别器就无法辨认出来是假的,因此生成器就会不断产生这附近的图片。
另外一个问题是Mode Dropping,它比上一个问题更难侦测到,先来直观说明问题的内容,看下图:
就是虽然产生的数据能够不集中于某一处,分布看起来也还行,但是只学习到真实分布的一部分,另外一部分完全没有学习到,从下图的例子中可以很明显地看出来,虽然在两次产生的图像集中看起来好像有分布得很均匀,但是我们可以发现第一次只有白人,第二次只有黄种人,这就说明它没有学习到真正的分布,只学习到其中某一部分的分布,黑色人种的图片完全没有学习到。
那么评估结果多样性的一个思路是:将产生的所有图片都丢进去一个图像分类系统之中,那么每张图片就会产生对应的分布,我们再将所有分布求和取平均,那么如果得到的最终分布越平坦,就说明多样性越好,如下图:
另外一种测量指标成为FID,其具体的做法为:将图片放进去影像辨识系统之后,由于要进行分类因此肯定最后会经过一个softmax环节,我们将进入softmax之前的最后一层的的输出的这个向量,用来代表这个图片,那么对于真实的图片和生成的图片就都可以得到很多的向量,再将这些向量来计算FID(具体的计算方法就不拓展了),那么FID的评价标准是两个分布越接近其数值就越小,不过计算过程中会假设两个都是高斯分布。这个方法还有一个问题就是为了模拟出真实的分布,它需要很多的样本经过影像辨识系统得到的向量,因此计算量会很大。
八、Conditional Generator
Conditional GAN就是输入的时候除了之前从分布中采样得到的z之外,还有一个x,它可以用x来指定y的输出,例如应用于文字转图像的例子:
那么这种情况下的训练过程也要进行调整,在训练辨别器的时候不仅仅要输入产生的图片,更要输入原始输入x,并且需要将它们进行配对,才能够让机器学习到看到这样配对的文字和图像才能够给高分,而往往在训练辨别器时还要加入一部分特殊的训练资料,即我们将原本数据中图和文字已经配对好的样本,都进行打乱,使得文字和图像并没有关系,那么用这种样本告诉机器说看到这样的样本也要给低分,那么机器才能够一方面学习到图像要接近于真实动漫人脸,还学习到要满足我们的输入x,如下图:
九、Cycle GAN
在之前的各种普通的网络结构中,一般样本都是有对应的标注的,即x和y之前的成对的,但是在一些训练任务中它们之间并没有成对,例如下图的影像风格转换的任务中,x是真实的人脸,而y要求是人脸的动漫版本,那么在这个任务中就不具有成对的x和y来进行训练了那么实际上,GAN在这种不成对的样本的训练任务中是可以发挥作用的。那么应该怎么应用呢?如果直接套用GAN的思想,如下图:
因为GAN的辨别器要求是辨别你生成器的输出是不是y的那个分布,那这个就会导致生成器发现只要生成一张是动漫人脸的图片就可以让辨别器打高分,而这个动漫人脸是否和输入的人脸相似这并不重要,可以说生成器完全忽略了输入,那么怎么解决这个问题呢?就用到了Cycle GAN,其具体的做法可以看下图:
其最重要的特点在于训练了两个生成器,多出来的生成器用于将第一个生成器生成的动漫人脸还原成真实的人脸,而我们训练的时候会要求原先的人脸和还原的人脸越接近越好。
但这个Cycle GAN好像并没有限制中间产生的动漫人脸必须和原先的人脸非常地相像,例如机器可能学习到原始人脸戴着眼镜就将眼睛去掉然后加上一颗痣,第二个生成器就学习到看到一颗痣就将痣去掉然后加上一副眼镜,这说明在Cycle GAN是没有对原始输入和产生的动漫人脸的相似度进行限制,但在实际训练中这种情况其实很少发生,可以认为网络架构不会去做这么复杂的问题,它会尽量去输出相似的东西而已,这也是在理论上和实际上的不同。
并且这个Cycle GAN可以是双向的,例如下图:
十、极大似然法推导
十一、GAN代码示例
一、导包
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim #优化
import numpy as np
import matplotlib.pyplot as plt #绘图
import torchvision #加载图片
from torchvision import transforms #图片变换
二、对数据做归一化(-1,1)
transform=transforms.Compose([
#将shanpe为(H,W,C)的数组或img转为shape为(C,H,W)的tensor
transforms.ToTensor(), #转为张量并归一化到【0,1】;数据只是范围变了,并没有改变分布
transforms.Normalize(0.5,0.5)#数据归一化处理,将数据整理到[-1,1]之间;可让数据呈正态分布
])
三、下载数据到指定的文件夹
train_ds = torchvision.datasets.MNIST('data',
train=True,
transform=transform,
download=True)
四、定义生成器
class Generator(nn.Module):
def __init__(self):
super(Generator,self).__init__()
self.main=nn.Sequential(
nn.Linear(100,256),
nn.ReLU(),
nn.Linear(256,512),
nn.ReLU(),
nn.Linear(512,784),
nn.Tanh()#对于生成器,最后一个激活函数是tanh,值域:-1到1
)
#定义前向传播
def forward(self,x): #x表示长度为100的noise输入
img = self.main(x)
img=img.view(-1,28,28)#转换成图片的形式
return img
五、定义判别器
class Discriminator(nn.Module):
def __init__(self):
super(Discriminator,self).__init__()
self.main = nn.Sequential(
nn.Linear(784,512),
nn.LeakyReLU(),
nn.Linear(512,256),
nn.LeakyReLU(),
nn.Linear(256,1),
nn.Sigmoid()
)
def forward(self,x):
x =x.view(-1,784) #展平
x =self.main(x)
return x
六、初始化模型,优化器及损失计算函数
#设备的配置
device='cuda' if torch.cuda.is_available() else 'cpu'
#初始化生成器和判别器把他们放到相应的设备上
gen = Generator().to(device)
dis = Discriminator().to(device)
#训练器的优化器
d_optim = torch.optim.Adam(dis.parameters(),lr=0.0001)
#训练生成器的优化器
g_optim = torch.optim.Adam(dis.parameters(),lr=0.0001)
#交叉熵损失函数
loss_fn = torch.nn.BCELoss()
七、GAN的训练
#训练循环
for epoch in range(50):
#初始化损失值
d_epoch_loss = 0
g_epoch_loss = 0
count = len(dataloader) #返回批次数
#对数据集进行迭代
for step,(img,_) in enumerate(dataloader):
img =img.to(device) #把数据放到设备上
size = img.size(0) #img的第一位是size,获取批次的大小
random_noise = torch.randn(size,100,device=device)
#判别器训练(真实图片的损失和生成图片的损失),损失的构建和优化
d_optim.zero_grad()#梯度归零
#判别器对于真实图片产生的损失
real_output = dis(img) #判别器输入真实的图片,real_output对真实图片的预测结果
d_real_loss = loss_fn(real_output,
torch.ones_like(real_output)
)
d_real_loss.backward()#计算梯度
#在生成器上去计算生成器的损失,优化目标是判别器上的参数
gen_img = gen(random_noise) #得到生成的图片
#因为优化目标是判别器,所以对生成器上的优化目标进行截断
fake_output = dis(gen_img.detach()) #判别器输入生成的图片,fake_output对生成图片的预测;detach会截断梯度,梯度就不会再传递到gen模型中了
#判别器在生成图像上产生的损失
d_fake_loss = loss_fn(fake_output,
torch.zeros_like(fake_output)
)
d_fake_loss.backward()
#判别器损失
d_loss = d_real_loss + d_fake_loss
#判别器优化
d_optim.step()
#生成器上损失的构建和优化
g_optim.zero_grad() #先将生成器上的梯度置零
fake_output = dis(gen_img)
g_loss = loss_fn(fake_output,
torch.ones_like(fake_output)
) #生成器损失
g_loss.backward()
g_optim.step()
#累计每一个批次的loss
with torch.no_grad():
d_epoch_loss +=d_loss
g_epoch_loss +=g_loss
#求平均损失
with torch.no_grad():
d_epoch_loss /=count
g_epoch_loss /=count
D_loss.append(d_epoch_loss)
G_loss.append(g_epoch_loss)
print('Epoch:',epoch)
gen_img_plot(gen,test_input)
由于迭代次数较少,所以得到的结果图像并不是很清晰:
总结
生成对抗网络(GAN)是一种强大的深度学习模型,由两个部分组成:生成器和判别器。生成器试图生成与真实数据相似的假数据,而判别器试图区分真实数据和假数据。这两者相互对抗,逐渐提高各自的性能。GAN通常被用于图像生成、超分辨率、样式转换等任务。然而,GAN的训练过程可能复杂且不稳定,需要仔细选择和调整超参数。