1. GAN 基本介绍
GAN 是一个生成网络,他有两个网络 G 和 D
- Generator:生成图片的网络,他可以接受一个随机噪声,这里我们一般把噪声设为标准高斯分布(当然也可以是其他的噪声分布),z N(0, 1),让z通过生成网络得到G(z),G(z)其实就是一幅图片
- Discriminator:判别网络,我们将生成器生成的图片通过判别网络进行判断,最终会得到一个标量,即D(G(z)),数值越高代表图片越真实,数值低代表图片越假
GAN 的主要目的就是通过将真实的图片(即数据集中的图片 GT)以及通过生成器G生成的图片输入到判别网络D中,让判别网络无法判断出这幅图片究竟是真实的图片还是通过噪声生成的图片,当训练完成时,D(G(z))=0.5
2. 数学分析
1. 生成器 G
我们有一批数据,假设是一些图片,他们的数据分布是Pdata(x),当然我们没有办法知道Pdata(x)的具体数值,不然我们就可以直接从里面采样得到图片了,所以我们需要求解此分布,这就需要用到生成器G了
首先我们从标准高斯噪声中进行采样得到z,然后我们让z通过生成器G就可以得到一个分布记为PG(x;θ),其中θ是一个参数用来控制PG的分布,假设我们从真实的数据分布Pdata(x)中得到一批分布x1,x2…xm,此时我们求一个θ∗来使PG(x)的取值最大,即求最大似然
$$
\begin{aligned}
\theta^{*} & = \underset {\theta}{\operatorname {arg,max}} \Pi_{i=1}{m}P_{G}(x{i}, \theta) \
& \rightarrow \underset {\theta}{\operatorname {arg,max}}~log \Pi_{i=1}{m}P_{G}(x{i}, \theta) \
& = \underset {\theta}{\operatorname {arg,max}}~\sum_{i=1}{m}logP_{G}(x{i}, \theta) \
& \approx \underset {\theta}{\operatorname {arg,max}}E_{xdata}[logP_{G}(x;\theta)]
\end{aligned}
$$
其中x data代表了从Pdata(x)取得的数据x1,x2…xm,接下来
θ∗=arg,maxθ∫xPdata(x)logPG(x;θ)dx−∫xPdata(x)logPdata(x)dx =arg,minθ∫xPdata(x)logPdata(x)dx−∫xPdata(x)logPG(x;θ)dx =arg,minθ∫xPdata(x)logPdata(x)PG(x;θ)dx =arg,minθ KL(Pdata(x)||PG(x;θ))
注意上式中减掉的∫xPdata(x)logPdata(x)dx由于这是一个常数,所以并不影响最终的结果,通过上述我们可以得知,最大化似然其实就是给生成器G找一个θg使得PG(x;θ)=Pdata(x)

到这里有的人就会想,既然我们要求这两个概率接近,那我们直接在这里进行反向传播来训练G不就可以了吗,当然不可以,因为我们对噪声z进行随机采样时,我们并不知道哪个噪声对应哪张图片,也就是说我们并不知道GT,因此从Pdata(x)中随机取样本与PG(x,θ)求距离时没有意义的,接下来我们看一看GAN是怎么做的
2. 判别器D
我们直接给出paper中的公式
minG maxDV(D,G)=Ex pdata(x)[logD(x)]+Ez pz(z)[log(1−D(G(z)))]
此时我们固定G,即
maxDV(D,G)=Ex pdata(x)[logD(x)]+Ex pG(x)[log(1−D(x))]
上式子就代表了PG与Pdata之间的差异,因为第一项代表了真实的数据,显然对于真实的数据判别器D要给一个很高的分数;后一项代表了噪声生成的数据,对于假的数据我们就要给一个很低的分值,所以第二项的分值要变小,但是由于log里面是负号,所以取反就要变大。因此如果来的是真实的数据第一项变大,来的是虚假的数据第二项变大
注意:在训练过程中是先训练1 k判别器,在训练一次生成器的。在训练D时,判别器是知道输入的数据是真实的(1)还是虚假的(0),因此在训练过程中D会变得越来越强。即来的是真实的数据,输出会增大变为1,对于噪声数据,输出会变小变为0
(1) 求D-max
下面我们对其进行求导算出最优的D
V(D)=Ex pdata(x)[logD(x)]+Ex pG(x)[log(1−D(x))] =∫xPdata(x)logD(x)dx+∫xPG(x)log(1−D(x))dx =∫x[Pdata(x)logD(x)+PG(x)log(1−D(x))]dx takef(D)=Pdata(x)logD(x)+PG(x)log(1−D(x)) =a×logD+b×log(1−D)
对f(D)求导
df(D)dD=a×1D+b×11−D×(−1)=0 a×(1−D)=b×D sol: D=aa+b D∗(x)=Pdata(x)Pdata(x)+PG(x)
在我们计算出D∗(x)后,带入最初式maxDV(D,G)=Ex pdata(x)[logD(x)]+Ex pG(x)[log(1−D(x))]中得
V(D∗)=Ex pdata(x)[logD(x)]+Ex pG(x)[log(1−D(x))] =∫xPdata(x)logPdata(x)Pdata(x)+PG(x)dx+∫xPG(x)log(1−Pdata(x)Pdata(x)+PG(x))dx =−2log2+∫xPdata(x)logPdata(x)(Pdata(x)+PG(x))2dx+∫xPG(x)log(PG(x)(Pdata(x)+PG(x))2)dx =−2log2+KL(Pdata(x)||Pdata(x)+PG(x))2)+KL(PG(x)||Pdata(x)+PG(x))2) =−2log2+2JSD(Pdata(x)||PG(x))
对于一个ln底数的Jensen–Shannon divergence取值范围为[0, ln2]
(2) 求G-min
通过上式我们可以发现固定G,V(D)的最小值为−2log2,最大值为0
我们已经得到了$\underset {D}{\operatorname {max}} V(D,G) = V(D^{}),接下来求解\underset {G}{\operatorname {min}}V(D^{}),即minG−2log2+2JSD(Pdata(x)||PG(x))$
直观的可以看出当PG(x)=Pdata(x)时,能取得最小值
上面可以看出,训练D的目的是让判别器能够识别那些图片是从真实样本分布中获得的,那些是通过噪声生成的,通过训练来让判别器变得更强,所以用的是maxD,但是训练G恰恰相反,训练G的目的是让生成器能够生成更加真实的图片,来“误导”判别器使其判断错误,所以用的是minG
通俗来讲,训练D的目的是使得D(x)的分布接近D∗(x)=Pdata(x)Pdata(x)+PG(x),而训练训练G的目的是使得PG(x)=Pdata(x),而我们最终的目的就是得到PG(x)=Pdata(x),所以如果想要得到一个较好的概率结果,每次训练时都应该先多训练几次D(k次),G一般一个batch就训练一次(理解为D是一个老师,G是一个学生,如果老师没有足够的水平(没训练好),怎么去指导学生呢),这样当最后G训练好的时候,D(x)=12
下面给出paper中的一个图,其中绿色的线代表PG(x)分布的变化,黑色的点线代表真实的概率分布Pdata(x),蓝色的线代表判别器D的最优解变化过程,可以发现最后变为了12

三、训练过程
训练过程是交替进行的,即先训练D,在训练G
算法流程:
- 先用k步来训练D→θd
- 在训练G→θg

注意上述步骤中V(D,G)=Ex pdata(x)[logD(x)]+Ez pz(z)[log(1−D(G(z)))] 在求第二步的时候,其实只有第二项包含了G,第二步把第一项给剔除了
下面贴上一段代码来感受以下训练过程,里面取了k=1
"""
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交替训练
如下图所示,我们要求的是找一个D使得结果最大,找一个G使得结果最小
这三个图没有训练顺序,所以仅作为一个参考,也就是说我们找到一个使得V最大的D∗1之后,在最大处通过改变G使得峰顶降低
图中绿色的虚线就是PG和Pdata之间的距离,训练G的目的就是使得二者距离相等,就时将顶部红色的点压到横坐标上

五、存在的问题
1. 为什么要训练k次D,在训练一次G
如图所示,我们训练一次D,同时更新一次G会出现的问题
假设我们训练了一次找到了一个D使得V较大,但是不一定非常大(注意这里陈述的和图中不太一致),然后我们更新G,会将此时V所在的点压低,这时我们如果在更新D,曲线会找到一个其他的D∗和使得V更大,这时的G不仅没有使得PG更接近Pdata,反而更远了,因此我们应该更新k次D以保证D已经达到了一个比较优的值
用公式表示:
$$
\begin{aligned}
max_{D}V(G_{0}, D_{0}) &= V(G_{0}, D_{0}^{}) \
afterupdateG:V(G_{1}, D_{0}^{}) &< V(G_{0}, D_{0}^{}) \
afterupdateD:V(G_{1}, D_{1}^{}) &> V(G_{0}, D_{0}^{*})
\end{aligned}
$$

2. 优化问题
G的损失函数为:
maxDV(D,G)=Ex pdata(x)[logD(x)]+Ex pG(x)[log(1−D(x))]
因为第一部分没有G的信息,因此损失函数可以化简为
maxDV(D,G)=Ex pG(x)[log(1−D(x))]
从图中可以看出log(1−D(x))在最初的时候值为0(即最大值),我们要降低这个值,但是他在最初的时候梯度很小,下降的很慢但是如果改成−log(D(x)),梯度很大下降的就快,所以这里做一个微小的变化,改变损失值之后,第二步的损失就完全等价于nn.BCELoss()
,如下
criterion = nn.BCELoss()
'''other codes'''
loss_G = criterion(f_logit, r_label) # 括号中的取值为[0, 1]
其中r_label
是1,所以二分类损失
L=1N∑iLi=1N∑i−[yilog(pi)+(1−yi)log(1−pi)]
其中yi是1,后面那部分就没了,就变成了L=−yilog(pi),其中yi=1,就变成和上面的−log(D(x))一模一样的式子了

本文由 Yonghui Wang 创作,采用
知识共享署名4.0
国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为:
Dec 19, 2024 12:13 pm