前两篇文章讨论了传统GAN的Loss,该Loss有些不足的地方,导致了GAN的训练十分困难,表现为:1、模式坍塌,即生成样本的多样性不足;2、不稳定,收敛不了。Martin Arjovsky在《Towards principled methods for training generative adversarial networks》、《Wasserstein GAN》文章中,对传统Loss造成训练困难的原因进行了讨论:因为真实样本的概率分布 ℙr P r 与生成器生成的样本概率分布 ℙg P g 的支撑集不同,又由于两者的流型(Manifold)的维度皆小于样本空间的维度,因而两者的流型基本上是不可能完全对齐的,因而即便有少量相交的点,它们在两个概率流型上的测度为0,可忽略,因而可以将两个概率的流型看成是可分离的,因而若是一个最优的判别器去判断则一定可以百分百将这两个流型分开,即无论我们的生成器如何努力皆获得不了分类误差的信息,这便是GAN训练困难的重要原因,有一篇博文(《令人拍案叫绝的Wasserstein GAN》)对上述两篇文章做了深入浅出的解释,总结一下是:
用KL Divergence和JS Divergence作为两个概率的差异的衡量,最关键的问题是若两个概率的支撑集不重叠,就无法让那个参数化的、可移动的概率分布慢慢地移动过来,以拟合目标分布。
于是文章提出一种新的Loss定义,即Wasserstein Distance,它可以作为两个概率分布的距离衡量指标,其定义如下:
(4)式要求得到上确界,上确界的具体函数形式我们不知道,但我们可以用神经网络来逼近它,这是判别器(Discriminator)的作用,也就是Discriminator网络充当了 f(x) f ( x ) 的角色,因此(4)等价于:
for epoch in range(opt.n_epochs):
for i, (imgs, _) in enumerate(dataloader):
# Configure input
real_imgs = Variable(imgs.type(Tensor))
# ---------------------
# Train Discriminator
# ---------------------
optimizer_D.zero_grad()
# Sample noise as generator input
z = Variable(Tensor(np.random.normal(0, 1, (imgs.shape[0], opt.latent_dim))))
# Generate a batch of images
fake_imgs = generator(z).detach()
# Adversarial loss
loss_D = -torch.mean(discriminator(real_imgs)) + torch.mean(discriminator(fake_imgs))
loss_D.backward()
optimizer_D.step()
# Clip weights of discriminator
for p in discriminator.parameters():
p.data.clamp_(-opt.clip_value, opt.clip_value)
# Train the generator every n_critic iterations
if i % opt.n_critic == 0:
# -----------------
# Train Generator
# -----------------
optimizer_G.zero_grad()
# Generate a batch of images
gen_imgs = generator(z)
# Adversarial loss
loss_G = -torch.mean(discriminator(gen_imgs))
loss_G.backward()
optimizer_G.step()
剩下的就是要保证 fθ(x) f θ ( x ) 满足K-Lipschitz条件,《Wasserstein GAN》做了一个简单地处理,因为判别器是由神经网络构成的,因此对每层的线性算子中参数做了一个夹逼,限制其取值范围,就可以实现。如上面代码的这个部分:
# Clip weights of discriminator
for p in discriminator.parameters():
p.data.clamp_(-opt.clip_value, opt.clip_value)
此取值范围既不能太大,又不能太小,到底取多少合适呢?《Wasserstein GAN》没做讨论,留给后来有心人。