目录
■ 简介
■ 第一步:将图像理解为一个概率分布的样本
你是怎样补全缺失信息的呢?
但是怎样着手统计呢?这些都是图像啊。
那么我们怎样补全图像?
■ 第二步:快速生成假图像
在未知概率分布情况下,学习生成新样本
[ML-Heavy] 生成对抗网络(Generative Adversarial Net, GAN) 的架构
使用G(z)生成伪图像
[ML-Heavy] 训练DCGAN
现有的GAN和DCGAN实现
[ML-Heavy] 在Tensorflow上构建DCGANs
在图片集上跑DCGAN
■ 第三步:找到用于图像补全最好的伪图像
使用 DCGAN 进行图像补全
[ML-Heavy] 到 pgpg 的投影的损失函数
[ML-Heavy] 使用tensorflow来进行DCGAN图像补全
补全图像
■ 结论
简介
内容识别填充(译注: Content-aware fill ,是 photoshop 的一个功能)是一个强大的工具,设计师和摄影师可以用它来填充图片中不想要的部分或者缺失的部分。在填充图片的缺失或损坏的部分时,图像补全和修复是两种密切相关的技术。有很多方法可以实现内容识别填充,图像补全和修复。在这篇博客中,我会介绍 RaymondYeh 和 Chen Chen 等人的一篇论文,“基于感知和语境损失的图像语义修补(Semantic Image Inpainting with Perceptual and ContextualLosses)”。论文在2016年7月26号发布于 arXiv 上,介绍了如何使用 DCGAN 网络来进行图像补全。博文面向一般技术背景的读者,部分内容需要有机器学习的背景。我在相关章节标注了[ML-Heavy]标签,如果你不想了解太多细节,可以跳过这些章节。我们只会涉及到填充人脸图像缺失部分的情况。博文相关 Tensorflow 代码已经发布到 GitHub 上:bamos/dcgan-completion.tensorflow 。
图像补全分为三个步骤。
-
首先我们将图像理解为一个概率分布的样本。
-
基于这种理解,学习如何生成伪图片。
-
然后我们找到最适合填充回去的伪图片。
使用photoshop来对图像缺失部分补全,并使用photoshop自动删除不要的部分。
下文将要介绍到的图像补全。图像的中心是自动生成的。源码可以从此处下载。
这些图像是我从 LFW 数据集中取得的一个随机样本。
第一步:将图像理解为一个概率分布的样本
1.你是怎样补全缺失信息的呢?
在上面的例子中,想象你正在构造一个可以填充缺失部分的系统。你会怎么做呢?你觉得人类大脑是怎么做的呢?你使用了什么样的信息呢?
在博文中,我们会关注两种信息:
语境信息:你可以通过周围的像素来推测缺失像素的信息。
感知信息:你会用“正常”的部分来填充,比如你在现实生活中或其它图片上看到的样子。
两者都很重要。没有语境信息,你怎么知道填充哪一个进去?没有感知信息,通过同样的上下文可以生成无数种可能。有些机器学习系统看起来“正常”的图片,人类看起来可能不太正常。
如果有一种确切的、直观的算法,可以捕获前文图像补全步骤介绍中提到的两种属性,那就再好不过了。对于特定的情况,构造这样的算法是可行的。但是没有一般的方法。目前最好的解决方案是通过统计和机器学习来得到一个近似的技术。
2.但是怎样着手统计呢?这些都是图像啊。
为了激发大家的思考,我们从一个很好理解、可以写成简洁形式的概率分布开始:一个正态分布。这是正态分布的概率密度函数(PDF)。你可以将PDF理解成在输入空间横向移动,纵轴表示某个值出现的概率。(如果你感兴趣,绘制这幅图的代码可以从 bamos/dcgan-completion.tensorflow:simple-distributions.py 下载。)
从这个分布中采样,就可以得到一些数据。需要搞清楚的是PDF和样本之间的联系。
从正态分布中的采样
2维图像的PDF和采样。 PDF 用等高线图表示,样本点画在上面。
这是1维分布,因为输入只能沿着一个维度。在两个维度上也可以这么做。
在图像和统计学之间,最关键的联系就是,我们可以将图像看作是从一个高维概率分布中得到的采样。概率分布对应的是图像的像素。想象你在用相机拍照。得到的图像是由有限个数的像素组成。当你通过相机拍照的时候,你就在从这个复杂的概率分布中进行采样。这个概率分布就决定了我们判断一张图片是正常的,还是不正常的。对于图片而言,与正态分布不同的是,我们无法得知真实的概率分布,我们只能去收集样本。
在这篇文章中,我们会使用彩色图像,它用 RGB颜色 表示。我们的图像宽64像素,高64像素,所以我们的概率分布是 64⋅64⋅3≈12k 维的。
3.那么我们怎样补全图像?
首先考虑多变量正态分布,以求得到一些启发。给定 x=1 , 那么 y 最可能的值是什么?我们可以固定x的值,然后找到使PDF最大的 y。
在多维正态分布中,给定x,得到最大可能的y
这个概念可以很自然地推广到图像概率分布。我们已知一些值,希望补全缺失值。这可以简单理解成一个最大化问题。我们搜索所有可能的缺失值,用于补全的图像就是可能性最大的值。
从正态分布的样本来看,只通过样本,我们就可以得出PDF。只需挑选你喜欢的 统计模型,然后拟合数据即可。
然而,我们实际上并没有使用这种方法。对于简单分布来说,PDF很容易得出来。但是对于更复杂的图像分布来说,就十分困难,难以处理。之所以复杂,一部分原因是复杂的条件依赖:一个像素的值依赖于图像中其它像素的值。另外,最大化一个一般的PDF是一个非常困难和棘手的非凸优化问题。
第二步:快速生成假图像
1.在未知概率分布情况下,学习生成新样本
除了学习如何计算PDF之外,统计学中另一个成熟的想法是学习怎样用 生成模型 生成新的(随机)样本。生成模型一般很难训练和处理,但是后来深度学习社区在这个领域有了一个惊人的突破。Yann LeCun 在这篇 Quora 回答中对如何进行生成模型的训练进行了一番精彩的论述,并将它称为机器学习领域近10年来最有意思的想法。
Yann LeCun 对生成对抗网络的介绍
将生成对抗网络类比为街机游戏。两个网络相互对抗,共同进步。就像两个人类在游戏中对抗一样
其它的深度学习方法,比如 VariationalAutoencoders(VAEs),也可以用来训练生成模型。在这篇博文中,我们用的是生成对抗网络(GenerativeAdversarial Nets,GANs)。
2.[ML-Heavy] 生成对抗网络(GenerativeAdversarial Net, GAN) 的架构
这个想法是 IanGoodfellow 等人在2014年NeuralInformation Processing Systems (NIPS) 研讨会上发表的里程碑式论文“生成对抗网络”(GenerativeAdversarial Nets,GANs)中提出的。主要思想是,我们定义一个简单、常用的分布,用pzpz表示。在下文中,我们使用pzpz来表示在-1到1闭区间上的均匀分布。我们将从分布中的一个采样记作 z∼pzz∼pz 。若 pzpz 是五维的,我们可以通过一行python的 numpy代码来进行采样:
- z =np.random.uniform(-1, 1, 5)
- array([0.77356483, 0.95258473,-0.18345086, 0.69224724, -0.34718733])
现在有了一个用于采样的简单分布,我们定义一个函数 G(z) 来从我们的原始概率分布中采样。
- def G(z):
- ...
- return imageSample
- z = np.random.uniform(-1,1, 5)
- imageSample =G(z)
那么我们怎样定义G(z),可以使它输入一个向量,输出一张图像?我们将使用深度神经网络。神经网络基础有很多教程,所以我不会在此介绍。推荐一些不错的参考,斯坦福CS231n课程,Ian Goodfellow 等人的 deeplearning book 、Image Kernels Explained Visually,以及 convolution arithmetic guide。
构造一个基于深度学习的 G(z)有很多种方式。原始的 GAN 论文提出了一个想法,一个训练过程,以及一个初步的实验结果。这个想法已经被极大地发扬了,其中一个想法在论文“基于深度卷积生成对抗网络的无监督表征学习(Unsupervised Representation Learning withDeep Convolutional Generative Adversarial Networks)”中提出,作者是 AlecRadford, Luke Metz, 和 SoumithChintala,发表在 2016International Conference on Learning Representations (ICLR, 读作“eye-clear”)上。这篇论文提出了深度卷积GANS(叫做DCGANs),使用微步长卷积来对图像进行上采样。
那么什么是微步长卷积,以及它是怎样对图像进行上采样的呢? VincentDumoulin 和 Francesco Visin 的论文“深度学习卷积运算指南(A guide to convolution arithmetic for deeplearning)”和卷积运算项目是对深度学习中的卷积运算的一个非常好的介绍。图例非常棒,可以让我们对微步长卷积的工作方式有一个直观的理解。首先,确保你搞懂了一般卷积如何将内核滑过输入空间(蓝色),得到输出空间(绿色)。此处,输出比输入要小。(如果不理解,参阅 CS231n CNN section 或 theconvolution arithmetic guide)
卷积运算图示,蓝色是输入,绿色是输出。
接下来,假设你有一个3X3的输入。我们的目标是进行上采样(upsample),这样,得到一个更大的输出。你可以将微步长卷积理解为将输入图像放大,然后在像素间插入0。然后在这个放大后的图像上进行卷积操作,得到一个较大的输出。此处,输出为5X5。
微步长卷积运算图示,蓝色是输入,绿色是输出。
插一段边注:进行上采样的卷积层有很多名字: 全卷积( fullconvolution), 网内上采样(in-networkupsampling), 微步长卷积(fractionally-stridedconvolution),反向卷积(backwardsconvolution),反卷积(deconvolution),上卷积(upconvolution),或者转置卷积(transposedconvolution)。非常不推荐使用术语“反卷积”,因为这个术语已经有其他含义了:在某种数学运算,以及计算机视觉的其它应用中,这个术语有完全不同的含义。
现在我们有了微步长卷积结构,可以得到G(z)的表达,以一个向量z∼pzz∼pz 作为输入,输出一张 64x64x3 的RGB图像。
使用 DCGAN 构造生成器的一种方法。图像来自DCGAN论文
DCGAN 论文也提出了其他的在训练 DCGANs 时的技巧和调整,比如批量正则化(batchnormalization)以及 leaky RELUs。
3.使用G(z)生成伪图像
让我们先停下来欣赏一下 G(z) 多么强大吧!DCGAN 论文给出了DCGAN在卧室数据集训练出来的样子。然后 G(z) 可以给出下面的伪图像,生成器认为的卧室是什么样子的。下面的图片都不在原始数据集里哦!
另外,你也可以在输入空间z进行代数运算。下面是一个生成人脸的网络
基于DCGAN的人脸代数运算 DCGAN论文
4.[ML-Heavy] 训练DCGAN
现在我们已经定义了G(z),并见识了它多么强大。那么我们怎么训练它呢?我们有很多未知的变量(参数),需要找到它们。此时,我们就要用到对抗网络了。
首先我们要定义一些符号。数据的概率分布(未知的)记作pdatapdata。那么G(z),(其中z∼pzz∼pz )可以理解为从一个概率分布中的采样。让我们把这个概率分布记作pgpg。
符号pzpdatapg含义z的概率分布,简单、已知图像的概率分布(未知),是图像数据样本的来源生成器G用来采样的概率分布,我们希望pg==pdata符号含义pzz的概率分布,简单、已知pdata图像的概率分布(未知),是图像数据样本的来源pg生成器G用来采样的概率分布,我们希望pg==pdata
判别器网络D(x)输入图像x,返回图像x是从pdatapdata的分布中采样的概率。理论上,当输入图像是从pdatapdata中采样得到时,判别器输出一个接近1的值,当输入图像是伪图像,比如pgpg采样得到的图像时,判别器输出一个接近0的值。在DCGANs中,D(x)是一个传统的卷积神经网络。
判别器卷积神经网络,图片来自 图像恢复论文
训练判别器的目标是:
1、对于真实数据分布x∼pdatax∼pdata的每一张图片,最大化D(x)。
2、对于不是真实数据分布x≁pdatax≁pdata的每一张图片,最小化D(x)。
生成器G(z)的训练目标是生成可以迷惑D的样本。输出是一张图像,可以作为判别器的输入。因此,生成器希望最大化D(G(z)),也就是最小化(1-D(G(z))),因为D是一个概率,取值在0和1之间。
论文中提出,对抗网络是通过下面的最小最大策略实现的。第一项中的数学期望遍历了真实数据分布,第二项的数学期望遍历了pzpz中的样本,也就是遍历了G(z)∼pgG(z)∼pg。
- minGmaxDEx∼pdatalog(D(x)+Ez∼pz[log(1−D(G(z)))]minGmaxDEx∼pdatalog(D(x)+Ez∼pz[log(1−D(G(z)))]
通过这个表达式关于D和G的参数的梯度,可以训练它们。我们知道如何快速计算这个表达式的每一个部分。数学期望可以通过大小为m的小批数据来估计,内侧的最大化可以通过k步梯度来估计。已经证明,k=1是比较适合训练的值。
我们用θdθd来表示判别器的参数,用θgθg来表示生成器的参数。关于用θdθd和θgθg的损失的梯度可以通过反向传播来计算,因为D和G都是由成熟的神经网络模块组成的。下面是GAN论文中的训练策略。理论上,训练结束后,pg==pdatapg==pdata。所以G(z)可以生成服从pdatapdata分布的样本。
GAN 论文中的训练算法。
5.现有的GAN和DCGAN实现
- 在 Github 上,你可以看到很多极棒的 GAN 和 DCGAN 实现。
- goodfeli/adversarial: GAN论文作者写的 Theano GAN 实现。
- tqchen/mxnet-gan: 非官方 MXNet GAN 实现。
- Newmu/dcgan_code: DCGAN论文作者写的 Theano GAN 实现。
- soumith/dcgan.torch: DCGAN论文作者之一 (Soumith Chintala) 的 Torch DCGAN 实现。
- carpedm20/DCGAN-tensorflow: 非官方 TensorFlow DCGAN 实现。
- openai/improved-gan: OpenAI 第一篇论文背后的代码。在 carpedm20/DCGAN-tensorflow 基础上进行了大量的修改。
- mattya/chainer-DCGAN: 非官方 Chainer DCGAN 实现。
- jacobgil/keras-dcgan: 非官方 (未完成) KerasDCGAN 实现。
我们会在 carpedm20/DCGAN-tensorflow 的基础上构造模型。
6.[ML-Heavy] 在Tensorflow上构建DCGANs
这部分的实现在我的 bamos/dcgan-completion.tensorflow Github库中。我需要强调的是,这部分的代码来自Taehoon Kim的 carpedm20/DCGAN-tensorflow 。在我自己的库中使用它,方便我们在下一部分图像补全中使用。
大部分实现代码在model.py中的一个python类,DCGAN中。把所有东西放进一个类中是有很多好处的,这样我们可以在训练结束后保留住中间过程,并在之后的使用中加载。
首先我们定义生成器和判别器结构。linear,conv2d_transpose, conv2d, 和 lrelu 函数在 ops.py 中定义。
- defgenerator(self, z):
- self.z_, self.h0_w, self.h0_b = linear(z,self.gf_dim*8*4*4, 'g_h0_lin', with_w=True)
- self.h0 = tf.reshape(self.z_, [-1, 4, 4,self.gf_dim * 8])
- h0 = tf.nn.relu(self.g_bn0(self.h0))
- self.h1, self.h1_w, self.h1_b =conv2d_transpose(h0,
- [self.batch_size, 8, 8, self.gf_dim*4],name='g_h1', with_w=True)
- h1 =tf.nn.relu(self.g_bn1(self.h1))
- h2, self.h2_w, self.h2_b =conv2d_transpose(h1,
- [self.batch_size, 16, 16,self.gf_dim*2], name='g_h2', with_w=True)
- h2 = tf.nn.relu(self.g_bn2(h2))
- h3, self.h3_w, self.h3_b =conv2d_transpose(h2,
- [self.batch_size, 32, 32,self.gf_dim*1], name='g_h3', with_w=True)
- h3 = tf.nn.relu(self.g_bn3(h3))
- h4, self.h4_w, self.h4_b =conv2d_transpose(h3,
- [self.batch_size, 64, 64, 3],name='g_h4', with_w=True)
- return tf.nn.tanh(h4)
- defdiscriminator(self, image, reuse=False):
- if reuse:
- tf.get_variable_scope().reuse_variables()
- h0 = lrelu(conv2d(image, self.df_dim,name='d_h0_conv'))
- h1 = lrelu(self.d_bn1(conv2d(h0,self.df_dim*2, name='d_h1_conv')))
- h2 = lrelu(self.d_bn2(conv2d(h1,self.df_dim*4, name='d_h2_conv')))
- h3 = lrelu(self.d_bn3(conv2d(h2,self.df_dim*8, name='d_h3_conv')))
- h4 = linear(tf.reshape(h3, [-1, 8192]), 1,'d_h3_lin')
- return tf.nn.sigmoid(h4), h4
当我们初始化这个类的时候,将要用到这两个函数来构建模型。我们需要两个判别器,它们共享(复用)参数。一个用于来自数据分布的小批图像,另一个用于生成器生成的小批图像。
- self.G =self.generator(self.z)
- self.D,self.D_logits = self.discriminator(self.images)
- self.D_,self.D_logits_ = self.discriminator(self.G, reuse=True)
接下来,我们定义损失函数。这里我们不用求和,而是用D的预测值和真实值之间的交叉熵(cross entropy),因为它更好用。判别器希望对所有“真”数据的预测都是1,对所有生成器生成的“伪”数据的预测都是0。生成器希望判别器对两者的预测都是1 。
- self.d_loss_real= tf.reduce_mean(
- tf.nn.sigmoid_cross_entropy_with_logits(self.D_logits,
- tf.ones_like(self.D)))
- self.d_loss_fake= tf.reduce_mean(
- tf.nn.sigmoid_cross_entropy_with_logits(self.D_logits_,
- tf.zeros_like(self.D_)))
- self.g_loss =tf.reduce_mean(
- tf.nn.sigmoid_cross_entropy_with_logits(self.D_logits_,
- tf.ones_like(self.D_)))
- self.d_loss =self.d_loss_real + self.d_loss_fake
将每个模型的变量汇总到一起,这样,它们可以分别训练。
- t_vars = tf.trainable_variables()
- self.d_vars =[var for var in t_vars if 'd_' in var.name]
- self.g_vars =[var for var in t_vars if 'g_' in var.name]
现在我们开始优化参数,使用 ADAM 优化。它是一种自适应非凸优化方法,在SGD面前很有竞争力,一般不需要手动调整学习率 (learning rate), 动量(momentum),以及其他超参数。
- d_optim =tf.train.AdamOptimizer(config.learning_rate, beta1=config.beta1) \
- .minimize(self.d_loss,var_list=self.d_vars)
- g_optim =tf.train.AdamOptimizer(config.learning_rate, beta1=config.beta1) \
- .minimize(self.g_loss,var_list=self.g_vars)
下面我们遍历数据。每一次迭代,我们采样一个小批数据,然后使用优化器来更新网络。有趣的是,如果G只更新一次,鉴别器的损失不会变成0。另外,我认为最后调用d_loss_fake 和 d_loss_real 进行了一些不必要的计算,因为这些值在 d_optim 和 g_optim 中已经计算过了。作为Tensorflow 的一个联系,你可以试着优化这一部分,并发送PR到原始的repo。
- for epoch inxrange(config.epoch):
- ...
- for idx in xrange(0, batch_idxs):
- batch_images = ...
- batch_z = np.random.uniform(-1, 1,[config.batch_size, self.z_dim]) \
- .astype(np.float32)
- # Update D network
- _, summary_str =self.sess.run([d_optim, self.d_sum],
- feed_dict={ self.images:batch_images, self.z: batch_z })
- # Update G network
- _, summary_str =self.sess.run([g_optim, self.g_sum],
- feed_dict={ self.z: batch_z })
- # Run g_optim twice to make sure thatd_loss does not go to zero (different from paper)
- _, summary_str =self.sess.run([g_optim, self.g_sum],
- feed_dict={ self.z: batch_z })
- errD_fake =self.d_loss_fake.eval({self.z: batch_z})
- errD_real =self.d_loss_real.eval({self.images: batch_images})
- errG = self.g_loss.eval({self.z:batch_z})
搞定!当然,完整的代码会有更多的注释,可以在 model.py 中查看。
7.在图片集上跑DCGAN
如果你跳过了上一节,但是想跑跑代码,这部分代码在 bamos/dcgan-completion.tensorflow Github 库中。我要再次强调这个代码来自 Taehoon Kim 的 carpedm20/DCGAN-tensorflow 。在这里我们用我的库,是因为进行下一步比较方便。警告,如果你没有支持CUDA的GPU,这部分网络的训练会非常慢。
首先,clone 我的 bamos/dcgan-completion.tensorflow Github库和 OpenFace 到本地。我们要用到 OpenFace 的 Python-Only 部分来进行图像预处理。别担心,你不需要安装OpenFace 的 Torch 依赖。创建新目录, clone 下面的资源库。
- git clonehttps://github.com/cmusatyalab/openface.git
- git clonehttps://github.com/bamos/dcgan-completion.tensorflow.git
接下来,安装 OpenCV 和支持python2 的 dlib。如果你感兴趣,可以尝试实现 dlib 对 python3 的支持。安装时候有一些小技巧,我写了一些笔记,在 OpenFace setup guide ,包括我安装的是那个版本、如何安装。接下来,安装 OpenFace 的python 库,这样我们可以对图像进行预处理。如果你不是用虚拟环境,在运行 setup.py 时你需要用 sudo 来进行全局安装。(如果对你来说这部分比较困难,也可以使用 OpenFace 的 Docker 安装。)
下面下载一个人脸图像数据集。数据集中有没有标注不重要,我们会删掉它。不完全列表如下:MS-Celeb-1M,CelebA, CASIA-WebFace, FaceScrub, LFW, 和 MegaFace。将图片放在目录dcgan-completion.tensorflow/data/your-dataset/raw 下,表明它是数据集的原始数据。
现在我们用 OpenFace 的 alignment 工具将图像预处理为 64X64 的数据。
- ./openface/util/align-dlib.pydata/dcgan-completion.tensorflow/data/your-dataset/raw aligninnerEyesAndBottomLipdata/dcgan-completion.tensorflow/data/your-dataset/aligned --size 64
最后我们将处理好图像的目录展平,这样目录下只有图像,没有子文件夹。
- cddcgan-completion.tensorflow/data/your-dataset/aligned
- find . -name'*.png' -exec mv {} . \;
- find . -type d-empty -delete
- cd ../../..
现在我们可以训练 DCGAN 了。安装 Tensorflow ,开始训练。
- ./train-dcgan.py--dataset ./data/your-dataset/aligned --epoch 20
你可以在 sample 文件夹中查看从生成器中随机抽样出来的样本发图像是什么样子。我在 CASIA-WebFace 数据集和 FaceScrub 数据集上训练,因为我手头就有这两个数据集。 14轮训练之后,我的样本是这样的。
在 CASIA-WebFace 和 FaceScrub 上训练14轮后的 DCGAN 的样本
你也可以在 TensorBoard 上查看 Tensorflow 图像,以及损失函数。
- tensorboard--logdir ./logs
TensorBoard 损失可视化图像。在训练过程中实时更新。
DCGAN 网络的TensorBoard可视化
第三步:找到用于图像补全最好的伪图像
1.使用 DCGAN 进行图像补全
既然我们已经有了鉴别器 D(x) 和生成器 G(z),我们怎么把它用在图像补全上呢?在这章我要介绍的是 RaymondYeh 和 Chen Chen 等人的一篇论文,“基于感知和语境损失的图像语义修补(Semantic Image Inpainting with Perceptual andContextual Losses)”。论文在2016年7月26号发布于 arXiv 上。
对于某个图片y进行图像补全,一个有道理但是不可行的方案是,对于缺失的像素,最大化D(y)。结果既不是数据分布(pdatapdata),也不是生成分布(pgpg)。我们期望的是,将y投影到生成分布上。
(a): 生成分布的 y 的理想重建(蓝色曲面)。(b):尝试通过对 D(y) 最大化来重建 y 的一个失败的例子。图像来自图像修复论文
2.[ML-Heavy] 到 pgpg 的投影的损失函数
为了给投影一个合理的定义,我们先为图像补全定义一些符号。我们使用一个二值掩码 M(mask), 也就是只有0、1两个值。值为1表示图像这部分我们想要保留,值为0表示这部分我们需要补全。现在我们可以定义,在给定了二值掩码M之后如何对y进行补全。将y中的元素和M中的元素相乘。两个矩阵对应位置元素相乘也叫做 Hadamard 积,用 M⊙yM⊙y 表示。M⊙yM⊙y 表示图像的原始部分。
二值掩码图例
接下来,假设我们已经找到了一个 z^z^, 可以生成一个对缺失值进行重构的合理的G(z^)G(z^)。补全的像素 (1−M)⊙G(z^)(1−M)⊙G(z^) 可以加到原始像素上,得到重构的图像:
- xreconstructed=M⊙y+(1−M)⊙G(z^)xreconstructed=M⊙y+(1−M)⊙G(z^)
现在我们要做的事情,就是找到一个适于补全图像的 G(z^)G(z^)。为了找到 z^z^ ,我们回顾一下文章开头提到的 语境 和 感知,将它们作为DCGANs的上下文。为此,我们定义了对于任意z∼pzz∼pz的损失函数。损失函数越小,说明 z^z^ 越合适。
语境损失:为了得到和输入图像相同的上下文,需要确保y已知像素对应位置的G(z)G(z)尽可能相似。所以,当 G(z) 的输出和 y 已知位置图像不相似的时候,需要对 G(z) 进行惩罚。为此,我们用 G(z) 减去 y 中对应位置的像素,然后得到它们不相似的程度:
- Lcontextual(z)=||M⊙G(z)−M⊙y||Lcontextual(z)=||M⊙G(z)−M⊙y||
其中||x||1=∑i|xi|||x||1=∑i|xi|是某个向量x的l1l1 范数。l2l2 范数也是可取的,但是论文指出,实践表明l1l1 范数效果更好。
理想情况下,已知部分的 y 和 G(z) 的像素是相等的。也就是对于已知位置的像素i, ||M⊙G(z)i−M⊙yi||=0||M⊙G(z)i−M⊙yi||=0 , Lcontextual(z)=0Lcontextual(z)=0 。
感知损失:为了重建一个看起来真实的图像,需要确保判别器判定图像看起来是真实的。为此,我们进行和训练 DCGAN 中相同的步骤。
- Lperceptual(z)=log(1−D(G(z)))Lperceptual(z)=log(1−D(G(z)))
最后,将语境损失和感知损失组合起来,就可以找到 z^z^ 了;
- L(z)=Lcontextual(z)+λLperceptual(z)z^=argminzL(z)L(z)=Lcontextual(z)+λLperceptual(z)z^=argminzL(z)
其中 λλ 是超参数,用来控制相比于感知损失,语境损失重要的程度。(我用的是默认的λ=0.1λ=0.1,并没有对这个值进行深入研究。)然后如前所述,使用 G(z) 来重建y中缺失的部分。
- Lcontextual(z)=||M⊙G(z)−M⊙y||Lcontextual(z)=||M⊙G(z)−M⊙y||
图像也使用了 poisson blending 来使图像变得平滑。
3.[ML-Heavy] 使用tensorflow来进行DCGAN图像补全
这一章给出了我对于 Taehoon Kim 的 carpedm20/DCGAN-tensorflow 代码的修改,用于图像补全。
- self.mask =tf.placeholder(tf.float32, [None] + self.image_shape, name='mask')
我们通过对梯度∇zL(z)∇zL(z)进行梯度下降,可以迭代地求出 argminzL(z)argminzL(z) 。我们定义了损失函数之后,Tensorflow 的 automatic differentiation 可以自动地为我们计算出这个值!所以,完整的基于DCGANs的实现可以通过在现有的DCGAN实现上添加4行Tensorflow代码来完成。(当然,实现它还需要一些非 Tensorflow代码。)
- self.contextual_loss= tf.reduce_sum(
- tf.contrib.layers.flatten(
- tf.abs(tf.mul(self.mask, self.G) -tf.mul(self.mask, self.images))), 1)
- self.perceptual_loss= self.g_loss
- self.complete_loss= self.contextual_loss + self.lam*self.perceptual_loss
- self.grad_complete_loss= tf.gradients(self.complete_loss, self.z)
接下来,我们定义掩码。我只是在图像的中央区域加了一个,你可以加一些别的,比如随机掩码,然后发一个pull请求。
- ifconfig.maskType == 'center':
- scale = 0.25
- assert(scale <= 0.5)
- mask = np.ones(self.image_shape)
- l = int(self.image_size*scale)
- u = int(self.image_size*(1.0-scale))
- mask[l:u, l:u, :] = 0.0
梯度下降方面,我们对于z在[-1, 1]上的投影,使用小批量、含动量的投影梯度下降。
- for idx inxrange(0, batch_idxs):
- batch_images = ...
- batch_mask = np.resize(mask,[self.batch_size] + self.image_shape)
- zhats = np.random.uniform(-1, 1,size=(self.batch_size, self.z_dim))
- v = 0
- for i in xrange(config.nIter):
- fd = {
- self.z: zhats,
- self.mask: batch_mask,
- self.images: batch_images,
- }
- run = [self.complete_loss,self.grad_complete_loss, self.G]
- loss, g, G_imgs = self.sess.run(run,feed_dict=fd)
- v_prev = np.copy(v)
- v = config.momentum*v - config.lr*g[0]
- zhats += -config.momentum * v_prev +(1+config.momentum)*v
- zhats = np.clip(zhats, -1, 1)
4.补全图像
选择一些用于图像补全的图片,将它们放到dcgan-completion.tensorflow/your-test-data/raw 。然后像之前dcgan-completion.tensorflow/your-test-data/aligned 那样排列整齐。这里我从LFW中随机抽出一些图像。我的DCGAN没有使用LFW的图像来训练。
你可以这样补全图像:
- ./complete.py./data/your-test-data/aligned/* --outDir outputImages
这段代码会生成图像,并周期性地将图像输出在 —outDir 文件夹中。你可以使用ImageMagick来生成一个gif:
- cd outputImages
- convert -delay10 -loop 0 completed/*.png completion.gif
最后的图像补全。图像的中心是自动生成的。源代码从此处下载。这是我随机从 LFW 中挑出的样本
结论
感谢阅读,现在我们成功了!在文章中,我们涉及了图像补全的一种方法:
1、将图像理解为概率的分布。
2、生成伪图像。
3、找到用于补全最好的伪图像。
我的例子是人脸,但是DCGANs也可以在其他类型的图像上使用。总体而言,GANs 训练比较困难,我们尚不清楚如何在一个特定种类的物体上进行训练,也不清楚如何在大图像上训练。然而,这是一个很有潜力的模型,我很期待GAN将为我们创造什么样的未来!