Auto Byte

专注未来出行及智能汽车科技

微信扫一扫获取更多资讯

Science AI

关注人工智能与其他前沿技术、基础学科的交叉研究与融合发展

微信扫一扫获取更多资讯

深度卷积生成式对抗网络

近年来,监督学习与卷积网络(CNNs)在计算机视觉应用中被广泛采用。相比之下,非监督和CNN的学习受到的关注较少。在这项工作中,该算法希望能增大监督学习和无人监督的情况下与CNNs的成功率。于是,2015年,一种叫做深卷积生成的CNNs被Alec Radford & Luke Metz等人提出。它是具有某些架构约束的对抗网络(DCGANs)。使用生成模型和判别模型,从物体物件到场景图像,学习到一种层次的表征。最后,使用学习到的特征实现新任务——阐明它们可以用于生成图像的表征。DCGANs是GAN结构的最早的重要发展。就训练和生成更高质量的样本来说,DCGANs更加稳定。

简介

近年来,监督学习与卷积网络(CNNs)在计算机视觉应用中被广泛采用。相比之下,非监督和CNN的学习受到的关注较少。在这项工作中,CNNs算法希望能增大监督学习和无人监督的情况下与CNNs的成功率。于是,2015年,一种叫做深度卷积生成的CNN被Alec Radford & Luke Metz等人提出。它是具有某些架构约束(architectural constraints)的对抗网络(DCGANs)。使用生成模型和判别模型,从物体物件到场景图像,学习到一种层次的表征。最后,使用学习到的特征实现新任务——阐明它们可以用于生成图像的表征。

相较有监督学习,CNN在无监督学习上的进展缓慢。本文结合CNN在有监督学习的成功和无监督学习,提出一类被称为“深度卷积生成对抗网络(DCGANs)”使用生成模型和判别模型,从物体物件到场景图像,学习到一种层次的表征。最后,使用学习到的特征实现新任务——阐明它们可以用于生成图像的表征。无监督地学习表征,用于有监督学习。通过GAN构建表征,然后重用部分生成模型、判别模型作为有监督学习的特征提取器。GAN是“最大似然方法”的一个有吸引力的替代方法。对于表征学习,无需启发式损失函数是有吸引力的。

DCGAN的贡献主要有以下几点:

提出和评估了一系列卷积GANs在结构拓扑方面约束条件,让其更加稳定。该网络名为深度卷积生成式对抗网络Deep Convolutional GANs

使用训练好的判别模型用于图像分类,和其他无监督方法的结果具有可比较性。

可视化了卷积核

生成模型具有向量算是运算性能

训练细节

预处理环节,将图像scale到tanh的[-1, 1]。

mini-batch训练,batch size是128.

所有的参数初始化由(0, 0.02)的正态分布中随即得到

LeakyReLU的斜率是0.2.

虽然之前的GAN使用momentum来加速训练,DCGAN使用调好超参的Adam optimizer。

learning rate=0.0002

将momentum参数beta从0.9降为0.5来防止震荡和不稳定。

All convolutional net全卷

第1个变化为所有的卷积网络用跨卷积(strided convolutions)来替换确定的空间池化函数(如最大池化),使网络可学到自己的空间下采样。将该方法用于生成网络,使其学习自己的空间上采样,同时用于判别网络。

判别模型:使用带步长的卷积(strided convolutions)取代了的空间池化(spatial pooling),容许网络学习自己的空间下采样(spatial downsampling)。

生成模型:使用微步幅卷积(fractional strided),容许它学习自己的空间上采样(spatial upsampling)。

删除卷积特征上的全连接层的趋势

第2个变化为删除卷积特征上的全连接层的趋势。最明显的例子是对艺术图像分类中的状态全局平均采样。发现全局平均采样更稳定,但收敛变慢。直接将卷积最高层分别连接至输入和输出效果很好。GAN的第1层输入为噪声的均匀分布,仅用矩阵乘法对输入使用全连接,但结果变形为维张量且用于卷积栈的输入。判别网络的最后1个卷积层展平并送入单个Sigmoid单元。下图为示例模型结构的可视化。

100维的噪声被投影到一个小空间幅度的卷积表征中。有四个微步幅卷积(在一些论文中,它们被误称为反卷积deconvolutions),然后将这些高层表征转换到64 * 64像素的RGB三通道图片。没有全连接层,没有池化层。

Batch归一化

通过归一化每个输入使其均值为,方差为来使学习稳定,从而有助于解决差的初始化引起的训练问题,且有助于深层模型中的梯度流动。证明使深度生成器开始学习,防止生成器从所有样本崩溃至单个点(GANs中常见的一种失败状况)。然而所有层直接用batchnorm将导致样本振荡和模型不稳定。不对生成网络的输出层和判别网络的输入层应用batchnorm来避免样本振荡和模型不稳定。

生成网络除了输出层用Tanh函数,其它层用ReLU激活函数。观察到有界的激活函数可使模型学习更快饱和及覆盖训练分布的颜色空间。发现判别器中的漏(leaky) ReLU激活函数尤其在高分辨率模型中的效果不错。与这里相反,原有的GAN论文用maxout激活函数。

实验

在LSUN卧室数据集上训练DCGAN,生成的图像非常逼真:

【描述来源:Paper,URL:https://arxiv.org/pdf/1511.06434.pdf%C3%AF%C2%BC%E2%80%B0

Code讲解

但是对于实验细节以及方法的介绍并不是很详细,于是便从源码入手来理解DCGAN的工作原理。

先看main.py

with tf.Session(config=run_config) as sess: if FLAGS.dataset == 'mnist': dcgan = DCGAN( sess, input_width=FLAGS.input_width, input_height=FLAGS.input_height, output_width=FLAGS.output_width, output_height=FLAGS.output_height, batch_size=FLAGS.batch_size, y_dim=10, c_dim=1, dataset_name=FLAGS.dataset, input_fname_pattern=FLAGS.input_fname_pattern, is_crop=FLAGS.is_crop, checkpoint_dir=FLAGS.checkpoint_dir, sample_dir=FLAGS.sample_dir)

因为我们使用DCGAN来生成MNIST数字手写体图像,注意这里的y_dim=10,表示0到9这10个类别,c_dim=1,表示灰度图像。

再看model.py:

def discriminator(self, image, y=None, reuse=False): with tf.variable_scope("discriminator") as scope: if reuse: scope.reuse_variables() yb = tf.reshape(y, [self.batch_size, 1, 1, self.y_dim]) x = conv_cond_concat(image, yb) h0 = lrelu(conv2d(x, self.c_dim + self.y_dim, name='d_h0_conv')) h0 = conv_cond_concat(h0, yb) h1 = lrelu(self.d_bn1(conv2d(h0, self.df_dim + self.y_dim, name='d_h1_conv'))) h1 = tf.reshape(h1, [self.batch_size, -1]) h1 = tf.concat_v2([h1, y], 1) h2 = lrelu(self.d_bn2(linear(h1, self.dfc_dim, 'd_h2_lin'))) h2 = tf.concat_v2([h2, y], 1) h3 = linear(h2, 1, 'd_h3_lin') return tf.nn.sigmoid(h3), h3

这里batch_size=64,image的维度为[64 28 28 1],y的维度是[64 10],yb的维度[64 1 1 10],x将image和yb连接起来,这相当于是使用了Conditional GAN,为图像提供标签作为条件信息,于是x的维度是[64 28 28 11],将x输入到卷积层conv2d,conv2d的代码如下:

def conv2d(input_, output_dim, k_h=5, k_w=5, d_h=2, d_w=2, stddev=0.02, name="conv2d"): with tf.variable_scope(name): w = tf.get_variable('w', [k_h, k_w, input_.get_shape()[-1], output_dim], initializer=tf.truncated_normal_initializer(stddev=stddev)) conv = tf.nn.conv2d(input_, w, strides=[1, d_h, d_w, 1], padding='SAME') biases = tf.get_variable('biases', [output_dim], initializer=tf.constant_initializer(0.0)) conv = tf.reshape(tf.nn.bias_add(conv, biases), conv.get_shape()) return conv

卷积核的大小为5*5,stride为[1 2 2 1],通过2的卷积步长可以替代pooling进行降维,padding=‘SAME’,则卷积的输出维度为[64 14 14 11]。然后使用batch normalization及leaky ReLU的激活层,输出与yb再进行concat,得到h0,维度为[64 14 14 21]。同理,h1的维度为[64 7*7*74+10],h2的维度为[64 1024+10],然后连接一个线性输出,得到h3,维度为[64 1],由于我们希望判别器的输出代表概率,所以最终使用一个sigmoid的激活。

def generator(self, z, y=None): with tf.variable_scope("generator") as scope: s_h, s_w = self.output_height, self.output_width s_h2, s_h4 = int(s_h/2), int(s_h/4) s_w2, s_w4 = int(s_w/2), int(s_w/4) # yb = tf.expand_dims(tf.expand_dims(y, 1),2) yb = tf.reshape(y, [self.batch_size, 1, 1, self.y_dim]) z = tf.concat_v2([z, y], 1) h0 = tf.nn.relu( self.g_bn0(linear(z, self.gfc_dim, 'g_h0_lin'))) h0 = tf.concat_v2([h0, y], 1) h1 = tf.nn.relu(self.g_bn1( linear(h0, self.gf_dim*2*s_h4*s_w4, 'g_h1_lin'))) h1 = tf.reshape(h1, [self.batch_size, s_h4, s_w4, self.gf_dim * 2]) h1 = conv_cond_concat(h1, yb) h2 = tf.nn.relu(self.g_bn2(deconv2d(h1, [self.batch_size, s_h2, s_w2, self.gf_dim * 2], name='g_h2'))) h2 = conv_cond_concat(h2, yb) return tf.nn.sigmoid( deconv2d(h2, [self.batch_size, s_h, s_w, self.c_dim], name='g_h3'))

output_height和output_width为28,因此s_h和s_w为28,s_h2和s_w2为14,s_h4和s_w4为7。在这里z为平均分布的随机分布数,维度为[64 100],y的维度为[64 10],yb的维度是[64 1 1 10],z与y进行一个concat得到[64 110]的tensor,输入到一个线性层,输出维度是[64 1024],再经过batch normalization以及ReLU激活,并与y进行concat,输出h0的维度是[64 1034],同样的再经过一个线性层输出维度为[64 128*7*7],再进行reshape并与yb进行concat,得到h1,维度为[64 7 7 138],然后输入到一个deconv2d,做一个反卷积,也就是文中说的fractional strided convolutions,再经过batch normalization以及ReLU激活,并与yb进行concat,输出h2的维度是[64 14 14 138],最后再输入到deconv2d层以及sigmoid激活,得到生成器的输出,维度为[64 28 28 1]。

生成器以及判别器的输出:

self.G = self.generator(self.z, self.y)self.D, self.D_logits = \ self.discriminator(inputs, self.y, reuse=False)self.D_, self.D_logits_ = \ self.discriminator(self.G, self.y, reuse=True)

其中D表示真实数据的判别器输出,D_表示生成数据的判别器输出。

再看损失函数:

self.d_loss_real = tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits( logits=self.D_logits, targets=tf.ones_like(self.D)))self.d_loss_fake = tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits( logits=self.D_logits_, targets=tf.zeros_like(self.D_)))self.g_loss = tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits( logits=self.D_logits_, targets=tf.ones_like(self.D_)))

即对于真实数据,判别器的损失函数d_loss_real为判别器输出与1的交叉熵,而对于生成数据,判别器的损失函数d_loss_fake为输出与0的交叉熵,因此判别器的损失函数d_loss=d_loss_real+d_loss_fake;生成器的损失函数是g_loss判别器对于生成数据的输出与1的交叉熵。优化器:

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)

for epoch in xrange(config.epoch): batch_idxs = min(len(data_X), config.train_size) // config.batch_size for idx in xrange(0, batch_idxs): batch_images = data_X[idx*config.batch_size:(idx+1)*config.batch_size] batch_labels = data_y[idx*config.batch_size:(idx+1)*config.batch_size] batch_images = np.array(batch).astype(np.float32)[:, :, :, None] 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.inputs: batch_images, self.z: batch_z, self.y:batch_labels, }) self.writer.add_summary(summary_str, counter) # Update G network _, summary_str = self.sess.run([g_optim, self.g_sum], feed_dict={ self.z: batch_z, self.y:batch_labels, }) self.writer.add_summary(summary_str, counter) # Run g_optim twice to make sure that d_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, self.y:batch_labels }) self.writer.add_summary(summary_str, counter) errD_fake = self.d_loss_fake.eval({ self.z: batch_z, self.y:batch_labels }) errD_real = self.d_loss_real.eval({ self.inputs: batch_images, self.y:batch_labels }) errG = self.g_loss.eval({ self.z: batch_z, self.y: batch_labels }) counter += 1

与论文中不同的是,这里在一个batch中,更新两次生成器,更新一次判别器。

对于MNIST手写数字数据集的生成情况如下:

【描述来源:github,URL:https://github.com/carpedm20/DCGAN-tensorflow】

发展历史

描述

第一个CNN模型诞生于1989年,发明人LeCun。需要指出的是,从诞生的第一天起,CNN自带deep属性。1998年,LeCun提出LeNet,并成功应用于美国手写数字识别。然而这是一种自下向上的一种学习方式。随后,监督式的CNN得到了广泛的关注,但是非监督式的CNN的发展有点缓慢。

GAN是“生成对抗网络”(Generative Adversarial Networks)的简称,由2014年还在蒙特利尔读博士的Ian Goodfellow引入深度学习领域。因为GAN是一种非监督形式的训练方法。随后GAN就在AI领域兴起了浪潮。2016年,GAN热潮席卷AI领域顶级会议,从ICLR到NIPS,大量高质量论文被发表和探讨。Yann LeCun曾评价GAN是“20年来机器学习领域最酷的想法”。

在GAN提出后,GAN与CNN的结合并没有达到一定的结果。如用CNNs对图像进行放大的历史尝试没有成功。这激发了LAPGAN (Denton et al.,2015)的作者们开发了一种替代的方法,来提高低分辨率的低分辨率生成的图像,这些图像可以更可靠地建模。当然也遇到了一些困难,2015年的DCGAN试图使用在受监督的文献中常用的CNN架构来衡量GANs。然而,在广泛的模型探索之后,发现了一系列的体系结构,他们可以使得数据集的训练稳定,并允许训练更高的分辨率和更深层的生成模型。

DCGAN在非监督的CNN的有效结合,也促使试了非监督式的CNN的快速发展,2016年,Wasserstein GAN的提出解决了GAN训练不稳定的问题,不再需要小心平衡生成器和判别器的训练程度,并且基本解决了collapse mode的问题,确保了生成样本的多样性。

主要事件

年份

事件

相关论文

2014

Goodfellow, I将GAN引入AI领域

Goodfellow, I., Pouget-Abadie, J., Mirza, M., Xu, B., Warde-Farley, D., Ozair, S., ... & Bengio, Y. (2014). Generative adversarial nets. In Advances in neural information processing systems (pp. 2672-2680).

2015

Radford, A., Metz, L.,提出非监督式的DCGANs

Radford, A., Metz, L., & Chintala, S. (2015). Unsupervised representation learning with deep convolutional generative adversarial networks. arXiv preprint arXiv:1511.06434.

2017

Arjovsky, M., Chintala, S.提出Wasserstein gan改进了DCGAN的一些问题

Arjovsky, M., Chintala, S., & Bottou, L. (2017). Wasserstein gan. arXiv preprint arXiv:1701.07875.

发展分析

瓶颈

自从2014年Ian Goodfellow提出以来,GAN就存在着训练困难、生成器和判别器的loss无法指示训练进程、生成样本缺乏多样性等问题。从那时起,很多论文都在尝试解决,但是效果不尽人意,比如最有名的一个DCGAN依靠的是对判别器和生成器的架构进行实验枚举《Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks》,最终找到一组比较好的网络架构设置,但是实际上是治标不治本,没有彻底解决问题。

未来发展方向

GAN就存在着训练困难、生成器和判别器的loss无法指示训练进程、生成样本缺乏多样性等问题。如何稳定的,简化训练等问题依旧是当下热门的研究问题。

Contributor: Ruiying Cai

简介