GluonCV复现Wasserstein GAN

简介

本篇博客将讨论GluonCV 0.3版本中新加入了Wasserstein GAN,包含了Wgan的一些实现细节和我踩到的一些坑。

Wgan介绍

Wgan是在Dcgan的基础上,使用wasserstein距离替代KL距离。 使用wasserstein距离的好处是,即使当两个概率分布是没有重合的时候,同样能衡量出一个距离,而这种情况下,dcgan中使用的KL距离是会为0,导致梯度消失,训练过程不稳定。 而如果要让discremator能够衡量出wasserstein距离,需要让discremator满足Leibniz连续。

具体到神经网络当中的话,就是需要让权重都小于一个比较小的数。

关键细节

Weight clip

对于discremator的权重参数,每个iter前都需要clip到-0.01到0.01的范围

for p in netD.collect_params():
    param = netD.collect_params(p)[p]
    param.set_data(mx.nd.clip(param.data(), opt.clamp_lower, opt.clamp_upper))

Weight的初始化

对于generator和discremator都需要初始化权重

for layer in layers:
        classname = layer.__class__.__name__
        if classname.find('Conv') != -1:
            layer.weight.set_data(mx.ndarray.random.normal(0.0,0.02,shape=layer.weight.data().shape))
        elif classname.find('BatchNorm') != -1:
            layer.gamma.set_data(mx.ndarray.random.normal(1.0, 0.02,shape=layer.gamma.data().shape))
            layer.beta.set_data(mx.ndarray.zeros(shape=layer.beta.data().shape))

关于generator和discremator的迭代次数

算法在每个epoch都会遍历完所有的数据batch,交替训练discremator和generator,在第一个epoch的前面25轮交替训练中,discremator用了100个batch更新100次,generator只用1个batch更新一次,之后的训练中,discremator更新5次,generator更新一次。

训练discremator

discremator的最后输出是没有sigmoid的

#真实样本计算出一个
errD_real = netD(data)
#随机噪声生成出一个图像
fake = netG(noise)
#detach操作可以减少计算量,梯度无需传递到generator
errD_fake = netD(fake.detach())
#计算损失函数
errD = errD_real - errD_fake

训练generator

#从随机噪声中生成一个图像
fake = netG(noise)
errG = netD(fake)
#计算损失函数
errG.backward()

Rmsprop

在wgan中,作者推荐使用Rmsprop作为优化器,在Pytorch中Rmsprop的实现公式是

在Mxnet中Rmsprop的实现公式是

只是一个防止分母为0的值,通常取默认值,因为这个值非常小,一般放在根号里面和外面影响不大,但因为在wgan中,一般也比较小,所以在就对结果产生比较大的影响了,当把画出来之后可以看到 前面部分出现了训练不稳定的现象。 解决方法是调小的值,将generator的参数设置为,将discremator的参数设置为,最后 训练结果如下

Cv2的resize函数的坑

在Pytorch中,对图像的处理都是调用的Pillow的函数,而在Mxnet中都是调用的Cv2的库,对数据集的原始图像进行resize对大部门任务来说都是第一步,而且resize一般都会使用默认的Bilinear算法。而在复现wgan的过程中,遇到了始终无法复现原版wgan的情况。如下图所示,两张图片都是在generator迭代了3万轮的时候生成出来的 可以看到,明显Mxnet训练出来wgan中图像更加杂乱。当把Mxnet.image中的image.imresize方法改为用Pillow库实现后,可以完美复现原版Wgan,于是我验证了一下,同一张图片被resize到64*64大小后,虽然Cv2和Pillow的大部分像素值非常接近,但还是有些像素值差了60+,两个算法的l1距离普遍在2万以上,虽然肉眼看不出来,但对结果产生了巨大的影响。 解决方法是将image.imresize的interpolation参数设置为bicubic,这样最后训练生成出来的图像和论文中是最为接近的。 最后generator迭代40万轮后的效果如下