前言¶
我們上一章使用MNIST數據集進行訓練,獲得一個可以分類手寫字體的模型。如果我們數據集的數量不夠,不足於讓模型收斂,最直接的是增加數據集。但是我們收集數據並進行標註是非常消耗時間了,而最近非常火的生成對抗網絡就非常方便我們數據的收集。對抗生成網絡可以根據之前的圖片訓練生成更多的圖像,已達到以假亂真的目的。
訓練並預測¶
創建一個GAN.py文件。首先導入所需要的Python包,其中matplotlib包是之後用於展示出生成的圖片。
import numpy as np
import paddle
import paddle.fluid as fluid
import matplotlib.pyplot as plt
定義網絡¶
生成對抗網絡由生成器和判別器組合,下面的代碼片段就是一個生成器,生成器的作用是儘可能生成滿足判別器條件的圖像。隨着以上訓練的進行,判別器不斷增強自身的判別能力,而生成器也不斷生成越來越逼真的圖片,以欺騙判別器。生成器主要由兩組全連接和BN層、兩組轉置卷積運算組成,其中最後一層的卷積層的卷積核數量是1,因爲輸出的圖像是一個灰度圖的手寫字體圖片。
# 定義生成器
def Generator(y, name="G"):
def deconv(x, num_filters, filter_size=5, stride=2, dilation=1, padding=2, output_size=None, act=None):
return fluid.layers.conv2d_transpose(input=x,
num_filters=num_filters,
output_size=output_size,
filter_size=filter_size,
stride=stride,
dilation=dilation,
padding=padding,
act=act)
with fluid.unique_name.guard(name + "/"):
# 第一組全連接和BN層
y = fluid.layers.fc(y, size=2048)
y = fluid.layers.batch_norm(y)
# 第二組全連接和BN層
y = fluid.layers.fc(y, size=128 * 7 * 7)
y = fluid.layers.batch_norm(y)
# 進行形狀變換
y = fluid.layers.reshape(y, shape=(-1, 128, 7, 7))
# 第一組轉置卷積運算
y = deconv(x=y, num_filters=128, act='relu', output_size=[14, 14])
# 第二組轉置卷積運算
y = deconv(x=y, num_filters=1, act='tanh', output_size=[28, 28])
return y
判別器的作用是訓練真實的數據集,然後使用訓練真實數據集模型去判別生成器生成的假圖片。這一過程可以理解判別器爲一個二分類問題,判別器在訓練真實數據集時,儘量讓其輸出概率爲1,而訓練生成器生成的假圖片輸出概率爲0。這樣不斷給生成器壓力,讓其生成的圖片儘量逼近真實圖片,以至於真實到連判別器也無法判斷這是真實圖像還是假圖片。以下判別器由三組卷積池化層和一個最後全連接層組成,全連接層的大小爲1,輸入一個二分類的結果。
# 判別器 Discriminator
def Discriminator(images, name="D"):
# 定義一個卷積池化組
def conv_pool(input, num_filters, act=None):
return fluid.nets.simple_img_conv_pool(input=input,
filter_size=5,
num_filters=num_filters,
pool_size=2,
pool_stride=2,
act=act)
with fluid.unique_name.guard(name + "/"):
y = fluid.layers.reshape(x=images, shape=[-1, 1, 28, 28])
# 第一個卷積池化組
y = conv_pool(input=y, num_filters=64, act='leaky_relu')
# 第一個卷積池化加回歸層
y = conv_pool(input=y, num_filters=128)
y = fluid.layers.batch_norm(input=y, act='leaky_relu')
# 第二個卷積池化加回歸層
y = fluid.layers.fc(input=y, size=1024)
y = fluid.layers.batch_norm(input=y, act='leaky_relu')
# 最後一個分類器輸出
y = fluid.layers.fc(input=y, size=1, act='sigmoid')
return y
定義訓練程序¶
定義四個Program和一個噪聲維度,其中使用三個Program分別進行訓練生成器生成圖片、訓練判別器識別真實圖片、訓練判別器識別生成器生成的假圖片,還要一個Program是用於初始化參數的。噪聲的作用是初始化生成圖片。
# 創建判別器D識別生成器G生成的假圖片程序
train_d_fake = fluid.Program()
# 創建判別器D識別真實圖片程序
train_d_real = fluid.Program()
# 創建生成器G生成符合判別器D的程序
train_g = fluid.Program()
# 創建共同的一個初始化的程序
startup = fluid.Program()
# 噪聲維度
z_dim = 100
獲取Program中的獨立參數,因爲我們同時訓練3個Program,其中訓練生成器或訓練判別器時,它們參數的更新不應該互相影響。就是訓練判別器識別真實圖片時,在更新判別器模型參數時,不要更新生成器模型的參數,同理更新生成器模型參數時,不要更新判別器的模型參數。
# 從Program獲取prefix開頭的參數名字
def get_params(program, prefix):
all_params = program.global_block().all_parameters()
return [t.name for t in all_params if t.name.startswith(prefix)]
定義一個判別器識別真實圖片的程序,這裏判別器傳入的數據是真實的圖片數據。這裏使用的損失函數是fluid.layers.sigmoid_cross_entropy_with_logits(),這個損失函數是求它們在任務上的錯誤率,他們的類別是互不排斥的。所以無論真實圖片的標籤是什麼,都不會影響模型識別爲真實圖片。這裏更新的也只有判別器模型的參數,使用的優化方法是Adam。
# 訓練判別器D識別真實圖片
with fluid.program_guard(train_d_real, startup):
# 創建讀取真實數據集圖片的data,並且label爲1
real_image = fluid.layers.data('image', shape=[1, 28, 28])
ones = fluid.layers.fill_constant_batch_size_like(real_image, shape=[-1, 1], dtype='float32', value=1)
# 判別器D判斷真實圖片的概率
p_real = Discriminator(real_image)
# 獲取損失函數
real_cost = fluid.layers.sigmoid_cross_entropy_with_logits(p_real, ones)
real_avg_cost = fluid.layers.mean(real_cost)
# 獲取判別器D的參數
d_params = get_params(train_d_real, "D")
# 創建優化方法
optimizer = fluid.optimizer.AdamOptimizer(learning_rate=2e-4)
optimizer.minimize(real_avg_cost, parameter_list=d_params)
這裏定義一個判別器識別生成器生成的圖片的程序,這裏是使用噪聲的維度進行輸入。這裏判別器識別的是生成器生成的圖片,這裏使用的損失函數同樣是fluid.layers.sigmoid_cross_entropy_with_logits()。這裏更新的參數還是判別器模型的參數,也是使用Adam優化方法。
# 訓練判別器D識別生成器G生成的圖片爲假圖片
with fluid.program_guard(train_d_fake, startup):
# 利用創建假的圖片data,並且label爲0
z = fluid.layers.data(name='z', shape=[z_dim, 1, 1])
zeros = fluid.layers.fill_constant_batch_size_like(z, shape=[-1, 1], dtype='float32', value=0)
# 判別器D判斷假圖片的概率
p_fake = Discriminator(Generator(z))
# 獲取損失函數
fake_cost = fluid.layers.sigmoid_cross_entropy_with_logits(p_fake, zeros)
fake_avg_cost = fluid.layers.mean(fake_cost)
# 獲取判別器D的參數
d_params = get_params(train_d_fake, "D")
# 創建優化方法
optimizer = fluid.optimizer.AdamOptimizer(learning_rate=2e-4)
optimizer.minimize(fake_avg_cost, parameter_list=d_params)
最後定義一個訓練生成器生成圖片的模型,這裏也克隆一個預測程序,用於之後在訓練的時候輸出預測的圖片。損失函數和優化方法都一樣,但是要更新的參數是生成器的模型參。
# 訓練生成器G生成符合判別器D標準的假圖片
with fluid.program_guard(train_g, startup):
# 噪聲生成圖片爲真實圖片的概率,Label爲1
z = fluid.layers.data(name='z', shape=[z_dim, 1, 1])
ones = fluid.layers.fill_constant_batch_size_like(z, shape=[-1, 1], dtype='float32', value=1)
# 生成圖片
fake = Generator(z)
# 克隆預測程序
infer_program = train_g.clone(for_test=True)
# 生成符合判別器的假圖片
p = Discriminator(fake)
# 獲取損失函數
g_cost = fluid.layers.sigmoid_cross_entropy_with_logits(p, ones)
g_avg_cost = fluid.layers.mean(g_cost)
# 獲取G的參數
g_params = get_params(train_g, "G")
# 只訓練G
optimizer = fluid.optimizer.AdamOptimizer(learning_rate=2e-4)
optimizer.minimize(g_avg_cost, parameter_list=g_params)
訓練並預測¶
通過由噪聲來生成假的圖片數據輸入。
# 噪聲生成
def z_reader():
while True:
yield np.random.normal(0.0, 1.0, (z_dim, 1, 1)).astype('float32')
讀取真實圖片的數據集,這裏去除了數據集中的label數據,因爲label在這裏使用不上,這裏不考慮標籤分類問題。
# 讀取MNIST數據集,不使用label
def mnist_reader(reader):
def r():
for img, label in reader():
yield img.reshape(1, 28, 28)
return r
把預測的圖片保存到本地目錄上,如果使用jupyter,可用把圖片打印到頁面上。
# 顯示圖片
def show_image_grid(images, pass_id=None):
# fig = plt.figure(figsize=(5, 5))
# fig.suptitle("Pass {}".format(pass_id))
# gs = plt.GridSpec(8, 8)
# gs.update(wspace=0.05, hspace=0.05)
for i, image in enumerate(images[:64]):
# 保存生成的圖片
plt.imsave("image/test_%d.png" % i, image[0])
# 以下代碼在jupyter可用
# ax = plt.subplot(gs[i])
# plt.axis('off')
# ax.set_xticklabels([])
# ax.set_yticklabels([])
# ax.set_aspect('equal')
# plt.imshow(image[0], cmap='Greys_r')
# plt.show()
將真實數據和噪聲生成的數據的生成一個reader。
# 生成真實圖片reader
mnist_generator = paddle.batch(
paddle.reader.shuffle(mnist_reader(paddle.dataset.mnist.train()), 30000), batch_size=128)
# 生成假圖片的reader
z_generator = paddle.batch(z_reader, batch_size=128)()
創建一個執行器,這裏使用的GPU進行訓練,因爲該網絡比較大,使用CPU訓練速度會非常慢。如果讀者沒有GPU只有,可以取消註釋place = fluid.CPUPlace()這行代碼,並註釋place = fluid.CUDAPlace(0)這行代碼,就可以使用CPU進行訓練了。
# 創建執行器
# place = fluid.CPUPlace()
place = fluid.CUDAPlace(0)
exe = fluid.Executor(place)
# 初始化參數
exe.run(startup)
獲取測試需要的噪聲數據,使用這些數據進行預測,獲取預測的圖片。
# 測試噪聲數據
test_z = np.array(next(z_generator))
開始訓練,這裏同時訓練了3個程序,分別是訓練判別器D識別生成器G生成的假圖片、訓練判別器D識別真實圖片、訓練生成器G生成符合判別器D標準的假圖片。通過不斷更新判別器的參數,使得判別器的識別能力越來越強。不斷更新生成器的參數,使得生成器生成的圖像越來越逼近真實圖像。在每一輪訓練結束後,進行一次預測,輸入生成器生成的圖片並顯示出來。
# 開始訓練
for pass_id in range(5):
for i, real_image in enumerate(mnist_generator()):
# 訓練判別器D識別生成器G生成的假圖片
r_fake = exe.run(program=train_d_fake,
fetch_list=[fake_avg_cost],
feed={'z': np.array(next(z_generator))})
# 訓練判別器D識別真實圖片
r_real = exe.run(program=train_d_real,
fetch_list=[real_avg_cost],
feed={'image': np.array(real_image)})
# 訓練生成器G生成符合判別器D標準的假圖片
r_g = exe.run(program=train_g,
fetch_list=[g_avg_cost],
feed={'z': np.array(next(z_generator))})
print("Pass:%d,fake_avg_cost:%f, real_avg_cost:%f, g_avg_cost:%f" % (pass_id, r_fake[0][0], r_real[0][0], r_g[0][0]))
# 測試生成的圖片
r_i = exe.run(program=infer_program,
fetch_list=[fake],
feed={'z': test_z})
# 顯示生成的圖片
show_image_grid(r_i[0], pass_id)



到處爲止,本章就結束了。通過學習本章,是不是覺得生成對抗網絡非常神奇呢,讀者可以參數一下其他的數據,通過生成對抗網絡生成更多有趣的圖像數據集。從本章可以瞭解到深度學習的強大,但深度學習遠遠不止這些,在下一章,我們使用深度學習中的強化學習,通過訓練獲取模型,使用模型來自己玩一個小遊戲。
同步到百度AI Studio平臺:http://aistudio.baidu.com/aistudio/projectdetail/29365
同步到科賽網K-Lab平臺:https://www.kesci.com/home/project/5bf8cd7c954d6e001066d82e
項目代碼GitHub地址:https://github.com/yeyupiaoling/LearnPaddle2/tree/master/note6
注意: 最新代碼以GitHub上的爲準
上一章:《PaddlePaddle從入門到煉丹》五——循環神經網絡¶
下一章:《PaddlePaddle從入門到煉丹》七——強化學習¶
參考資料¶
- https://www.cnblogs.com/max-hu/p/7129188.html
- https://github.com/oraoto/learn_ml/blob/master/paddle/gan-mnist-split.ipynb
- https://blog.csdn.net/somtian/article/details/72126328
- http://www.paddlepaddle.org/documentation/api/zh/1.1/layers.html#sigmoid-cross-entropy-with-logits