【TensorFlow-windows】学习笔记七——生成对抗网络
前言
既然學習了變分自編碼(VAE),那也必須來一波生成對抗網絡(GAN)。
國際慣例,參考網址:
論文: Generative Adversarial Nets
PPT:Generative Adversarial Networks (GANs)
Generative Adversarial Nets in TensorFlow
GAN原理學習筆記
GAN — Ways to improve GAN performance
理論
粗略點的講法就說:一個生成器GGG,一個判別器DDD,前者用來將噪聲輸入轉換成圖片,后者判別當前輸入圖片是真實的還是生成的。
為了更詳細地了解GAN,還是對論文進行簡要的組織、理解吧。有興趣直接看原始論文,這里進行部分關鍵內容的摘抄。
任意的GGG和DDD函數空間都存在特定解,GGG要能表示訓練集的分布,而DDD一定是等于12\frac{1}{2}21?,也就是說判別器無法分辨當前輸入是真的還是假的,這樣就達到了魚目混珠的效果。在GAN中,使用多層感知器構建GGG和DDD,整個模型可以使用反向傳播算法學習。
論文里面有一句很好的話解釋了GAN的動機:目前深度學習在判別模型的設計中取得了重大成功,但是在生成模型卻鮮有成效,主要原因在于在極大似然估計和相關策略中有很多難以解決的概率計算難題(想想前一篇博客的變分自編碼的理論,闊怕),而且丟失了生成背景下的分段線性單元的優勢,因此作者就提出了新的生成模型估計方法,避開這些難題,也就是傳說中的GAN。它的訓練完全不需要什么鬼似然估計,只需要使用目前炒雞成功的反傳和dropout算法。
為了讓生成器學到數據分布pgp_gpg?,需要定義一個先驗的噪聲輸入pz(z)p_z(z)pz?(z),然后使用G(z;θg)G(z;\theta_g)G(z;θg?)將其映射到數據空間,這里的GGG是具有參數θg\theta_gθg?的多層感知器。然后定義另一個多層感知器D(x;θd)D(x;\theta_d)D(x;θd?),輸出一個標量。D(x)D(x)D(x)代表的是xxx來自于真實樣本而非生成的樣本pgp_gpg?的概率,我們訓練DDD去最大化將正確標簽同時賦予訓練集和GGG生成的樣本的概率,也就是DDD把真的和假的圖片都當成真的了。同時要去訓練GGG去最小化log?(1?D(G(z)))\log (1-D(G(z)))log(1?D(G(z))),是為了讓生成的圖片被賦予正樣本標簽的概率大點,損失函數就是:
min?Gmax?DV(D,G)=Ex~pdata(x)[log?D(x)]+Ez~pz(z)[log?(1?D(G(z)))]\min_G \max_D V(D,G)=E_{x\sim p_{data}(x)}[\log D(x)]+E_{z\sim p_z(z)}[\log(1-D(G(z)))] Gmin?Dmax?V(D,G)=Ex~pdata?(x)?[logD(x)]+Ez~pz?(z)?[log(1?D(G(z)))]
在優化DDD的時候,在訓練的內循環中是無法完成的,計算上不允許,并且在有限數據集上會導致過擬合,因此可以以k:1k:1k:1 的訓練次數比例分別優化DDD和GGG。這能夠讓DDD保持在最優解附近,只要GGG變化比較緩慢。
而且在實際中,上式可能無法提供足夠的梯度讓GGG很好地學習,在訓練早期,當GGG很差的時候,DDD能夠以很高的概率將其歸為負樣本,因為生成的數據與訓練數據差距很大,這樣log?(1?D(G(z)))\log(1-D(G(z)))log(1?D(G(z)))就飽和了,與其說最小化log?(1?D(G(z)))\log(1-D(G(z)))log(1?D(G(z)))不如去最大化log?(D(G(z)))\log(D(G(z)))log(D(G(z))),這個目標函數對GGG和DDD的收斂目標不變,但是能早期學習具有更強的梯度。
訓練算法:
外層一個大循環就不說了,對所有的批數據迭代,內層有一個小循環,控制上面說的判別器DDD與生成器GGG的訓練比例為k:1k:1k:1的:
-
以下步驟執行kkk次:
-
從噪聲先驗pg(z)p_g(z)pg?(z)中采樣mmm個噪聲樣本{z(1),? ,z(m)}\{z^{(1)},\cdots,z^{(m)}\}{z(1),?,z(m)}
-
從原始樣本分布pdata(x)p_{data}(x)pdata?(x)中選取mmm個樣本x(1)?x(m){x^{(1)}\cdots x^{(m)}}x(1)?x(m),說這么復雜,原始樣本的分布不就是原始樣本么,直接從原始樣本里面選一批數據就行了
-
更新判別器
?θd1m∑i=1m[log?D(x(i))+log?(1?D(G(z(i))))]\nabla_{\theta_d}\frac{1}{m}\sum_{i=1}^m \left[\log D\left(x^{(i)}\right)+\log \left(1-D\left(G\left(z^{(i)}\right)\right)\right)\right] ?θd??m1?i=1∑m?[logD(x(i))+log(1?D(G(z(i))))]
-
-
從噪聲先驗pg(z)p_g(z)pg?(z)中采樣mmm個噪聲樣本{z(1),? ,z(m)}\{z^{(1)},\cdots,z^{(m)}\}{z(1),?,z(m)}
-
更新生成器
?θg1m∑i=1mlog?(1?D(G(z(i))))\nabla \theta_g \frac{1}{m}\sum_{i=1}^m \log\left(1-D\left(G \left( z^{(i)}\right) \right)\right) ?θg?m1?i=1∑m?log(1?D(G(z(i))))
后面作者又證明了兩個內容:
- pg=pdatap_g=p_{data}pg?=pdata?的全局最優
- 訓練算法的收斂性
身為一個合格的程序猿,還是很有必要看看數學推導的o(╯□╰)o雖然不一定能懂
先看一個簡單的式子:y→alog?(y)+blog?(1?y)y\to a\log(y)+b\log(1-y)y→alog(y)+blog(1?y),這個式子在[0,1][0,1][0,1]范圍取得最大值的點是在aa+b\frac{a}{a+b}a+ba?,證明很簡單,直接兩邊求導,令y′=0y'=0y′=0就能算出來。
再看看我們的優化目標,當給定生成器GGG的時候,也就是GGG固定的時候:
V(G,D)=∫xpdata(x)log?D(x)dx+∫zpz(z)log?(1?D(g(z)))dz=∫xpdata(x)log?(D(x))+pg(x)log?(1?D(x))dxV(G,D)=\int_x p_{data}(x)\log D(x)dx+\int _zp_z(z)\log(1-D(g(z)))dz\\ =\int _xp_{data}(x)\log(D(x))+p_g(x)\log(1-D(x))dx V(G,D)=∫x?pdata?(x)logD(x)dx+∫z?pz?(z)log(1?D(g(z)))dz=∫x?pdata?(x)log(D(x))+pg?(x)log(1?D(x))dx
長得挺像,那么DDD的最優解就是:
DG?(x)=pdata(x)pdata(x)+pg(x)D_G^*(x)=\frac{p_{data}(x)}{p_{data}(x)+p_g(x)} DG??(x)=pdata?(x)+pg?(x)pdata?(x)?
對DDD的訓練目標可以看成最大化對數似然去估算條件概率P(Y=y∣x)P(Y=y|x)P(Y=y∣x),這里YYY表示xxx來自于原始數據pdatap_{data}pdata?(此時y=1y=1y=1)還是生成數據pgp_gpg?(此時y=0y=0y=0),所以損失函數又可以寫成:
C(G)=max?DV(G,D)=Ex~pdata[log?DG?(x)]+Ez~pz[log?(1?DG?(G(z)))]=Ex~pdata[log?DG?(x)]+Ez~pg[log?(1?DG?(x))]=Ex~pdata[log?pdata(x)pdata(x)+pg(x)]+Ex~pg[log?pg(x)pdata(x)+pg(x)]\begin{aligned} C(G)&=\max_D V(G,D)\\ &=E_{x\sim p_{data}}\left[\log D_G^*(x)\right]+E_{z\sim p_z}\left[\log(1-D^*_G(G(z)))\right]\\ &=E_{x\sim p_{data}}\left[\log D_G^*(x)\right]+E_{z\sim p_g}\left[\log(1-D^*_G(x))\right]\\ &=E_{x\sim p_{data}}\left[\log\frac{p_{data}(x)}{p_{data}(x)+p_g(x)}\right]+E_{x\sim p_g}\left[\log\frac{p_g(x)}{p_{data}(x)+p_g(x)}\right] \end{aligned} C(G)?=Dmax?V(G,D)=Ex~pdata??[logDG??(x)]+Ez~pz??[log(1?DG??(G(z)))]=Ex~pdata??[logDG??(x)]+Ez~pg??[log(1?DG??(x))]=Ex~pdata??[logpdata?(x)+pg?(x)pdata?(x)?]+Ex~pg??[logpdata?(x)+pg?(x)pg?(x)?]?
理論1 當且僅當pg=pdatap_g=p_{data}pg?=pdata?的時候,訓練目標C(G)C(G)C(G)達到全局最小,此時,C(G)C(G)C(G)收斂到值?log?4-\log 4?log4
證明:當pg=pdatap_g=p_{data}pg?=pdata?的時候,DG?(x)=12D^*_G(x)=\frac{1}{2}DG??(x)=21?,因此C(G)=log?12+log?12=?log?4C(G)=\log \frac{1}{2}+\log\frac{1}{2}=-\log 4C(G)=log21?+log21?=?log4,感覺正常應該是:
Ex~pdata[?log?2]+Ex~pg[?log?2]E_{x\sim p_{data}}[-\log 2]+E_{x\sim p_g}[-\log 2] Ex~pdata??[?log2]+Ex~pg??[?log2]
但是作者貌似讓Ex~pdata=Ex~pg=1E_{x\sim p_{data}}=E_{x\sim p_g}=1Ex~pdata??=Ex~pg??=1了,我估計是因為收斂到最終解的時候,理想狀態是判別器無法分辨哪個真哪個假,所以都當成正樣本了。這樣還能將C(G)C(G)C(G)變形:
C(G)=?log?4+KL(pdata∥pdata+pg2)+KL(pg∥pdata+pg2)C(G)=-\log 4+KL\left(p_{data}\parallel\frac{p_{data}+p_g}{2}\right)+KL\left(p_g\parallel \frac{p_{data}+p_g}{2}\right) C(G)=?log4+KL(pdata?∥2pdata?+pg??)+KL(pg?∥2pdata?+pg??)
其實最終理想狀態下后面兩個KL距離是等于0的,代表衡量的兩個分布一樣。
這里作者提到了一個表達式稱為Jensen-Shannon divergence,衡量模型分布和數據生成過程:
C(G)=?log?4+2?JSD(pdata∥pg)C(G)=-\log 4+2\cdot JSD(p_{data}\parallel p_g) C(G)=?log4+2?JSD(pdata?∥pg?)
這個JSDJSDJSD始終是非負的,當且僅當pdata=pgp_{data}=p_gpdata?=pg?的時候取000,意思就是生成模型能夠完美生成數據分布。
接下來看看算法收斂性
理論2 如果GGG和DDD有足夠的容量,并且在訓練算法的每一步,給定GGG時判別器都能達到它的最優,那么pgp_gpg?的更新便可以通過優化
Ex~pdata[log?DG?(x)]+Ex~pg[log?(1?DG?(x))]E_{x\sim p_{data}}\left[\log D_G^*(x)\right]+E_{x\sim p_g}\left[\log (1-D_G^*(x))\right] Ex~pdata??[logDG??(x)]+Ex~pg??[log(1?DG??(x))]
然后pgp_gpg?就收斂到了pdatap_{data}pdata?
證明就不看了,因為我不是特別懂作者那一段文字,我們只需要知道收斂結果就是pg=pdatap_g=p_{data}pg?=pdata?就行了。
代碼實現-模型訓練與保存
老流程:讀數據、初始化相關參數、定義數據接受接口、初始化權重和偏置、構建基本模塊(生成器和判別器)、構建模型、定義損失和優化器、訓練
讀取數據
這個就不說了,全博客通用:
IMG_HEIGHT=28 IMG_WIDTH=28 CHANNELS=3 #讀取數據集 def read_images(dataset_path,batch_size):imagepaths,labels=list(),list()data=open(dataset_path,'r').read().splitlines()for d in data:imagepaths.append(d.split(' ')[0])labels.append(int(d.split(' ')[1]))imagepaths=tf.convert_to_tensor(imagepaths,dtype=tf.string)labels=tf.convert_to_tensor(labels,dtype=tf.int32)image,label=tf.train.slice_input_producer([imagepaths,labels],shuffle=True)image=tf.read_file(image)image=tf.image.decode_jpeg(image,channels=CHANNELS)image=tf.image.rgb_to_grayscale(image) image=tf.reshape(image,[IMG_HEIGHT*IMG_WIDTH])image=tf.cast(image,tf.float32)image = image / 255.0image=tf.convert_to_tensor(image)inputX,inputY=tf.train.batch([image,label],batch_size=batch_size,capacity=batch_size*8,num_threads=4)return inputX,inputY定義相關參數
主要是學習率,訓練次數,輸入單元、隱單元、輸出單元的神經元個數
#定義相關參數 learning_rate=0.0002 num_steps=1000 batch_size=128 disp_step=1000 num_class=10 gen_hid_num=256 dis_hid_num=256 noise_dim=100 num_input=IMG_HEIGHT*IMG_WIDTH定義數據接收接口
需要注意GAN主要有兩類接口,生成器接收的是噪聲輸入,判別器接收的是真實圖片或者生成的圖片
#建立生成器、判別器的接收接口 gen_input=tf.placeholder(tf.float32,shape=[None,noise_dim],name='gen_input') dis_input=tf.placeholder(tf.float32,shape=[None,num_input],name='dis_input')初始化權重
#定義權重 def glorot_init(shape):return tf.random_normal(shape=shape,stddev=1/tf.sqrt(shape[0]/2.0))weights={'gen_hidden1':tf.Variable(glorot_init([noise_dim,gen_hid_num])),'gen_out':tf.Variable(glorot_init([gen_hid_num,num_input])),'dis_hidden1':tf.Variable(glorot_init([num_input,dis_hid_num])),'dis_out':tf.Variable(glorot_init([dis_hid_num,1])) } biases={'gen_hidden1':tf.Variable(tf.zeros([gen_hid_num])),'gen_out':tf.Variable(tf.zeros([num_input])),'dis_hidden1':tf.Variable(tf.zeros([dis_hid_num])),'dis_out':tf.Variable(tf.zeros([1])) }基本模塊:生成器和判別器
#定義基本模塊 def generator(x):hidden_layer=tf.add(tf.matmul(x,weights['gen_hidden1']),biases['gen_hidden1'])hidden_layer=tf.nn.relu(hidden_layer)out_layer=tf.add(tf.matmul(hidden_layer,weights['gen_out']),biases['gen_out'])out_layer=tf.nn.sigmoid(out_layer)return out_layerdef discriminator(x):hidden_layer=tf.add(tf.matmul(x,weights['dis_hidden1']),biases['dis_hidden1'])hidden_layer=tf.nn.relu(hidden_layer)out_layer=tf.add(tf.matmul(hidden_layer,weights['dis_out']),biases['dis_out'])out_layer=tf.nn.sigmoid(out_layer)return out_layer構建模型
注意我們的測試函數是生成器,最終需要它來生成圖片,所以需要加入函數中
#生成器 gen_sample=generator(gen_input) tf.add_to_collection('generation',gen_sample) #判別器 dis_real=discriminator(dis_input) dis_fake=discriminator(gen_sample)定義損失和優化器
損失函數包含生成器和判別器,但是針對生成器,我們上面說過,最小化log?(1?D(G(z)))\log (1-D(G(z)))log(1?D(G(z)))不如最大化log?(D(G(z)))\log(D(G(z)))log(D(G(z))),所以生成器的損失可以定義為負對數,這樣就把最大化又變成最小化了:
gen_loss = -tf.reduce_mean(tf.log(disc_fake))當然你也可以使用最小化的方法,此博文即用最小化log?(1?D(G(z)))\log(1-D(G(z)))log(1?D(G(z)))的方法:
G_loss = tf.reduce_mean(tf.log(1-prob_artist1))判別器還是老樣子最大化log?(D(x))?log?(D(G(z)))\log (D(x))-\log(D(G(z)))log(D(x))?log(D(G(z))),加個負號也是最小化了:
disc_loss = -tf.reduce_mean(tf.log(disc_real) + tf.log(1. - disc_fake))【注】其實有的時候也可以直接用交叉熵來定義損失,讓判別器對真實圖片的標簽接近1,對假圖片的判別標簽接近0
d_loss_real = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits = D, labels = tf.ones_like(D))) d_loss_fake = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits = _D, labels = tf.zeros_like(_D))) d_loss = d_loss_real + d_loss_fake而對于生成器,希望判別器對假圖片的判別標簽接近1:
g_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits = _D, labels = tf.ones_like(_D)))話不多說,接下來定義優化器:
optimizer_gen=tf.train.AdamOptimizer(learning_rate=learning_rate) optimizer_dis=tf.train.AdamOptimizer(learning_rate=learning_rate)但是因為采用的類似于固定梯度下降法,即在更新生成器時,判別器參數不動,同理更新判別器時生成器參數不動,所以需要先指定分開訓練的時候分別對誰求梯度:
#因為采用了固定梯度下降,所以必須知道每個優化器需要優化什么 gen_var=[weights['gen_hidden1'],weights['gen_out'],biases['gen_hidden1'],biases['gen_out']] dis_var=[weights['dis_hidden1'],weights['dis_out'],biases['dis_hidden1'],biases['dis_out']]這樣就可以針對性求解了:
#優化 train_gen=optimizer_gen.minimize(gen_loss,var_list=gen_var) train_dis=optimizer_dis.minimize(dis_loss,var_list=dis_var)訓練模型與保存
#初始化 init=tf.global_variables_initializer() saver=tf.train.Saver() input_image,input_label=read_images('./mnist/train_labels.txt',batch_size) with tf.Session() as sess:sess.run(init)coord=tf.train.Coordinator()tf.train.start_queue_runners(sess=sess,coord=coord)for step in range(1,num_steps):time_start = time.time()batch_x,batch_y=sess.run([input_image,tf.one_hot(input_label,num_class,1,0)])z=np.random.uniform(-1.0,1.0,size=(batch_size,noise_dim))sess.run([train_gen,train_dis],feed_dict={dis_input:batch_x,gen_input:z})if step%1000==0 or step==1: g_loss,d_loss=sess.run([gen_loss,dis_loss],feed_dict={gen_input:z,dis_input:batch_x})time_end=time.time()print('step:%i----Generator loss:%f-----Discriminator Loss:%f' %(step,g_loss,d_loss))coord.request_stop()coord.join()print('optimization finished')saver.save(sess,'./GAN_mnist_model/GAN_mnist')我沒有訓練多少次,有興趣的可以多訓練,最終讓判別器的損失接近0.50.50.5就說明接近最優解了,我的訓練結果:
step:1----Generator loss:0.393680-----Discriminator Loss:1.626469 step:1000----Generator loss:3.580971-----Discriminator Loss:0.078812 step:2000----Generator loss:4.907338-----Discriminator Loss:0.037951 step:3000----Generator loss:5.269949-----Discriminator Loss:0.015779 step:4000----Generator loss:3.202836-----Discriminator Loss:0.119377 step:5000----Generator loss:3.977841-----Discriminator Loss:0.140365 step:6000----Generator loss:3.546029-----Discriminator Loss:0.111060 step:7000----Generator loss:3.723459-----Discriminator Loss:0.099416 step:8000----Generator loss:4.479396-----Discriminator Loss:0.130558 step:9000----Generator loss:4.041896-----Discriminator Loss:0.132201 step:10000----Generator loss:3.873767-----Discriminator Loss:0.241299 step:11000----Generator loss:4.237263-----Discriminator Loss:0.162134 step:12000----Generator loss:3.463274-----Discriminator Loss:0.223905 step:13000----Generator loss:3.941289-----Discriminator Loss:0.261881 step:14000----Generator loss:3.292896-----Discriminator Loss:0.356275 optimization finished【更新日志】2018-8-27
還是依據論文流程,把判別器的訓練放在前面
代碼實現-模型調用
還是老套路:
-
載入模型
sess=tf.Session() new_saver=tf.train.import_meta_graph('./GAN_mnist_model/GAN_mnist.meta') new_saver.restore(sess,'./GAN_mnist_model/GAN_mnist') -
載入運算圖
graph=tf.get_default_graph() print(graph.get_all_collection_keys()) #['generation', 'queue_runners', 'summaries', 'train_op', 'trainable_variables', 'variables'] -
獲取預測函數和數據接收接口
gen=graph.get_collection('generation') gen_input=graph.get_tensor_by_name('gen_input:0') -
隨便丟個噪聲給生成器
noise_input=np.random.uniform(-1.0,1.0,size=[1,100]) g=sess.run(gen,feed_dict={gen_input:noise_input}) gen_img=g[0]*255.0 gen_img=gen_img.reshape(28,28) plt.imshow(gen_img) plt.show()
后記
效果貌似不是特別好呢,可能訓練次數不是特別夠,也可能傳統的GAN結構對手寫數字的生成能力不夠,需要加深層數或者使用更好的GAN變種算法,后續打算再找幾個GAN算法研究研究。這里先貼一下這篇博客關于GAN的損失函數的對比
訓練代碼:鏈接:https://pan.baidu.com/s/12_DNKILTtletYbDDhHDi6Q 密碼:vyu7
測試代碼:鏈接:https://pan.baidu.com/s/1rvAKjBnazzKRiL7nPiufVQ 密碼:nreb
【更新日志】2018-8-27
從論文來看,我們一般需要先訓練判別器,再訓練生成器,但是TensorFlow-Examples中給的例子是
建議還是改一下:
_, _, gl, dl = sess.run([train_disc,train_gen, disc_loss, gen_loss],feed_dict=feed_dict)但是收斂度遇到問題了,目前正在解決。
【更新日志2018-8-29日】
找到未收斂或者收斂程度不好的原因了,要使用AdamOptimizer優化器,不要使用AdagradOptimizer優化器
總結
以上是生活随笔為你收集整理的【TensorFlow-windows】学习笔记七——生成对抗网络的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 女子宿舍禁爱令声优介绍 美少女角色介绍
- 下一篇: 《炉石传说》全新冒险模式“怪物狩猎”免费