更深入理解StyleGAN,究竟什么在控制人脸生成,我该如何控制?
Dec 9, 2019
原创:岐山凤鸣,转载请注明本站域名
理解可能有偏差,有错误请指出~
参考:
[1] Image2StyleGAN: How to Embed Images Into the StyleGAN Latent Space?
[2] A Style-Based Generator Architecture for Generative Adversarial Networks
前言:这篇StyleGAN的follow文章很有点意思,也是19年ICCV的Oral论文,是对StyleGAN进行的更多的理解和分析,而正是这些理解和分析,让StyleGAN有了超越仅仅生成高清图像的领域,进而对GAN有了更多的理解也有了更多的操作空间。什么是对GAN的理解?GAN本质是根据一个空间的分布(如512d向量空间),得到另一个空间的分布(如1024x1024x3的人脸空间),如果对这个有更多理解后,对原空间进行一些操作,从而让目标空间根据你想要的结果发生变化,这是非常awesome的。
理解StyleGAN
先放一张用烂的神图:
这图直接表明了StyleGAN的网络结构,很直观有两部分组成,一部分是左边的mapping网络和右边的Synthesis网络。
先明确输入,输入是一段随机分布latent,记为zz
,首先zz
经过Norm后,直接由多个串联的FC进行映射,映射到latent space \omegaω
,关于这个的中文说法,有的说是"潜空间"或者"隐空间",总之我认为这个就是一个编码空间,每段编码经过仿射变换意味着一个 style,之后将style进行合成就能形成人脸。
那么原始输入latent映射到了latent space之后,假如原始latent的shape是[512],那当前映射后的shape还是是[ 512],再经过一些变换,得到n个[512]输入上图的A即仿射变换后得到n个style,其中每个style的编码是(2)的向量,即为(y_{s,i}, y_{b,i})(ys,i,yb,i)
,i表示第几层。这里在代码实现的时候,直接将向量经过多个个FC后,broadcast到n个layer后经过updating moving average + style mixing regularization + truncation trick后即可,可直接输入(n,512)的编码dlatent。
这里知道了A的输出是什么之后,再看最右边,是n个高斯噪音输入,这个是直接对特征图作用的,关于GAN中加噪音的原因可以参考别的资料,这里基本就是引入随机的细节。接下来是右边网络的细节部分,看下两图:
这是一个自顶向下的结构,最顶上的输入是一个const的比如4x4x512的矩阵,层层会进行传递,每层都包括style的输入和噪音的输入,输出是一个[res, res, channel]的矩阵,跨层的时候需要降采样,层内传递需要一个AdaIN模块和3x3的conv,最底层输出的是[1024,1024,3]的生成图像。AdaIN的模块公式上面给了,意思很简单,对特征图进行标准化后,和两个style值进行平移和缩放,然后一起经过conv。这个操作很像BN,但不同的是两个style值是根据编码来的,而不是特征图,这里对理解来说很重要,理解了BN也就能理解这个AdaIN模块的重要性。
简略的说,从代码层面考虑,整个过程很简单,就这样:
- 输入一个随机向量z,shape=(512,1)
- 多个FC对随机向量直接进行变换,得到
z_{latent}zlatent
,shape=(512,1) - 复制n层
z_{latent}zlatent
,n表示右边结构的层数,shape=(n,512),经过uodating moving average + style mixing regularization + truncation后,得到dlatent,shape=(n,512) - 先得到一个初始const图,往下进行输入
- 进行每层结构的计算,结构开始需要一个降采样,比如(4,4,512)降采样到(8,8,512),给feature_map加点高斯噪音,然后标准化。对输入的512的latent进行放射变换得到两个y,根据两个y对标准化的feature_map进行放缩和平移,之后输入conv进行channel变化
- 将n个结构都跑完,前几个结构都是4x4,8x8这种小的,主要是学的轮廓啊,头发在哪啊这些玩意,中间的几个结构比如32x32,64x64这种大一点的,主要是学的更多的人脸表情啊,眼睛啊什么的,最后几个更大的比如256x256,512x512,1024x1024学的就是超细节的比如毛发啊,颜色啊什么的。
- 输出最后的1024x1024x3
所以可以看到这个过程并不难,有兴趣的同学可以去读源码,读源码的话,一个完整的链是这样的:
- 302行的G_style是完整的生成器流程
- 334行定义synthesis网络,即440行的G_synthesis,同时了解总层数这些信息,dlatent的shape即是(n_layers,512)这种
- 338行定义mapping网络,即384行的G_mapping
- 根据随机的latent生成dlatent,四个步骤嘛,分别是345行的mapping,然后是348行的update,然后是356行的style mixing,然后是369行的truncation,最后得到dlatent
- 378行直接将dlatent输入syntheis网络即可,噪音均在中间生成。
只有上述过程全部了解,才能开始下面的内容,所以要理解如何进行嵌入和编辑,必须对StyleGAN有很完整的理解。
到底什么东西在控制人脸生成?
一句话,dlatent,其shape=(n_layers, 512)。
这到底是什么东西?
刚刚已经说了,它的前面几层在做什么?前面几层是4x4,8x8这种小图,前面几层的风格即是脑袋轮廓,头发位置这些显而易见的宏观风格。
所以它的中间几层,不断的一点点放大,去学的细节就会越来越多,n_layers越多,给的细节就越丰富,细节程度是自顶向下的那种,最顶部的细节是粗糙的,最底部的细节是最精细的。
每一个结构里,输入的dlatent 512编码,都仿射变换到了两个数y,然后带噪声的特征图通过标准化后用这两个y进行平移和放缩(AdaIN),所以实际控制风格的只有y,y来自于dlatent,这即表示输入当前层的dlatent即包含风格。
举个很简单的例子,比如你最后要输出256x256x3的清晰图像。那么整个res的变化是:
(4,4) -> (8,8) -> (16,16) -> (32,32) -> (64,64) -> (128,128) -> (256,256)
这一共是7个要变化的res,也就是需要7个结构,每个结构是包含两层的(一层欠采样,一层正常变换),所以一共14层,那么原始的z编码后是得到(14, 512)的编码,分别输入这14层。
第一层之前首先先init了一个(4,4,512)的东西,这时候还不是图像,然后加个噪音后标准化,将dlatent的第一个(512)提出来放射变换到y1,y2后对标准化后的图进行控制平移和放缩,然后一层层的输入。
所以就是这么简单,(14,512)的编码,一层层逐级的在控制人脸的生成。如果你改变(14,512)的前两三层,可能这个人的脑袋就变了,如果改了最后两三层,可能这个人的肤色什么的,毛孔什么的会发生变化。
如何控制人脸的特征生成?
既然了解了什么在控制人脸生成后,我们要进行控制,就变得很简单,只需要控制这个dlatent即可了。
那么到底如何控制?比如我现在有一张照片I,里面描述的是一个人严肃的表情,我想让他笑起来怎么办?
那么首先,先预训练上述的StyleGAN,得到了G,其中输入(14,512)的编码,会通过G得到一张256,256的图,这时候需要做一个类似AE结构的训练,训练什么?
我们得训练一个编码器,输入I后,得到人脸识别的emb前一层的feature_map,然后用这个feature_map经过一个编码结构,得到(14,512)的编码,然后输入参数冻结的G,得到了假图I',再通过一个预训练的人脸识别网络N(I, I')增加一个损失L给整个编码器,训练编码器。
这到底在干嘛?很简单,我们希望能够得到I这张照片的编码而已,也就是希望控制预训练且参数冻结的G生成出I这张照片。这样我们才能得到I的dlatent,得到dlatent后,通过控制dlatent才能编辑I这个人脸的特征,比如发色什么的。
到这步,我们手上得到了两个东西,生成器G,编码器E,要让I笑起来,怎么办?
这时候需要得到一组人脸数据对(I_1, I_2)(I1,I2)
,满足一个条件,前者是严肃不笑的照片,后者是笑的照片,都进入E后得到一组两个编码(E_1,E_2)(E1,E2)
,根据之前的理论,很明显啊,这一组从E_1 -> E_2E1−>E2
的方向向量,即是控制人脸笑起来的关键因素,当然这个方向向量也是(14,512)的shape。所以现在唯一要做的,就是求解出这个方向向量,然后作用到E(I)上,那么G(E(I) + \vec{w})G(E(I)+w)
就是让I笑起来的图了。
其他的例如表情迁移(哭->笑)、特征迁移(白发->黑发)这些都用类似的操作就可以很容易的实现。关于求解这个向量,这个问题下节进行详述。
542305306@qq.com · Github · RSS · 某只Ecohnoch · © 2015 Gaohaoyang · Designed by HyG
岐山凤鸣
[闲谈]论被StyleGAN摧残的一天
Dec 9, 2019
原创:岐山凤鸣,转载请注明本站域名
这段时间做了很多GAN相关的研究,因为希望将之前投稿CVPR的文章结合GAN一起,做一点更强的工作,甚至是开创性的工作。
所以我兴冲冲的从DCGAN开始,复现了一个当前国内还没有的仅靠一段声纹生成人脸的应用,具体可以从Demo这里看到。当然,这个是从DCGAN出来的,人脸呢,很模糊,细节基本都不清晰,分辨率更是只有可怜的64x64,还不能改。
于是从DCGAN转战到17年传言巨强的WGAN,根据WGAN-GP的各种原理,对我的网络进行了从网络结构,到loss层面的大修改,这时候我还不能充分的理解WGAN带来的巨大的变革,只能从它论文中对DCGAN错误的推导,和大家纷纷惊叹的评论中知道,这个解决了很多原生GAN的问题,让调参变得更简单。
从这篇知乎文章中,了解到它的改进主要基于四点:
- D去掉了sigmoid
- D和G的loss,不再用以前的sigmoid交叉熵形式,而是直接对向量编码的输出取sum
- D的参数截断
- 采用不基于动量的优化算法
相比于具体的技术细节,我这里更想分享一下我个人的经历和感受。到这里为止,两年来所有的深度学习模型,机器学习模型,我都尽量的从头到尾,自己按照论文和相关的代码进行复现,自己调出一套最好的参数,从来不会直接把别人的代码弄下来,简单复现一下就再也不去探讨细了。而遇到WGAN的时候,出现了第一个问题,那就是明明计算图、输出均是按照正确的形式搭的,在进行了从DCGAN形式到WGAN形式的转变后,G竟然无法生成正常的图像,它的输出变成了一团糟的乱码。
我尝试把loss再改回去,发现就没有问题了。这仿佛在说DCGAN比WGAN的效果要好,于是回头进行了各种检查,发现了两个问题:
- 我的框架里除了D做判别外,还有一个分类器C做人脸的类别分类
- GAN的调参之路是比图像识别更难的,我还需要进一步的调整参数
所以就陷入了问题,上述两个总结一点便是经验不足,所以这里我陷入了迷茫,究竟要怎么改?要严格改为和已有代码仓库中一样的参数么?另外一条线里,我在研究GANimation的损失,这里同样遇到了问题,讲究太多了。这才深深明白深度学习被称为炼丹的原因,在之前图像识别的境界里没有遇到这么多和理论复杂的参数。
之后放弃了WGAN的调参,一边进行动画的分析,一边继续寻找更好的人脸GAN,直到我遇到了StyleGAN,这下可把我震撼到了,高分辨率的生成,举个刚出炉的生成的栗子:
很难相信这是用算法生成出的假脸,因为细节实在是太丰富了,用我自己的眼睛去看,我甚至相信这确实就是一张正常的照片,但可惜世界上不存在这样的人。有好事者之后做了一个很火的网站,https://thispersondoesnotexist.com/,每一次刷新都能得到一张不存在的人脸图,这非常的神奇,不得不说NVIDIA大法好。
于是我对着StyleGAN源码,准备进行重现和学习,先按照Guideline进行了test的尝试,这时候我还没意识到什么,只是感叹这个跨平台的性能做的太好了,直接下完代码后,就能够跑起来得到测试结果。直到看了看细节,再看了看train,我惊呆了,同样是TensorFlow,为什么他这么秀?TensorFlow的代码居然可以写成这样。
这里必须要原谅我的无知,在我早期进行图像识别时,一般是先参考TF官方的源代码的示例,进行搭建,从给出的各个经典模型里进行代码写法的归纳总结,但torch的代码看多了以后,我一度认为Python里torch的代码写的比TF优雅多了,而且更加的pythonic。我在使用TF进行计算图搭建,数据处理的时候,往往都写成流处理的模式,和写sh脚本一样,达到效果即可,很难实现其的封装性。
原本的TF流程大致是这样:
- 原始数据的索引构建
- 数据输入、预处理的离线或Tensor行为
- 计算图的搭建,从Placeholder到opt.minize
- 训练流程与测试流程的搭建
而这四步,都是顺序的,流处理形式的,通过这样的方法,我基本都能够很容易的进行他人代码的复现,并且能找到对应的bug,了解更多的细节,我一度认为我自己写代码的瓶颈,在于代码的优雅度,而不在其他方面。现在StyleGAN的源码给了我更多的思路,原来的方法虽然能实现功能,但并不高效,部署效果不好,提示信息不够,高层的逻辑难搞。NVIDIA给出的方案是:
- 预先定义好一套深度学习的内核系统,可以随便复制到其他的项目下进行使用。
- 训练的时候,采用字典驱动的方法,直接链接对象,无论是数据,方法等。
- 测试/部署的时候,无论计算图和预训练和参数,直接加载预训练好的二进制项目。
纸上得来终觉浅,看上去很简单,各位看官可以自行去研究上述的Style源码,会发现非常的头疼,封装性高到很难在里面进行一些修改,但即便如此,可读性又维护在一个很高的层次,这是非常的厉害,正符合去突破我当前存在的瓶颈:代码质量不高、工程化不足。今天被StyleGAN摧残了一整天,但我也成功跑起来了我自己收集的Chinese数据集和修改后的版本,看着训练数的跳动,从4x4到8x8到16x16最终回到1024x1024的过程也是非常的令人兴奋。之后会进一步的修改Demo中提供的展示,希望有一天自己做的东西,不管是功能、技术还是优雅的层面,也能得到如此的认可。
542305306@qq.com · Github · RSS · 某只Ecohnoch · © 2015 Gaohaoyang · Designed by HyG
Loading [MathJax]/extensions/MathMenu.js 岐山凤鸣 文章