GAN的原理

@TOC

一、GAN基本介绍

GAN是一个生成网络,他有两个网络G和D

  • Generator:生成图片的网络,他可以接受一个随机噪声,这里我们一般把噪声设为标准高斯分布(当然也可以是其他的噪声分布),,让通过生成网络得到其实就是一幅图片
  • Discriminator:判别网络,我们将生成器生成的图片通过判别网络进行判断,最终会得到一个标量,即,数值越高代表图片越真实,数值低代表图片越假

GAN的主要目的就是通过将真实的图片(即数据集中的图片GT)以及通过生成器生成的图片输入到判别网络中,让判别网络无法判断出这幅图片究竟是真实的图片还是通过噪声生成的图片,当训练完成时,

二、数学分析

1. 生成器G

我们有一批数据,假设是一些图片,他们的数据分布是,当然我们没有办法知道的具体数值,不然我们就可以直接从里面采样得到图片了,所以我们需要求解此分布,这就需要用到生成器

首先我们从标准高斯噪声中进行采样得到,然后我们让通过生成器就可以得到一个分布记为,其中是一个参数用来控制的分布,假设我们从真实的数据分布中得到一批分布,此时我们求一个来使的取值最大,即求最大似然

其中代表了从取得的数据,接下来

注意上式中减掉的由于这是一个常数,所以并不影响最终的结果,通过上述我们可以得知,最大化似然其实就是给生成器找一个使得

到这里有的人就会想,既然我们要求这两个概率接近,那我们直接在这里进行反向传播来训练不就可以了吗,当然不可以,因为我们对噪声进行随机采样时,我们并不知道哪个噪声对应哪张图片,也就是说我们并不知道,因此从中随机取样本与求距离时没有意义的,接下来我们看一看GAN是怎么做的

2. 判别器D

我们直接给出paper中的公式

此时我们固定,即

上式子就代表了之间的差异,因为第一项代表了真实的数据,显然对于真实的数据判别器要给一个很高的分数;后一项代表了噪声生成的数据,对于假的数据我们就要给一个很低的分值,所以第二项的分值要变小,但是由于里面是负号,所以取反就要变大。因此如果来的是真实的数据第一项变大,来的是虚假的数据第二项变大
注意:在训练过程中是先训练判别器,在训练一次生成器的。在训练时,判别器是知道输入的数据是真实的(1)还是虚假的(0),因此在训练过程中会变得越来越强。即来的是真实的数据,输出会增大变为1,对于噪声数据,输出会变小变为0

(1) 求D-max

下面我们对其进行求导算出最优的

求导

在我们计算出后,带入最初式中得

对于一个底数的Jensen–Shannon divergence取值范围为

(2) 求G-min

通过上式我们可以发现固定的最小值为,最大值为0
我们已经得到了,接下来求解,即

直观的可以看出当时,能取得最小值

上面可以看出,训练的目的是让判别器能够识别那些图片是从真实样本分布中获得的,那些是通过噪声生成的,通过训练来让判别器变得更强,所以用的是,但是训练恰恰相反,训练的目的是让生成器能够生成更加真实的图片,来“误导”判别器使其判断错误,所以用的是

通俗来讲,训练的目的是使得的分布接近,而训练训练的目的是使得,而我们最终的目的就是得到,所以如果想要得到一个较好的概率结果,每次训练时都应该先多训练几次(k次),一般一个batch就训练一次(理解为是一个老师,是一个学生,如果老师没有足够的水平(没训练好),怎么去指导学生呢),这样当最后训练好的时候,

下面给出paper中的一个图,其中绿色的线代表分布的变化,黑色的点线代表真实的概率分布,蓝色的线代表判别器的最优解变化过程,可以发现最后变为了

三、训练过程

训练过程是交替进行的,即先训练,在训练

算法流程:

  • 先用步来训练
  • 在训练

注意上述步骤中 在求第二步的时候,其实只有第二项包含了,第二步把第一项给剔除了

下面贴上一段代码来感受以下训练过程,里面取了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
""" 
Train D
将噪声通过G在通过D得到值与0用BCELoss求损失-->f_loss
将真实的图片通过D得到的值与1用BCELoss求损失-->r_loss
将两个损失进行反向传播
"""
z = torch.randn(bs, z_dim).cuda() # torch.Size(bs=64, z_dim=100)
r_imgs = imgs.cuda() # torch.Size([64, 3, 64, 64])
f_imgs = G(z) # torch.Size([64, 3, 64, 64])

# label
r_label = torch.ones((bs)).cuda() # torch.Size([64])
f_label = torch.zeros((bs)).cuda() # torch.Size([64])

# dis
r_logit = D(r_imgs.detach()) # torch.Size([64])
f_logit = D(f_imgs.detach()) # torch.Size([64])

# compute loss
r_loss = criterion(r_logit, r_label)
f_loss = criterion(f_logit, f_label)
loss_D = (r_loss + f_loss) / 2

# update model
D.zero_grad()
loss_D.backward()
opt_D.step()

"""
train G
将噪声通过G在通过D得到的值与1求BCELoss-->loss
将loss进行反向传播
"""
# leaf
z = torch.randn(bs, z_dim).cuda() # torch.Size(64, 100)
f_imgs = G(z) # torch.Size([64, 3, 64, 64])

# dis
f_logit = D(f_imgs) # torch.Size([64])

# compute loss
loss_G = criterion(f_logit, r_label)

# update model
G.zero_grad()
loss_G.backward()
opt_G.step()

四、训练图形化

1. 整体模型图

首先给出一个GAN的整体模型图

2. D&&G交替训练

如下图所示,我们要求的是找一个使得结果最大,找一个使得结果最小
这三个图没有训练顺序,所以仅作为一个参考,也就是说我们找到一个使得最大的之后,在最大处通过改变使得峰顶降低

图中绿色的虚线就是之间的距离,训练的目的就是使得二者距离相等,就时将顶部红色的点压到横坐标上

五、存在的问题

1. 为什么要训练k次D,在训练一次G

如图所示,我们训练一次,同时更新一次会出现的问题
假设我们训练了一次找到了一个使得较大,但是不一定非常大(注意这里陈述的和图中不太一致),然后我们更新,会将此时所在的点压低,这时我们如果在更新,曲线会找到一个其他的和使得更大,这时的不仅没有使得更接近,反而更远了,因此我们应该更新以保证已经达到了一个比较优的值

用公式表示:

2. 优化问题

的损失函数为:

因为第一部分没有的信息,因此损失函数可以化简为

从图中可以看出在最初的时候值为0(即最大值),我们要降低这个值,但是他在最初的时候梯度很小,下降的很慢但是如果改成,梯度很大下降的就快,所以这里做一个微小的变化,改变损失值之后,第二步的损失就完全等价于nn.BCELoss(),如下

1
2
3
criterion = nn.BCELoss()
'''other codes'''
loss_G = criterion(f_logit, r_label) # 括号中的取值为[0, 1]

其中r_label是1,所以二分类损失

其中是1,后面那部分就没了,就变成了,其中,就变成和上面的一模一样的式子了

Error: Not Found