天天看点

GAN的实现

本篇是《GAN实战对抗生成网络》一书的第一个例程,下载书本源代码链接,不过书本中的主程序的的损失函数部分代码需要做些改动,详情可参考GAN系列:代码阅读(2)——Generative Adversarial Networks & 李宏毅老师GAN课程P1+P4和详解GAN代码之逐行解析GAN代码。

备注:本人此处的代码以及我参考的两处博客代码均可跑通,不过win7,8G运存大约需要数小时,运行结果图可见本人下篇文章.

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec      #gridspec module: 给图像分格 #gridspec是图片排列工具,在训练过程中用于输出可视化结果
import os
from tensorflow.examples.tutorials.mnist import input_data   #导入手写数字数据集

           
# 定义服从正态分布的函数,产生参数
#初始化参数时使用的xavier_init函数
def xavier_init(size):
    # 产生标准差的过程
    input_dim = size[0]
    #初始化标准差
    xavier_variance = 1. / tf.sqrt(input_dim/2.)
    # 噪声服从正态分布,size[0]应该是噪声向量长度
    return tf.random_normal(shape=size, stddev=xavier_variance)

def save(saver, sess, logdir, step): #保存模型的save函数
   model_name = 'model' #模型名前缀
   checkpoint_path = os.path.join(logdir, model_name) #保存路径
   saver.save(sess, checkpoint_path, global_step=step) #保存模型
   print('The checkpoint has been created.')

# 定义画图函数,samples是要画的图像集
def plot(samples):
    # 在matplotlib中,整个图像为一个Figure对象。在Figure对象中可以包含一个或者多个Axes对象。
    # 每个Axes(ax)对象都是一个拥有自己坐标系统的绘图区域
    # 创建图像窗口,定义尺寸4*4
    fig = plt.figure(figsize=(4, 4))
    # gridspec将整个图像分格:4*4共16个
    gs = gridspec.GridSpec(4, 4)
    # 调整图像布局参数,只影响当前Gridspec对象
    gs.update(wspace=0.05, hspace=0.05)

    # 遍历图像集,拼成一个大图用来展示结果
    for i, sample in enumerate(samples):
        # title为图像标题,Axis为坐标轴,Tick为刻度线,Tick Label为刻度注释
        ax = plt.subplot(gs[i])
        plt.axis('off')
        ax.set_xticklabels([])
        ax.set_yticklabels([])
        ax.set_aspect('equal')
        # 把图像变回28*28,cmap表示使用'Greys_r'灰度图
        plt.imshow(sample.reshape(28, 28), cmap='Greys_r')

    # 返回整张大图
    return fig
           
# Random noise setting for Generator
# placeholder实例通常用来为算法的实际输入值作占位符,赋值时必须用sess.run中的optional argument:feed_dict进行赋值
## Z为随机噪声的batch,向量长度为100
Z = tf.placeholder(tf.float32, shape=[None, 100], name='Z')


#Generator parameter settings
# 生成器第一层权重和偏置变量
G_W1 = tf.Variable(xavier_init([100, 128]), name='G_W1')
G_b1 = tf.Variable(tf.zeros(shape=[128]), name='G_b1')
# 生成器第二层权重和偏置变量,输出784维向量,作为图片
G_W2 = tf.Variable(xavier_init([128, 784]), name='G_W2')
G_b2 = tf.Variable(tf.zeros(shape=[784]), name='G_b2')
# 整合生成器参数
theta_G = [G_W1, G_W2, G_b1, G_b2]


# Generator Network
# 定义生成器函数:z是输入的随机噪声batch矩阵,网络为全连接
#生成器generator(z)从随机分布中(本例采用统一分布)接收一个100维向量作为输入,并产生一个符合MNIST规范(28X28)的784维向量。
#这里的z是G(z)的先验。通过这种方式可以学习到先验空间和Pdata(真实数据分布)空间之间的映射
def generator(z):
    # matmul:矩阵相乘
    # 生成器激活函数为relu,只有第一层激活
    G_h1 = tf.nn.relu(tf.matmul(z, G_W1) + G_b1)
    G_log_prob = tf.matmul(G_h1, G_W2) + G_b2
    # 第二层输出前用sigmoid函数,为什么?答:为了进行二分类,或者说是为了输出真假概率. 评:回答错误,判别器是为了二分类,生成器不是
    G_prob = tf.nn.sigmoid(G_log_prob)

    return G_prob


#Input Image MNIST setting for Discriminator [28x28=784]
#X表示真实图片batch,每个图片向量长度为784:28*28
X = tf.placeholder(tf.float32, shape=[None, 784], name='X')


#Discriminator parameter settings
#Variable创建变量后必须赋值才能使用,在计算图的运算过程中,其值会一直保存到程序运行结束,
#而一般的tensor张量在tensorflow运行过程中只是在计算图中流过,并不会保存下来
# varibale主要用来保存tensorflow构建的一些结构中的参数,
# 这些参数才不会随着运算的消失而消失,才能最终得到一个模型
# 创建初始随机噪声的权重和偏置变量
# 权重服从正态分布,784*128(因为下一层有128个神经元?);偏置初始化为0
D_W1 = tf.Variable(xavier_init([784, 128]), name='D_W1')
D_b1 = tf.Variable(tf.zeros(shape=[128]), name='D_b1')
# 继续创建下一层权重和偏置的变量
# 权重依然为正态分布,128*1(判别器网络只有两层?);偏置依然为0
D_W2 = tf.Variable(xavier_init([128, 1]), name='D_W2')
D_b2 = tf.Variable(tf.zeros(shape=[1]), name='D_b2')

#整合所有判别器参数
theta_D = [D_W1, D_W2, D_b1, D_b2]


# Discriminator Network
# 定义判别器函数:x是输入的图像batch矩阵
#判别器discriminator(x)以MNIST图像作为输入并返回一个代表真实图像概率的标量。即输入的是图像,输出的是概率(标量)
def discriminator(x):
    # 网络也为全连接,最后sigmoid输出,可以理解为二分类器
    D_h1 = tf.nn.relu(tf.matmul(x, D_W1) + D_b1)
    D_logit = tf.matmul(D_h1, D_W2) + D_b2
    D_prob = tf.nn.sigmoid(D_logit)
    # 判别器sigmoid前的结果D_logit也输出了
    return D_prob, D_logit



           
# 把随机噪声batch矩阵输入生成器中,产生生成图像batch矩阵,取得生成器的生成结果
G_sample = generator(Z)

# 把真实图像batch矩阵输入判别器得到结果,取得判别器判别的真实手写数字的结果
# D_logit_real是sigmoid之前的结果,用于logistic回归
D_real, D_logit_real = discriminator(X)

# 把生成图像batch矩阵输入判别器得到结果,取得判别器判别的生成的手写数字的结果
D_fake, D_logit_fake = discriminator(G_sample)

# GAN论文中loss定义:reduce_mean因为求均值降维,得到的loss是标量
# Loss functions from the paper
#D_loss = -tf.reduce_mean(tf.log(D_real) + tf.log(1. - D_fake))
#G_loss = -tf.reduce_mean(tf.log(D_fake))
#此处是github下载的源代码,总是迭代一会之后就会显示loss为nan

#Tensorflow的优化器只能做最小化,因此为了最大化损失函数,我们在上面的代码中给损失加上了一个负号。与此同时,根据论文的伪代码算法,
#我们最好最大化tf.reduce_mean(tf.log(D_fake)),而不是最小化tf.reduce_mean(tf.log(1. - D_fake))。

# Alternative losses:
# -------------------
# 分别计算正负样本与标签1和0之间的交叉熵,再合并
# 和原始方法比,相当于给判别器打分一个基准:0和1
#对判别器对真实样本的判别结果计算误差(将结果与1比较)
D_loss_real = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_real, labels=tf.ones_like(D_logit_real)))
#对判别器对虚假样本(即生成器生成的手写数字)的判别结果计算误差(将结果与0比较)
D_loss_fake = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_fake, labels=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, labels=tf.ones_like(D_logit_fake)))

# 优化算法为Adam,两者都是minimize
# 原本的判别器loss形式导致其值越大,判别器效果越好;现在的形式表示loss值越小,性能越好,因此是minimize
#接下来我们通过下面的过程一步步训练损失函数:
# Update D(X)'s parameters only,var_list=theta_D
D_solver = tf.train.AdamOptimizer().minimize(D_loss, var_list=theta_D)

# Update G(Z)'s parameters only,var_list=theta_G
G_solver = tf.train.AdamOptimizer().minimize(G_loss, var_list=theta_G)

# 定义采样函数:随机生成-1到1的值,m*n
def sample_Z(m, n):
    return np.random.uniform(-1., 1., size=[m, n])

batch_size = 128
# 随机噪声向量长度
Z_dim = 100

#会话对象封装了执行操作对象和计算张量对象的环境
sess = tf.Session()
# global_variables_initializer:Returns an Op that initializes global variables
# 就是初始化参数的操作
sess.run(tf.global_variables_initializer())

# 载入数据
mnist = input_data.read_data_sets('MNIST/', one_hot=True)

# 如果路径中没有输出图像的文件夹,创建一个
if not os.path.exists('output/'):
    os.makedirs('output/')

i = 0

# 一共训练1000000次
for itr in range(10000):
    # 每1000次迭代输出一次生成结果并保存
    if itr % 1000 == 0:
        # 一次结果中包含16个小图片
        samples = sess.run(G_sample, feed_dict={Z: sample_Z(16, Z_dim)})
        fig = plot(samples)
        plt.savefig('output/{}.png'.format(str(i).zfill(3)), bbox_inches='tight')
        i += 1
        plt.close(fig)

    # 按照设置的batch_size从训练集中取图片
    X_mb, _ = mnist.train.next_batch(batch_size)

    # 训练过程:判别器和生成器训练同样次数?是因为用了Adam的优化算法吗?
    _, D_loss_curr = sess.run([D_solver, D_loss], feed_dict={X: X_mb, Z: sample_Z(batch_size, Z_dim)})
    _, G_loss_curr = sess.run([G_solver, G_loss], feed_dict={Z: sample_Z(batch_size, Z_dim)})

    # 每1000次输出当前迭代的次数,判别器和生成器优化前的loss
    if itr % 1000 == 0:
        print('Iter: {}'.format(itr))
        print('D loss: {:.4}'. format(D_loss_curr))
        print('G_loss: {:.4}'.format(G_loss_curr))
        print()
        
#训练过程中会出现loss为nan,具体原因有可能是梯度爆炸,详情见https://blog.csdn.net/qq_33485434/article/details/80733251
           

继续阅读