到目前为止,我们学习的神经网络,都是输入一个x,经过一个复杂的函数,输出y。x可以是sequence,可以是图片,可以是数据;y可以是类别,可以是sequence。
接下来,我们要把神经网络作为一个generator来使用。network会同时看x和z得到输出。z是simple distribution。回忆CS229机器学习课程,想到可以使用正态分布、均匀分布等,我们自己决定。
z是不固定的。每次使用,随机生成一个z。因此,我们的神经网络的输出,就变成了一个复杂的分布,如图:
我们的输出变成了distribution。这种神经网络被称为generator,实现同样的输入可能有不同的输出。我们现在来学习GAN(生成对抗网络)。
先以unconditional generation为例。先把x拿掉。generator现在的输入就是z。假设z是normal distribution,维度自定。simple出一个z之后,给generator,生成结果。
现在介绍discriminator。它用来判断输入的图片是真的还是AI生成的。
discriminator与generator的结构可以由我们自己设计,可以用CNN、transformer等模型。
所以,GAN的过程是这样的,discriminator与generator“协同进化”:
现在来看算法概览。下文用G指generator,D指discriminator。
算法概览
在开始之前,初始化G和D的参数。
第一步,固定G的参数,train并update属于D的参数。我们也有一个database,里面有正常的数据,sample一些出来,与G生成的数据,一起去训练D。D会因此学习到什么样的数据是真的数据,什么样的数据是G生成的数据。
从图中可以看出,“二次元图片生成”任务,对于D来说,就是一个简单的regression:二次元标1,非二次元标0。
第二步,固定D的参数,train并update属于G的参数。我们的目的是,让G学会“糊弄”D。具体实现是这样的:G随便吃一个distribution的向量,产生数据,数据给D,D给图片打分,G的目标是让D的打分越高越好。
通俗一点,可以看作把G和D接起来,看作一个large network。不过,这个network有的参数不能改,有的可以改。让这个large network的输出(打分)越大越好,如图,
这个训练过程与之前训练神经网络没有不同。
随后,两个步骤反复执行。
来看看progressive GAN的结果。改变对G输入的向量,能得到一系列过度人物脸部图片,
理论介绍与WGAN
回顾一下我们的目标,
现在需要明确,我们究竟要maximize或minimize什么。
$$
G^* = \arg\min_G \text{Div}(P_G, P_{\text{data}})
$$
Div是Divergence(散度) 的缩写,用来衡量两个概率分布差异的函数。
如何计算Divergence?先从 $ P_G 和 P_{\text{data}} $ 中sample出一些数据,
接下来,就是
现在来详细解释这个图。
图的左上角表示D的训练目标就是看到 $ P_{\text{data}} $ 给较高的分数,反之给较低的分数。 这个训练目标用数学表示就是图的左下角。E指的是期望,求平均。
综上,我们的总体目的是:
$$
\min_G \max_D V(D, G) = \mathbb{E}{x \sim P{\text{data}}} [\log D(x)] + \mathbb{E}_{z \sim P_z} [\log(1 - D(G(z)))]
$$
先不证明。
这个原始损失函数就是基于JS divergence(Jensen-Shannon divergence(詹森-香农散度))的,公式:
设有两个概率分布 P 和 Q,则:
$$
\text{JS}(P \parallel Q) = \frac{1}{2} \text{KL}(P \parallel M) + \frac{1}{2} \text{KL}(Q \parallel M)
$$
其中:
- $ M = \frac{1}{2}(P + Q) $ :是 P 和 Q 的平均分布。
- $ \text{KL}(P \parallel M) $ :是 P 相对于 M 的 KL散度。
对于两个概率分布 P(x) 和 Q(x),KL 散度的定义是:
$$
D_{\mathrm{KL}}(P \parallel Q) = \sum_x P(x) \log \frac{P(x)}{Q(x)}
$$
(或在连续分布中用积分替代求和)
- P(x):真实的分布(观测到的)
- Q(x):你用来近似的分布(模型预测的)
- 如果 P = Q,那么 KL 散度为 0,表示“完美近似”
- KL 散度越大,表示 Q 假装成 P 的能力越差
但是,詹森-香农散度其实并不那么适合,并且不管用不用它,GAN模型都非常难训练。现在要讨论一些GAN的训练技巧:
首先,我们要了解,$P_{data}$和$P_G$都是在高维空间中,低维的流形(Manifold(流形)是一个在局部看起来像欧几里得空间的空间)。这很好理解——高维空间中,肯定只有一个非常小的范围的向量代表的图像是“二次元人物”等等。我们对高维空间进行随机取样,肯定很难取到能生成图片的向量。目标分布与生成分布之间的重叠在某些情况下可以忽略(overlap can be ignored)。
同时,我们根本不知道$P_{data}$和$P_G$的分布 ,只能靠sampling来管中窥豹。这更让它们难以重叠。
综上,$P_{data}$和$P_G$ 被看为几乎没有重叠 。但是,如果两个distribution不重叠,那么JS divergence的结果永远是log2。所以,不采用JS divergence。这样的loss函数太差。
如果采用binary classifier,会达到100%的准确率,这也不行。可以理解为机器死记硬背下了结果。你根本不知道训练时,参数有没有优化得更好。
所以,我们要探寻新的方法去量化loss。我们现在来看看WGAN(Wasserstein GAN)。
Wasserstein distance的想法是这样的:想象你在开一台推土机,把分布P想象成一堆土,把分布Q想象成土堆放的目的地。
把P“挪到”Q的平均移动距离,就是Wasserstein distance。也就是
$$
W(P, Q) = \inf_{\gamma \in \Pi(P, Q)} \mathbb{E}_{(x, y) \sim \gamma} [d(x, y)]
$$
数学公式较为抽象。我们来看看具体的细节,首先,把“土堆”变化的可能方法有无限种,用不同的方法,算出来的距离也不同,
为了让Wasserstein distance有确定的值,我们的方法是:穷举所有的“moving plan”,看哪一个距离最小,作为Wasserstein distance。
看起来Wasserstein distance的计算很麻烦。我们先不讲具体方法,来看看假设计算成功了,有什么样的优势。
与JS distance对比一下,可以看到,
优势很明显。
WGAN用它来定义损失函数,现在,只需要maximize这个函数即可,
$$
L_{\text{WGAN}} = \mathbb{E}{x \sim p{\text{data}}} [D(x)] - \mathbb{E}{x \sim p{\text{gen}}} [D(G(z))]
$$
同时,这里有一个限制:D不能是一个随便的function,必须是一个1-Lipschitz的function,什么是 Lipschitz 函数?一个函数$ f: \mathbb{R}^n \to \mathbb{R} $被称为 K-Lipschitz,如果对任意两个输入 x 和 y,都有:
$$
|f(x) - f(y)| \leq K \cdot |x - y|
$$
其中:
- K 是一个常数,叫做 Lipschitz 常数。
- ∥x−y∥ 是输入的距离(通常是欧几里得距离)。
如果 K = 1,这个函数就叫做 1-Lipschitz 函数。
换句话说:
一个 1-Lipschitz 函数不能改变太快。两个输入点越靠近,它们的输出值也必须越接近,增长的“斜率”不能超过 1。
为什么要这样?如果不做任何限制,那么D(x)在定义的函数种,前一部分越大越好,后一部分越小越好,在generated与real真的没有重叠的情况下,会给real无限大的正值,给generated负无穷。因此,这个training根本无法收敛。加上这个限制以后,这个函数的max才会是Wasserstein distance。
为什么加上这个限制就可以解决刚才的问题?因为它要求discriminator不可以变化剧烈。既然要够平滑,那么就没法generated特别大而real特别小。
现在讨论如何做到1-Lipschitz的限制。
最原始的平滑方法是:
确实是很朴素的思路。之后人们又想出了新的方法:gradient penalty,具体思路是这样的:在生成的data取一个sample,在real的data取一个sample,两点连线之间再取一个sample,让这个点的gradient接近一。
此外,还有很多别的方法。效果比较好的是spectral normalization。
我们现在解决了loss的问题。但是,GAN依然非常难以训练。问题本质在哪里?
G、D是互相成长的。假设某一次D没有train好,没办法分辨生成图片与真实图片的区别,那么G就没有“可以进步的目标”,也就是train会相应“跟着停下来”。有时需要调整Hyperparameter才能继续train。
这就要求G、D在训练时要“棋逢对手”,任何一方不能“放弃比赛”。
GAN for sequence generation来生成文字是很困难的,来看看这个例子。我们现在将一个seq2seq的model decoder作为G。难点在于,如果通过梯度下降训练G,想办法让它的数据在D的输出得分最高,是做不到的。假设现在改变了decoder的参数(小小的变化),那么它输出的distribution也会有小小的变化。由于这个变化很小很小,不会影响到分数最大(max)的token是什么。最后,就会导致D对这个数据给出的分数不变。
等一下!有一个地方值得思考!既然在这个问题中,对token的选择有max,因此导致gradient失效(具体是什么失效?是没办法对一个“选择”操作求导数,因此梯度就断了 ),那么在CNN中不也常用max pooling池化吗?为什么那里没有出现梯度消失的问题?
CNN中的 max pooling虽然选了最大值,但它是对连续值操作的,而且它在反向传播中是有定义梯度的。
具体来说:
- 假设一个pooling window是
[0.2, 0.8, 0.5]
,最大值是0.8。 - 反向传播时,梯度只传给了“最大值那个位置”,也就是0.8的那个神经元,CNN 中的 max pooling 是在 前向传播中选出最大值的位置,然后在 反向传播时把梯度传给最大值所在的位置,别的都设为 0。
- 所以虽然不是“所有地方都传梯度”,但梯度还是有的,是可导的。
这跟token选择的argmax不同:
- max pooling是作用在连续空间上,有梯度定义;
- token选择是离散空间的argmax或sampling,是不可导的操作。
这不是典型的“梯度消失”(gradient vanishing),而是 梯度不存在(non-differentiability of sampling)
总结如下:
操作 | 是否离散 | 是否可导 | 是否能传播梯度 | 为什么 |
---|---|---|---|---|
Argmax / Sampling(用于序列生成) | ✅ 是 | ❌ 否 | ❌ 不行 | 离散选择,不可导 |
Max Pooling(用于CNN) | ✅ 是 | ✅ 是(sub-gradient) | ✅ 可以 | 只把梯度传给最大值的位置 |
Softmax + Sampling | ✅ 是 | ❌ 否 | ❌ 不行 | 采样阶段仍然是离散的 |
Softmax(本身) | ❌ 否 | ✅ 是 | ✅ 可以 | 是连续可导的函数 |
对于max pooling与Argmax / Sampling的对比,我感觉这个例子可以加深理解:
假设你有这样一个输入:
1 | 输入: [2, 5, 1, 3] |
你做一个 size=2 的 max pooling(步长=2),你就会变成:
1 | max(2,5) = 5 |
直接扔了别的值,拿最大的走了。虽然在前向传播里只保留了最大值,但记住了它来自谁。反向传播的时候,比如 loss 对输出的梯度是:
1 | dL/d[5,3] = [a, b] |
只把这个梯度 a 传给“5 的来源”(也就是 5 本人),b 传给 “3 的来源”(3 本人),其他的输入值全都不给梯度,设为 0。
最终回传的梯度是这样:
1 | 输入: [2, 5, 1, 3] |
数学上,max(x₁, x₂, …, xₙ) 的导数是一个 subgradient:
- 它只对最大值的位置为 1,其它为 0
- 如果有多个最大值并列,那它会在它们之间分配梯度(可以随机,也可以平均)
为什么不能对 argmax 用这个办法?因为 argmax 是更“粗暴”的操作:
- Max pooling:对一个小窗口选最大,记录来源,还能传回去。
- Argmax:输出整个向量的 index(比如 “第 7 个词”),不保留值,只保留位置,梯度根本没法定义在哪个维度传回去。
我突然有个有点幼稚的问题,毕竟我是初学者,
为什么 GAN 生成序列的时候不能用 max pooling避免“梯度不存在”?非得用 argmax?
我经过思考,发现,如果要对序列的某一步的softmax结果进行 max pooling,就必须让步长等于词表的长度。否则,就会出现这样的情况:
max pooling 取每两个值 pool 一下,变成:
1 | max([0.0002, 0.001]) = 0.001 |
这确实得到了一个更短的向量,改变了词的概率分布,但没法再知道每个新位置对应哪个词了,因为池化把词位置信息搞没了。
现在来让max pooling的步长等于词列表的长度 (即对整个softmax输出做pooling),那么实际上就是选出概率最大值。这时候就会变成选择softmax输出中的最大概率,就是在离散空间中进行argmax操作。那显然在这种情况下,梯度传播也会遇到不存在的挑战,因为最大值的选择是离散的,这仍然是一个不可导的操作。当最大值的位置选定后,其它位置的梯度就会是0。因此,模型也只能根据最大概率的位置更新参数,而无法根据其它位置的概率来调整模型。梯度失效。
现在我们回到刚才的问题。GAN遇到了不能用gradient descent train的问题,可以当作 reinforcement learning 的问题,硬做一下。
对于GAN结果的评估,有很多方法。例如,用image classifier,结果的几率分布越集中,就说明效果更好。但是,这种方法会被 mode collapse的问题“骗过去”。如图,蓝色星星是真正的数据分布,红色的是GAN生成的data。可以注意到,GAN没有完整地学习到数据的分布,但是评估效果表面上会很好。在“二次元人物生成”任务中,GAN生成了很多同一张二次元人物的面孔,这张面孔反复出现,本质就是GAN没有学好分布,
如何避免?目前还在研究。例如,BGAN会把train point一路上记下来,在mode collapse之前,把train停下来。
现在来看一个更难被侦测的问题:mode dropping。这个问题是,真实的数据分布大于生成的数据。不像mode collapse那样“反复生成同一张图”,看起来样本之间不完全重复,但遗漏了部分真实分布的支持区域。
维度 | Mode Collapse | Mode Dropping |
---|---|---|
概念 | 生成器输出收敛到少数几个模式 | 生成器不覆盖全部真实模式 |
样本多样性 | 极低(甚至完全相同) | 一定多样性,但丢了一部分 |
体现 | 所有输出几乎一样 | 输出不包含某些“类型”的数据 |
原因 | 生成器收敛到骗过判别器的“捷径” | GAN训练中未能探索完整数据空间 |
是否输出单一模式 | ✅ 是 | ❌ 不一定 |
真实分布 vs 生成分布 | 生成分布过窄 | 生成分布偏移或缺少区域 |
怎么做去看diversity够不够呢?过去,常常用image classify,把所有的distribution平均起来,如果平均的distribution非常集中,就代表多样性不够。
还有一些方法,先不赘述。
现在来看看conditional GAN。这个可以完成text to image、pix2pix 的任务。
还有有趣的sound to image任务,其中输入的声音从左到右变大,
现在来看看GAN的一个应用:之前都是supervised的情况下,现在来看看GAN在不成对、不标记的data下的训练。什么样的任务是unpaired data呢?像image style transfer这样的任务,比如,要将真人的照片转成二次元风格的照片。
在这种情况下,如何解决?cycle GAN可以解决。为了让输入与输出相关,会训练两个generator,一个将人物转为二次元,一个将二次元转回人物,用cycle consistency loss作为loss函数,Cycle consistency指的是循环一致性:
假设有两个映射:
- G:A→B
- F:B→A
那么cycle consistency要求:
$$
F(G(a)) \approx a \quad \text{for all } a \in A
$$
$$
G(F(b)) \approx b \quad \text{for all } b \in B
$$
损失叫cycle consistency loss,比如用L1距离就是:
$$
\mathcal{L}{cycle}(G, F) = \mathbb{E}{a \sim p_{data}(a)}[|F(G(a)) - a|1] + \mathbb{E}{b \sim p_{data}(b)}[|G(F(b)) - b|_1]
$$
会不会有这样的问题发生?第一个generator学会“在domain x”里看到一张带眼镜的图片,就在生成的图片左上方做一个标记;第二个generator学会看到生成的图片左上方有一个标记,就生成一张戴眼镜的图片。而生成的二次元图片中不带有眼镜。这种问题是会发生的,需要解决。
很多人在同一时期(17年3、4月)提出了相似的cycle GAN的模型。
cycle GAN也可以用在文字风格上。例如,将negative的句子“你真笨”变为positive的句子“你真聪明”。
这种unsupervised的方法还可以运用在之前学习的任务中,