1 算法步骤
上一篇提到的GAN的最优化问题是,本文记录如何求解这一问题。
首先为了表示方便,记,这里让最大的可视作常量。
第一步,给定初始的,使用梯度上升找到 ,最大化。关于梯度下降,可以参考笔者另一篇文章《BP神经网络原理-CSDN博客》误差反向传播的部分。
第二步,使用梯度下降法,找到最佳的参数.其中为学习率。
得到
之后这两步交替进行。
这里的是有max运算的,可以被微分吗?答案是可以的
引用李宏毅老师的例子,是有max运算的,相当于分段函数,在求微分的时候,根据当前x落在哪个区域决定微分的形式如何。
2 算法与JS散度的关系
上述算法第一步训练时本质是增大JS散度,第二步训练时看起来是减小JS散度,但实际上不完全等同。
如下图所示,左侧表示算法第一步根据得到了最优的。当进行到算法第二步,需要根据找到一个更小的JS散度,如右图所示,选择了从而使得。虽然此时JS散度更小,但是由于更换成了,将更新参数变成,此时JS散度更大了。只能说不能让更新得太多,否则不能达到减小JS散度的目标。回到文物造假的例子,造假者收到鉴宝者的反馈后,应该微调技艺,而不是彻底更换技艺,否则只能从头来过。
从快速收敛的角度来说,应该不能更新太过,但是如果太小也忽略了更好的形式,可能陷入局部最优。
3 实际训练过程
实际训练时,我们是无法计算出真实数据或生成数据实际的期望的,只能通过抽样近似得到期望。因此实际的做法如下:
3.1 第一步,初始化
初始化生成器和判别器。
3.2 第二步,固定G,训练D
从分布函数(如高斯分布)中随机抽样出m个样本输入给,输出m个样本。本质上概率分布转化器——将高斯分布的噪声转变成样本的分布。从真实数据中随机抽样出m个样本,将二者输入给。训练的参数使其接收时打出0分,接收时打出1分,即最大化
建模成分类或回归问题均可。
使用梯度上升法,
实际中需要更新多次,使得V值最大。这一步实际上只找到了一个的下限(lower bound),原因是:(1)训练次数不会非常大,没法训练到收敛;(2)即使能收敛,也可能只是一个局部最优解;(3)推导时假设了可以是任意的函数,即针对不同的x都给出最高的值,但实际中这个假设不成立。
3.3 第三步,固定D,训练G
从分布函数(如高斯分布)中随机抽样出另外m个样本
更新的参数使得下式最小:
其中第一项与无关,因此只需要看第二项。
根据上文的讨论,这里一般只训练一次,避免改变过多,无法收敛。
实践中是将和合在一起作为一个大的神经网络,前几层是,后几层是,中间有一个隐含层是的输出,就是GAN希望得到的输出。第二步和第三步可分别固定神经网络中的某几层参数不动,训练其它层参数。
4 Python实现
关于GAN的代码,参考了https://github.com/junqiangchen/GAN。项目可以产生数字图片和人脸图片,其中人脸图片的生成使用了GAN的变种——WGAN,之后会专门讨论,本文讨论最原始的GAN模型。
4.1 使用新版tensorflow需要修改的地方
原始的代码直接运行是不通的,需要做一些调整;原始代码采用的是旧版Tensorflow(V1),如果安装了新版TensorFlow(V2)也需要做调整;有些包如果安装的新版同样不支持部分API,需要替换。具体如下表所示
问题 | 调整方法 | 备注 |
部分文件路径不对 | 调整路径,例如 调整为 | 其他几处不再赘述 |
imresize报错 | 例如 调整为 | 最新版本scipy不支持此函数,将 imresize(test_image, (init_width * scale_factor, init_height * scale_factor)) 替换为 resize( Image.fromarray(test_image).resize(init_width * scale_factor, init_height * scale_factor)) |
imsave 报错 | 例如 调整为 | 最新版本scipy不支持此函数,替换成cv2。个人认为最后应该乘255,因为原始数据是0~1的数据,直接存会存成几乎黑白的图片,需要还原 |
使用新版tensorflow的问题 | 将 替换为 | 新版tensorflow提供了向下兼容的compat.v1的使用方式,统一替换即可。同时要取消eager_execution模式,新版默认是“即时计算”模式,如果兼容旧版则应取消该模式。 |
4.2 GAN的代码解析
代码位置:gan/GAN/genmnist/mnist_model.py, class名为GANModel
4.2.1 Generator
定义在_GAN_generator函数中,总结为以下要点:
(1)含有五层网络,除最后一层,其他层在进入下一层之前都用batch_normalization归一化+relu激活函数
g4 = tf.contrib.layers.batch_norm(g4, epsilon=1e-5, is_training=self.phase, scope='bn4')
g4 = tf.nn.relu(g4)
(2)每一层都定义w和b,使用truncated_normal,即截断异常值的正态分布
tf.truncated_normal_initializer
(3)第1~2层使用全连接层,即使用w乘输入,并加上b偏置
tf.matmul(g1, g_w2) + g_b2
(4)第3~4层使用反卷积运算。是卷积运算的逆过程,关于反卷积的介绍笔者正在整理
tf.nn.conv2d_transpose(x, W, output_shape, strides=[1, strides, strides, 1], padding='SAME')
(5)第5层使用卷积运算,并使用tanh激活函数
g5 = convolution_2d(g4, g_w5)
4.2.2 Discriminator
与Generator类似,简述如下:
(1)共4层,其中1、2层使用卷积,3、4层使用全连接
(2)卷积后使用平均池化
d1 = average_pool_2x2(d1)
(3)最后一层使用sigmoid将输出控制在0~1之间
out = tf.nn.sigmoid(out_logit)
4.2.3 损失函数
Generator的损失函数为
-tf.reduce_mean(tf.log(self.D_fake))
对应前文提到的。注意这里是用,方向是一样的,之后笔者会讨论他们的区别。
Discriminator的损失函数为
-tf.reduce_mean(tf.log(self.D_real) + tf.log(1 - self.D_fake))
对应前文提到的
4.2.5 训练
定义D和G的训练函数,使得各自损失函数最小化。
trainD_op = tf.train.AdamOptimizer(learning_rate, beta1).minimize(self.d_loss, var_list=D_vars)
trainG_op = tf.train.AdamOptimizer(learning_rate, beta1).minimize(self.g_loss, var_list=G_vars)
先让D预训练30次,然后D和G交替训练。为什么先让D预训练30次?笔者认为D本质上就是个图片分类器,可以不依赖于G,比较好训练,预训练可以加快收敛速度。
训练时使用feed“喂数据”
feed_dict={self.X: batch_xs, self.Z: z_batch, self.phase: 1}
其中self.X表示真实的图片,self.Z表示噪声,self.phase表示batchnorm训练阶段还是测试阶段。
4.2.6 预测
生成随机噪声Z之后,喂给G,即可生成图片
outimage = self.Gen.eval(feed_dict={self.Z: z_batch, self.phase: 1}, session=sess)
不过笔者对这里的phase有些疑问,是否应该设置为0?恕笔者对Tensorflow不熟,代码解析有些走马观花,没有深究细节以及为什么这么写,等功力提高再回过头来优化。
至此,原始GAN的算法以及Python实现已介绍完毕,下一篇笔者将拓展讨论一些细节并介绍GAN的变种。