python深度學習---生成式對抗網絡(GAN)

 生成式對抗網絡(GAN,generative adversarial network)由 Goodfellow 等人於 2014 年提出,它可以替代VAE來學習圖像的潛在空間。 '''

# 它能夠迫使生成圖像與真實圖像在統計上幾乎無法區分,從而生成相當逼真的合成圖像。
#GAN工作原理:一個僞造者網絡和一個專家網絡,二者訓練的目的都是爲了打敗彼此
    因此,GAN由以下兩部分組成。
    生成器網絡(generator network):它以一個隨機向量(潛在空間中的一個隨機點)作爲輸入,並將其解碼爲一張合成圖像。
    判別器網絡(discriminator network)或對手(adversary):以一張圖像(真實的或合成的均可)作爲輸入,並預測該圖像是來自訓練集還是由生成器網絡創建。

    訓練生成器網絡的目的是使其能夠欺騙判別器網絡,因此隨着訓練的進行,它能夠逐漸生 成越來越逼真的圖像,即看起來與真實圖像無法區分的人造圖像,以至於判別器網絡無法區分二者;
    與此同時,判別器也在不斷適應生成器逐漸提高的能力,爲生成圖像的真實性設置了很高的標準。一旦訓練結束,生成器就能夠將其輸入空間中的任何點轉換爲一張可信圖像
    通常來說,梯度下降是沿着靜態的損失地形滾下山坡。但對於GAN而言,每下山一步, 都會對整個地形造成一點改變。它是一個動態的系統,其最優化過程尋找的不是一個最小值, 而是兩股力量之間的平衡。
    因此,GAN的訓練極其困難,想要讓GAN正常運行,需要對模型架構和訓練參數進行大量的仔細調整
'''

深度卷積生成式對抗網絡(DCGAN,deep convolutional GAN),即生成器和判別器都是深度卷積神經網絡的GAN。特別地,它在生成器中使用Conv2DTranspose 層進行圖像上採樣。

    GAN的簡要實現流程如下所示。
       (1) generator網絡將形狀爲(latent_dim,)的向量映射到形狀爲(32, 32, 3)的圖像。
       (2) discriminator 網絡將形狀爲(32, 32, 3)的圖像映射到一個二進制分數,用於評估圖像爲真的概率。
       (3) gan網絡將generator網絡和discriminator網絡連接在一起:gan(x) = discriminator (generator(x))。生成器將潛在空間向量解碼爲圖像,判別器對這些圖像的真實性進行評估,因此這個 gan 網絡是將這些潛在向量映射到判別器的評估結果。
       (4) 我們使用帶有“真”/“假”標籤的真假圖像樣本來訓練判別器,就和訓練普通的圖像分類模型一樣。
       (5) 爲了訓練生成器,我們要使用 gan 模型的損失相對於生成器權重的梯度。這意味着, 在每一步都要移動生成器的權重,其移動方向是讓判別器更有可能將生成器解碼的圖像劃分爲“真”。換句話說,我們訓練生成器來欺騙判別器。

    大量技巧:
        1.我們使用tanh 作爲生成器最後一層的激活,而不用 sigmoid,後者在其他類型的模型中更加常見
        2.我們使用正態分佈(高斯分佈)對潛在空間中的點進行採樣,而不用均勻分佈。
        3.隨機性能夠提高穩健性。訓練GAN得到的是一個動態平衡,所以GAN可能以各種方式“卡住”。在訓練過程中引入隨機性有助於防止出現這種情況。我們通過兩種方式引入隨機性: 一種是在判別器中使用dropout,另一種是向判別器的標籤添加隨機噪聲
        4.稀疏的梯度會妨礙GAN的訓練。在深度學習中,稀疏性通常是我們需要的屬性,但在GAN中並非如此。有兩件事情可能導致梯度稀疏:最大池化運算和 ReLU 激活。我們推薦使用步進卷積代替最大池化來進行下采樣,還推薦使用 LeakyReLU 層來代替 ReLU 激活。LeakyReLU 和 ReLU類似,但它允許較小的負數激活值,從而放寬了稀疏性限制。
        5.在生成的圖像中,經常會見到棋盤狀僞影,這是由生成器中像素空間的不均勻覆蓋導致的(見圖 8-17)。爲了解決這個問題,每當在生成器和判別器中都使用步進的 Conv2DTranpose 或 Conv2D 時,使用的內核大小要能夠被步幅大小整除。
'''

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import pylab
from pandas import DataFrame, Series
from keras import models, layers, optimizers, losses, metrics
from keras.utils.np_utils import to_categorical

plt.rcParams['font.sans-serif'] = ['SimHei']  #指定默認字體
plt.rcParams['axes.unicode_minus'] = False  #解決保存圖像是負號'-'顯示爲方塊的問題


#GAN生成器網絡
#generator 模型,它將一個向量(來自潛在空間,訓練過程中對其隨機 採樣)轉換爲一張候選圖像。GAN常見的諸多問題之一,就是生成器“卡在”看似噪聲的生成圖像上。一種可行的解決方案是在判別器和生成器中都使用 dropout。
import keras

latent_dim=32
height=32
width=32
channels=3

generator_input=keras.Input(shape=(latent_dim,))
x=layers.Dense(128*16*16)(generator_input)
x=layers.LeakyReLU()(x)
x=layers.Reshape((16,16,128))(x)#將輸入轉換爲大小爲 16×16 的 128 個通道的特徵圖

x=layers.Conv2D(256,5,padding='same')(x)
x=layers.LeakyReLU()(x)

x=layers.Conv2DTranspose(256,4,strides=2,padding='same')(x)#上採樣爲32*32
x=layers.LeakyReLU()(x)

x=layers.Conv2D(256,5,padding='same')(x)
x=layers.LeakyReLU()(x)
x=layers.Conv2D(256,5,padding='same')(x)
x=layers.LeakyReLU()(x)

x=layers.Conv2D(channels,7,activation='tanh',padding='same')(x)#生成一個大小爲 32×32 的單通道特徵圖 (即 CIFAR10 圖像的形狀)
generator=keras.models.Model(generator_input,x)
generator.summary()


#GAN判別器網絡
#discriminator模型,它接收一張候選圖像(真實的或合成的)作爲輸入,並將其劃分到這兩個類別之一:“生成圖像”或“來自訓練集的真實圖像”

discrimination_input=layers.Input(shape=(height,width,channels))#判別器輸入爲生成圖像與真實圖像的拼接,以判斷圖像的‘真假’
x=layers.Conv2D(128,3)(discrimination_input)
x=layers.LeakyReLU()(x)
x=layers.Conv2D(128,4,strides=2)(x)#卷積窗口4*4,步幅爲2
x=layers.LeakyReLU()(x)
x=layers.Conv2D(128,4,strides=2)(x)
x=layers.LeakyReLU()(x)
x=layers.Conv2D(128,4,strides=2)(x)
x=layers.LeakyReLU()(x)
x=layers.Flatten()(x)
x=layers.Dropout(0.4)(x)
x=layers.Dense(1,activation='sigmoid')(x)#分類層(真或假)
discriminator=keras.models.Model(discrimination_input,x)#將判別器模型實例化,這裏它將形狀爲 (32, 32, 3)的輸入轉換爲一個二進制分類決策(真/假)
discriminator.summary()
discriminator_optimizer=optimizers.RMSprop(
    lr=0.0008,
    clipvalue=1.0,#優化器中使用梯度裁剪(限制梯度的範圍)[它是一個動態的系統,其最優化過程尋找的不是一個最小值,而是兩股力量之間的平衡。]
    decay=1e-8#爲了穩定訓練過程,使用學習率衰減
)
discriminator.compile(optimizer=discriminator_optimizer,loss='binary_crossentropy')


#對抗網絡
'''
    最後,我們要設置GAN,將生成器和判別器連接在一起。
    訓練時,這個模型將讓生成器向某個方向移動,從而提高它欺騙判別器的能力。這個模型將潛在空間的點轉換爲一個分類決策(即“真”或“假”),它訓練的標籤都是“真實圖像”。
    因此,訓練 gan 將會更新 generator 的權重,使得 discriminator 在觀察假圖像時更有可能預測爲“真”。
    請注意,有一點很重要,就是在訓練過程中需要將判別器設置爲凍結(即不可訓練),這樣在訓練 gan 時它的權重纔不會更新。
    如果在此過程中可以對判別器的權重進行更新,那麼我們就是在訓練判別器始終預測“真”,但這並不是我們想要的!
'''
discriminator.trainable=False#將判別器權重設置爲不可訓練 (僅應用於 gan 模型)

gan_input=keras.Input(shape=(latent_dim,))
gan_output=discriminator(generator(gan_input))
gan=keras.models.Model(gan_input,gan_output)
gan_optimizer = keras.optimizers.RMSprop(
    lr=0.0004,
    clipvalue=1.0,
    decay=1e-8
)
gan.compile(optimizer=gan_optimizer, loss='binary_crossentropy')

#訓練DCGAN
'''
每輪都進行以下操作:
    (1) 從潛在空間中抽取隨機的點(隨機噪聲)。
    (2) 利用這個隨機噪聲用 generator 生成圖像。
    (3) 將生成圖像與真實圖像混合。
    (4) 使用這些混合後的圖像以及相應的標籤(真實圖像爲“真”,生成圖像爲“假”)來訓練 discriminator,如圖 8-18 所示。
    (5) 在潛在空間中隨機抽取新的點。
    (6) 使用這些隨機向量以及全部是“真實圖像”的標籤來訓練gan。這會更新生成器的權重(只更新生成器的權重,因爲判別器在 gan中被凍結),其更新方向是使得判別器能夠將生成圖像預測爲“真實圖像”。這個過程是訓練生成器去欺騙判別器。
'''
import os
import keras
from  keras.preprocessing import image

(x_train, y_train), (_, _) = keras.datasets.cifar10.load_data()
x_train = x_train[y_train.flatten() == 6]#選擇青蛙圖像(類別編號爲 6)
print(x_train.shape)#(5000, 32, 32, 3)
x_train = x_train.reshape(
    (x_train.shape[0],) +
    (height, width, channels)).astype('float32') / 255.#數據標準化
iterations=10000
batch_size=20
save_dir='datasets/gan_output'
start=0#記錄當前批處理的位置
for step in range(iterations):
    random_latent_vectors=np.random.normal(size=(batch_size,latent_dim))#潛在空間中採樣隨機點
    generated_images=generator.predict(random_latent_vectors)#利用生成器解碼爲虛假圖像
    stop=start+batch_size
    real_images=x_train[start:stop]#
    combined_images=np.concatenate([generated_images,real_images])#拼接,默認0軸(縱向)
    labels=np.concatenate([np.ones((batch_size,1)),np.zeros((batch_size,1))])#列向量,1表示生成的圖像,0表示真實的圖像
    labels+=0.05*np.random.random(labels.shape)#向標籤中添加隨機噪聲
    d_loss=discriminator.train_on_batch(combined_images,labels)#返回判別器損失:使用的是二進制交叉熵
    random_latent_vectors=np.random.normal(size=(batch_size,latent_dim))
    misleading_targets=np.zeros((batch_size,1))
    a_loss=gan.train_on_batch(#通過gan模型訓練生成器
        random_latent_vectors,
        misleading_targets#凍結判別器權重(置0)
    )
    start+=batch_size
    if start>len(x_train)-batch_size:
        start=0
    if step%100==0:#每100步保存並繪圖
        gan.save_weights('gan.h5')#保存模型權重
        print('discriminator loss:', d_loss)
        print('adversarial loss',a_loss)
        img = image.array_to_img(generated_images[0] * 255., scale=False)#轉換成圖像並保存
        img.save(os.path.join(save_dir, 'generated_frog' + str(step) + '.png'))
        img=image.array_to_img(real_images[0]*255.,scale=False)
        img.save(os.path.join(save_dir,'real_frog'+str(step)+'.png'))

 訓練時你可能會看到,對抗損失開始大幅增加,而判別損失則趨向於零,即判別器最終支配了生成器。如果出現了這種情況,你可以嘗試減小判別器的學習率,並增大判別器的 dropout 比率。

 

 

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