GAN之MINIST数据生成

mac2024-10-08  56

1.GAN的基本原理其实非常简单,这里以生成图片为例进行说明。假设我们有两个网络,G(Generator)和D(Discriminator)。正如它的名字所暗示的那样,它们的功能分别是:

G是一个生成图片的网络,它接收一个随机的噪声z,通过这个噪声生成图片,记做G(z)。

D是一个判别网络,判别一张图片是不是“真实的”。它的输入参数是x,x代表一张图片,输出D(x)代表x为真实图片的概率,如果为1,就代表100%是真实的图片,而输出为0,就代表不可能是真实的图片。

在训练过程中,生成网络G的目标就是尽量生成真实的图片去欺骗判别网络D。而D的目标就是尽量把G生成的图片和真实的图片分别开来。这样,G和D构成了一个动态的“博弈过程”

最后博弈的结果是什么?在最理想的状态下,G可以生成足以“以假乱真”的图片G(z)。对于D来说,它难以判定G生成的图片究竟是不是真实的,因此D(G(z)) = 0.5。

这样我们的目的就达成了:我们得到了一个生成式的模型G,它可以用来生成图片。

以上只是大致说了一下GAN的核心原理,如何用数学语言描述呢?这里直接摘录论文里的公式: (1)优化D: 优化第一项是真是样本x输入的时候,结果越大越好;对于噪声等的输入z,生成的假样本G(z)要越小越好 (2)优化G: 优化生成器时和真是样本没关系,故不需要考虑;这时候只有假样本,但生成器希望假样本越逼真越好(接近1),故D(G(z)越大越好,则最小化1-D(G(z))

2.GAN的特点:

(1)相比较传统的模型,他存在两个不同的网络,而不是单一的网络,并且训练方式采用的是对抗训练方式 (2)GAN中G的梯度更新信息来自判别器D,而不是来自数据样本

GAN 的优点:

(1) GAN是一种生成式模型,相比较其他生成模型(玻尔兹曼机和GSNs)只用到了反向传播,而不需要复杂的马尔科夫链

(2)相比其他所有模型, GAN可以产生更加清晰,真实的样本

(3)GAN采用的是一种无监督的学习方式训练,可以被广泛用在无监督学习和半监督学习领域

(4)相比于变分自编码器, GANs没有引入任何决定性偏置( deterministic bias),变分方法引入决定性偏置,因为他们优化对数似然的下界,而不是似然度本身,这看起来导致了VAEs生成的实例比GANs更模糊

(5)相比VAE, GANs没有变分下界,如果鉴别器训练良好,那么生成器可以完美的学习到训练样本的分布.换句话说,GANs是渐进一致的,但是VAE是有偏差的

(6)GAN应用到一些场景上,比如图片风格迁移,超分辨率,图像补全,去噪,避免了损失函数设计的困难,不管三七二十一,只要有一个的基准,直接上判别器,剩下的就交给对抗训练了。

GAN的缺点:

(1)训练GAN需要达到纳什均衡,有时候可以用梯度下降法做到,有时候做不到.我们还没有找到很好的达到纳什均衡的方法,所以训练GAN相比VAE或者PixelRNN是不稳定的,但我认为在实践中它还是比训练玻尔兹曼机稳定的多

(2)GAN不适合处理离散形式的数据,比如文本

(3)GAN存在训练不稳定、梯度消失、模式崩溃的问题(目前已解决)

5.为什么GAN中的优化器不常用SGD

(1)SGD容易震荡,容易使GAN训练不稳定, (2)GAN的目的是在高维非凸的参数空间中找到纳什均衡点,GAN的纳什均衡点是一个鞍点,但是SGD只会找到局部极小值,因为SGD解决的是一个寻找最小值的问题,GAN是一个博弈问题。

6.训练GAN的一些技巧

(1). 输入规范化到(-1,1)之间,最后一层的激活函数使用tanh(BEGAN除外)

(2). 使用wassertein GAN的损失函数,

(3). 如果有标签数据的话,尽量使用标签,也有人提出使用反转标签效果很好,另外使用标签平滑,单边标签平滑或者双边标签平滑

(4). 使用mini-batch norm, 如果不用batch norm 可以使用instance norm 或者weight norm

(5). 避免使用RELU和pooling层,减少稀疏梯度的可能性,可以使用leakrelu激活函数

(6). 优化器尽量选择ADAM,学习率不要设置太大,初始1e-4可以参考,另外可以随着训练进行不断缩小学习率,

(7). 给D的网络层增加高斯噪声,相当于是一种正则 生成对抗网络GAN(Generative adversarial networks)是最近很火的深度学习方法,要理解它可以把它分成生成模型和判别模型两个部分,简单来说就是:两个人比赛,看是 A 的矛厉害,还是 B 的盾厉害。比如,有一个业余画家总喜欢仿造著名画家的画,把仿造的画和真实的画混在一起,然后有一个专家想办法来区分那些是真迹,那些是赝品。通过不断的相互博弈,业余画家的仿造能力日益上升,与此同时,通过不断的判断结果反馈,积累了不少经验,专家的鉴别能力也在上升,进一步促使业余专家的仿造能力大幅提升,最后使得业余专家的仿造作品无限接近与真迹,使得鉴别专家无法辨别,最后判断的准确率为0.5。 ##概念与过程的形式化

理论完美的生成器

该算法的目标是令生成器生成与真实数据几乎没有区别的样本,即一个造假一流的 A,就是我们想要的生成模型。数学上,即将随机变量生成为某一种概率分布,也可以说概率密度函数为相等的:P_G(x)=P_data(x)。这正是数学上证明生成器高效性的策略:即定义一个最优化问题,其中最优生成器 G 满足 P_G(x)=P_data(x)。如果我们知道求解的 G 最后会满足该关系,那么我们就可以合理地期望神经网络通过典型的 SGD 训练就能得到最优的 G。

最优化问题

正如最开始我们了解的警察与造假者案例,定义最优化问题的方法就可以由以下两部分组成。首先我们需要定义一个判别器 D 以判别样本是不是从 P_data(x) 分布中取出来的,因此有:   其中 E 指代取期望。这一项是根据「正类」(即辨别出 x 属于真实数据 data)的对数损失函数而构建的。最大化这一项相当于令判别器 D 在 x 服从于 data 的概率密度时能准确地预测 D(x)=1,即:

  另外一项是企图欺骗判别器的生成器 G。该项根据「负类」的对数损失函数而构建,即:

因为 x<1 的对数为负,那么如果最大化该项的值,则需要令均值 D(G(z))≈0,因此 G 并没有欺骗 D。为了结合这两个概念,判别器的目标为最大化:

  给定生成器 G,其代表了判别器 D 正确地识别了真实和伪造数据点。给定一个生成器 G,上式所得出来的最优判别器可以表示为 (下文用 D_G表示)。定义价值函数为:   然后我们可以将最优化问题表述为:   现在 G 的目标已经相反了,当 D=D_G时,最优的 G 为最小化前面的等式。在论文中,作者更喜欢求解最优化价值函的 G 和 D 以求解极小极大博弈:   对于 D 而言要尽量使公式最大化(识别能力强),而对于 G 又想使之最小(生成的数据接近实际数据)。整个训练是一个迭代过程。其实极小极大化博弈可以分开理解,即在给定 G 的情况下先最大化 V(D,G) 而取 D,然后固定 D,并最小化 V(D,G) 而得到 G。其中,给定 G,最大化 V(D,G) 评估了 P_G 和 P_data 之间的差异或距离。

最后,我们可以将最优化问题表达为:   上文给出了 GAN 概念和优化过程的形式化表达。通过这些表达,我们可以理解整个生成对抗网络的基本过程与优化方法。本质上,还是通过误差方向传播的方式来更新参数。当然,有了这些概念我们完全可以直接在 GitHub 上找一段 GAN 代码稍加修改并很好地运行它。但如果我们希望更加透彻地理解 GAN,更加全面地理解实现代码,那么我们还需要知道很多推导过程。比如什么时候 D 能令价值函数 V(D,G) 取最大值、G 能令 V(D,G) 取最小值,而 D 和 G 该用什么样的神经网络(或函数),它们的损失函数又需要用什么等等。总之,还有很多理论细节与推导过程需要我们进一步挖掘。

import tensorflow as tf #导入tensorflow from tensorflow.examples.tutorials.mnist import input_data #导入手写数字数据集 import numpy as np #导入numpy import matplotlib.pyplot as plt #plt是绘图工具,在训练过程中用于输出可视化结果 import matplotlib.gridspec as gridspec #gridspec是图片排列工具,在训练过程中用于输出可视化结果 import os #导入os def xavier_init(size): #初始化参数时使用的xavier_init函数 in_dim = size[0] xavier_stddev = 1. / tf.sqrt(in_dim / 2.) #初始化标准差 return tf.random_normal(shape=size, stddev=xavier_stddev) #返回初始化的结果 X = tf.placeholder(tf.float32, shape=[None, 784]) #X表示真的样本(即真实的手写数字) D_W1 = tf.Variable(xavier_init([784, 128])) #表示使用xavier方式初始化的判别器的D_W1参数,是一个784行128列的矩阵 D_b1 = tf.Variable(tf.zeros(shape=[128])) #表示全零方式初始化的判别器的D_1参数,是一个长度为128的向量 D_W2 = tf.Variable(xavier_init([128, 1])) #表示使用xavier方式初始化的判别器的D_W2参数,是一个128行1列的矩阵 D_b2 = tf.Variable(tf.zeros(shape=[1])) ##表示全零方式初始化的判别器的D_1参数,是一个长度为1的向量 theta_D = [D_W1, D_W2, D_b1, D_b2] #theta_D表示判别器的可训练参数集合 Z = tf.placeholder(tf.float32, shape=[None, 100]) #Z表示生成器的输入(在这里是噪声),是一个N列100行的矩阵 G_W1 = tf.Variable(xavier_init([100, 128])) #表示使用xavier方式初始化的生成器的G_W1参数,是一个100行128列的矩阵 G_b1 = tf.Variable(tf.zeros(shape=[128])) #表示全零方式初始化的生成器的G_b1参数,是一个长度为128的向量 G_W2 = tf.Variable(xavier_init([128, 784])) #表示使用xavier方式初始化的生成器的G_W2参数,是一个128行784列的矩阵 G_b2 = tf.Variable(tf.zeros(shape=[784])) #表示全零方式初始化的生成器的G_b2参数,是一个长度为784的向量 theta_G = [G_W1, G_W2, G_b1, G_b2] #theta_G表示生成器的可训练参数集合 def sample_Z(m, n): #生成维度为[m, n]的随机噪声作为生成器G的输入 return np.random.uniform(-1., 1., size=[m, n]) def generator(z): #生成器,z的维度为[N, 100] G_h1 = tf.nn.relu(tf.matmul(z, G_W1) + G_b1) #输入的随机噪声乘以G_W1矩阵加上偏置G_b1,G_h1维度为[N, 128] G_log_prob = tf.matmul(G_h1, G_W2) + G_b2 #G_h1乘以G_W2矩阵加上偏置G_b2,G_log_prob维度为[N, 784] G_prob = tf.nn.sigmoid(G_log_prob) #G_log_prob经过一个sigmoid函数,G_prob维度为[N, 784] return G_prob #返回G_prob def discriminator(x): #判别器,x的维度为[N, 784] D_h1 = tf.nn.relu(tf.matmul(x, D_W1) + D_b1) #输入乘以D_W1矩阵加上偏置D_b1,D_h1维度为[N, 128] D_logit = tf.matmul(D_h1, D_W2) + D_b2 #D_h1乘以D_W2矩阵加上偏置D_b2,D_logit维度为[N, 1] D_prob = tf.nn.sigmoid(D_logit) #D_logit经过一个sigmoid函数,D_prob维度为[N, 1] return D_prob, D_logit #返回D_prob, D_logit G_sample = generator(Z) #取得生成器的生成结果 D_real, D_logit_real = discriminator(X) #取得判别器判别的真实手写数字的结果 D_fake, D_logit_fake = discriminator(G_sample) #取得判别器判别的生成的手写数字的结果 #对判别器对真实样本的判别结果计算误差(将结果与1比较) D_loss_real = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_real, targets=tf.ones_like(D_logit_real))) #对判别器对虚假样本(即生成器生成的手写数字)的判别结果计算误差(将结果与0比较) D_loss_fake = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_fake, targets=tf.zeros_like(D_logit_fake))) #判别器的误差 D_loss = D_loss_real + D_loss_fake #生成器的误差(将判别器返回的对虚假样本的判别结果与1比较) G_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_fake, targets=tf.ones_like(D_logit_fake))) mnist = input_data.read_data_sets('../../MNIST_data', one_hot=True) #mnist是手写数字数据集 D_solver = tf.train.AdamOptimizer().minimize(D_loss, var_list=theta_D) #判别器的训练器 G_solver = tf.train.AdamOptimizer().minimize(G_loss, var_list=theta_G) #生成器的训练器 mb_size = 128 #训练的batch_size Z_dim = 100 #生成器输入的随机噪声的列的维度 sess = tf.Session() #会话层 sess.run(tf.initialize_all_variables()) #初始化所有可训练参数 def plot(samples): #保存图片时使用的plot函数 fig = plt.figure(figsize=(4, 4)) #初始化一个4行4列包含16张子图像的图片 gs = gridspec.GridSpec(4, 4) #调整子图的位置 gs.update(wspace=0.05, hspace=0.05) #置子图间的间距 for i, sample in enumerate(samples): #依次将16张子图填充进需要保存的图像 ax = plt.subplot(gs[i]) plt.axis('off') ax.set_xticklabels([]) ax.set_yticklabels([]) ax.set_aspect('equal') plt.imshow(sample.reshape(28, 28), cmap='Greys_r') return fig path = '/data/User/zcc/' #保存可视化结果的路径 i = 0 #训练过程中保存的可视化结果的索引 for it in range(1000000): #训练100万次 if it % 1000 == 0: #每训练1000次就保存一下结果 samples = sess.run(G_sample, feed_dict={Z: sample_Z(16, Z_dim)}) fig = plot(samples) #通过plot函数生成可视化结果 plt.savefig(path+'out/{}.png'.format(str(i).zfill(3)), bbox_inches='tight') #保存可视化结果 i += 1 plt.close(fig) X_mb, _ = mnist.train.next_batch(mb_size) #得到训练一个batch所需的真实手写数字(作为判别器的输入) #下面是得到训练一次的结果,通过sess来run出来 _, D_loss_curr, D_loss_real, D_loss_fake, D_loss = sess.run([D_solver, D_loss, D_loss_real, D_loss_fake, D_loss], feed_dict={X: X_mb, Z: sample_Z(mb_size, Z_dim)}) _, G_loss_curr = sess.run([G_solver, G_loss], feed_dict={Z: sample_Z(mb_size, Z_dim)}) if it % 1000 == 0: #每训练1000次输出一下结果 print('Iter: {}'.format(it)) print('D loss: {:.4}'. format(D_loss_curr)) print('G_loss: {:.4}'.format(G_loss_curr)) print()
最新回复(0)