生成對抗網絡GAN詳解與代碼


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,而不是來自數據樣本

3. GAN 的優點:

    (1) GAN是一種生成式模型,相比較其他生成模型(玻爾茲曼機和GSNs)只用到了反向傳播,而不需要復雜的馬爾科夫鏈

    (2)相比其他所有模型, GAN可以產生更加清晰,真實的樣本

    (3)GAN采用的是一種無監督的學習方式訓練,可以被廣泛用在無監督學習和半監督學習領域

    (4)相比於變分自編碼器, GANs沒有引入任何決定性偏置( deterministic bias),變分方法引入決定性偏置,因為他們優化對數似然的下界,而不是似然度本身,這看起來導致了VAEs生成的實例比GANs更模糊

    (5)相比VAE, GANs沒有變分下界,如果鑒別器訓練良好,那么生成器可以完美的學習到訓練樣本的分布.換句話說,GANs是漸進一致的,但是VAE是有偏差的

    (6)GAN應用到一些場景上,比如圖片風格遷移,超分辨率,圖像補全,去噪,避免了損失函數設計的困難,不管三七二十一,只要有一個的基准,直接上判別器,剩下的就交給對抗訓練了。

 4. 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的網絡層增加高斯噪聲,相當於是一種正則

7.GAN實戰

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()

 參考博客:

https://blog.csdn.net/m0_37407756/article/details/75309670

https://blog.csdn.net/jiongnima/article/details/80033169


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM