男女速变 —— GAN

本篇还是按上一篇的规矩,先上图:
首先看汉子变女神:
男女速变 —— GAN_第1张图片男女速变 —— GAN_第2张图片男女速变 —— GAN_第3张图片男女速变 —— GAN_第4张图片
男女速变 —— GAN_第5张图片男女速变 —— GAN_第6张图片
男女速变 —— GAN_第7张图片男女速变 —— GAN_第8张图片
再来看美女变猛男:

男女速变 —— GAN_第9张图片男女速变 —— GAN_第10张图片男女速变 —— GAN_第11张图片男女速变 —— GAN_第12张图片男女速变 —— GAN_第13张图片男女速变 —— GAN_第14张图片男女速变 —— GAN_第15张图片男女速变 —— GAN_第16张图片男女速变 —— GAN_第17张图片男女速变 —— GAN_第18张图片

最后,向泰坦尼克CP致敬:
男女速变 —— GAN_第19张图片男女速变 —— GAN_第20张图片
让我穿越命运之门
寻找另一个你的眼神
如梦幻中的星辰
飘落那冰冷红尘
璀璨色彩
照耀飘飘长发
暮色阴沉
雕刻时光里的皱纹

我要是再写得飘逸一点,也能进梨花诗派吧。。。

这个效果明显比上一篇的VAE要好得多,全部代码在github/face_gan(Starred by AttGAN作者),和VAE一样使用tensorflow和tensorflow内置的Keras,项目的pictures目录中有更多的样例图片。

像那些不是GAN的GAN说的,其实face_gan也和StarGAN、AttGAN一样不能算一个严格意义上的GAN,但因为一说GAN大家就明白怎么回事,所以这个GitHub项目和本文都是用GAN的名字。

毋庸讳言,face_gan严重的参考了StarGAN和AttGAN,毕竟任务目标是差不多的。下面我会介绍这个项目一些具体的技术细节,希望读者能通过本文更深入的了解GAN、卷积网络和深度学习训练。

网络结构

face_gan的生成器基本使用了StarGAN的生成器网络结构(而StarGAN是从CycleGAN那里学来的,呵呵~):
男女速变 —— GAN_第21张图片
两次Down Sampling后直接接残差块,然后再两次Up Sampling恢复原始尺寸。分辨率不降太多,保持足够多的空间位置信息,对提高图像质量很有帮助,网络也更容易训练。不过face_gan和StarGAN有三个区别:

  • StarGAN使用了6个Residual Block,face_gan使用9个。之所以增加3个是因为:StarGAN输入128x128的图像,而face_gan输入160x160(最早想用facenet做Perceptual Loss作为重构损失,facenet输入160x160,不想resize图像所以face_gan的输入就也用160x160了。后来决定直接用pixel的L1做重构损失,但输入尺寸就没再改。),是StarGAN的四分之五倍,增加3层Residual Block相应的增大网络的感受野。从结果看,增加的残差块确实对图像质量有一定的提高。
  • StarGAN和AttGAN的上采样均采用转置卷积,实测感觉Upsampling2D+Conv2D的效果更好,所以采用Upsampling2D+Conv2D。
  • StarGAN的Residual Block的实现其实并不标准,分支最后没加ReLU激活,应该是为了避免输出只有大于0的部分。face_gan使用标准的pre-activation方式来保证更广的输出范围。

StarGAN和AttGAN的discriminator网络结构采用的都是最通用形式,对抗损失/训练方式都使用WGAN-GP(所以不能用Batch Normalization),face_gan也一样。只不过StarGAN的discriminator没用任何Normalization,face_gan的discriminator用的是Instance Normalization(新版的Keras不能直接支持Layer Normalization了,所以我也就没试),实测对训练的稳定和收敛还是有帮助的。

另外,face_gan所有的初始化都用的是glorot_normal,Keras缺省glorot_uniform,感觉glorot_normal初始化的网络更不容易陷入病态。

损失和训练方法

StarGAN,AttGAN,face_gan本质上都是使用相同的组成部分构成损失,但训练方式略有不同。

StarGAN如下图:男女速变 —— GAN_第22张图片
AttGAN如下图:
男女速变 —— GAN_第23张图片
它们都使用原图 X a X^a Xa训练分类,使用转换属性后的 X b ^ X^{\hat b} Xb^训练生成器转换属性的能力,都使用像素的L1距离作为重构损失。face_gan最开始想使用facenet做Perceptual Loss,但感觉并不好用,有可能Perceptual Loss的约束太强,不利于属性转换。而直接使用像素损失的效果也不错,还更加节省运算和内存,何乐而不为呢?

StarGAN和AttGAN主要的区别是StarGAN使用 G ( G ( X a , b ) , a ) G(G(X^a, b), a) G(G(Xa,b),a)训练重构损失,而AttGAN使用 G ( X a , a ) G(X^a, a) G(Xa,a)训练重构损失。另外StarGAN把目标属性b和 X a X^a Xa一起输入G,而AttGAN在G的瓶颈处输入b。

face_gan看上去是上面两者的混合:
男女速变 —— GAN_第24张图片
然而face_gan并不是为了和这两者有所区别而故意把它们的训练流程混合在一起。

实际上,StarGAN使用 G ( G ( X a , b ) , a ) G(G(X^a, b), a) G(G(Xa,b),a)训练重构损失存在问题。在这个流程里,把 X a X^a Xa转换成 X b ^ X^{\hat b} Xb^时,无法保证 X a X^a Xa的原始信息完全包含在 X b ^ X^{\hat b} Xb^的像素里,当 X b ^ X^{\hat b} Xb^再次通过生成器输出 X a ^ X^{\hat a} Xa^时,却要求 X a ^ X^{\hat a} Xa^尽可能还原 X a X^a Xa,这本身就是不可能完成的任务(或者说贝叶斯误差不可能为零)。举例来说,假设第一步把一个金发美女的照片和属性“黑发”输入生成器,这时生成器输出的是一张黑发美女照片,第二步则是把这幅黑发美女的照片和属性“金发”输入生成器,输出金发美女照片,并且希望这张照片和原始照片尽量相同,可是在第二步中,生成器的输入只有一张黑发美女的照片,现在告诉它要转换成金发,它怎么能知道有多金?!为了达到重构损失最小,唯一合理的就是输出一个金发的平均颜色。StarGAN论文中Figure 8的样例图片(应该是使用实际生成的样图)就展现了这种情况,棕发美女的还原图发色和原来是不一样的!StarGAN这样做是因为CycleGAN就是这样做的,而它学习了CycleGAN的方法。所以实际上CycleGAN也一样有这个贝叶斯误差,但是StarGAN的任务场景和CycleGAN不一样,它并不一定要使用CycleGAN的这种cycle consistency loss,而只需要使用AttGAN的方式就可以了。

归根结底,属性是一个非常抽象的信息,实际图像中包含的复杂的细节信息可以归纳成某种属性,但从一个属性还原出原始的细节是不可能的。StarGAN这个问题的结果是导致/强迫它做属性转换时尽可能的保留原始图像的信息,尽可能少的修改原图。它在做男女转换时都非常微妙,头发绝对不动,比如下面这两张图:
男女速变 —— GAN_第25张图片男女速变 —— GAN_第26张图片
这是face_gan把 G ( X a , a ) G(X^a, a) G(Xa,a)改成 G ( G ( X a , b ) , a ) G(G(X^a, b), a) G(G(Xa,b),a)用于重构损失,训练8个epochs(从第5个epoch开始做学习率衰减)的结果,你似乎能看出生成器想动头发又不敢动,生怕还原不回去的挣扎的样子。。。

所以face_gan采用AttGAN的方式训练重构损失。这种方式下,重构损失可以最大程度的帮助保留原图的信息,属性转换时又可以无拘无束的放飞自我~~

但是face_gan并没有像AttGAN一样在生成器的瓶颈处输入目标属性,主要是觉得这样似乎没有必要,属性在网络的开始就加进去,暗示着网络从一开始就朝着属性转换这个目标努力。实际上,这个决定对最终的效果应该没有什么影响。AttGAN强调编码器解码器的概念,所以它把属性插入到网络瓶颈处输入,这种结构可以做到不同人脸先融合再编辑属性,不过在它的论文里没有提。

face_gan和StarGAN实现/AttGAN实现在训练方法上另一个不同的地方是,StarGAN和AttGAN都是用类似下面的方式(参见StarGAN/solver.py)随机生成目标属性:

# Generate target domain labels randomly.
rand_idx = torch.randperm(label_org.size(0))
label_trg = label_org[rand_idx]

我猜测之所以使用这种方式,一个很可能的原因是:它们都是训练网络进行多属性转换,而很多属性在数据集里是数据不平衡的,比如Bald这个标签,大部分人脸应该都不是秃子。上面的代码保证了属性转换任务训练的平衡性,虽然训练量会缩小到相对少的标签数量。
另一个原因也许是它们想降低训练的难度,在一次训练中,只有一部分属性被要求转换。

但因为face_gan只专注于男女转换这一项任务,所以这两个原因都不适合,CelebA里男女基本各半,不存在不平衡问题。因此face_gan每次训练都做相反的属性转换:

target_label = 1 - label

需要说明的是做多属性转换其实并没有什么技术上的障碍,目前只做性别转换主要是为了控制项目范围,并且男女转换在所有属性转换里无疑是最有趣的。

GAN训练中的其他超参数

要实现人脸属性转换这个目标,引入对抗训练是必需的。单靠分类约束,根本无法生成满意的效果。这一点AttGAN的论文也提到过,没有对抗,生成图像基本就相当于对抗攻击样本,可以骗过分类器,但人眼基本看不出图像产生了什么变化。

face_gan/face_adversarial_translator.py把一些可调的超参数或者权重都放在文件开头,方便调整,但其实缺省的数值基本就都是最优的。下面对一些超参数进行一下说明:

  • 优化器:WGAN的论文建议GAN不使用带动量的优化器,可能是GAN的训练过程中优化的方向要经常改变,不像一般的网络会向一个固定的方向收敛。所以动量反而对GAN的训练有损害。加入梯度惩罚后可以用Adam了,不过用的时候基本都把 β 1 \beta1 β1设成0.5,也就是只保留很近的几次动量。StarGAN和AttGAN这样做,我在face_gan里也毫不犹豫的这样做 ?
  • batch_size:很多任务都需要适当的引入方差,face_gan里这个最优值是16,32或更大反而不好。
  • 学习率衰减:因为face_gan的网络结构是模仿StarGAN的,所以学习率衰减也使用StarGAN式的线性衰减。
  • 重构损失权重 lambda_rec:这项非常重要,前面提过StarGAN重构损失的训练方式会使属性转换后的图像只有一些微妙/微小的变化,如果重构损失权重设成100,即使不使用它的训练方式,也会有同样的效果。我希望属性转换能有一些生动的变化,所以尽管设成100会提高生成图像的清晰度,仍旧把这项权重设成10。
  • 其它权重:lambda_cls_gen和lambda_g_w可以一起调整,单调lambda_cls_gen可能会导致训练不稳。因为生成图像的属性类别和它的真实度有竞争/对抗的关系。两者一起提高会生成更加夸张的属性转换效果。

训练经验

深度学习项目开发中有许多重要的经验,本文只想说非常重要的一点,那就是在训练中提供/打印出足够的信息。比如face_gan的损失由很多部分组成:

discriminator_loss = wasserstein_loss + lambda_gp * gradient_penalty_loss() + lambda_cls_real * real_cls_loss
generator_loss = generator_wasserstein_loss + lambda_rec * generator_rec_loss + lambda_cls_gen * gen_class_loss

各项损失不能失衡,如果只是笼统的显示discriminator_loss和generator_loss,调整权重时就无从下手,如果训练过程出现问题,也无法知道具体是哪一项造成的。face_gan/face_adversarial_translator.py除了会打印这些loss,还打印出了图像准确度,分类准确度这些metrics作为参考。

在训练过程中进行监控尤其对GAN非常必要,一旦发现位面崩溃,就要立即撤出。。。本文的图样是由最后一次正式训练得到的模型生成的,实际上在那次训练中,epoch 4的末尾到epoch 5开始阶段位面险些崩溃,我都要中止那次训练了,它又从悬崖边溜达了回来。因此大概浪费了一个epoch的训练effort,不过我实在不愿花时间再来一次了,所以就用这最后一次的结果,训练的log上传在github上,供大家参考。如果训练真的崩溃了,不要灰心,有可能只是一次恰好陷入病态的初始化,重新来过就行 ? 当然,如果是系统性的问题,就需要根据前述的各项损失数值状况进行分析解决了。

评价GAN图像生成任务的效果,不仅要靠loss和metrics的数值,更需要根据生成图像的主观感受。因此在训练过程中,测试一下图像生成也非常重要。
比如前面提到的训练崩溃,在face_gan中生成的图像会有黑洞或白洞:
男女速变 —— GAN_第27张图片
看着这些洞洞,仿佛又有了位面穿越的感觉。。。。
另外,更重要的是根据训练中生成的图像效果进行调整,不要等到训练完毕再进行。

问题和改进

现在face_gan生成的图像主要存在3个问题:

  1. 和原图比略有模糊(如果原图比较高清的话)
  2. 属性转换后,和原图不同的头发的生成质量不高
  3. 女变男长发变短发时,消去的区域生成器不知道如何补完

可能的改进方法包括:

  • 生成高质量的头发需要空间上大范围的关联信息,使用SAGAN的技术应该可以解决,并且实现简单,增加的运算负担小。很有可能对提高生成图像的整体质量都有帮助。
  • WGAN-GP使用梯度惩罚,对当前的生成数据分布具有高度的依赖性。随着训练过程进行,生成的数据分布空间会逐渐变化,就会导致这种方法对李氏连续约束的不稳定。谱归一化直接作用于网络参数,稳定性更高。但是谱归一化在Keras里实现起来比较麻烦,暂时不会考虑。

至于生成真实的图像去填补消去的区域,主要依赖GAN能学到多少真实分布的信息,暂时没有有把握的思路。

人脸属性转换的这个系列到此就结束了,让人工智能进行创造这个梦想才刚刚开始实现!

你可能感兴趣的:(人工智能,深度学习与人工智能)