看GAN如何一步步控制图像生成风格?详解StyleGAN进化过程

点击上方“小白学视觉”,选择加"星标"或“置顶

重磅干货,第一时间送达

来源:公众号 机器之心 授权

GAN 能够有条不紊地控制其生成图像的风格吗?

你了解自己的风格吗?大部分 GAN 模型并不了解。那么,GAN 能够有条不紊地控制其生成图像的风格吗?

01ef0ae4c6b35b07750ba10be1333482.png

原版 GAN 基于潜在因子(latent factor)z 生成图像。通常,潜在因子 z 采样自正态或均匀分布,它们决定了生成内容的类型和风格。

f0874957e83d3dedafaf9521ab8c002f.png

基于此,我们需要解答以下两个重要问题:

  1. 为什么 z 是均匀或正态分布?

  2. 既然 z 包含元信息,那么它是否应在每个卷积层生成数据的过程中发挥更主要的作用?(而不是仅作为第一层的输入)

注意:本文将使用「风格」(style)来指代元信息,其包含类型信息和风格信息。

下图是 StyleGAN2 生成的图像:

46baffce523c9a45d88cec5968938b75.png

潜在因子 z

机器学习中的潜在因子通常彼此独立,以简化模型训练过程。例如,身高和体重具备高度相关性(个子越高的人通常体重更大)。因此,基于身高、体重计算得到的身体质量指数(body mass index,BMI)较常用于衡量人体肥胖程度,其所需的训练模型复杂度较低。而彼此独立的因子使得模型更易于解释。

在 GAN 中,z 的分布应与真实图像的潜在因子分布类似。如果我们从正态或均匀分布中采样 z,则优化后的模型可能需要 z 来嵌入类型和风格以外的信息。例如,我们为军人生成图像,并基于男性化程度和头发长度这两个潜在因子来可视化训练数据集的数据分布。下图中缺失的左上角表示男性军人不允许留长发。

4c1e889d21b78d5b16894cb950ec791d.png

如果我们均匀采样此空间,则生成器会试图生成留长发的男性军人图像。这不会成功,因为我们没有相关的训练数据。换个角度来看,当采样使用正态或均匀分布时,模型学习的是哪些潜在因子呢?这似乎更加错综复杂了。正如 StyleGAN 论文所写的那样:「这导致了一定程度上不可避免的纠缠(entanglement)」。

在 logistic 回归中,我们利用基变换(change of basis)来创建二分类类别的线性边界。而 StyleGAN 使用一种叫做映射网络(mapping network)的深度网络,将潜在因子 z 转换成中间潜在空间 w。

e6a376d829e88c146555cf5782a7da0e.png

从概念上来看,StyleGAN 将可进行均匀或正态分布采样的空间(下图中)扭曲成潜在特征空间(下图左),从而轻松生成图像。该映射网络旨在创建彼此独立的特征,以便生成器更容易地执行渲染,同时避免训练数据集中不曾出现的特征组合。

69628c390bc1d3cb04d5909d0a7b94ec.png

StyleGAN 引入映射网络 f,利用八个全连接层将 z 转换成中间潜在空间。w 可被视为新的 z (z』)。通过该网络,512 维的潜在空间 z 可被转换为 512 维的中间潜在空间 w。

6032acf73fa723686abb1fdfced1dadb.png

基于风格的生成器

在原版 GAN 中,潜在因子 z 仅作为深度网络第一个层的输入。我们可能认为,随着网络的深入,z 的作用会逐渐消失。

而基于风格的生成器使用单独学得的仿射运算 A 在每一层中转换 w。转换后的 w 将作为风格信息作用于空间数据。

43b3e97b5c799743e1912d9cee3ebfe7.png

StyleGAN 论文最初使用的是 Progress GAN 网络,并重用了很多超参数,包括 Adam 优化器参数。然后研究者更改模型设计进行多次试验,查看模型性能是否有所改进。

0d13c7f38f06f51ca437ba5ae1593770.png

第一个改进版本 (B) 用双线性采样(bilinear sampling)来替换判别器和生成器中的最近邻上采样/下采样。然后进一步调参,模型训练时间也会增加。

第二个改进版本 (C) 添加了映射网络和风格化(styling)。对于后者,AdaIN(自适应实例归一化)取代 PixelNorm 对空间数据执行风格化处理。

bedde17df663924f8635400d3bde85c2.png

AdaIN 的定义如下:

4b70b9522dca0c9d78007e895cdaefea.png

在此过程中,首先对输入特征图应用实例归一化。然后,StyleGAN 利用风格信息对每个归一化空间特征图执行缩放,添加偏置(μ 和 σ 分别表示输入特征图 xᵢ 的平均差和标准差)。StyleGAN 计算每一层的风格值对 (y(s, i), y(b, i)) 作为 w 的缩放值和偏置值,从而将风格应用于空间特征图 i。归一化特征影响应用于空间位置的风格化程度。

在原版 GAN 中,第一层的输入是潜在因子 z。实验结果表明,向 StyleGAN 第一层添加可变输入毫无益处,因此将可变输入替换为常量输入。

至于改进版本 (D),其第一层的输入被替换为学得的常数矩阵,矩阵维度为 4×4×512。

25136709363359e85e400f46f388cb77.png

StyleGAN 论文中的「Style」指数据的主要属性,如姿势和身份。在改进版本 (E) 中,SytleGAN 向空间数据引入噪声,从而创建随机变化(stochastic variation)。

28854b5a5b84721ee80089352129f635.png

例如,实验中添加的噪声可以为头发(见下图)、胡茬、雀斑或毛孔创建不同的随机变化。

34fba8252a4f981e916d32d901daa722.png

例如,对于 8×8 的空间层,创建一个 8×8 的矩阵,且其中元素包含不相关的高斯噪声(Gaussian noise)。该矩阵被所有特征图共享。但是 StyleGAN 为每个特征图分别学习一个单独的缩放因子,并将该因子与噪声矩阵相乘,之后再添加至前一层的输出。

eb9375d351bee7311d4040777d39fb0e.png

噪声创建了渲染变体,与不包含噪声或噪声仅应用于特定分辨率的情况相比,其优势如下图所示。StyleGAN 论文还表示其缓解了其它 GAN 方法中常出现的重复模式问题。

79b4d7ded11b7d195f4bda4e4a8fa14e.png

总之,当风格被全局应用于特征图时,它覆盖图像的关键属性。噪声引入了像素级局部变化,并带来随机变化,从而生成特征的局部变体。

关于噪声如何导致不同的图像渲染结果,参见以下视频:

最后一个改进版本 (E) 涉及混合正则化。

风格混合与混合正则化

之前我们生成潜在因子 z,并作为生成风格的单一源头。而使用混合正则化后,我们转而使用另一个潜在因子 z₂,在达到特定空间分辨率之后再生成风格。

5a5ea16573bca5b7e5c09da6dc73fc42.png

如下图所示,我们使用生成图像「source B」的潜在因子得到粗糙空间分辨率(4×4 到 8×8)的风格,使用「source A」的潜在因子得到精细空间分辨率的风格。因此,生成的图像具备 source B 的高级风格,如姿势、发型、脸型和眼镜,而生成图像中的所有颜色(眼睛、头发、光线)和更精细的人脸特征则来自 source A。

ec3afc31667f7dbcc76854fcbf197bbf.png

如下图所示,如果我们使用 source B 的中分辨率(16×16 到 32×32)风格,则生成图像从 source B 处继承到较小规模的人脸特征、发型和眼睛状态(睁开/闭上),而来自 source A 的姿势、脸型和眼镜则被保留。最后一列中,模型从 source B 中复制了高分辨率风格(64×64 到 1024×1024 分辨率),这主要影响图像的色调和微结构。

dee0e6f94b6822178bd4ee287f7a88d8.png

在训练过程中,一定比例的图像是使用两个随机潜码(latent code)生成的,而不是仅使用一个。

训练

与 CelebA-HQ 数据集相比,FFHQ(Flickr-Faces-HQ,高清人脸数据集)质量更高,覆盖范围更大,如年龄、种族、图像背景以及眼镜、帽子等配饰。在 StyleGAN 中,CelebA-HQ 数据集训练过程中使用 WGAN-GP 作为损失函数,而 FFHQ 数据集则使用非饱和 GAN 损失函数和 R₁正则化项,如下所示:

1005b4a4611a1fbcd91fda402456e47a.png

w 中的截断技巧

z 或 w 中的低概率密度区域可能不具备足以准确学习模型的训练数据。

cd1936b57060a6aeba68a120d72ab9cb.png

因此,在生成图像时,我们可以避开这些区域,以变化为代价来换取图像质量的改进。这可以通过截断 z 或 w 来实现。在 StyleGAN 中,截断 w 即可实现此目标:

e6cf6e4e80ad1808ad7007e9ae136740.png

其中 ψ 表示风格缩放(style scale)。

但截断仅在低分辨率层上执行(比如 4×4 至 32×32 空间层,ψ = 0.7)。这可以确保不影响高分辨率细节。

当 ψ 为 0 时,它生成的平均人脸如下图所示。随着 ψ 值的调整,我们可以看到人物视线、眼镜、年龄、肤色、头发长度和性别等属性的变化,如从戴眼镜到不戴眼镜。

f4e5abc4e871d7d67115879ee30982e2.png

感知路径长度

StyleGAN 论文还提出一种度量 GAN 性能的新型指标——感知路径长度(perceptual path length)。GAN 逐步改变潜在因子 z 中的某个特定维度,进而可视化其语义。

3bb1d899c603cbd42800fc9b0c69c672.png

此类潜在空间插值可以得到令人震惊的非线性视觉变化。例如,两端图像中均未出现的特征可能出现在中间图像中。这标志着潜在空间和变化因子具备高度相关性。因此,我们可以通过度量执行插值时的累积变化,来量化这些变化。

首先,我们使用 VGG16 嵌入来度量两个图像之间的感知差异。如果我们将潜在空间插值路径分割为线性片段,则可以对每个片段添加所有感知差异。差异值越低,则 GAN 图像的质量越高。详细数学定义参见 StyleGAN 论文。

StyleGAN 中存在的问题

StyleGAN 生成图像中存在类似水滴的斑状伪影,在生成器网络的中间特征图中此类伪影更加明显。这一问题似乎出现在所有 64×64 分辨率特征图中,且在分辨率更高的特征图中更为严重。

a8993857f7a3241a44ec13709b04abab.png

GAN 技术已经很成熟,现在我们可以很容易地放大图像,查看伪图像检测过程中异常图像模式出现的区域。

4cc209dffe79966923d3c6bdbeb67a4b.png

StyleGAN2 论文将这一问题归因于 AdaIN 中的实例归一化。AdaIN 原本用于风格迁移,迁移过程中输入的一些重要信息被丢失。

dce10b288f793807a4a79369289bab1c.png

StyleGAN2 论文将这一发现表述如下:

我们认为问题出在 AdaIN 运算,它可以分别对每个特征图的均值和方差执行归一化,由此可能摧毁在特征的幅度中找到的任何彼此相关的信息。我们假设这种水滴状伪影出现的原因是生成器有意将信号强度信息传递通过实例归一化:通过创建主导统计数据的强局部尖峰,生成器可以像在其它地方一样有效缩放该信号。

此外,StyleGAN2 提出一种替代设计方案来解决渐进式增长导致的问题,以稳定高分辨率训练。

a436ea15d51f2d05cfda52e701ec641a.png

如上图所示,即使使用渐进式增长生成的人脸图像改变方向,其牙齿中缝(蓝线)没有发生变化。

在探讨 StyleGAN2 之前,我们先重新绘制 StyleGAN 设计图(下图右)。该设计的 AdaIN 模块同样分为两个模块,但此图添加了偏置,而原始设计图中省略了这一项。(注意,目前模型设计没有任何改变)

33e0236103562af0a79daac44d500d91.png

StyleGAN2

权重解调(weight demodulation)

在实验结果的支持下,StyleGAN2 做出了以下改变:

  • 移除(简化)初期处理常数的方式;

  • 归一化特征时无需求均值;

  • 将噪声模块从风格模块中移出。

25859aa9a16cfeed05227affc64a154e.png

然后,StyleGAN2 利用权重解调简化模型设计,如下图所示。它重新访问实例归一化设计 (Norm std),意图用另一种不会造成水滴状伪影的归一化方法取代它。下图右是使用权重解调得到的新设计。

122bbd0738baafaa7b67fc38f2af9be3.png

权重解调增加了以下改变:

1. 调制 (mod std) 后是卷积 (Conv 3×3),二者组合起来可用于缩放卷积权重,并实现为上图右中的 Mod。(这并未改变模型设计)

47bde3f916a392a2cc8c435258dcdb64.png

其中 i 是输入特征图。

2. 然后用 Demod 对权重执行归一化:

48b61f9ed1cbfd22528a1a322cb9bd52.png

归一化后的新权重是:

f93102db54ed0921482218745861ef5d.png

该公式添加较小值 ε,以避免数值不稳定问题。尽管从数学角度来看,这与实例归一化不同,但它对输出特征图执行归一化后得到标准差,并实现了与其它归一化方法类似的目标(即令训练过程更加稳定)。实验结果表明,水滴状伪影问题得到了解决。

StyleGAN2 做出的改进

现在,我们来看 StyleGAN2 的改进版本。下图总结了多种模型改动,以及对应的 FID 分数改进情况(FID 分数越小,模型性能越好)。

7c8355a7e00e3121bbc203199576774c.png

懒惰式正则化(Lazy regularization)

StyleGAN 对 FFHQ 数据集应用 R₁正则化。懒惰式正则化表明,在成本计算过程中忽略大部分正则化成本也不会带来什么坏处。事实上,即使每 16 个 mini-batch 仅执行一次正则化,模型性能也不会受到影响,同时计算成本有所降低。

路径长度正则化

如前所述,路径长度可用于度量 GAN 性能。一个可能的麻烦是,插值路径上不同片段之间的路径距离变动很大。简而言之,我们希望连续的线性插值点之间的图像距离类似。也就是说,潜在空间中的位移会导致图像空间中出现同样幅度的变化,而这与潜在因子的值无关。因此,我们添加一个正则化项,如下所示:

2c4a0d382a42a0225d00e01fd963831c.png

当图像空间中的变化与预计位移不同时,成本增加。图像空间中的变化基于梯度计算得到,预计位移则近似于目前的运行平均值。

此处不再详述,代码参见:https://github.com/NVlabs/stylegan2/blob/7d3145d23013607b987db30736f89fb1d3e10fad/training/loss.py,读者可以据此运行 debugger。

渐进式增长

StyleGAN 使用渐进式增长来稳定高分辨率图像的训练。上文我们提到了渐进式增长的问题,StyleGAN2 寻求一种替代设计,允许深度较大的网络也能具备良好的训练稳定性。ResNet 使用残差连接(skip connection)来实现这一目标。因此 StyleGAN2 探索了残差连接设计和其它与 ResNet 类似的残差概念。对于这些设计,我们使用双线性滤波器对前一层执行上采样/下采样,并尝试学习下一层的残差值。

1a06631db7243d10cefd7fd37f9712b0.png

下图展示了判别器和生成器之间存在残差连接的 MSG-GAN 模型。

96d399ce82c15a3090dc4026574f9845.png

下表展示了不同方法的性能提升情况。

a123973528d4cc253dab712161f8a75e.png

大型网络

在这些改动之后,我们进一步分析了高分辨率层对图像生成的影响。StyleGAN2 论文度量了不同模型层输出图像的变化。下图左表示每个层对生成图像的贡献,横轴表示训练过程。

在训练初期,低分辨率层占主导地位。然而,随着更多训练迭代的完成,高分辨率层(尤其是 1024 × 1024 层)的贡献不如预计的多。研究者怀疑这些层的容量不够大。事实的确如此,当高分辨率层中的特征图数量翻倍时,其影响力显著上升(右图)。

9fe3f6fc6414c65b0346dd4cde081624.png

原文链接:https://medium.com/@jonathan_hui/gan-stylegan-stylegan2-479bdf256299

 
 

好消息!

小白学视觉知识星球

开始面向外开放啦👇👇👇

 
 

e085c981ce3aa432cde2ed4225374e8a.jpeg

下载1:OpenCV-Contrib扩展模块中文版教程

在「小白学视觉」公众号后台回复:扩展模块中文教程,即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。


下载2:Python视觉实战项目52讲
在「小白学视觉」公众号后台回复:Python视觉实战项目,即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。


下载3:OpenCV实战项目20讲
在「小白学视觉」公众号后台回复:OpenCV实战项目20讲,即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。


交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~
GAN 图像风格迁移是一种利用生成对抗网络(GAN)实现的图像风格变换技术。下面是一个使用 TensorFlow 实现 GAN 图像风格迁移的示例代码: ```python import tensorflow as tf import numpy as np import argparse import os import sys import time import datetime import random import cv2 def build_parser(): parser = argparse.ArgumentParser() parser.add_argument('--model', type=str, default='model.ckpt', help='Model checkpoint to load') parser.add_argument('--input_img', type=str, help='Input image file path') parser.add_argument('--output_img', type=str, help='Output image file path') parser.add_argument('--style_img', type=str, help='Style image file path') parser.add_argument('--content_weight', type=float, default=1.0, help='Weight of content loss') parser.add_argument('--style_weight', type=float, default=5.0, help='Weight of style loss') parser.add_argument('--tv_weight', type=float, default=1e-3, help='Weight of total variation loss') parser.add_argument('--learning_rate', type=float, default=1e-3, help='Learning rate') parser.add_argument('--num_iters', type=int, default=1000, help='Number of iterations') parser.add_argument('--save_every', type=int, default=100, help='Save checkpoint every N iterations') parser.add_argument('--print_every', type=int, default=10, help='Print loss every N iterations') parser.add_argument('--gpu', type=int, default=0, help='GPU device ID') return parser def build_vgg19(input_tensor): vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet', input_tensor=input_tensor) vgg.outputs = [vgg.layers[9].output, vgg.layers[13].output, vgg.layers[17].output, vgg.layers[21].output] return vgg def gram_matrix(x): features = tf.keras.backend.batch_flatten(tf.keras.backend.permute_dimensions(x, (2, 0, 1))) gram = tf.keras.backend.dot(features, tf.keras.backend.transpose(features)) return gram def content_loss(content, generated): return tf.reduce_mean(tf.square(content - generated)) def style_loss(style, generated): S = gram_matrix(style) G = gram_matrix(generated) channels = 3 size = 256 * 256 return tf.reduce_mean(tf.square(S - G)) / (4. * (channels ** 2) * (size ** 2)) def total_variation_loss(x): a = tf.square(x[:, :255, :255, :] - x[:, 1:, :255, :]) b = tf.square(x[:, :255, :255, :] - x[:, :255, 1:, :]) return tf.reduce_mean(tf.pow(a + b, 1.25)) def build_model(content, style, generated): content_loss_val = content_loss(content, generated) style_loss_val = style_loss(style, generated) tv_loss_val = total_variation_loss(generated) loss = args.content_weight * content_loss_val + args.style_weight * style_loss_val + args.tv_weight * tv_loss_val optimizer = tf.train.AdamOptimizer(args.learning_rate).minimize(loss) return loss, optimizer def preprocess_img(img): img = cv2.resize(img, (256, 256)) img = img.astype(np.float32) / 255.0 img = np.expand_dims(img, axis=0) return img def postprocess_img(img): img = np.squeeze(img, axis=0) img = np.clip(img * 255.0, 0, 255).astype(np.uint8) return img def main(args): # Set GPU device os.environ['CUDA_VISIBLE_DEVICES'] = str(args.gpu) # Create session config = tf.ConfigProto() config.gpu_options.allow_growth = True sess = tf.Session(config=config) # Build VGG19 model input_tensor = tf.placeholder(tf.float32, shape=(None, 256, 256, 3)) vgg = build_vgg19(input_tensor) # Load images content_img = cv2.imread(args.input_img) style_img = cv2.imread(args.style_img) # Preprocess images content_img = preprocess_img(content_img) style_img = preprocess_img(style_img) # Build model content_tensor = vgg(input_tensor)[0] style_tensors = [vgg(style_img)[i] for i in range(4)] generated_tensor = tf.Variable(content_img, dtype=tf.float32) loss_op, optimizer_op = build_model(content_tensor, style_tensors, generated_tensor) # Load checkpoint saver = tf.train.Saver() saver.restore(sess, args.model) # Train model for i in range(args.num_iters): _, loss_val = sess.run([optimizer_op, loss_op]) if i % args.print_every == 0: print('[{:04d}/{:04d}] loss = {:.4f}'.format(i+1, args.num_iters, loss_val)) if i % args.save_every == 0: saver.save(sess, args.model) generated_img = sess.run(generated_tensor) generated_img = postprocess_img(generated_img) cv2.imwrite(args.output_img, generated_img) # Save final result saver.save(sess, args.model) generated_img = sess.run(generated_tensor) generated_img = postprocess_img(generated_img) cv2.imwrite(args.output_img, generated_img) if __name__ == '__main__': parser = build_parser() args = parser.parse_args() main(args) ``` 使用方法: 1. 下载并安装 TensorFlow。 2. 下载预训练的 VGG19 模型([vgg19.npy](https://github.com/machrisaa/tensorflow-vgg/blob/master/vgg19.npy))并将其放在代码所在目录下。 3. 执行以下命令: ```bash python gan_image_style_transfer.py --input_img input.jpg --output_img output.jpg --style_img style.jpg ``` 其中 `input.jpg` 是输入图像,`style.jpg` 是风格图像,`output.jpg` 是输出图像。可以通过调整命令行参数来改变模型训练的参数和行为。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值