学习GAN过程中整理的文字,其中很多来自令人拍案叫绝的Wasserstein GAN
回顾一下,原始GAN中判别器要最小化如下损失函数,尽可能把真实样本分为正例,生成样本分为负例:
其中 Pr 是真实样本分布, Pg 是由生成器产生的样本分布。对于生成器,Goodfellow一开始提出来一个损失函数,后来又提出了一个改进的损失函数,分别是
后者在WGAN两篇论文中称为“the - log D alternative”或“the - log D trick”。WGAN前作分别分析了这两种形式的原始GAN各自的问题所在,下面分别说明
一句话概括:
判别器越好,生成器梯度消失越严重。
由Generative Adversarial Nets Ian中理论推导得最优判别器为:
我们就可以看看在极端情况——判别器最优时,生成器的损失函数变成什么。生成器的loss加上一个不依赖于生成器的项,使之变成
代入最优判别器即 D∗(x) ,再进行简单的变换可以得到
变换成这个样子是为了引入Kullback–Leibler divergence(简称KL散度)和Jensen-Shannon divergence(简称JS散度)这两个重要的相似度衡量指标,所以接下来介绍这两个重要的配角——KL散度和JS散度:
于是生成器的loss函数就可以继续写成
当 P_r 与 P_g 的支撑集(support)是高维空间中的低维流形(manifold)时, P_r 与 P_g 重叠部分测度(measure)为0的概率为1。
由上式可知在(近似)最优判别器下,最小化生成器的loss等价于最小化 P_r 与 P_g 之间的JS散度,而由于 P_r 与 P_g 几乎不可能有不可忽略的重叠,所以无论它们相距多远JS散度都是常数 log2 ,最终导致生成器的梯度(近似)为0,梯度消失。
判别器训练得太好,生成器梯度消失,生成器loss降不下去
一句话概括:
最小化第二种生成器loss函数“- log D trick”,会等价于最小化一个不合理的距离衡量,导致两个问题,一是梯度不稳定,二是collapse mode即多样性不足
最终得到最小化公式“- log D trick”等价于最小化
这个等价最小化目标存在两个严重的问题。第一是它同时要最小化生成分布与真实分布的KL散度,却又要最大化两者的JS散度,一个要拉近,一个却要推远!这在直观上非常荒谬,在数值上则会导致梯度不稳定,这是后面那个JS散度项的毛病。
第二,即便是前面那个正常的KL散度项也有毛病。因为KL散度不是一个对称的衡量, KL(Pg||Pr) 与 KL(Pr||Pg) 是有差别的。以前者为例
当 Pg(x)→0 而 Pr(x)→1 时, Pg(x)logPg(x)Pr(x)→0 ,对 KL(Pg||Pr) 贡献趋近0
当 Pg(x)→1 而 Pr(x)→0 时, Pg(x)logPg(x)Pr(x)→+∞ ,对 KL(Pg||Pr) 贡献趋近正无穷
换言之, KL(Pg||Pr) 对于上面两种错误的惩罚是不一样的,第一种错误对应的是“生成器没能生成真实的样本”,惩罚微小;第二种错误对应的是“生成器生成了不真实的样本” ,惩罚巨大。第一种错误对应的是缺乏多样性,第二种错误对应的是缺乏准确性。这一放一打之下,生成器宁可多生成一些重复但是很“安全”的样本,也不愿意去生成多样性的样本,因为那样一不小心就会产生第二种错误,得不偿失。这种现象就是大家常说的collapse mode。
TV 距离
KL散度
Kullback–Leibler divergence,相对熵,衡量两个概率分布P(x),Q(x)的距离:
这是一个非对称距离: DKL(P||Q)≠DKL(Q||P) .
JS距离
Jensen–Shannon divergence,基于KL散度发展而来,是对称度量:
其中 M=12(P+Q) 。
Wasserstein距离
Wasserstein距离又叫Earth-Mover(EM)距离,定义如下:
解释如下: Π(Pr,Pg) 是 P_r 和 P_g 组合起来的所有可能的联合分布的集合,反过来说, Π(Pr,Pg) 中每一个分布的边缘分布都是 P_r 和 P_g 。对于每一个可能的联合分布 γ 而言,可以从中采样 (x,y)∼γ 得到一个真实样本 x 和一个生成样本 y ,并算出这对样本的距离 ||x−y|| ,所以可以计算该联合分布 γ 下样本对距离的期望值 E(x,y)∼γ[||x−y||] 。 在所有可能的联合分布中能够对这个期望值取到的下界 infγ∼Π(Pr,Pg)E(x,y)∼γ[||x−y||] ,就定义为Wasserstein距离。
Wasserstein距离相比KL散度、JS散度的优越性在于,即便两个分布没有重叠,Wasserstein距离仍然能够反映它们的远近。
既然Wasserstein距离有如此优越的性质,如果我们能够把它定义为生成器的loss,不就可以产生有意义的梯度来更新生成器,使得生成分布被拉向真实分布吗?
没那么简单,因为Wasserstein距离定义(公式12)中的 infγ∼Π(Pr,Pg) 没法直接求解,不过没关系,作者用了一个已有的定理把它变换为如下形式
上式是指 f 函数必须满足
Lipschitz连续。它其实就是在一个连续函数f上面额外施加了一个限制,要求存在一个常数 K≥0 使得定义域内的任意两个元素 x1 和 x2 都满足
简单理解,比如说f的定义域是实数集合,那上面的要求就等价于f的导函数绝对值不超过K。实际实现时可以通过限制神经网络 fθ 的所有参数 wi 的不超过某个范围[-c, c]来实现。
此时求解w距离可以近似变成求解如下形式
生成器要近似地最小化Wasserstein距离,由于Wasserstein距离的优良性质,我们不需要担心生成器梯度消失的问题。再考虑到L 的第一项与生成器无关,就得到了WGAN的生成器loss。loss越小越好。
我们可以构造一个含参数w、最后一层不是非线性激活层的判别器网络 f_w ,在限制w不超过某个范围的条件下,使得L值取得最大来近似
Wasserstein距离。
注意原始GAN的判别器做的是真假二分类任务,所以最后一层是sigmoid,但是现在WGAN中的判别器 f_w 做的是近似拟合Wasserstein距离,属于回归任务,所以要把最后一层的sigmoid拿掉。WGAN的判别器loss为:
所做的改变
1.判别器最后一层去掉sigmoid
2.生成器和判别器的loss不取log
3.每次更新判别器的参数之后把它们的绝对值截断到不超过一个固定常数c
4.不要用基于动量的优化算法(包括momentum和Adam),推荐RMSProp,SGD。(实验验证)
解决的问题
1.彻底解决GAN训练不稳定的问题,不再需要小心平衡生成器和判别器的训练程度
2.基本解决了collapse mode的问题,确保了生成样本的多样性
3.训练过程中终于有一个像交叉熵、准确率这样的数值来指示训练的进程,这个数值越小代表GAN训练得越好,代表生成器产生的图像质量越高。(gloss越小或dloss越大???)
4.以上一切好处不需要精心设计的网络架构,最简单的多层全连接网络就可以做到
代码为tensorpack/examples/GAN/WGAN.py\
数据集为celebA/Align\&Cropped images
6.1loss
1.GAN LOSS
实现
方式一:直接实现。
方式二:交叉熵的形式(DCGAN也是这种形式)
交叉熵可在神经网络(机器学习)中作为损失函数,p表示真实标记的分布,q则为训练后的模型的预测标记分布,交叉熵损失函数可以衡量p与q的相似性。交叉熵作为损失函数还有一个好处是使用sigmoid函数在梯度下降时能避免均方误差损失函数学习速率降低的问题,因为学习速率可以被输出的误差所控制。tensorflow中自带的函数可以轻松的实现交叉熵的计算。
交叉熵容易跟相对熵搞混,二者联系紧密,但又有所区别。假设有两个分布p,q,则它们在给定样本集上的相对熵定义如下:
可以看出,交叉熵与相对熵仅相差了H(p),当p已知时,可以把H(p)看做一个常数,此时交叉熵与KL距离在行为上是等价的,都反映了分布p,q的相似程度。最小化交叉熵等于最小化KL距离。它们都将在p=q时取得最小值H(p)(p=q时KL 距离为0)。
tf.nn.softmax_ cross_entropy_with_logits(_sentinel=None,
labels=None, logits=None, dim=-1, name=None)
Computes softmax cross entropy between logits and labels.
注意:如果labels的每一行是one-hot表示,也就是只有一个地方为1,其他地方为0,可以使用tf.sparse_softmax_cross_entropy_with_logits()
警告:
这个操作的输入logits是未经缩放的,该操作内部会对logits使用softmax操作
参数labels,logits必须有相同的形状 [batch_size, num_classes] 和相同的类型(float16, float32, float64)中的一种
参数:_sentinel: 一般不使用
labels: labels的每一行 labels[i] 必须为一个概率分布
logits: 未缩放的对数概率
dims: 类的维度,默认-1,也就是最后一维
name: 该操作的名称
返回值:长度为 batch_size 的一维Tensor\
2.WGAN loss
实现
self.d_loss = tf.reduce_mean(vecneg - vecpos, name='d_loss')
self.g_loss = tf.negative(tf.reduce_mean(vecneg), name='g_loss')
6.2具体实验
代码未做修改,WGAN网络架构与DCGAN相同,改动:优化器改为RMS,loss。
跑200个epoch,g_loss和d_loss按原理和具体实现应该是逐渐减小?但是生成的图片效果并不好。