GAN
詳解GAN代碼之逐行解析GAN代碼
本文鏈接:https://blog.csdn.net/jiongnima/article/details/80033169
訓練數據集:手寫數字識別
下載鏈接:https://pan.baidu.com/s/1d9jX5xLHd1x3DFChVCe3LQ?密碼:ws28
? ?在本篇博客中,筆者將逐行解析一下NIPS 2014的Generative Adversarial Networks(生成對抗網絡,簡稱GAN)代碼,該篇文章作為GAN系列的開山之作,在近3年吸引了無數學者的目光。在2017-2018年,各大計算機頂會中也都能看到各種GAN的身影。因此,本篇博客就來逐行解析一下使用GAN生成手寫數字的代碼。
? ?在正式開始之前,筆者想說的是,如果要使得本篇博客對各位讀者朋友的學習有幫助,請各位讀者朋友們先熟悉生成對抗網絡的基本原理。由于對于生成對抗網絡的原理詳解網絡上的資源比較多,在本篇博客中筆者就不再對生成對抗網絡的原理進行解釋,而是給大家推薦一些對生成對抗網絡原理進行了解的鏈接:
1. 直接進行論文閱讀:https://arxiv.org/abs/1406.2661
2. 一篇通俗易懂,形象的GAN原理解釋:一文看懂生成式對抗網絡GANs:介紹指南及前景展望
3. 一篇比較詳細的CSDN博文:生成式對抗網絡GAN研究進展(二)——原始GAN
4. 知乎專欄上的文章:GAN原理學習筆記
? ?如果對生成對抗網絡原理已經熟稔的讀者朋友,請自動忽略以上鏈接。并且,筆者以下放出的代碼注釋是參考了github上面的代碼,鏈接https://github.com/wiseodd/generative-models
? ?在這里筆者也想衷心感謝一下這位wiseodd大神,在他的generative-models下面的關于生成模型的代碼非常全面,本文解析的代碼路徑是該工程下面的GAN/vanilla_gan/gan_tensorflow.py文件。筆者沿用了作者的代碼,只是增加了模型保存與summary記錄的少量代碼,下面放出代碼及注釋:
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 #導入osdef 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.')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_probdef 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_logitdef 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 figG_sample = generator(Z) #取得生成器的生成結果 D_real, D_logit_real = discriminator(X) #取得判別器判別的真實手寫數字的結果 D_fake, D_logit_fake = discriminator(G_sample) #取得判別器判別的生成的手寫數字的結果D_loss_real = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_real, labels=tf.ones_like(D_logit_real))) #對判別器對真實樣本的判別結果計算誤差(將結果與1比較) D_loss_fake = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_fake, labels=tf.zeros_like(D_logit_fake))) #對判別器對虛假樣本(即生成器生成的手寫數字)的判別結果計算誤差(將結果與0比較) D_loss = D_loss_real + D_loss_fake #判別器的誤差 G_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_fake, labels=tf.ones_like(D_logit_fake))) #生成器的誤差(將判別器返回的對虛假樣本的判別結果與1比較)dreal_loss_sum = tf.summary.scalar("dreal_loss", D_loss_real) #記錄判別器判別真實樣本的誤差 dfake_loss_sum = tf.summary.scalar("dfake_loss", D_loss_fake) #記錄判別器判別虛假樣本的誤差 d_loss_sum = tf.summary.scalar("d_loss", D_loss) #記錄判別器的誤差 g_loss_sum = tf.summary.scalar("g_loss", G_loss) #記錄生成器的誤差summary_writer = tf.summary.FileWriter('snapshots/', graph=tf.get_default_graph()) #日志記錄器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 #生成器輸入的隨機噪聲的列的維度mnist = input_data.read_data_sets('../../MNIST_data', one_hot=True) #mnist是手寫數字數據集sess = tf.Session() #會話層 sess.run(tf.global_variables_initializer()) #初始化所有可訓練參數if not os.path.exists('out/'): #初始化訓練過程中的可視化結果的輸出文件夾os.makedirs('out/')if not os.path.exists('snapshots/'): #初始化訓練過程中的模型保存文件夾os.makedirs('snapshots/')saver = tf.train.Saver(var_list=tf.global_variables(), max_to_keep=50) #模型的保存器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('out/{}.png'.format(str(i).zfill(3)), bbox_inches='tight') #保存可視化結果i += 1plt.close(fig)X_mb, _ = mnist.train.next_batch(mb_size) #得到訓練一個batch所需的真實手寫數字(作為判別器的輸入)#下面是得到訓練一次的結果,通過sess來run出來_, D_loss_curr, dreal_loss_sum_value, dfake_loss_sum_value, d_loss_sum_value = sess.run([D_solver, D_loss, dreal_loss_sum, dfake_loss_sum, d_loss_sum], feed_dict={X: X_mb, Z: sample_Z(mb_size, Z_dim)})_, G_loss_curr, g_loss_sum_value = sess.run([G_solver, G_loss, g_loss_sum], feed_dict={Z: sample_Z(mb_size, Z_dim)})if it%100 ==0: #每過100次記錄一下日志,可以通過tensorboard查看summary_writer.add_summary(dreal_loss_sum_value, it)summary_writer.add_summary(dfake_loss_sum_value, it)summary_writer.add_summary(d_loss_sum_value, it)summary_writer.add_summary(g_loss_sum_value, it)if it % 1000 == 0: #每訓練1000次輸出一下結果save(saver, sess, 'snapshots/', it)print('Iter: {}'.format(it))print('D loss: {:.4}'. format(D_loss_curr))print('G_loss: {:.4}'.format(G_loss_curr))print()? ?在上面的代碼中,各位讀者朋友可以看到,生成器與判別器都是使用多層感知機實現的(沒有使用卷積神經網絡)。生成器的輸入是隨機噪聲,生成的是手寫數字,生成器與判別器均使用Adam優化器進行訓練并訓練100w次。
? ?在上面的代碼中,筆者添加了各種summary保存了訓練中的誤差,結果如下所示。
? ?判別器判別真實樣本的誤差變化:
? ?判別器判別虛假樣本(即生成器G生成的手寫數字)的誤差變化:
? ?判別器的誤差變化(上面兩者之和):
? ?生成器的誤差變化:
? ?下面是訓練過程中輸出的可視化結果,筆者選擇了一些,大家可以看到,生成器輸出結果最開始非常糟糕,但是隨著訓練的進行到訓練中期輸出效果越來越好:
? ?訓練2k次的輸出:
? ?訓練6k次的輸出:
? ?訓練4.2w次的輸出
? ?訓練14.4w次的輸出:
? ?訓練24.4w次的輸出:
? ?訓練31.6w次的輸出:
? ?在訓練的后期(訓練80w次之后),大家從生成器的誤差曲線可以看出,生成器的誤差陡增,生成效果也相應變差了(如下圖所示),這是生成器與判別器失衡的結果。
? ?訓練85.7w次的輸出:
? ?訓練93.6w次的輸出:
? ?訓練97.2w次的輸出:
總結
- 上一篇: 详解梯度爆炸和梯度消失
- 下一篇: CGAN