GAN 的原理
paperreading
本文字数:3k 字 | 阅读时长 ≈ 13 min

GAN 的原理

paperreading
本文字数:3k 字 | 阅读时长 ≈ 13 min

1. GAN 基本介绍

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

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

2. 数学分析

1. 生成器 G

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

首先我们从标准高斯噪声中进行采样得到\(z\),然后我们让\(z\)通过生成器\(G\)就可以得到一个分布记为\(P_{G}(x;\theta)\),其中\(\theta\)是一个参数用来控制\(P_{G}\)的分布,假设我们从真实的数据分布\(P_{data}(x)\)中得到一批分布\(\{x_{1},x_{2}...x_{m}\}\),此时我们求一个\(\theta^{*}\)来使\(P_{G}(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_{x~data}[logP_{G}(x;\theta)] \end{aligned} \]

其中\(x~data\)代表了从\(P_{data}(x)\)取得的数据\(\{x_{1},x_{2}...x_{m}\}\),接下来

\[ \begin{aligned} \theta^{*} & = \underset {\theta}{\operatorname {arg\,max}} \int_{x}P_{data}(x)logP_{G}(x;\theta)dx- \int_{x}P_{data}(x)logP_{data}(x)dx \\ & =\underset {\theta}{\operatorname {arg\,min}} \int_{x}P_{data}(x)logP_{data}(x)dx- \int_{x}P_{data}(x)logP_{G}(x;\theta)dx \\ & =\underset {\theta}{\operatorname {arg\,min}} \int_{x}P_{data}(x)log\frac{P_{data}(x)}{P_{G}(x;\theta)}dx \\ & = \underset {\theta}{\operatorname {arg\,min}}~KL(P_{data}(x) || {P_{G}(x;\theta)}) \end{aligned} \]

注意上式中减掉的\(\int_{x}P_{data}(x)logP_{data}(x)dx\)由于这是一个常数,所以并不影响最终的结果,通过上述我们可以得知,最大化似然其实就是给生成器\(G\)找一个\(\theta_{g}\)使得\(P_{G}(x;\theta) = P_{data}(x)\)

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

2. 判别器D

我们直接给出paper中的公式

\[ \underset {G}{\operatorname {min}}~\underset {D}{\operatorname {max}} V(D,G) = E_{x~p_{data}(x)}[logD(x)] + E_{z~p_{z}(z)}[log(1-D(G(z)))] \]

此时我们固定\(G\),即

\[ \underset {D}{\operatorname {max}} V(D,G) = E_{x~p_{data}(x)}[logD(x)] + E_{x~p_{G}(x)}[log(1-D(x))] \]

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

(1) 求D-max

下面我们对其进行求导算出最优的\(D\)

\[ \begin{aligned} V(D) &= E_{x~p_{data}(x)}[logD(x)] + E_{x~p_{G}(x)}[log(1-D(x))] \\ & = \int_{x}P_{data}(x)logD(x)dx+ \int_{x}P_{G}(x)log(1-D(x))dx \\ & = \int_{x}[P_{data}(x)logD(x) + P_{G}(x)log(1-D(x))]dx \\ take f(D) & = P_{data}(x)logD(x) + P_{G}(x)log(1-D(x)) \\ & = a\times logD + b\times log(1-D) \end{aligned} \]

对\(f(D)\)求导
\[ \begin{aligned} & \frac{df(D)}{dD} = a\times \frac{1}{D} + b\times \frac{1}{1-D}\times (-1) = 0 \\ & a\times (1-D) = b \times D \\ & sol:~~ D = \frac{a}{a+b} \\ & D^{*}(x) = \frac{P_{data}(x)}{P_{data}(x) + P_{G}(x)} \end{aligned} \]

在我们计算出\(D^{*}(x)\)后,带入最初式\(\underset {D}{\operatorname {max}} V(D,G) = E_{x~p_{data}(x)}[logD(x)] + E_{x~p_{G}(x)}[log(1-D(x))]\)中得

\[ \begin{aligned} V(D^{*}) & = E_{x~p_{data}(x)}[logD(x)] + E_{x~p_{G}(x)}[log(1-D(x))] \\ & = \int_{x}P_{data}(x)log\frac{P_{data}(x)}{P_{data}(x) + P_{G}(x)}dx+ \int_{x}P_{G}(x)log(1-\frac{P_{data}(x)}{P_{data}(x) + P_{G}(x)})dx \\ & = -2log2 + \int_{x}P_{data}(x)log\frac{P_{data}(x)}{\frac{(P_{data}(x) + P_{G}(x))}{2}}dx+ \int_{x}P_{G}(x)log(\frac{P_{G}(x)}{\frac{(P_{data}(x) + P_{G}(x))}{2}})dx \\ & = -2log2 + KL(P_{data}(x) || \frac{P_{data}(x) + P_{G}(x))}{2}) + KL(P_{G}(x) || \frac{P_{data}(x) + P_{G}(x))}{2}) \\ & = -2log2 + 2JSD(P_{data}(x) || P_{G}(x)) \\ \end{aligned} \]

对于一个\(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^{*})\),即
\[ \underset {G}{\operatorname {min}}\{-2log2 + 2JSD(P_{data}(x) || P_{G}(x))\} \]

直观的可以看出当\(P_{G}(x) = P_{data}(x)\)时,能取得最小值

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

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

下面给出paper中的一个图,其中绿色的线代表\(P_{G}(x)\)分布的变化,黑色的点线代表真实的概率分布\(P_{data}(x)\),蓝色的线代表判别器\(D\)的最优解变化过程,可以发现最后变为了\(\frac{1}{2}\)

三、训练过程

训练过程是交替进行的,即先训练\(D\),在训练\(G\)

算法流程:

注意上述步骤中\(V(D,G) = E_{x~p_{data}(x)}[logD(x)] + E_{z~p_{z}(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\)使得峰顶降低

图中绿色的虚线就是\(P_{G}\)和\(P_{data}\)之间的距离,训练\(G\)的目的就是使得二者距离相等,就时将顶部红色的点压到横坐标上

五、存在的问题

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

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

用公式表示:

\[ \begin{aligned} max_{D}V(G_{0}, D_{0}) &= V(G_{0}, D_{0}^{*}) \\ after~~update~~G:V(G_{1}, D_{0}^{*}) &< V(G_{0}, D_{0}^{*}) \\ after~~update~~D:V(G_{1}, D_{1}^{*}) &> V(G_{0}, D_{0}^{*}) \end{aligned} \]

2. 优化问题

\(G\)的损失函数为:

\[ \underset {D}{\operatorname {max}} V(D,G) = E_{x~p_{data}(x)}[logD(x)] + E_{x~p_{G}(x)}[log(1-D(x))] \]

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

\[ \underset {D}{\operatorname {max}} V(D,G) = E_{x~p_{G}(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 = \frac{1}{N}\sum_{i}L_{i} = \frac{1}{N}\sum_{i}-[y_{i}log(p_{i})+(1-y_{i})log(1-p_{i})] \]

其中\(y_{i}\)是1,后面那部分就没了,就变成了\(L = -y_{i}log(p_{i})\),其中\(y_{i} = 1\),就变成和上面的\(-log(D(x))\)一模一样的式子了

Sep 06, 2026
Aug 01, 2026