本文是
[1]
的译文, 按照作者Connor Shorten
的说法, 此博客讨论的是StyleGAN2的诸如weight demodulation, path length regularization和去掉progressive growing等信息。虽然我去年底自己复现过StyleGAN2的pytorch版, 但对这些内容也有些忘记了,借此机会复习下。
对于StyleGAN不了解的小伙伴,建议先看下别人写的关于StyleGAN的基本介绍,然后再来看本文,相信会收获更多~
0. 前言
来自Nvidia 赫尔辛基实验室的Karras等人提出的StyleGAN1的架构在Flicker-Faces-HQ (FFHQ)等数据集上表现非常惊人,相比之前的DCGAN和Conditional GAN等结构。
以FFHQ数据集为例,StyleGAN不但可以生成逼真的人脸(几乎无懈可击),而且能够对人脸进行解耦, 以便对人脸进行编辑(19年的论文,截至2020年10月中旬,引用已破千(截至2020年10月20日,是1232.),在twitter上也经常有用StyleGAN的latent space做文章的工作,比较典型的厉害人物有CUHK的沈宇军和周博磊)。
StyleGAN生成的人脸图像
在StyleGAN中, Frechet Inception Distance (FID) 是最常用的衡量图片真实性的指标(由法国科学家提出,也称遛狗绳距离)。
第一代StyleGAN
[2]
的实验表,可见,StyleGAN1在FFHQ最好的FID为4.40,FID越低越好。
第二代StyleGAN
[3]
的实验表,可见,StyleGAN2在FFHQ上的表现比第一代更好,其中,FID值由4.40提升到2.84. 算的上是巨大的提升了。
本文主要讨论StyleGAN2相比StyleGAN1在架构上的改变,正是这些改变使得FID指标有了显著提升~,定性观察上面,StyleGAN2的artifacts出现频率也显著比StyleGAN1低。
并基于很多人对latent space进行魔改的需求,对latent space进行平滑操作。
原作者搞了个17分钟的解释StyleGAN2的视频在下面,如果感兴趣的小伙伴,可以饭墙去看看[4]
1. 重大改进
1.1 Weight Demodulation
karras和ming-yu liu等大佬非常擅长用改造的normalization层进行image synthesis任务(如GauGAN和StyleGAN)。如GauGAN中的SPADE和StyleGAN的AdaIN。
对StyleGAN,karras等人使用AdaIN (adaptive instance normalization)来控制source vector w w w对生成人脸的影响程度。
对StyleGAN2来讲,作者们调整了AdaIN的使用,从而有效的避免了水印artifacts(water droplet artifacts)。
AdaIN是被用于快速Neural Style Transfer的normalization层。在Neural Style Transfer任务中,研究者们发现:AdaIN可以显著的解耦(remarkable disentanglement) low-level的"style"特征和high-level的"content"特征。
图像来自<Arbitrary Style Transfer in real-time with Adaptive Instance Normalization> authored by Xun Huang and Serge Belongie.
不同于一般的风格迁移(Style Transfer)任务,AdaIN的出现使得风格迁移任务从受限于一种风格或者需要lengthy optimization process的情况中摆脱了出来。
AdaIN表明,仅通过归一化统计就可以将风格(style)和内容(content)结合起来。
Karras等人在StyleGAN2中解释了AdaIN这种归一化方法会"discard information in feature maps encoded in the relative magnitudes of activations"。
生成器(Generator)通过将信息sneaking在这些层中来克服信息丢失的问题,但是这却带来了水印问题(water-droplet artifacts)。
对于为什么鉴别器(Discriminator)不能识别水印,作者与读者有同样的困惑。
在StyleGAN2中,AdaIN被重构为Weight Demodulation, 如下所示:
- AdaIN 层的含义: 同BN层类似,其目的是对网络中间层的输出结果进行scale和shift,以增加网络的学习效果,避免梯度消失。相对于BN是学习当前batch数据的mean和variance,Instance Norm则是采用了单张图片。AdaIN则是使用learnable的scale和shift参数去对齐特征图中的不同位置。
a,b为StyleGAN的细节图,c和d为StyleGAN2的细节图。可以看出
[5]
,normalization中不再需要mean,只计算std即可。并将noise模块移除style box中,如下所示[5]
Weight demodulation的处理流程如下
-
① 如上图里面的©所示,
Conv 3x3
后面的Mod std
被用于对卷积层的权重进行scaling(缩放)
w i j k ′ = s i × w i j k w_{ijk}^{'} = s_{i} \times w_{ijk} wijk′=si×wijk, 这里的 i i i表示第 i i i个特征图。 -
② 接着对卷积层的权重进行demod
σ j = ( ∑ i , k ( w i j k ′ ) 2 ) \sigma_j = \sqrt (\sum_{i,k} (w_{ijk}^{'})^2) σj=(i,k∑(wijk′)2)
那么,得到新的卷积层权重为
加一个小的 ϵ \epsilon ϵ是为了避免分母为0,保证数值稳定性。尽管这种方式与Instance Norm并非在数学上完全等价,但是weight demodulation同其它normalization 方法一样,使得输出特征图有着standard的unit和deviation。此外,将scaling参数挪为卷积层的权重使得计算路径可以更好的并行化。这种方式使得训练加速了约40%(从每秒处理37张图片到每秒处理61张图片—不知道作者用的是什么机器)。
1.2 Progressive growth
StyleGAN图像对鼻子和眼睛等面部特征有很强的位置偏好。作者Karras等人将此归因于progressive growing training (同Karras作品PGGAN[6]
,18年的论文,引用已经有2200左右了,Karras太强了。。。)
Progressive growing指的是:“先训一个小分辨率的图像,训好了之后再逐步过渡到更高分辨率的图像。然后稳定训练当前分辨率,再逐步过渡到下一个更高的分辨率。”
尽管对研究者来说,复现progressive growing非常令人头大(因为Karras他们全都是用tensorflow 1.x写的这些网络)。这需要复杂的循环和策略设计。但是总的来说,progressive growing还是属于一种比较直观的对高分辨率图像合成的coarse-to-fine的方式。这是因为一般的GAN在训练超高分辨率( 102 4 2 1024^2 10242)的图像生成上面相当不稳定,按照作者的话说:
The discriminator will easily distinguish real and fake images, resulting in the generator unable to learn anything during training.
不同于StyleGAN第一代使用progressive growing的策略,StyleGAN2开始寻求其它的设计以便于让网络更深,并有着更好的训练稳定性。
对Resnet结构而言,网络加深是通过skip connection实现的。所以StyleGAN2采用了类似ResNet的残差连接结构(residual block)。对于这些设计,我们使用双线性滤波对前一层进行上/下采样,并尝试学习下一层的残差值(residual value)。
如下图所示,Karras等人使用来自TomTom公司和Adobe研究院的作者提出的“Multi-Scale Gradients for Generative Adversarial Networks” MSG-GAN[7]
来revisit之前的progressive growing的方式,从而更好的利用不同尺度的信息。
正是受MSG-GAN的启发,StyleGAN2设计了一个新的架构来利用图像生成的多个尺度信息(不需要像progressive growing那样麻烦了),他们通过一个resnet风格的跳跃连接在低分辨率的特征映射到最终生成的图像(下图的绿色block)
StyleGAN2的网络结构(受MSG-GAN启发)。
Here is the performance improvement using different approaches. FID越低越好,可以看出对G output加skip和对D 增加residual的组合效果最好。
作者表明MSG-GAN的方式同progressive growing类似,训练的早期更多地依赖低频/分辨率来对最终的输出产生影响。下图显示了特征图对最后输出的贡献情况。对这一点的研究促使Karras等人扩大网络规模,使1024x1024的分辨率对最终输出贡献更大。
1.3 Lazy Regularization
StyleGAN1对FFHQ数据集使用了R1正则化。实验结果表明,在评估计算代价的时候,regularization是可以忽略的。实际上,即使每隔16个mini-batch使用regularization的方式,模型效果依旧很不错,因此,在StyleGAN2中,我们可以使用lazy regularization的策略,即对数据分布开始跑偏的时候才使用R1 regularization,其它时候不使用。
1.4 Path Length Regularization
Path Length Regularization的意义是使得latent space的插值变得更加smooth和线性。简单来说,当在latent space中对latent vector进行插值操作时,我们希望对latent vector的等比例的变化直接反映到图像中去。即:“在latent space和image space应该有同样的变化幅度(线性的latent space)”(比如说,当你对某一组latent vector进行了5个degree的偏移,那么反映在最后的生成图像中,应该是同样的效果, 如下图所示)。
Karras等人通过对生成器(Generator) 增加了一个loss项来达到这个目标。
代码如下,简单来说就是计算生成器生成的图像对其latent vector的梯度(Jacobian矩阵), ∣ ∣ J w T y ∣ ∣ 2 ||J_w^Ty||_2 ∣∣JwTy∣∣2就是代码中的pl_lengths
, a a a为pl_mean_var
的移动平均: pl_mean_var
= pl_mean_var
+ 0.01 * ( m e a n mean mean(pl_lengths
) - pl_mean_var
)。
这种方法"dramatically facilitates projecting images back into the latent space"(让图像得到更加线性的对应latent vector)。
#----------------------------------------------------------------------------
# Non-saturating logistic loss with path length regularizer from the paper
# "Analyzing and Improving the Image Quality of StyleGAN", Karras et al. 2019
def G_logistic_ns_pathreg(G, D, opt, training_set, minibatch_size, pl_minibatch_shrink=2, pl_decay=0.01, pl_weight=2.0):
_ = opt
latents = tf.random_normal([minibatch_size] + G.input_shapes[0][1:])
labels = training_set.get_random_labels_tf(minibatch_size)
fake_images_out, fake_dlatents_out = G.get_output_for(latents, labels, is_training=True, return_dlatents=True)
fake_scores_out = D.get_output_for(fake_images_out, labels, is_training=True)
loss = tf.nn.softplus(-fake_scores_out) # -log(sigmoid(fake_scores_out))
# Path length regularization.
with tf.name_scope('PathReg'):
# Evaluate the regularization term using a smaller minibatch to conserve memory.
if pl_minibatch_shrink > 1:
pl_minibatch = minibatch_size // pl_minibatch_shrink
pl_latents = tf.random_normal([pl_minibatch] + G.input_shapes[0][1:])
pl_labels = training_set.get_random_labels_tf(pl_minibatch)
fake_images_out, fake_dlatents_out = G.get_output_for(pl_latents, pl_labels, is_training=True, return_dlatents=True)
# Compute |J*y|.
pl_noise = tf.random_normal(tf.shape(fake_images_out)) / np.sqrt(np.prod(G.output_shape[2:]))
pl_grads = tf.gradients(tf.reduce_sum(fake_images_out * pl_noise), [fake_dlatents_out])[0]
pl_lengths = tf.sqrt(tf.reduce_mean(tf.reduce_sum(tf.square(pl_grads), axis=2), axis=1))
pl_lengths = autosummary('Loss/pl_lengths', pl_lengths)
# Track exponential moving average of |J*y|.
with tf.control_dependencies(None):
pl_mean_var = tf.Variable(name='pl_mean', trainable=False, initial_value=0.0, dtype=tf.float32)
pl_mean = pl_mean_var + pl_decay * (tf.reduce_mean(pl_lengths) - pl_mean_var)
pl_update = tf.assign(pl_mean_var, pl_mean)
# Calculate (|J*y|-a)^2.
with tf.control_dependencies([pl_update]):
pl_penalty = tf.square(pl_lengths - pl_mean)
pl_penalty = autosummary('Loss/pl_penalty', pl_penalty)
# Apply weight.
#
# Note: The division in pl_noise decreases the weight by num_pixels, and the reduce_mean
# in pl_lengths decreases it by num_affine_layers. The effective weight then becomes:
#
# gamma_pl = pl_weight / num_pixels / num_affine_layers
# = 2 / (r^2) / (log2(r) * 2 - 2)
# = 1 / (r^2 * (log2(r) - 1))
# = ln(2) / (r^2 * (ln(r) - ln(2))
#
reg = pl_penalty * pl_weight
return loss, reg
#----------------------------------------------------------------------------
2. 感谢阅读
感谢阅读StyleGAN2的重要特性分析,谢谢~
参考文献
[1] StyleGAN2–medium
[2] StyleGAN: A Style-Based Generator Architecture for Generative Adversarial Networks
[3] StyleGAN2: Analyzing and Improving the Image Quality of StyleGAN
[4] StyleGANv2 Explained!
[5] GAN — StyleGAN & StyleGAN2 – Jonathan Hui
[6] PGGAN
[7] MSG-GAN
[8] StyleGAN2-pytorch