Tensorflow2.0學習(十二) — DCGAN(深度卷積生成對抗網絡)實戰

這一節將會談到如何使用tensoflow2.0去成功復現出DCGAN的結構並應用在我們的MNIST數據集上。在這裏,我們簡單的說一下GAN和DCGAN的相關知識。更仔細的原理部分大家可以參照其它博主的博客或者我的後續系列的分享中也會詳細提到。

這裏附上原官方教程的鏈接:https://tensorflow.google.cn/tutorials/generative/dcgan

GAN全稱爲Generative Adversarial Networks(生成式對抗網絡)。這個模型網絡可以通過學習一個類別的圖片,然後相應的產生該類別的圖片。簡單來說就是通過不斷學習從而生成出一些以假亂真,現實中原來中不存在的圖片。從下面的圖中可以看到,我們傳進一堆動漫圖片之後,最後模型訓練完後也可以生成出相似的動漫圖片。

那麼GAN的結構是什麼?主要有兩個部分:生成器和判別器。生成器的作用是在於生成圖片,而判別器的作用則是判別這張圖片生成的質量,也就是真(1)和假(0)。那麼這中間模型學習的地方在哪呢?簡單來說就是,生成器先輸入一個隨機噪聲,生成出一張圖片,然後判別器將他和真實的圖片一起進行判別。如果生成器生成的圖片判別的結果爲假,則說明生成的圖片質量不過關。這時候將會根據生成器和判別器的損失函數進行優化並重新讓生成器學習生成質量更高的圖片,直到判別器認爲生成器生成的圖片已經足夠以假亂真的時候,訓練才結束。

那麼DCGAN又是什麼呢?其實它是一種GAN的改進模型。因爲GAN訓練時的不穩定和不可控性等等原因,因此有研究者對它的結構進行了修改。DCGAN將判別器和生成器中原有的多層感知機模型都替換成了CNN(卷積神經網絡),同時爲了使整個網絡可微,拿掉了CNN 中的池化層,另外將全連接層以全局池化層替代以減輕計算量。

簡單介紹了GAN和DCGAN的知識後,我們可以看看代碼的搭建過程,可以更一步加深理解。

一.數據集的加載和預處理

1.相關庫的導入。

from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf
import glob
import imageio
import matplotlib.pyplot as plt
import numpy as np
import os
import PIL
from tensorflow.keras import layers
import time
from IPython import display

2.下載MNIST手寫數據集。

(train_images, train_labels), (_, _) = tf.keras.datasets.mnist.load_data()

3.圖片預處理,修改圖片形狀以及標準化。這裏將圖片標準化到[-1,1]區間,因此使用127.5。

train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')
train_images = (train_images - 127.5) / 127.5 # 將圖片標準化到 [-1, 1] 區間內

4.打亂圖片順序並將數據集批量化。

BUFFER_SIZE = 60000
BATCH_SIZE = 256
train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)

二.構建生成器

1.定義生成器。一開始層的input_shape定爲100,因爲我們準備生成大小爲1x100大小噪聲並傳入模型。爲什麼不是100x1而是1x100?因爲模型的第一層爲100x12544(7*7*256個神經元)。因此爲了滿足矩陣相乘,我們將大小設置爲該尺寸。相乘完後我們得到了1x12544大小的向量。但是因爲最後輸出的圖片是以28x28x1的形式的(和MNIST數據集匹配)。因從我們需要先將圖片reshape成(7x7x256),之後再通過3次反捲積生成圖片(28,28,1)的滿足要求的圖片大小。

def make_generator_model():
    model = tf.keras.Sequential()
    model.add(layers.Dense(7*7*256, use_bias=False, input_shape=(100,)))
    model.add(layers.BatchNormalization())
    model.add(layers.LeakyReLU())

    model.add(layers.Reshape((7, 7, 256))) #修改圖片大小
    assert model.output_shape == (None, 7, 7, 256) # batch size 沒有限制

    model.add(layers.Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False)) #反捲積
    assert model.output_shape == (None, 7, 7, 128)
    model.add(layers.BatchNormalization())
    model.add(layers.LeakyReLU())

    model.add(layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False))
    assert model.output_shape == (None, 14, 14, 64)
    model.add(layers.BatchNormalization())
    model.add(layers.LeakyReLU())

    model.add(layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False, activation='tanh'))
    assert model.output_shape == (None, 28, 28, 1)

    return model

2.測試模型的可行性。隨機生成1x100的噪聲序列傳入模型並查看結果。

generator = make_generator_model()

noise = tf.random.normal([1,100])
generated_image = generator(noise, training=False) #設置模型爲未訓練模式

plt.imshow(generated_image[0, :, :, 0], cmap='gray')

可以看出圖片生成成功,但是這時候的圖片就是一堆噪聲。因此我們需要後續的訓練使其生成的結果像各種數字。

三.構建判別器

1.判別器的構造比較簡單,其實就是對圖片不斷的進行卷積並提取特徵,最後轉爲一維向量得到結果。這和我們前面的分類案例的過程差不多。

def make_discriminator_model():
    model = tf.keras.Sequential()
    model.add(layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same',
                                     input_shape=[28, 28, 1]))
    model.add(layers.LeakyReLU())
    model.add(layers.Dropout(0.3))

    model.add(layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same'))
    model.add(layers.LeakyReLU())
    model.add(layers.Dropout(0.3))

    model.add(layers.Flatten())
    model.add(layers.Dense(1))

    return model

2.將我們剛纔生成的噪聲圖片傳入判別器,驗證判別器的可行性。

discriminator = make_discriminator_model()
decision = discriminator(generated_image)
print (decision)

驗證成功。

四.模型參數設置

1.定義損失函數。因爲是真(1)和假(0)分類,因此我們選用binarycrossentropy。這裏的from_logits意思是直接讓分類結果爲1或0,而不是返回概率。

cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)

2.定義判別器損失函數。因爲真實圖片最後判別的結果必須要爲1,因此real_loss的意思是真實的圖片x和正確判別結果y(全部爲1的圖片矩陣)的loss值。而假的圖片最後判別的結果是0,因此fake_loss的意思是生成的假圖片x和正確判別結果y(全部爲0的圖片矩陣的)的loss值。

def discriminator_loss(real_output, fake_output):
    real_loss = cross_entropy(tf.ones_like(real_output), real_output)
    fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)
    total_loss = real_loss + fake_loss #總的loss
    return total_loss

3.定義生成器損失函數。因爲要讓生成器生成以假亂真的圖片,如果判別器判別的結果爲(1)真,則會傳給生成器(1)真。因此在生成器中將判別器的判別結果和1(真)之間的loss即爲我們的生成器損失函數。

def generator_loss(fake_output):
    return cross_entropy(tf.ones_like(fake_output), fake_output)

4.定義優化器。

generator_optimizer = tf.keras.optimizers.Adam(1e-4)
discriminator_optimizer = tf.keras.optimizers.Adam(1e-4)

5.定義checkpoint。訓練時自動保存。

checkpoint_dir = './training_checkpoints'
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,
                                 discriminator_optimizer=discriminator_optimizer,
                                 generator=generator,
                                 discriminator=discriminator)

6.定義訓練次數和噪聲維度等參數。

EPOCHS = 40
noise_dim = 100
num_examples_to_generate = 16
seed = tf.random.normal([num_examples_to_generate, noise_dim]) #隨機種子

五.模型訓練

1.定義每步模型訓練的過程。這裏我們要通過前幾節提到的gradient函數自動求微分。

def train_step(images):
    noise = tf.random.normal([BATCH_SIZE, noise_dim])

    with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
        generated_images = generator(noise, training=True) #生成的圖片

        real_output = discriminator(images, training=True) #真實圖片的判別結果
        fake_output = discriminator(generated_images, training=True) #生成圖片的判別結果

        gen_loss = generator_loss(fake_output) #生成器損失函數
        disc_loss = discriminator_loss(real_output, fake_output) #判別器損失函數

    gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables) #生成器的梯度
    gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables) #判別器的梯度

    generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables)) #對生成器進行優化
    discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables)) #對判別器進行優化

2.定義模型訓練函數。

def train(dataset, epochs):
    for epoch in range(epochs): #總訓練的次數
        start = time.time()

        for image_batch in dataset: #一次訓練時訓練所有的batch
            train_step(image_batch)

        display.clear_output(wait=True)
        generate_and_save_images(generator,
                             epoch + 1,
                             seed)

    # 每 15 個 epoch 保存一次模型
        if (epoch + 1) % 15 == 0:
            checkpoint.save(file_prefix = checkpoint_prefix)

        print ('Time for epoch {} is {} sec'.format(epoch + 1, time.time()-start))

  # 最後一個 epoch 結束後生成圖片
    display.clear_output(wait=True)
    generate_and_save_images(generator,
                           epochs,
                           seed)

3.進行模型訓練,並查看最後一次的訓練結果。

train(train_dataset, EPOCHS)

可以看出訓練了幾十次後,模型生成的圖片很接近手寫的數字了。

 

以上就是這節分享的內容,謝謝大家的支持和觀看!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章