1. 程式人生 > >《PaddlePaddle從入門到煉丹》六——生成對抗網路

《PaddlePaddle從入門到煉丹》六——生成對抗網路

文章目錄

前沿

我們上一章使用MNIST資料集進行訓練,獲得一個可以分類手寫字型的模型。如果我們資料集的數量不夠,不足於讓模型收斂,最直接的是增加資料集。但是我們收集資料並進行標註是非常消耗時間了,而最近非常火的生成對抗網路就非常方便我們資料的收集。對抗生成網路可以根據之前的圖片訓練生成更多的影象,已達到以假亂真的目的。

首先匯入所需要的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"):
    with fluid.unique_name.guard(name + "/"):
        # 第一組全連線和BN層
        y =
fluid.layers.fc(y, size=1024, act='relu') y = fluid.layers.batch_norm(y, act='relu') # 第二組全連線和BN層 y = fluid.layers.fc(y, size=128 * 7 * 7) y = fluid.layers.batch_norm(y, act='relu') # 進行形狀變換 y = fluid.layers.reshape(y, shape=(-1, 128, 7, 7)) # 第一組轉置卷積運算
y = fluid.layers.image_resize(y, scale=2) y = fluid.layers.conv2d(y, num_filters=64, filter_size=5, padding=2, act='relu') # 第二組轉置卷積運算 y = fluid.layers.image_resize(y, scale=2) y = fluid.layers.conv2d(y, num_filters=1, filter_size=5, padding=2, act='relu') return y

判別器的作用是訓練真實的資料集,然後使用訓練真實資料集模型去判別生成器生成的假圖片。這一過程可以理解判別器為一個二分類問題,判別器在訓練真實資料集時,儘量讓其輸出概率為1,而訓練生成器生成的假圖片輸出概率為0。這樣不斷給生成器壓力,讓其生成的圖片儘量逼近真實圖片,以至於真實到連判別器也無法判斷這是真實影象還是假圖片。以下判別器由三組卷積池化層和一個最後全連線層組成,全連線層的大小為1,輸入一個二分類的結果。

# 判別器 Discriminator
def Discriminator(images, name="D"):
    # 一組卷積層和BN層
    def conv_bn(input, num_filters, filter_size):
        y = fluid.layers.conv2d(input=input,
                                num_filters=num_filters,
                                filter_size=filter_size,
                                stride=1,
                                bias_attr=False)
        # 啟用函式為leaky ReLU
        y = fluid.layers.batch_norm(y, act="leaky_relu")
        return y

    with fluid.unique_name.guard(name + "/"):
        # 第一組卷積池化
        y = conv_bn(images, num_filters=32, filter_size=3)
        y = fluid.layers.pool2d(y, pool_size=2, pool_stride=2)
        # 第二組卷積池化
        y = conv_bn(y, num_filters=64, filter_size=3)
        y = fluid.layers.pool2d(y, pool_size=2, pool_stride=2)
        # 第三組卷積池化
        y = conv_bn(y, num_filters=128, filter_size=3)
        y = fluid.layers.pool2d(y, pool_size=2, pool_stride=2)
        # 全連線輸出層
        y = fluid.layers.fc(y, size=1)
    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

顯示圖片,構建一個8*8的圖片陣列,把預測的圖片打印出來。

# 顯示圖片
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]):
        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)

到處為止,本章就結束了。通過學習本章,是不是覺得生成對抗網路非常神奇呢,讀者可以引數一下其他的資料,通過生成對抗網路生成更多有趣的影象資料集。從本章可以瞭解到深度學習的強大,但深度學習遠遠不止這些,在下一章,我們使用深度學習中的強化學習,通過訓練獲取模型,使用模型來自己玩一個小遊戲。

本文章由夜雨飄零製作

有疑問可以加QQ群:432676488(PaddlePaddle 交流社群)或518588005(百度AI Studio)交流哦

參考資料