TensorFlow深度學習實戰——DCGAN詳解與實現
- 0. 前言
- 1. DCGAN 架構
- 2. 構建 DCGAN 生成手寫數字圖像
- 2.1 生成器與判別器架構
- 2.2 構建 DCGAN
- 相關鏈接
0. 前言
深度卷積生成對抗網絡 (Deep Convolutional Generative Adversarial Network
, DCGAN
) 是一種基于生成對抗網絡 (Generative Adversarial Network, GAN) 的深度學習模型,主要用于生成圖像。它結合了卷積神經網絡 (Convolutional Neural Network,CNN) 和生成對抗網絡的優勢,以更高效地生成質量更高的圖像。
1. DCGAN 架構
深度卷積生成對抗網絡 (Deep Convolutional Generative Adversarial Network
, DCGAN
) 引入了卷積神經網絡 (Convolutional Neural Network,CNN) 的結構,主要設計思想是使用卷積層而不使用池化層或分類層。使用卷積的步幅參數和轉置卷積執行下采樣(維度減少)和上采樣(維度增加)。
相比于原始生成對抗網絡 (Generative Adversarial Network, GAN),DCGAN
的主要變化包括:
- 網絡完全由卷積層組成。池化層替換為步幅卷積(即,在使用卷積層時,將步幅從
1
增加為2
)用于判別器,而生成器使用轉置卷積 - 移除卷積后的全連接分類層
- 為了提高訓練的穩定性,在每個卷積層后使用批歸一化
DCGAN
的基本思想與原始 GAN
相同,生成器接受 100
維的噪聲輸入,經過全連接層后重塑形狀后,通過卷積層處理,生成器架構如下:
判別器接收圖像(可以是生成器生成的圖像或來自真實數據集的圖像),圖像經過卷積處理和批歸一化處理。在每一步卷積中通過步幅參數進行下采樣。卷積層的最終輸出展平后,輸入到一個具有單個神經元的分類層:
生成器和判別器組合在一起形成 DCGAN
。訓練過程與原始 GAN
相同,首先在一個批數據上訓練判別器,然后凍結判別器,訓練生成器,并重復以上過程。實踐證明,使用學習率為 0.002
的 Adam
優化器能得到更穩定的結果。接下來,使用 Tensorflow
實現一個用于生成 MNIST
手寫數字圖像的 DCGAN
。
2. 構建 DCGAN 生成手寫數字圖像
在本節中,構建一個用于生成 MNIST
手寫數字圖像的 DCGAN
。
2.1 生成器與判別器架構
生成器通過順序添加網絡層構建。第一層是一個全連接層,接受 100
維的噪聲作為輸入,全連接層將 100
維的輸入擴展為一個大小為 128 × 7 × 7
的一維向量。這樣做的目的是為了最終得到大小為 28 × 28
的輸出,也就是 MNIST
手寫數字圖像的標準大小。該向量重塑為一個大小為 7 × 7 × 128
的張量,然后使用 TensorFlow
的 UpSampling2D
層進行上采樣。需要注意的是,該層只是通過將行和列翻倍來放大圖像,并沒有可訓練權重,因此計算開銷較小。
Upsampling2D
層將 7 × 7 × 128
(行 × 列 × 通道)的圖像的行和列翻倍,得到大小 14 × 14 × 128
的輸出。上采樣后的圖像傳遞給一個卷積層,卷積層學習填充上采樣圖像中的細節,卷積的輸出傳遞到批歸一化層。批歸一化后的輸出經過 ReLU
激活。重復以上結構,即:上采樣-卷積-批歸一化-ReLU
。在生成器中,具有兩個這樣的結構,第一個卷積層中使用 128
個卷積核,第二個使用 64
個卷積核。最終輸出使用一個卷積層,使用尺寸為 3 x 3
的單個卷積核和 tanh
激活函數,生成 28 × 28 × 1
的圖像:
def build_generator(self):model = Sequential()model.add(Dense(128 * 7 * 7, activation="relu", input_dim=self.latent_dim))model.add(Reshape((7, 7, 128)))model.add(UpSampling2D())model.add(Conv2D(128, kernel_size=3, padding="same"))model.add(BatchNormalization(momentum=0.8))model.add(Activation("relu"))model.add(UpSampling2D())model.add(Conv2D(64, kernel_size=3, padding="same"))model.add(BatchNormalization(momentum=0.8))model.add(Activation("relu"))model.add(Conv2D(self.channels, kernel_size=3, padding="same"))model.add(Activation("tanh"))model.summary()noise = Input(shape=(self.latent_dim,))img = model(noise)return Model(noise, img)
生成器模型架構如下:
也可以使用轉置卷積層,轉置卷積層不僅對輸入圖像進行上采樣,而且在訓練過程中學習如何填充細節。因此,可以用一個轉置卷積層來替代上采樣和卷積層,轉置卷積層執行的是反卷積操作。
接下來,構建判別器。判別器類似于標準卷積神經網絡,但區別在于,使用步幅為 2
的卷積層來代替最大池化層。還添加了 dropout
層以避免過擬合,并使用批歸一化以提高準確性和加快收斂速度,激活函數使用 leaky ReLU
。在判別器中,使用了三個卷積層,分別具有 32
、64
和 128
個卷積核。最后一個卷積層的輸出展平后傳遞給一個具有單個單元的全連接層。輸出用于將圖像分類為真實圖像或偽造圖像:
def build_discriminator(self):model = Sequential()model.add(Conv2D(32, kernel_size=3, strides=2, input_shape=self.img_shape, padding="same"))model.add(LeakyReLU(alpha=0.2))model.add(Dropout(0.25))model.add(Conv2D(64, kernel_size=3, strides=2, padding="same"))model.add(ZeroPadding2D(padding=((0,1),(0,1))))model.add(BatchNormalization(momentum=0.8))model.add(LeakyReLU(alpha=0.2))model.add(Dropout(0.25))model.add(Conv2D(128, kernel_size=3, strides=2, padding="same"))model.add(BatchNormalization(momentum=0.8))model.add(LeakyReLU(alpha=0.2))model.add(Dropout(0.25))model.add(Conv2D(256, kernel_size=3, strides=1, padding="same"))model.add(BatchNormalization(momentum=0.8))model.add(LeakyReLU(alpha=0.2))model.add(Dropout(0.25))model.add(Flatten())model.add(Dense(1, activation='sigmoid'))model.summary()img = Input(shape=self.img_shape)validity = model(img)return Model(img, validity)
判別器模型架構如下:
2.2 構建 DCGAN
通過將生成器和判別器組合在一起得到完整的 GAN
:
from tensorflow.keras.datasets import mnist
from tensorflow.keras.layers import Input, Dense, Reshape, Flatten, Dropout
from tensorflow.keras.layers import BatchNormalization, Activation, ZeroPadding2D
from tensorflow.keras.layers import LeakyReLU
from tensorflow.keras.layers import UpSampling2D, Conv2D
from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.optimizers import Adamimport matplotlib.pyplot as plt
import sys
import numpy as npclass DCGAN():def __init__(self, rows, cols, channels, z = 10):# Input shapeself.img_rows = rowsself.img_cols = colsself.channels = channelsself.img_shape = (self.img_rows, self.img_cols, self.channels)self.latent_dim = zoptimizer_1 = Adam(0.0002, 0.5)optimizer_2 = Adam(0.0002, 0.5)# Build and compile the discriminatorself.discriminator = self.build_discriminator()self.discriminator.compile(loss='binary_crossentropy',optimizer=optimizer_1,metrics=['accuracy'])# Build the generatorself.generator = self.build_generator()# The generator takes noise as input and generates imgsz = Input(shape=(self.latent_dim,))img = self.generator(z)# For the combined model we will only train the generatorself.discriminator.trainable = False# The discriminator takes generated images as input and determines validityvalid = self.discriminator(img)# The combined model (stacked generator and discriminator)# Trains the generator to fool the discriminatorself.combined = Model(z, valid)self.combined.compile(loss='binary_crossentropy', optimizer=optimizer_2)def build_generator(self):model = Sequential()model.add(Dense(128 * 7 * 7, activation="relu", input_dim=self.latent_dim))model.add(Reshape((7, 7, 128)))model.add(UpSampling2D())model.add(Conv2D(128, kernel_size=3, padding="same"))model.add(BatchNormalization(momentum=0.8))model.add(Activation("relu"))model.add(UpSampling2D())model.add(Conv2D(64, kernel_size=3, padding="same"))model.add(BatchNormalization(momentum=0.8))model.add(Activation("relu"))model.add(Conv2D(self.channels, kernel_size=3, padding="same"))model.add(Activation("tanh"))model.summary()noise = Input(shape=(self.latent_dim,))img = model(noise)return Model(noise, img)def build_discriminator(self):model = Sequential()model.add(Conv2D(32, kernel_size=3, strides=2, input_shape=self.img_shape, padding="same"))model.add(LeakyReLU(alpha=0.2))model.add(Dropout(0.25))model.add(Conv2D(64, kernel_size=3, strides=2, padding="same"))model.add(ZeroPadding2D(padding=((0,1),(0,1))))model.add(BatchNormalization(momentum=0.8))model.add(LeakyReLU(alpha=0.2))model.add(Dropout(0.25))model.add(Conv2D(128, kernel_size=3, strides=2, padding="same"))model.add(BatchNormalization(momentum=0.8))model.add(LeakyReLU(alpha=0.2))model.add(Dropout(0.25))model.add(Conv2D(256, kernel_size=3, strides=1, padding="same"))model.add(BatchNormalization(momentum=0.8))model.add(LeakyReLU(alpha=0.2))model.add(Dropout(0.25))model.add(Flatten())model.add(Dense(1, activation='sigmoid'))model.summary()img = Input(shape=self.img_shape)validity = model(img)return Model(img, validity)
使用 binary_crossentropy
損失函數定義生成器和判別器的損失。生成器和判別器的優化器在初始化方法中定義。最后,定義了一個 TensorFlow
檢查點,用于在模型訓練過程中保存生成器和判別器模型。
DCGAN
的訓練過程與原始 GAN
相同,在每一步中,首先將隨機噪聲輸入到生成器中。生成器的輸出與真實圖像用于訓練判別器,然后訓練生成器,使其生成能夠欺騙判別器的圖像。GAN
的訓練通常需要幾百到數千個訓練 epoch
:
def train(self, epochs, batch_size=256, save_interval=50):# Load the dataset(X_train, _), (_, _) = mnist.load_data()# Rescale -1 to 1X_train = X_train / 127.5 - 1.X_train = np.expand_dims(X_train, axis=3)# Adversarial ground truthsvalid = np.ones((batch_size, 1))fake = np.zeros((batch_size, 1))for epoch in range(epochs):# ---------------------# Train Discriminator# ---------------------# Select a random half of imagesidx = np.random.randint(0, X_train.shape[0], batch_size)imgs = X_train[idx]# Sample noise and generate a batch of new imagesnoise = np.random.normal(0, 1, (batch_size, self.latent_dim))gen_imgs = self.generator.predict(noise)# Train the discriminator (real classified as ones and generated as zeros)d_loss_real = self.discriminator.train_on_batch(imgs, valid)d_loss_fake = self.discriminator.train_on_batch(gen_imgs, fake)d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)# ---------------------# Train Generator# ---------------------# Train the generator (wants discriminator to mistake images as real)g_loss = self.combined.train_on_batch(noise, valid)# Plot the progressprint ("%d [D loss: %f, acc.: %.2f%%] [G loss: %f]" % (epoch, d_loss[0], 100*d_loss[1], g_loss))# If at save interval => save generated image samplesif epoch % save_interval == 0:self.save_imgs(epoch)
最后,定義輔助函數保存圖像:
def save_imgs(self, epoch):r, c = 5, 5noise = np.random.normal(0, 1, (r * c, self.latent_dim))gen_imgs = self.generator.predict(noise)# Rescale images 0 - 1gen_imgs = 0.5 * gen_imgs + 0.5fig, axs = plt.subplots(r, c)cnt = 0for i in range(r):for j in range(c):axs[i,j].imshow(gen_imgs[cnt, :,:,0], cmap='gray')axs[i,j].axis('off')cnt += 1fig.savefig("images/dcgan_mnist_%d.png" % epoch)plt.close()
訓練 DCGAN
模型:
dcgan = DCGAN(28,28,1)
dcgan.train(epochs=5000, batch_size=128, save_interval=50)
隨著訓練的進行,GAN
學習生成手寫數字的能力逐漸增強:
在第 50
個訓練 epoch
,生成的手寫數字圖像質量有了顯著提升:
下圖是將 DCGAN
應用到名人圖像數據集中的一些生成結果:
相關鏈接
TensorFlow深度學習實戰(1)——神經網絡與模型訓練過程詳解
TensorFlow深度學習實戰(2)——使用TensorFlow構建神經網絡
TensorFlow深度學習實戰(3)——深度學習中常用激活函數詳解
TensorFlow深度學習實戰(4)——正則化技術詳解
TensorFlow深度學習實戰(5)——神經網絡性能優化技術詳解
TensorFlow深度學習實戰(6)——回歸分析詳解
TensorFlow深度學習實戰(7)——分類任務詳解
TensorFlow深度學習實戰(8)——卷積神經網絡
TensorFlow深度學習實戰(9)——構建VGG模型實現圖像分類
TensorFlow深度學習實戰(10)——遷移學習詳解
TensorFlow深度學習實戰(11)——風格遷移詳解
TensorFlow深度學習實戰(14)——循環神經網絡詳解
TensorFlow深度學習實戰(15)——編碼器-解碼器架構
TensorFlow深度學習實戰(16)——注意力機制詳解
TensorFlow深度學習實戰(23)——自編碼器詳解與實現
TensorFlow深度學習實戰(24)——卷積自編碼器詳解與實現
TensorFlow深度學習實戰(25)——變分自編碼器詳解與實現
TensorFlow深度學習實戰(26)——生成對抗網絡詳解與實現