前言
既然學習了變分自編碼(VAE),那也必須來一波生成對抗網絡(GAN)。
國際慣例,參考網址:
論文: Generative Adversarial Nets
PPT:Generative Adversarial Networks (GANs)
Generative Adversarial Nets in TensorFlow
GAN原理學習筆記
GAN — Ways to improve GAN performance
理論
粗略點的講法就說:一個生成器,一個判別器,前者用來將噪聲輸入轉換成圖片,後者判別當前輸入圖片是真實的還是生成的。
爲了更詳細地瞭解GAN,還是對論文進行簡要的組織、理解吧。有興趣直接看原始論文,這裏進行部分關鍵內容的摘抄。
任意的和函數空間都存在特定解,要能表示訓練集的分佈,而一定是等於,也就是說判別器無法分辨當前輸入是真的還是假的,這樣就達到了魚目混珠的效果。在GAN中,使用多層感知器構建和,整個模型可以使用反向傳播算法學習。
論文裏面有一句很好的話解釋了GAN的動機:目前深度學習在判別模型的設計中取得了重大成功,但是在生成模型卻鮮有成效,主要原因在於在極大似然估計和相關策略中有很多難以解決的概率計算難題(想想前一篇博客的變分自編碼的理論,闊怕),而且丟失了生成背景下的分段線性單元的優勢,因此作者就提出了新的生成模型估計方法,避開這些難題,也就是傳說中的GAN。它的訓練完全不需要什麼鬼似然估計,只需要使用目前炒雞成功的反傳和dropout算法。
爲了讓生成器學到數據分佈,需要定義一個先驗的噪聲輸入,然後使用將其映射到數據空間,這裏的是具有參數的多層感知器。然後定義另一個多層感知器,輸出一個標量。代表的是來自於真實樣本而非生成的樣本的概率,我們訓練去最大化將正確標籤同時賦予訓練集和生成的樣本的概率,也就是把真的和假的圖片都當成真的了。同時要去訓練去最小化,是爲了讓生成的圖片被賦予正樣本標籤的概率大點,損失函數就是:
在優化的時候,在訓練的內循環中是無法完成的,計算上不允許,並且在有限數據集上會導致過擬合,因此可以以 的訓練次數比例分別優化和。這能夠讓保持在最優解附近,只要變化比較緩慢。
而且在實際中,上式可能無法提供足夠的梯度讓很好地學習,在訓練早期,當很差的時候,能夠以很高的概率將其歸爲負樣本,因爲生成的數據與訓練數據差距很大,這樣就飽和了,與其說最小化不如去最大化,這個目標函數對和的收斂目標不變,但是能早期學習具有更強的梯度。
訓練算法:
外層一個大循環就不說了,對所有的批數據迭代,內層有一個小循環,控制上面說的判別器與生成器的訓練比例爲的:
-
以下步驟執行次:
-
從噪聲先驗中採樣個噪聲樣本
-
從原始樣本分佈中選取個樣本,說這麼複雜,原始樣本的分佈不就是原始樣本麼,直接從原始樣本里面選一批數據就行了
-
更新判別器
-
-
從噪聲先驗中採樣個噪聲樣本
-
更新生成器
後面作者又證明了兩個內容:
- 的全局最優
- 訓練算法的收斂性
身爲一個合格的程序猿,還是很有必要看看數學推導的o(╯□╰)o雖然不一定能懂
先看一個簡單的式子:,這個式子在範圍取得最大值的點是在,證明很簡單,直接兩邊求導,令就能算出來。
再看看我們的優化目標,當給定生成器的時候,也就是固定的時候:
長得挺像,那麼的最優解就是:
對的訓練目標可以看成最大化對數似然去估算條件概率,這裏表示來自於原始數據(此時)還是生成數據(此時),所以損失函數又可以寫成:
理論1 當且僅當的時候,訓練目標達到全局最小,此時,收斂到值
證明:當的時候,,因此,感覺正常應該是:
但是作者貌似讓了,我估計是因爲收斂到最終解的時候,理想狀態是判別器無法分辨哪個真哪個假,所以都當成正樣本了。這樣還能將變形:
其實最終理想狀態下後面兩個KL
距離是等於0的,代表衡量的兩個分佈一樣。
這裏作者提到了一個表達式稱爲Jensen-Shannon divergence
,衡量模型分佈和數據生成過程:
這個始終是非負的,當且僅當的時候取,意思就是生成模型能夠完美生成數據分佈。
接下來看看算法收斂性
理論2 如果和有足夠的容量,並且在訓練算法的每一步,給定時判別器都能達到它的最優,那麼的更新便可以通過優化
然後就收斂到了
證明就不看了,因爲我不是特別懂作者那一段文字,我們只需要知道收斂結果就是就行了。
代碼實現-模型訓練與保存
老流程:讀數據、初始化相關參數、定義數據接受接口、初始化權重和偏置、構建基本模塊(生成器和判別器)、構建模型、定義損失和優化器、訓練
讀取數據
這個就不說了,全博客通用:
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.0
image=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_layer
def 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)
定義損失和優化器
損失函數包含生成器和判別器,但是針對生成器,我們上面說過,最小化不如最大化,所以生成器的損失可以定義爲負對數,這樣就把最大化又變成最小化了:
gen_loss = -tf.reduce_mean(tf.log(disc_fake))
當然你也可以使用最小化的方法,此博文即用最小化的方法:
G_loss = tf.reduce_mean(tf.log(1-prob_artist1))
判別器還是老樣子最大化,加個負號也是最小化了:
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')
我沒有訓練多少次,有興趣的可以多訓練,最終讓判別器的損失接近就說明接近最優解了,我的訓練結果:
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
還是依據論文流程,把判別器的訓練放在前面
d_loss,g_loss=sess.run([dis_loss,gen_loss],feed_dict={dis_input:batch_x,gen_input:z})
代碼實現-模型調用
還是老套路:
-
載入模型
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_gen, train_disc, gen_loss, disc_loss],
feed_dict=feed_dict)
建議還是改一下:
_, _, gl, dl = sess.run([train_disc,train_gen, disc_loss, gen_loss],
feed_dict=feed_dict)
但是收斂度遇到問題了,目前正在解決。
【更新日誌2018-8-29日】
找到未收斂或者收斂程度不好的原因了,要使用AdamOptimizer
優化器,不要使用AdagradOptimizer
優化器