再读GAN

最近看了台湾大学李宏毅老师关于深度学习的系列教程(Machine Learning and Having It Deep and Structured),收获颇多。教程主要介绍了深度学习的基本知识,并且介绍了大量比较新的技术,例如Seq2Seq、Attention、Generative Model、Language Model等等。与以往其他“以公式为中心”的教程不同,这个教程从“定性”的角度介绍了这些技术,能让我们详细了解到这些技术存在的合理性。同时又有着详细的公式推导,并且将事实之间的逻辑关系梳理的非常清晰。

接下来我准备写几篇博文,将教程中关于GAN部分的一些内容转换成文字记述下来。

下面大部分图片都是来源于李宏毅老师课程视频和演示文稿,如有侵权请联系我删减。


Auto Encoder

在讲GAN之前,首先我们先回顾一下“Generation”这件事。

“Generation”就像是创作,通过学习输入数据内部的Pattern,然后再利用这个模型去产生新的数据。所产生出来的内容应该能够“模仿”数据,而不是“抄袭”数据。

举个例子,Auto Encoder这个模型大家肯定都知道。通过一个神经网络构成的Encoder,将输入空间映射到编码空间中,再通过一个(通常是对称的)神经网络构成的Decoder,将编码空间映射到输出空间。模型的训练目标则是让输出空间尽可能等于输入空间。

通常来说,编码空间维度要比输入空间和输出空间小。所以如果信息从Encoder流动到Decoder,则显然编码空间对输入数据的编码包含了决定数据空间的绝大部分信息。从一个角度来说它相当于是对信息的编码式压缩。从另一个角度来说,如果说数据空间是映射到高维空间的低维流型,则编码空间可能就能代表这个流型空间。

还有就是,如果我们真的学到了编码空间,那么任何编码,只要能正确地输入到Decoder中,都应该能生成出“逼真”的数据。

并且,能够生成出“逼真”数据的编码,其自身还应该能反映出与生成数据之间的关系。比如,在流行空间上的小幅度游走,反映在数据空间也应该是连续而又缓慢变化的。

GAN

除了Auto Encoder以外,还有另外一种生成模型,就是这篇文章的主角:GAN。 GAN包括了两个部分,生成器(Generator)和判别其(Discriminator)。生成器生成尽可能“逼真”的图片,而判别其则负责鉴别出图片到底是真的还是生成出来的。这两部分通过演化的方法交替训练。

训练方式如上图:我们首先有第一代Generator,生成的图片并不太好,这时候训练出第一代Discriminator来让它鉴别图像真假。当第一代Discriminator能区分出图像真伪之后,再以这个Discriminator为评判尺度,来训练出第二代Generator。当第二代Generator足够强大到生成能骗过第一代Discriminator的时候再训练一个能足以鉴别出第二代Generator所生成的图像的第二代Discriminator。这个过程依此类推,来让Generator生成连人类都难以分辨的图片,让Discriminator根本无法区分图片是真是假(图像识别正确的概率是\(0.5\)(识别正确是指“真图认为是真图、假图认为是假图”))。

下面详细介绍一下GAN的结构:

GAN主要由两部分组成,一部分是Generator(蓝色),一部分是Discriminator(绿色)。首先我们从一个随机prior分布(uniform或者normal)采样一个向量。然后将这个向量送入到生成器中,它会产生一张图片。然后将这张图片送到判别器中鉴别真伪。而训练过程就是先固定生成器训练判别器,当判别器收敛之后再训练生成器,交替进行。

Basic Idea of GAN

Maximum Likelihood Estimation

假设给定一个分布\(P_{data}(x)\)。我们现在想要用另一个由\(\theta\)控制的分布\(P_G(x;\theta)\)来逼近\(P_{data}(x)\)。

比如说,\(P_G\)是一个GMM (Gaussian Mixture Model, 高斯混合模型),而\(\theta\)是各个高斯component之间的均值和方差,我们应该如何拟合呢?

这时候用极大似然估计就是个很intuitive的想法。形式化流程:

  1. 从\(P_{data}(x)\)中采样得到一组数据\(\{x^1, x^2, \dots, x^m\}\)
  2. 计算生成的数据对\(P_G(x^i; \theta)\)的似然函数:
    $$ L=\prod_{i=1}^mP_G(x^i; \theta)
    $$
  3. 求解能够让似然函数最大的\(\theta^*\)

那我们要如何得到这个\(\theta^*\)呢?

$$ \begin{align} \theta^* &= \arg\max_\theta\prod_{i=1}^mP_G(x^i;\theta) \\ &= \arg\max_\theta\log\prod_{i=1}^mP_G(x^i;\theta) \\ &=\arg\max_\theta\sum_{i=1}^m\log P_G(x^i; \theta) \\ &\approx\arg\max_\theta E_{x\sim P_{data}}[\log P_G(x;\theta)] \end{align} $$

我们再来看一下这个公式,想要得到optimal \(\theta^*\),我们需要找到能让整体似然函数最大的\(\theta\)。由于求\(\log\)并不改变单调性,所以我们可以利用\(\log\)将连乘变连加。

而后面的“约等于”则比较值得一提。如果我们想要得到一个\(P_{data}(x)\)的最佳近似,那么按照极大似然的理论,我们需要将\(P_{data}(x)\)所有定义域内的\(x\)都采样到并且用来计算似然函数。然而这个操作显然是不可行的,我们不可能将任何样本都采样到。变通的方法就是我们通过采集大量的样本来让极大似然估计的结果约等于真是结果,所以说当我们的\(m\)足够大,这个约等于是趋向于等于的。

那么,我们所定义的这个\(P_G(x; \theta)\)长什么样子呢?假设我们可以用一个神经网络来表示这个\(P_G(x; \theta)\):

这个神经网络将一个prior分布映射到\(P_G(x; \theta)\),并通过loss function使得\(P_G(x; \theta)\)与\(P_{data}(x)\)尽可能相似。

这个映射由一个模型\(G(z)=x\)控制。输入一个从\(P_{prior}(z)\)采样得到的向量,并得到一个输出\(x\)。当\(P_G(x; \theta)\)能很好地模仿\(P_{data}\),则这个\(x\)应该会让\(P_{data}(x)\)取得一个很大的数值。即图中的蓝色区域。

换句话说,我们定义了一个generative model\(G(z)\),它由\(\theta\)控制,我们希望找到一个\(\theta\)能让生成出来的这个分布\(P_G(x; \theta)\)尽可能地与\(P_{data}(x)\)接近。

而我们的\(P_G(x)\)可以描述为:

$$ P_G(x)=\int_z P_{prior}(z)I_{[G(z)=x]}dz
$$

其中\(I\)表示指示函数:里面的值成立则等于\(1\),否则等于\(0\)。这个式子实际上是在说,一个数据空间中的样本\(x\)出现的机率,实际上等于对整个\(P_{prior}(z)\)上每一个元素求积分。比如说如果\(P_{prior}\)是Gaussian分布,则当\(z\)在Gaussian中心附近时,这个\(P_{prior}(z)\)就会大一些,否则就会小一些。然后计算使用这个\(z\)通过生成器\(G\)生成的数据是否等于真实数据空间中的样本\(x\)。

显然,如果将\(P_G(x; \theta)\)定义成这个样子,它就能起到与\(P_{data}(x)\)同样的作用:当\(P_G(x)\)比较大,就说明\(P_{data}(x)\)也会比较大,说明\(x\)有可能是真实数据空间中的数据。

但是这样虽然能直接定义出\(P_G(x)\)的解析式,但是由于其中的\(G(z)\)是用各种复杂结构的神经网络所定义的,所以它很难用来计算似然函数。

那么,如何在没法计算似然函数的情况下通过调整模型参数\(\theta\)来让\(P_G\)接近\(P_{data}\)呢?这时候我们就可以使用GAN了!

替代方法:不用最大化似然函数的GAN

首先我们有一个生成器\(G\),它可以是一个函数(不一定非要是神经网络),它接受一个输入\(z\),产生一个输出\(x\)。给定一个prior分布\(P_{prior}(z)\),和一个由函数\(G\)定义的分布\(P_G(x)\)。

我们还有一个判别器D,D也可以是一个函数(也不一定必须是神经网络)。它输入一个\(x\),输出一个\(scalar\)。这个判别器的作用就是在衡量\(P_G(x)\)和\(P_{data}(x)\)这两个分布之间的相近的程度。

D有什么用呢?实际上我们可以加个D看作是在计算两个分布之间的距离(diverge),也就是说,我们可以通过下面的公式去寻找一个optimal的生成器\(G^*\):

$$ G^*=\arg\min_G\max_D V(G, D)
$$

$$ V(G, D)=E_{x\sim P_{data}}[\log D(x)] + E_{x\sim P_{G}}[\log(1-D(x))]
$$

说到这里,一个最大最小优化问题很容易让人当作是“分别找一个\(G\)和\(D\),分别最小化和最大化\(V(G,D)\)”。但实际上我们需要做的是这样两件事:

  1. 首先,对于\(\max_DV(G,D)\)这一项来说,只要有不同的\(G_i\),就能得到一个在给定这个\(G_i\)时能够让\(\max_DV(G,D)\)取得最大值的\(D_i\)。这样我们会得到一大堆不同的\(V(G, D_i)\)。(注意此处并不是\(\arg\max_DV(G,D)\),仅仅是\(\max_DV(G,D)\),所以我们需要的并不是找到对应的一个由\(D\)组成的集合。而是要得到一个由\(V(G, D)\)组成的集合,集合中的每一个元素都应该是一个\(V(G, D)\)的值)
  2. 从这样一组\(V(G, D)\)中去挑选“那个能让\(V(G, D)\)最小的\(G\)”
  3. 这样我们得到的那个\(G\)就是optimal的\(G^*\)了。
判别器的训练过程

下面解释的再详细一些。我们先来看看公式中的\(\max_DV(G,D)\)部分。

我们可以固定\(G_i\),将\(D\)作为自变量,将\(V(G_i,D)\)作为因变量,就能够画出一张\(D\)关于\(V(G_i,D)\)的曲线图。

假设在整个\(G\)的参数空间中只存在三个不同的\(G\)(当然,由于\(G\)是由神经网络定义的,所以应该有无数的参数,也就有无数的\(G\),这里为了简便解释,就假定只有三个):

这三张图是当\(G\)分别是\(G_1\),\(G_2\),\(G_3\)时\(D\)和\(V(G_1, D)\),\(V(G_2, D)\),\(V(G_3, D)\)的曲线。

三张曲线的红点就是能够在\(G\)分别是\(G_1\),\(G_2\),\(G_3\)时所能去得到的最大的\(V(G_1, D_1)\),\(V(G_2, D_2)\),\(V(G_3, D_3)\)(y轴),和对应的最好的\(D_1\),\(D_2\),\(D_3\)(x轴)

从图里能看出来其实\(G_3\)就是\(G^*\)。

那么这个\(\max_DV(G, D)\)究竟是个啥呢?当给定一个\(G\),\(\max_DV(G, D)\)能够衡量分布\(P_G\)和\(P_{data}\)之间的“差异”。即上图中红线与x轴垂直距离就是这个差异。我们希望训练得到的\(D\)能够尽可能地区分开\(P_G\)与\(P_{data}\)。

当我们由能最好地区分开\(P_G\)与\(P_{data}\)的\(D\)之后,任务就变成了找到一个能让\(D\)无法区分开\(P_G\)和\(P_{data}\)的\(G\)。也就是说,我们通过寻找\(G\)的过程去修改\(P_G\),使得它能尽可能地与\(P_data\)相似。

接下来我们看看该如何找到一个让\(V\)最大的\(D^*\)。我们来看看\(V(G,D)\)里面到底是啥:

$$ \begin{align} V(G,D) &=E_{x\sim P_{data}}[\log D(x)]+E_{x\sim P_G}[\log(1-D(x))] \\
&=\int_xP_{data}(x)\log D(x)dx+\int_xP_G(x)\log(1-D(x))dx \\ &=\int_x[P_{data}(x)\log D(x)+P_G(x)\log(1-D(x))]dx \end{align} $$

也就是说,当给定一个\(x\),寻找最优的\(D^*\),就是在maximize

$$ P_{data}(x)\log D(x)+P_G(x)\log(1-D(x))
$$

当每一个\(x\)都能让上面的式子最大,那求积分之后的值也一定是最大的。那么现在的问题就是,究竟\(D(x)\)取什么的时候能够让上面的式子最大呢?

我们可以给上面公式的各个部分起个别名:

$$ \underbrace{P_{data}(x)}_a\log\underbrace{D(x)}_D+\underbrace{P_G(x)}_b\log(1-\underbrace{D(x)}_D) $$

那么想要找到\(D^*\)就变成了最大化函数:\(f(D)=a\log(D)+b\log(1-D)\)

这个函数啥时候最大?求导等于\(0\)然后解出来就是最大!

$$ \frac{df(D)}{dD} = a\times\frac{1}{D}+b\times\frac{1}{1-D}\times(-1) = 0 \\ a\times\frac{1}{D^*} = b\times\frac{1}{1-D^*} \\
a\times(1-D^*)=b\times D^* \\
a-aD^* = bD^* \\
D^*=\frac{a}{a+b}
$$

所以:

$$ D^*(x)=\frac{P_{data}(x)}{P_{data}(x)+P_G(x)}
$$

这个式子的取值范围应该是\([0, 1]\)。因为这个式子肯定非负(因为都是概率的运算)所以大于\(0\),其次分母一般比分子大所以小于等于\(1\)。

上面的式子说明,当给定\(G\)时,能让\(V(G, D)\)取得最大值的最优的\(D\)应该是\(\frac{P_{data}(x)}{P_{data}(x)+P_G(x)}\):

前两张图中的红点所对应的\(D_i^*(x)\)如上面的两个公式,而这个点到x轴的垂直投影距离就是分布\(P_{G_i}\)和\(P_{data}\)之间的“差异”。而\(D_i^*\)对应的位置就是能让这个差异最大的位置,也就是说,\(D_i^*\)的“鉴别”能力最好,即能更容易区分出数据\(x\)到底是来源于\(P_{data}\)、还是来源于\(P_{G_i}\)。

那么我们为什么会认为这个\(V(G_i, D_i^*)\)就是一个能够衡量两个分布\(P_{data}\)和\(P_{G_i}\)的距离呢?下面我们做一些简单的推导。

首先,我们将解出来的\(D^*(x)=\frac{P_{data}(x)}{P_{data}(x)+P_G(x)}\)带入到最优化的公式里面去:

$$ \begin{align} & \max_DV(G, D) = V(G, D^*) \\ &= E_{x\sim P_{data}}\left[\log\frac{P_{data}(x)}{P_{data}(x)+P_G(x)}\right] + E_{x\sim P_G}\left[\log(1-\frac{P_{data}(x)}{P_{data}(x)+P_G(x)})\right] \\ &= E_{x\sim P_{data}}\left[\log\frac{P_{data}(x)}{P_{data}(x)+P_G(x)}\right] + E_{x\sim P_G}\left[\log\frac{P_G(x)}{P_{data}(x)+P_G(x)}\right] \\ &= \int_x P_{data}(x)\log\frac{P_{data}(x)}{P_{data}(x)+P_G(x)}dx + \int_xP_G(x)\log\frac{P_G(x)}{P_{data}(x)+P_G(x)}dx \\ &= \int_x P_{data}(x)\log\frac{\frac{1}{2}\times P_{data}(x)}{\frac{P_{data}(x)+P_G(x)}{2}}dx + \int_xP_G(x)\log\frac{\frac{1}{2}\times P_G(x)}{\frac{P_{data}(x)+P_G(x)}{2}}dx \\ &= 2\log\frac{1}{2} + \int_x P_{data}(x)\log\frac{P_{data}(x)}{\frac{P_{data}(x)+P_G(x)}{2}}dx + \int_xP_G(x)\log\frac{P_G(x)}{\frac{P_{data}(x)+P_G(x)}{2}}dx \\ &= -2\log2 + \int_x P_{data}(x)\log\frac{P_{data}(x)}{\frac{P_{data}(x)+P_G(x)}{2}}dx + \int_xP_G(x)\log\frac{P_G(x)}{\frac{P_{data}(x)+P_G(x)}{2}}dx \\ &=-2\log2+\mathbf{KL}(P_{data}(x)||\frac{P_{data(x)}+P_G(x)}{2})+\mathbf{KL}(P_G(x)||\frac{P_{data}(x)+P_G(x)}{2}) \\ &=-2\log2 +2\mathbf{JSD}(P_{data}(x)||P_G(x)) \end{align} $$ 上面的公式看起来好像比较复杂,但实际上只是几步简单的替换和变形。第四步中,给分子分母上下都乘\(\frac{1}{2}\),然后再将分子部分的\(\frac{1}{2}\)提取出来,会单独产生一项\(-2\log2\)。

而提取之后剩下的两项分别时\(P_{data}(x)\)对\(\frac{P_{data}(x)+P_G(x)}{2}\)和\(P_G(x)\)对\(\frac{P_{data}(x)+P_G(x)}{2}\)的KL(Kullback-Leibler)距离。

JSD:

\(\mathbf{JSD}(P||Q)=\frac{1}{2}D(P||M)+\frac{1}{2}D(Q||M)\)

\(M=\frac{1}{2}(P+Q)\)

这两项距离之和刚好就等于\(P_{data}(x)\)和\(P_G(x)\)的JSD(Jesen-Shannon divergence)的二倍。

而JSD就是一个度量两个分布差异情况的工具!所以说,我们上面说到的\(\max_DV(G_i, D_i^*)\)就能够被用来度量分布\(P_{G_i}(x)\)与\(P_{data}(x)\)的相似性。

JSD也是有取值范围的。如果当两个分布完全一样,JSD为\(\log2\),当两个分布完全没有交集则JSD是\(0\),所以\(-2\log2 \le \max_DV(G, D) \le 0\)。即当两个分布完全一样时,\(\max_DV(G, D)=-2\log2\)。当两个分布完全不一样时,\(\max_DV(G, D)=0\)。

说到这里,讲一句题外话。我们公式推导到这里,是因为我们用了最原始的 GAN 的那个优化函数。其实对于 GAN 来说,只要能有类似的作用(度量分布间距离),将 \(V(G,D)\) 定义成其他形式也是可以的。不同的 \(V(G, D)\) 可能最终会产生不同的度量方式、不同的divergence。例如后面出现的 Wasserstein GAN 就是利用了 EM 距离(Earth Mover's Dicergence),所以它的 \(V(G, D)\) 是另外一个形式。

我们再重新来归纳一下刚才做的事情。

首先假定我们有一个生成器\(G\)和一个判别器\(D\),我们定义了一个函数\(V(G, D)=E_{x\sim P_{data}}[\log D(x)]+E_{x\sim P_G}[\log(1-D(x))]\)。

我们的目标是寻找一个最优的\(G^*\),它应该是:

$$ G\^*=\arg\minG\maxDV(G, D)
$$

为了求解这个优化像,我们可以假设当给定一个\(G\)时:

$$ \max_DV(G, D)=V(G, D^*)=-2\log2+2\mathbf{JSD}(P_{data}(x)||P_G(x)) $$

那么我们有了\(D^*\),有了\(\max_DV(G, D)\),该如何优化\(G\),来让\(P_G(x)=P_{data}(x)\)呢?

生成器的训练过程

我们之前提到过optimal的\(G^*\),如果将其中的 \(\max_DV(G,D)\) 用\(L(G)\)来表示:

$$ G^*=\arg\min_G\underbrace{\max_DV(G,D)}_{L(G)}
$$

所以说想要找到最好的\(G\)就变成了最小化损失函数\(L(G)\)的问题:

$$ \theta_G\leftarrow\theta_G-\eta \frac{\partial L(G)}{\partial \theta_G} $$

说到这里再提一件事。就是这个函数 \(L(G)\) 带有一个求 \(\max_D\) 的操作。那么,包含诸如 \(\max\)、\(\min\) 等操作的函数是否能进行求导呢?

其实是可以的(想想 CNN 中的 Max Pooling 操作,也是可以求导利用反向传播进行训练的)。该如何计算呢?其实很简单。

假设我们现在有一个函数,这个函数由三个函数的 \(\max\) 操作组成:

$$ f(x)=\max{D_1(x), D_2(x), D_3(x)} $$

如何对计算 \(\frac{df(x)}{dx}\) 呢?我们可以先将组成 \(f(x)\) 的三个函数 \(D_1(x), D_2(x), D_3(x)\) 画出来:

从图中我们可以看出来,这个函数 \(f(x)\) 在定义域内分别受三个不同的函数主导。 \(f(x)\) 真正的曲线其实就是这三条曲线所形成的包络面。而 \(f(x)\) 的梯度则应该是 \(x\) 所在位置对应的值最大的那个函数 \(D_i\) 的梯度: \(\frac{dD_i(x)}{dx}\)

我们之前也提到过,损失函数\(L(G)\)中的\(V(G,D)\)部分实际上是分布\(P_{data}(x)\)和\(P_G(x)\)之间的JS距离。也就是说,最小化损失函数的过程实际上是在减小这两个分布的差异,而\(\theta_G\)能够控制分布\(P_G\),所以说,最小化损失函数的过程,就是在“让\(P_G(x)\)尽可能低像\(P_{data}(x)\)”。

但是当我们成功地将\(G\)优化到收敛之后,我们的训练就结束了嘛?并不是!不要忘了,\(G\)的改变将会影响\(D\)。也就是说,之前已经最优化的\(D_1\),在优化得到\(G_1\)之后就已经不是最优的判别器了!这时候需要根据\(P_{G_1}\)和\(P_{data}\),重新优化判别器,得到一个在\(G=G_1\)的情况下最优的判别器\(D_2\),然后再用同样的方法继续依次得到\(G_2\),\(D_3\),\(G_3\),等等。

Put it all together! 开始工程上的实现!

我们上面进行了一系列公式推导以保证GAN模型是有效的。

我们首先定义了一个判别器和一个生成器。然后我们认为当我们想要得到一个对\(P_{data}\)的完美近似\(P_G^*\),就需要做这样的一个优化得到\(G^*=\arg\minG\maxDV(G, D)\)。而我们上面证明过,\(V(G, D)\)是一个能衡量\(P_G\)和\(P_{data}\)距离的不错的度量。所以我们只要能优化这个函数就能得到那个完美的\(G^*\)了!

那么,我们在工程上该如何实现?

我们要优化的函数是\(V(G, D)=E_{x\sim P_{data}}[\log D(x)]+E_{x\sim P_G}[\log(1-D(x))]\)

公式中所包含的的期望需要计算全积分,这在工程中显然是不可能的。所以替代方法就是采样。首先我们从\(P_{data}(x)\)中采样一组样本\(\{x^1, x^2, \dots, x^m\}\)。然后从\(P_G(x)\)中采样出一组样本\(\{\tilde{x}^1, \tilde{x}^2, \dots, \tilde{x}^m\}\)。

然后去最大化这个式子:

$$ \tilde{V}=\frac{1}{m}\sum{i=1}^m\log D(x^i) + \frac{1}{m}\sum{i=1}^m\log(1-(D(\tilde{x}^i))) $$

从另一个角度来看这个公式,它其实是二类分类问题中的“交叉熵损失函数”。

在二类分类问题中,如果分类器是\(f(x)\),想要最小化它的交叉熵需要这样计算:

如果\(x\)是一个正样本,那我们就最小化\(-\log f(x)\),即最大化\(\log f(x)\)。这样做的目的是“让输入正样本时,让分类器尽可能输出\(1\)”。

如果\(x\)是一个负样本,那我们就最小化\(-log(1-f(x))\),即最大化\(\log(1-f(x)))\)。这样做的目的是“让输入负样本时,让分类器尽可能输出\(0\)”

实际上最大化\(\tilde{V}\),就是在最小化这个函数:

$$ L=-\frac{1}{m}\sum_{i=1}^m\log D(x^i)-\frac{1}{m}\sum_{i=1}^m
\log(1-D(\tilde{x}^i)) $$

然后我们就可以用TensorFlow之类的框架进行最小化的优化了~

整个算法可以用下面的伪代码来表示:

  • 初始化D的\(\theta_d\)和G的\(\theta_g\)
  • 在每一个训练循环进行:
    • 从数据分布\(P_{data}(x)\)中采样\(m\)个样本\(\{x^1, x^2, \dots, x^m\}\)
    • 从先验噪声分布\(P_{prior}(z)\)中采样\(m\)个样本\(\{z^1, z^2, \dots, z^m\}\)
    • 将这些噪声样本输入生成器\(G\),得到生成样本\(\{\tilde{x}^1,\tilde{x}^2,\dots,\tilde{x}^m\}, \tilde{x}^i=G(z^i)\)
    • 更新判别器的参数\(\theta_d\),即最大化:
      • \(\tilde{V}=\frac{1}{m}\sum_{i=1}^m\log D(x^i)+\frac{1}{m}\sum_{i=1}^m\log(1-D(\tilde{x}^i))\)
      • \(\theta_d\leftarrow\theta_d+\eta\nabla\tilde{V}(\theta_d)\)
    • 从先验噪声分布\(P_{prior}(z)\)中采样\(m\)个样本\(\{z^1,z^2,\dots,z^m\}\)
    • 更新生成器的参数\(\theta_g\),即最小化:
      • \(\require{cancel}\tilde{V}=\cancel{\frac{1}{m}\sum_{i=1}^m\log D(x^i)}+\frac{1}{m}\sum_{i=1}^m\log(1-D(G(z^i)))\)
      • \(\theta_g\leftarrow\theta_g-\eta\nabla\tilde{V}(\theta_g)\)

上面公式中更新参数\(\theta_g\)时需要的优化目标\(\tilde{V}\)被划掉是因为它内部没有参数\(\theta_g\),所以并不会对更新\(\theta_g\)有帮助。

按照上面的伪代码我们仅能进行\(\theta_g\)和\(\theta_d\)的训练。从而就能得到我们生成器和判别器。

但是在实际操作中,我们用到的并不是用上面的目标函数做优化。主要原因是目标函数后面的“鉴别生成样本”的那一项。我们将这一项的图画出来:

图中的红线就是我们在目标函数中用到的曲线(横轴是\(D(x)\))。这个曲线虽然是单调递减的凸函数,但实际上在训练初期,由于生成器还不能很好地生成逼真的样本,所以很容易被判别器识别出是伪造的数据,所以\(D(x)\)的取值很接近\(0\)。而在\(0\)附近这个函数的梯度会比较低,所以很难得到训练。替代方法就是用上面蓝色的曲线,即将目标函数的第二项换成\(-\log(D(x))\)。这个函数有着相同的单调性,而且在生成器性能不好时能提供较大的梯度,在生成器性能快要能骗过判别器时(\(D(x)\approx 1\))提供一个较低的梯度。这也比较符合我们平时对机器学习算法的直觉:刚开始训练时优化快一些,快要收敛时优化慢一些。

GAN训练的几个问题

下面说几个在使用原始GAN训练中可能会遇到的一些问题。

训练不稳定

训练原始GAN是一件非常困难的事情。难主要体现在训练过程中可能并不收敛、训练出的生成器根本不能产生有意义的内容等方面。另一方面,虽然说我们优化的目标函数是JSD,它应该能体现出两个分布的距离。并且这个距离最好一开始比较大,后来随着训练\(G\)过程的深入,这个距离应该慢慢变小才比较好。但实际上这只是我们理想中的情况。

在实际应用中我们会发现,\(D\)的Loss Function非常容易变成\(0\),而且在后面的训练中也已知保持着\(0\),很难发生改变。这个现象是为什么呢?其实这个道理很简单。虽然说JSD能够衡量两个分布之间的距离,但实际上有两种情况可能会导致JSD永远判定两个分布距离“无穷大”(\(\mathbf{JSD}(P_{data}(x)||P_G(x))=\log2\))。从而使得Loss Function永远是\(0\):

$$ \max_DV(G, D)=-2\log2+2\underbrace{\mathbf{JSD}(P_{data}(x)||P_G(x))} {\log2}= 0 $$

第一种情况,就是判别器\(D\)太“强”了导致产生了过拟合。例如下图:

上图蓝色和橙色分别是两个分布,我们能发现分布之间确实有一些重叠,所以按理来说JSD不应该是\(\log2\)。但由于我们用的是Sample的方式采样一部分样本进行训练,所以当判别器足够“强”的时候,就很有可能找到一条分界线强行将两类样本分开,从而让两类样本之间被认为完全不存在重叠。

发生这个情况之后怎么办?我们可以尝试传统的正则化方法(regularization等),也可以减少模型的参数让它变得弱一些。但是我们训练的目的就是要找到一个“很强”的判别器,我们在实际操作中是很难界定到底要将判别器调整到什么水平才能满足我们的需要:既不会太强,也不会太弱。还有一点就是我们之前曾经认为这个判别器应该能够测量JSD,但它能测量JSD的前提就是它必须非常强,能够拟合任何数据。这就跟我们“不想让它太强”的想法有矛盾了,所以实际操作中用regularization等方法很难做到好的效果。

第二种情况,就是数据本身的特性。一般来说,生成器产生的数据都是一个映射到高维空间的低维流型。而低维流型之间本身就“不是那么容易”产生重叠的。如下图所示:

也就是说,想要让两个概率分布“碰”到一起的概率并不是很高。就像我们从几百米的高空中扔下两根筷子,由于风力等作用的影响,两支筷子落地的位置方向完全不同,当两根筷子完全没有重叠的时候,他们之间的“Divergence”(类似于JSD)永远是\(\log2\)。只有当两只筷子恰好有重叠时,他们的Divergence才可能不是\(\log2\)。并且随着重叠部分逐渐增加,他们的Divergence逐渐减小到\(0\)。

这会导致什么现象?参考我们交替训练\(D\)和\(G\)的过程。这个训练实际上是在做一种“进化”训练。整个过程如下图:

刚开始训练时\(P_{G_0}(x)\)与\(P_{data}(x)\)有一定的距离。我们希望随着迭代过程的进行,从\(P_{G_0}(x)\)开始,每一代生成器\(P_{G_i}(x)\)都能一步一步接近\(P_{data}(x)\)。然而虽然我们能看到\(P_{G_{50}}(x)\)要比\(P_{G_0}(x)\)更接近\(P_{data}(x)\),但实际上这这种接近反应在JSD上都是\(\log2\)。也就是说,直到\(P_{G_i}(x)\)和\(P_{data}(x)\)产生重叠之前,这两个分布的JSD永远是\(\log2\)。这显然不能给我们的梯度下降算法提供足够的帮助。

解决方法有两种,一种是给数据加噪声,让生成器和真实数据分布“更容易”重叠在一起:

但是这个方法缺点在于,我们的目标是训练准确的数据(例如高清图片等)。加入噪声势必会影响我们生成数据的质量。一个简单的做法是让噪声的幅度随着时间缩小。不过操作起来也是比较困难的。

除此之外还有另一种方法。既然JSD效果不好,那我们是不是可以换一个Loss Function,使得哪怕两个分布一直毫无重叠,但是都能提供一个不同的连续的的“距离的度量”?后面我们会介绍到WGAN,它的主要功能就是提供了Earth Mover's Divergence。这个Divergence就能够做到类似的事情,我们后续会介绍到。

Mode Collapse

最后再说一下训练中可能遇到的另一个问题:所有的输出都一样!这个现象被称为Mode Collapse。这个现象产生的原因可能是由于真实数据在空间中很多地方都有一个较大的概率值,但是我们的生成模型没有直接学习到真实分布的特性。为了保证最小化损失,它会宁可永远输出一样但是肯定正确的输出,也不愿意尝试其他不同但可能错误的输出。也就是说,我们的生成器有时可能无法兼顾数据分布的所有内部模式,只会保守地挑选出一个肯定正确的模式。

假设我们要学习的真实分布是这个样子:

而我们所设想的学习过程应该是这个样子:

根据我们的想象,我们生成器所代表的概率分布应该从最开始与真实分布毫不相关的状态,逐渐朝着真实分布方向转变。

但实际上却是事与愿违:

模型在学习到一个真实分布的模式之后,并不会继续学习其他模式,而是尝试立刻将这个模式忘掉,转而去学习其他模式。并且在迭代过程中不断在各个模式中跳跃。

但是关于这个情况产生的原因,并没有太好的定论。

Friskit

继续阅读此作者的更多文章