GAN

实现

ERAF post this 9837 words blog on July 28, 2018

不知道上一篇看得怎么样,毕竟GAN也是刚开始看,也只是止步于大致理解的范围 内部的公式也没有详细的推导过几遍,但这里马上会讲一下对于其中G和D的优化更新过程,而对于这部分更新的算法 本质上就如同上一篇「对于等号右边直观地理解」,章节所说的意义,对于D来说只是希望最大化$E_{x~p_{data}(x)}[logD(x)]+E_{z~p_z(z)}[log(1-D(G(z)))]$ ,而对于G来说 只是最小化$ E_{z~p_z(z)} (log(1-D(G(z))))$;

本文的实现是借助GAN来生成手写数字,基于python3和tensorflow1.3;

根据上一篇的理解 我们也都知道,对于该算法来说 需要两类网络:生成器和判别器,其中的关系和输入输出分别如下图:

而整体的优化过程中的的优化的值函数:$min_Gmax_DV(D,G)=E_{x~p_{data}(x)}[logD(x)]+E_{z~p_z(z)}[log(1-D(G(z)))]$

在针对D和G使用随机梯度下降的时候,也就需要设置不同的损失函数,如下是论文中给出的算法描述;

可以看到对于这两个模型的更新 是两个网络交叉训练完成的;首先对于D和G各自初始化有$G_0$和$D_0$ ;固定$G_0$后 训练$D_0$ 找到$max_DV(D_0,G_0)$ ;然后在固定$D_0$ 训练$G_0$ ;训练过程使用梯度下降方法,以此类推,训练$D_1,G_1,D_2,G_2,…$;

训练D,D是希望V(G, D)越大越好,所以是加上梯度(ascending)。训练G时,V(G, D)越小越好,所以是减去梯度(descending)。整个训练过程交替进行。

具体的来说 我们肯定不可能只是设置一个值函数V就拿来更新,还是老老实实的对应G和D分别设置损失函数,但是目的还是如上所说 两者对于V体现出来的期待都是和上面一样的;

我们可以看到,对于D来说,discriminator的目的是希望让$G(z)$ 被判断为真的概率$D(G(z))$尽可能的小 也就是说反之 希望$(1-D(G(z)))$ 尽可能的大,同时也希望 让来自真实数据$x$被判断为真的概率$D(x)$ 尽可能的大,这样的话 对于$D(G(z))$和$D(x)$ 我们分别将其放在一个二分类(0-1)问题之中,那么前者就是被希望认为接近于0,而后者就是被希望接近于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))) 
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

当然,我们也可以直接从原来的V出发来理解:毋庸置疑的是,discriminator希望的是V(D,G)这个整体$E_{x~p_{data}(x)}[logD(x)]+E_{z~p_z(z)}[log(1-D(G(z)))]$ 尽可能的max;于是也正是如算法描述的那样 是进行加上了梯度,写成代码的时候 在前面加上负号 有 $-E_{x~p_{data}(x)}[logD(x)]-E_{z~p_z(z)}[log(1-D(G(z)))]$ 来进行min;将其拆分 于是也正是写作:$\frac{1}{m}\sum_{i=1}^{m} [-1logD(x^{(i)})-0log(1-D(x^{(i)}))]$以及$\frac{1}{m}\sum_{i=1}^{m} [-1log(1-D(G(z^{(i)})))-0log(D(G(z^{(i)})))]$;写成交叉熵的形式 也就和上面是一样的了;

对于G来说 目的无疑是希望当接受来自$G(z)$ 的时候,被判断为正确的概率尽可能大也就是说$max_G E_{z~p_z(z)} (log(D(G(z))))$ ,换句话说就是希望有着 $min_G E_{z~p_z(z)} (log(1-D(G(z))))$ ; 尽管算法中使用的是这种转化过后的结果,但在实际的应用中 人们优化损失函数使用的往往是 max log D这种未转化的;于是对于其损失函数的构建也就有着:

G_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_fake, labels=tf.ones_like(D_logit_fake))) 

再然后就是设置输入以及建立一些用到的函数 比如G和D等;

对于输入,参考上面的图我们知道 应该包含两种输入 一种是随机噪声「用于生成伪造数据」 一种是数据集数据「也就是真是数据」这里因为我们是用来生成手写数字 那么真是数据这里就是mnist数据集了,而噪声选用均匀分布噪声;于是也有:

X=tf.placeholder(tf.float32,shape=[None,784])
Z=tf.placeholder(tf.float32,shape=[None,100])

def sample_Z(m,n):
    return np.random.uniform(-1,1,size=[m,n])
mnist = input_data.read_data_sets('./mnist', one_hot=True)
X_mb, _ = mnist.train.next_batch(mb_size)

sess.run(.., feed_dict={X: X_mb, Z: sample_Z(mb_size, Z_dim)})

而相关参数也是一样 这里我们有着两个神经网络,于是相关的变量的设置如下:

对于判别器来说,需要的输入是图片进行flatten之后的矩阵;其中经过一个128单元隐层然后输出一个判别的概率结果;

而对于生成器来说,输入的是噪声 然后经过一个128单元的隐层 输出一个和图片flatten后矩阵shape一样的结果

#从正态分布输出随机值
def xavier_init(size):
    in_dim = size[0]
    xavier_stddev = 1. / tf.sqrt(in_dim / 2.)
    return tf.random_normal(shape=size, stddev=xavier_stddev)

D_W1=tf.Variable(xavier_init([784,128]))
D_b1=tf.Variable(tf.zeros(shape=[128]))

D_W2 = tf.Variable(xavier_init([128, 1]))
D_b2 = tf.Variable(tf.zeros(shape=[1]))

theta_D = [D_W1, D_W2, D_b1, D_b2]

G_W1 = tf.Variable(xavier_init([100, 128]))
G_b1 = tf.Variable(tf.zeros(shape=[128]))

G_W2 = tf.Variable(xavier_init([128, 784]))
G_b2 = tf.Variable(tf.zeros(shape=[784]))

theta_G = [G_W1, G_W2, G_b1, G_b2]

然后就是几个用到的函数:生成器和判别器:

#生成模型
def generator(z):
    G_h1 = tf.nn.relu(tf.matmul(z, G_W1) + G_b1)
    G_log_prob = tf.matmul(G_h1, G_W2) + G_b2
    G_prob = tf.nn.sigmoid(G_log_prob)

    return G_prob

#判别模型
def discriminator(x):
    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)

    return D_prob, D_logit

#画图函数
def plot(samples):
    fig = plt.figure(figsize=(4, 4))
    gs = gridspec.GridSpec(4, 4)
    gs.update(wspace=0.05, hspace=0.05)

    for i, sample in enumerate(samples):
        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

接下来 就是设置相关参数进行训练的时候了:

#关于来自于生成器和判别器所需要的输出 用于后面的损失函数构建
G_sample=generator(Z)
D_real,D_logit_real=discriminator(X)
D_fake, D_logit_fake = discriminator(G_sample)

#loss function和optimizer
D_loss_real = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_real, targets=tf.ones_like(D_logit_real))) 
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

G_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_fake, targets=tf.ones_like(D_logit_fake))) 

D_solver = tf.train.AdamOptimizer().minimize(D_loss, var_list=theta_D)
G_solver = tf.train.AdamOptimizer().minimize(G_loss, var_list=theta_G)

#开始运行 迭代次数设置为10000 
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())

    for it in range(10000):
        if it%1000==0:
            samples=sess.run(G_sample,feed_dict={Z:sample_Z(16,Z_dim)})
            fig=plot(samples)
            plt.savefig("out/{}.png".format(str(i).zfill(3)), bbox_inches='tight')
            i+=1
            plt.close(fig)
        X_mb, _ = mnist.train.next_batch(mb_size)
        _, D_loss_curr = sess.run([D_solver, 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:
            print('Iter: {}'.format(it))
            print('D loss: {:.4}'. format(D_loss_curr))
            print('G_loss: {:.4}'.format(G_loss_curr))

简单的运行结果:

name: GeForce GTX 960M major: 5 minor: 0 memoryClockRate(GHz): 1.176
pciBusID: 0000:01:00.0
totalMemory: 2.00GiB freeMemory: 1.65GiB
2018-07-29 15:44:38.436601: I C:\tf_jenkins\home\workspace\rel-win\M\windows-gpu\PY\35\tensorflow\core\common_runtime\gpu\gpu_device.cc:1120] Creating TensorFlow device (/device:GPU:0) -> (device: 0, name: GeForce GTX 960M, pci bus id: 0000:01:00.0, compute capability: 5.0)
Iter: 0
D loss: 2.182
G_loss: 1.512
Iter: 1000
D loss: 0.03771
G_loss: 9.551
Iter: 2000
D loss: 0.009744
G_loss: 6.254
Iter: 3000
D loss: 0.05684
G_loss: 7.012
Iter: 4000
D loss: 0.147
G_loss: 5.546
Iter: 5000
D loss: 0.1608
G_loss: 6.116
Iter: 6000
D loss: 0.1224
G_loss: 6.683
Iter: 7000
D loss: 0.2813
G_loss: 4.842
Iter: 8000
D loss: 0.3479
G_loss: 4.619
Iter: 9000
D loss: 0.5232
G_loss: 3.558

生成的最后图:

因为不是专门搞这方面的 于是也看出太出来上面的loss值表达出来的优化的好坏;

以下有一个关于训练GAN的技巧和训练的总结:https://github.com/soumith/ganhacks

首先放一张最后代码的tensorboard的效果图,然后就是全部代码「修改成支持tensorboard查看的版本」:

# -*- coding: utf-8 -*-
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import os

#读入数据
mnist = input_data.read_data_sets('./mnist', one_hot=True)#代码和数据集文件夹放在同一目录下

#从正态分布输出随机值
def xavier_init(size):
    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],name="real_data")

with tf.name_scope("Discriminator_variable"):
    D_W1 = tf.Variable(xavier_init([784, 128]))
    D_b1 = tf.Variable(tf.zeros(shape=[128]))
    D_W2 = tf.Variable(xavier_init([128, 1]))
    D_b2 = tf.Variable(tf.zeros(shape=[1]))
    theta_D = [D_W1, D_W2, D_b1, D_b2]
    tf.summary.histogram("D_W1",D_W1)
    tf.summary.histogram("D_W2",D_W2)
    tf.summary.histogram("D_b1",D_b1)
    tf.summary.histogram("D_b2",D_b2)

#生成模型的输入和参数初始化
#使用tf.summary.histogram来制作直方图
Z = tf.placeholder(tf.float32, shape=[None, 100],name="noise")

with tf.name_scope("Generator_variable"):
    G_W1 = tf.Variable(xavier_init([100, 128]))
    G_b1 = tf.Variable(tf.zeros(shape=[128]))
    G_W2 = tf.Variable(xavier_init([128, 784]))
    G_b2 = tf.Variable(tf.zeros(shape=[784]))
    theta_G = [G_W1, G_W2, G_b1, G_b2]
    tf.summary.histogram("G_W1",G_W1)
    tf.summary.histogram("G_W2",G_W2)
    tf.summary.histogram("G_b1",G_b1)
    tf.summary.histogram("G_b2",G_b2)

#随机噪声采样函数
def sample_Z(m, n):
    return np.random.uniform(-1., 1., size=[m, n])

#生成模型
def generator(z):
    with tf.name_scope("generator"):
        G_h1 = tf.nn.relu(tf.matmul(z, G_W1) + G_b1)
        G_log_prob = tf.matmul(G_h1, G_W2) + G_b2
        G_prob = tf.nn.sigmoid(G_log_prob)

        return G_prob

#判别模型
def discriminator(x):
    with tf.name_scope("discriminator"):
        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)

        return D_prob, D_logit

#画图函数
def plot(samples):
    fig = plt.figure(figsize=(4, 4))
    gs = gridspec.GridSpec(4, 4)
    gs.update(wspace=0.05, hspace=0.05)

    for i, sample in enumerate(samples):
        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

#喂入数据
G_sample = generator(Z)
D_real, D_logit_real = discriminator(X)
D_fake, D_logit_fake = discriminator(G_sample)

# 计算losses:
#使用tf.summary.scalar来记录
with tf.name_scope("D_LOSS"):
    D_loss_real = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_real, labels=tf.ones_like(D_logit_real))) 
    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
    tf.summary.scalar('D_loss',D_loss)
with tf.name_scope("G_LOSS"):
    G_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_fake, labels=tf.ones_like(D_logit_fake))) 
    tf.summary.scalar('G_loss',G_loss)
with tf.name_scope("D_train"):
    D_solver = tf.train.AdamOptimizer().minimize(D_loss, var_list=theta_D)
with tf.name_scope("G_train"):      
    G_solver = tf.train.AdamOptimizer().minimize(G_loss, var_list=theta_G)

mb_size = 128
Z_dim = 100


if not os.path.exists('out/'):
    os.makedirs('out/')

i = 0
#tf.summary.merge_all对所有训练图进行合并打包
with tf.Session() as sess:
    merged = tf.summary.merge_all()
    writer = tf.summary.FileWriter("logs/", sess.graph)
    sess.run(tf.global_variables_initializer())

    #用sess.run一下打包的图,并添加相应的记录
    for it in range(10000):
        if it%1000==0:
            samples=sess.run(G_sample,feed_dict={Z:sample_Z(16,Z_dim)})
            fig=plot(samples)
            plt.savefig("out/{}.png".format(str(i).zfill(3)), bbox_inches='tight')
            i+=1
            plt.close(fig)
        X_mb, _ = mnist.train.next_batch(mb_size)
        _, D_loss_curr = sess.run([D_solver, 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%100==0:
            rs=sess.run(merged,feed_dict={X: X_mb, Z: sample_Z(mb_size, Z_dim)})
            writer.add_summary(rs,it)
            
        #
        if it % 1000 == 0:
            print('Iter: {}'.format(it))
            print('D loss: {:.4}'. format(D_loss_curr))
            print('G_loss: {:.4}'.format(G_loss_curr))

顺带后面随手记一句,修改成可记录summary 用来tensorboard来查看的版本的时候,出现了提示real_data的placeholder的不对应的情况,仔细查看了关于D_loss发现并未发生变动,然后看到writer.add_summary(sess.run(merged),it) 这一句,emmmmm (捂脸)都已经sess.run了 居然没有feed_dict,肯定会出问题啊,这里的merged记录了包括variable和loss,后者的loss肯定需要input关于 real_data啊;