CycleGAN 无依赖风格转换
学习资料:
- 我制作的GAN简介短片
- 我制作的看图
说画
的GAN - 论文 Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks
- 本节代码
- 代码有我自己定义的依赖utils.py, visual.py, mnist_ds.py
怎么了¶
CycleGAN 是一种奇妙的 GAN,它与前面的 Pix2Pix 和 CCGAN 都不一样。我非常喜欢 CycleGAN 的模式,也为它能够解决的问题感到震撼。 前阵子我在抖音的时候,发现了它这种视频,那时候我还不懂GAN,这种视频效果让我百思不得其解。 用真人的图像,变成卡通图像。
如果你用监督学习来做吧,你哪来那么多带着卡通图像的真人图像,让一个画家画断手也弄不来一一对应的人脸卡通画呀。那这种人脸卡通化的技术又是怎么搞的呢? 后来我深入了GAN,了解到了 CycleGAN 这种技术后,这个问题才迎刃而解。即使抖音中的卡通化不是按照 CycleGAN 来搞的, 那么肯定还是借鉴了 CycleGAN 的思路来做的。
所以一句话来描述 CycleGAN 做的事情:训练风格转换时,不要求具备一一对应的风格转换数据,也能学习到风格转换的规律。 我详细展开一下,通常在训练风格转换时,都是拿着原图 X 和 原图对应的转换后的 Y 来作为一对监督数据,把 X 转换成 Y,但是很多时候, 我们并不能方便地获取到转换后的风格图像(毕竟我们不愿看到很多画家画断手)。CycleGAN 就提供了另一种思路,没有一一对应的 XY 是吗?没问题, 你多给我点 X 和 多给我点 Y,他们对不对应没关系,我照样可以训练风格迁移。
上图就是论文中经典的生成结果,比如普通马变斑马,简笔画填充,图片虚化等。 更多有关于GAN做图片生成图片的技术,我在这个短视频中做了具体介绍了。 这次教学中,为了减少训练时间,统一横向对比其他教学中的模型, 我还是用 mnist 数据做了这样一个实验,让 6 和 9 两个数字进行风格迁移,就类似马和斑马的风格迁移一样。
怎么训练¶
为了实现无监督形式的风格迁移,我们第一个反应应该是下面这种方式。构建两套GAN的 pic2pic 系统,分别训练一个 猫狗生成器
和 狗猫生成器
。 用这两种生成器进行猫狗两者的互相风格迁移。
CycleGAN 用了另一种非常优雅的方式来结合这两种生成器,在不浪费计算资源的前提下,复用两种生成器, 在训练猫狗生成器
的时候,也同时能训练一把狗猫生成器
。一石二鸟,一举两得。这就是 CycleGAN 的魔法。
上图是paper中的图示,看起来是有那么一点复杂,我们化简一下,看看化简之后的结构图和我画的上一张 猫狗生成器
和 狗猫生成器
有啥差别。
和我画的上一张互相独立的两个生成器对比,这张图片中展示的则是相互依赖的生成器。为什么要做成依赖的?因为与其单独训练两个生成器, 不如利用这两者之间的互相转换关系,多利用一层有价值的关系信息,将他们捆绑起来,促进生成器的效用。所以不管是在训练猫生狗
还是在训练狗生猫
时, 都用到了 猫狗生成器
和 狗猫生成器
。 而且是以一种 AutoEncoder 的方式训练这两种生成器, 让他们具备抽取有用信息的能力。
CycleGAN | AutoEncoder | |
---|---|---|
压缩/Encode/迁移 | 生成风格化的原图 | 生成对原图的理解 |
解压/Decode/重构 | 从风格图重构原图 | 从理解中重构原图 |
CycleGAN 模型要想办法抽离原图的关键信息,构造由关键信息组成的风格图片;再尝试从风格化的图片中找到原图的信息,并重构回原图片。 所以至始至终,在这条转变的线路中流淌着的就是被模型提取出来的原图关键信息。 对比 原图 和 重构图 之间的差别,我们就实现了 AutoEncoder 的计算误差的基础功能。但问题来了,我们怎么保证风格化的图就是我们想要的风格呢? 在 AutoEncoder 中,不用管压缩后的理解
是什么,因为那不重要,我们不会使用到它。但是 CycleGAN 中,风格化图片是有用的, 我们怎么确保它能是一张风格化图片?这就是 CycleGAN 和 AutoEncoder 最大的差别了。 我们可以借鉴 GAN 的 Discriminator 来控制中间这个生成的风格化图片,让它趋近真实图片。
所以整个 CycleGAN 的训练中,有两种误差:
- Discriminator 的真假判断误差,确保迁移后/风格化后的图长得像一张真实的图片;
- 和 AutoEncoder 类似的重构误差 (cycle-consistency loss),保证
G
和F
两个模型理解原图信息,能找到原图关键信息。
对应的误差计算公式分别是:
将他们两个误差整合起来就变成了这样:
接下来我们来撸一撸代码。
秀代码¶
如果想直接看全部代码, 请点击这里去往我的Github. 我在这里说一下代码当中的重点部分。
在训练 CycleGAN 的时候,我们已经提到,为了训练效率,它会同时利用两种不同风格的数据,在一个 cycle 中训练两种数据。所以他的 train()
方法稍微有点不同。 在我用 mnist 做 demo 的这个例子中,我会让 CycleGAN 转换 6 和 9,让第一个生成器 G
从 6 生成 9,让第二个生成器 F
从 9 生成 6。 所以在每次 gan.step()
的时候,我都把 6 和 9 同时传给它。
在 CycleGAN 内部,如上面画的结构图,它其实同时包含了两种风格变换器
,也就是两个生成器,我在代码中用:
self.g12
:从风格1生成风格2self.g21
:从风格2生成风格1
同时 CycleGAN 也会有两个判别器:
self.d12
:检验self.g12
生成效果self.d21
:检验self.g21
生成效果
而这里面的 self._get_generator
和 self._get_discriminator
基本上都是复用了 CCGAN 定义的结构。 Generator 是一个 pic2pic 的结构,Discriminator 是一个典型二分类的结构。
CycleGAN 代码最最重点的部分是在它如何训练的部分,前面这些都是结构性的铺垫。在训练 Discriminator 的时候还好说,和普通的 GAN 没太多差别, 只是需要同时计算两个 Discriminator 的 loss 而已。
而 Generator 的训练就相对比较复杂一点,中间包含了计算:
- Cycle consistency loss
- Discriminator 的 Adversarial loss
- Identity loss
前面两个loss我们都介绍过,那这个 Identity loss 又是个什么鬼?在原论文中,它觉得在真实照片和画作互转的时候,是和现实生活中 马与斑马 互转这种情况不同的。 画作中,背景色等很多环境信息也要进行转移,如果不加这个 Identity Loss,这些信息的转移会存在偏差。所以这是一个选着性添加的loss。
这里面我单独将 cycle()
和 identity()
拿出来写,这样逻辑会稍微清晰点,在计算 cycle()
的时候,简化起来是这样一个逻辑: g21(g12(img1))
和 g12(g21(img2))
,将原图变出去,再变回来。在计算一下变回来后还像不像原图。 而 identity()
就是在检测风格化后的画作和原画作差距多少?它需要尽量减少迁移后的差距,保留原画作主色调。
最后train出来,效果就如下图啦,可以看出,6具有的一些属性,比如高矮胖瘦都成功的传递到9身上了。反之9也传递到6身上了。这就是 CycleGAN 来回迁移的独特魅力, 同时也意味着,g12
和 g21
保持着内在的联系,他们是镜像的同属性风格变换器。
问题¶
其实 CycleGAN 并不完美,后作还有很多提升它效果的论文,我用这套框架在 CelebA 数据集上试了试,实现男女性别转换。虽然转换成功了,但是普遍还是有点糊。 特别是男转女的时候。我猜可能是男生头发本来就少,还要生成变化莫测的女生头发,男转女的生成器负担太大了。反之,女转男稍稍好一点。哈哈哈,是不是女汉子要比娘炮更好当?? 这个实验的代码在这CelebA CycleGAN。
除了训练难度,对数据分布的识别可能也不太好,比如论文中一张经典图片。要做马转斑马,但是马上的人也被当成马的一部分,被转成斑马了, 所以常听到的人马兽
,人马兽
是真的吗?
总结¶
CycleGAN 是我见识到的 GAN 应用比较有趣的其中一个,不同于经典 GAN 的训练,它利用了 Cycle 循环的训练方法,同时训练 来回变身
大法, 让基佬,Gay老不用花钱整容也可以翻身了。