從零開始學keras之生成對抗網絡GAN
生成對抗網絡主要分為生成器網絡和判別器網絡。
生成器網絡:他以一個隨機向量(潛在空間的一個隨機點)作為輸入,并將其解碼成一張合成圖像。
判別器網絡:以一張圖像(真實的或合成的均可)作為輸入,并預測該圖像是來自訓練集還是生成器網絡創建。
本節將會介紹如何用 Keras 來實現形式最簡單的 GAN。GAN 屬于高級應用,所以本書不會深入介紹其技術細節。我們具體實現的是一個深度卷積生成式對抗網絡(DCGAN,deep convolutional GAN),即生成器和判別器都是深度卷積神經網絡的 GAN。特別地,它在生成器中使用 Conv2DTranspose 層進行圖像上采樣。 我們將在 CIFAR10 數據集的圖像上訓練 GAN,這個數據集包含 50 000 張 32×32 的 RGB圖像,這些圖像屬于 10 個類別(每個類別 5000 張圖像)。為了簡化,我們只使用屬于“frog”(青蛙)類別的圖像。
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 模型的損失相對于生成器權重的梯度。這意味著,在每一步都要移動生成器的權重,其移動方向是讓判別器更有可能將生成器解碼的圖像劃分為“真”。換句話說,我們訓練生成器來欺騙判別器。
訓練gan的技巧
訓練 GAN 和調節 GAN 實現的過程非常困難。你應該記住一些公認的技巧。與深度學習中的大部分內容一樣,這些技巧更像是煉金術而不是科學,它們是啟發式的指南,并沒有理論 的支持。這些技巧得到了一定程度的來自對現象的直觀理解的支持,經驗告訴我們,它們的效果都很好,但不一定適用于所有情況。
下面是本節實現 GAN 生成器和判別器時用到的一些技巧。這里并沒有列出與 GAN 相關的 全部技巧,更多技巧可查閱關于 GAN 的文獻。
我們使用 tanh 作為生成器最后一層的激活,而不用 sigmoid,后者在其他類型的模型中更加常見。
我們使用正態分布(高斯分布)對潛在空間中的點進行采樣,而不用均勻分布。
隨機性能夠提高穩健性。訓練 GAN 得到的是一個動態平衡,所以 GAN 可能以各種方式“卡住”。在訓練過程中引入隨機性有助于防止出現這種情況。我們通過兩種方式引入隨機性: 一種是在判別器中使用 dropout,另一種是向判別器的標簽添加隨機噪聲。
稀疏的梯度會妨礙 GAN 的訓練。在深度學習中,稀疏性通常是我們需要的屬性,但在GAN 中并非如此。有兩件事情可能導致梯度稀疏:最大池化運算和 ReLU 激活。我們推薦使用步進卷積代替最大池化來進行下采樣,還推薦使用 LeakyReLU 層來代替 ReLU 激活。LeakyReLU 和 ReLU 類似,但它允許較小的負數激活值,從而放寬了稀疏性限制。
在生成的圖像中,經常會見到棋盤狀偽影,這是由生成器中像素空間的不均勻覆蓋導致的。為了解決這個問題,每當在生成器和判別器中都使用步進的 Conv2DTranpose 或 Conv2D 時,使用的內核大小要能夠被步幅大小整除。
生成器
首先,我們來開發 generator 模型,它將一個向量(來自潛在空間,訓練過程中對其隨機采樣)轉換為一張候選圖像。GAN 常見的諸多問題之一,就是生成器“卡在”看似噪聲的生成圖像上。一種可行的解決方案是在判別器和生成器中都使用 dropout。
import Keras
from keras import layers
import numpy as np
latent_dim = 32
height = 32
width = 32
channels = 3
generator_input = keras.Input(shape=(latent_dim,))
# First, transform the input into a 16x16 128-channels feature map
# 將輸入轉換為大小為 16×16 的128 個通道的特征圖
x = layers.Dense(128 * 16 * 16)(generator_input)
x = layers.LeakyReLU()(x)
x = layers.Reshape((16, 16, 128))(x)
# Then, add a convolution layer(添加一個卷積層)
x = layers.Conv2D(256, 5, padding='same')(x)
x = layers.LeakyReLU()(x)
# Upsample to 32x32(上采樣為 32×32)
x = layers.Conv2DTranspose(256, 4, strides=2, padding='same')(x)
x = layers.LeakyReLU()(x)
# Few more conv layers
x = layers.Conv2D(256, 5, padding='same')(x)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(256, 5, padding='same')(x)
x = layers.LeakyReLU()(x)
# Produce a 32x32 1-channel feature map(生成一個大小為 32×32 的單通道特征圖(即 CIFAR10 圖像的形狀))
x = layers.Conv2D(channels, 7, activation='tanh', padding='same')(x)
generator = keras.models.Model(generator_input, x)
#將生成器模型實例化,它將形狀為 (latent_dim,)的輸入映射到形狀為 (32, 32, 3) 的圖像
generator.summary()
判別器
接下來,我們來開發 discriminator 模型,它接收一張候選圖像(真實的或合成的)作為輸入,并將其劃分到這兩個類別之一:“生成圖像”或“來自訓練集的真實圖像”
discriminator_input = layers.Input(shape=(height, width, channels))
x = layers.Conv2D(128, 3)(discriminator_input)
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.Conv2D(128, 4, strides=2)(x)
x = layers.LeakyReLU()(x)
x = layers.Flatten()(x)
# One dropout layer - important trick!
# 一個 dropout 層:這是很重要的技巧
x = layers.Dropout(0.4)(x)
# Classification layer(判別層)
x = layers.Dense(1, activation='sigmoid')(x)
discriminator = keras.models.Model(discriminator_input, x)
discriminator.summary()
# To stabilize training, we use learning rate decay
# and gradient clipping (by value) in the optimizer.
# 將判別器模型實例化,它將形狀為 (32, 32, 3)的輸入轉換為一個二進制分類決策(真 / 假)
discriminator_optimizer = keras.optimizers.RMSprop(lr=0.0008, clipvalue=1.0, decay=1e-8)
# clipvalue=1.0:在優化器中使用梯度裁剪(限制梯度值的范圍)
# decay=1e-8:為了穩定訓練過程,使用學習率衰減
discriminator.compile(optimizer=discriminator_optimizer, loss='binary_crossentropy')
對抗網絡
最后,我們要設置 GAN,將生成器和判別器連接在一起。訓練時,這個模型將讓生成器向某個方向移動,從而提高它欺騙判別器的能力。這個模型將潛在空間的點轉換為一個分類決策(即 “真”或“假”),它訓練的標簽都是“真實圖像”。因此,訓練 gan 將會更新 generator 的權重, 使得 discriminator 在觀察假圖像時更有可能預測為“真”。請注意,有一點很重要,就是在訓練過程中需要將判別器設置為凍結(即不可訓練),這樣在訓練 gan 時它的權重才不會更新。 如果在此過程中可以對判別器的權重進行更新,那么我們就是在訓練判別器始終預測“真”,但這并不是我們想要的!
# 將判別器權重設置為不可訓練(僅應用于 gan 模型)
discriminator.trainable = False
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。
(5) 在潛在空間中隨機抽取新的點。
(6) 使用這些隨機向量以及全部是“真實圖像”的標簽來訓練 gan。這會更新生成器的權重(只更新生成器的權重,因為判別器在 gan 中被凍結),其更新方向是使得判別器能夠將生成圖像預測為“真實圖像”。這個過程是訓練生成器去欺騙判別器。
import os
from keras.preprocessing import image
# Load CIFAR10 data(加載 CIFAR10數據)
(x_train, y_train), (_, _) = keras.datasets.cifar10.load_data()
# Select frog images (class 6)(選擇青蛙圖像(類別編號為 6))
x_train = x_train[y_train.flatten() == 6]
# Normalize data(數據標準化)
x_train = x_train.reshape(
(x_train.shape[0],) + (height, width, channels)).astype('float32') / 255.
iterations = 10000
batch_size = 20
save_dir = 'data/gan_images/'
# 指定保存生成 圖像的目錄
# Start training loop(開始循環)
start = 0
for step in range(iterations):
# Sample random points in the latent space(在潛在空間中采樣隨機點)
random_latent_vectors = np.random.normal(size=(batch_size, latent_dim))
# Decode them to fake images(將這些點解碼為虛假圖像)
generated_images = generator.predict(random_latent_vectors)
# Combine them with real images(將這些虛假圖像與真實圖像合在一起)
stop = start + batch_size
real_images = x_train[start: stop]
combined_images = np.concatenate([generated_images, real_images])
# Assemble labels discriminating real from fake images
#合并標簽,區分真實和虛假的圖像
labels = np.concatenate([np.ones((batch_size, 1)),
np.zeros((batch_size, 1))])
# Add random noise to the labels - important trick!
#向標簽中添加隨機噪聲,這是一個很重要的技巧
labels += 0.05 * np.random.random(labels.shape)
# Train the discriminator(訓練判別器)
d_loss = discriminator.train_on_batch(combined_images, labels)
# sample random points in the latent space(在潛在空間中采樣隨機點)
random_latent_vectors = np.random.normal(size=(batch_size, latent_dim))
# Assemble labels that say "all real images"(合并標簽,全部是“真實圖像”(這是在撒謊,通過 gan 模型)
misleading_targets = np.zeros((batch_size, 1))
# Train the generator (via the gan model,
# where the discriminator weights are frozen)
#來訓練生成器(此時凍結判別器權重)
a_loss = gan.train_on_batch(random_latent_vectors, misleading_targets)
start += batch_size
if start > len(x_train) - batch_size:
start = 0
# Occasionally save / plot
if step % 100 == 0:
# Save model weights(保存模型權重)
gan.save_weights('gan.h5')
# Print metrics(將指標打印出來)
print('discriminator loss at step %s: %s' % (step, d_loss))
print('adversarial loss at step %s: %s' % (step, a_loss))
# Save one generated image(保存一張生成圖像)
img = image.array_to_img(generated_images[0] * 255., scale=False)
img.save(os.path.join(save_dir, 'generated_frog' + str(step) + '.png'))
# Save one real image, for comparison(保存一張真實圖像,用于對比)
img = image.array_to_img(real_images[0] * 255., scale=False)
img.save(os.path.join(save_dir, 'real_frog' + str(step) + '.png'))
下面展示生成的圖像
import matplotlib.pyplot as plt
# sample random points in the latent space(在潛在空間中采樣隨機點)
random_latent_vectors = np.random.normal(size=(10, latent_dim))
# Decode them to fake images(將這些點解碼為虛假圖像)
generated_images = generator.predict(random_latent_vectors)
for i in range(generated_images.shape[0]):
img = image.array_to_img(generated_images[i] * 255., scale=False)
plt.figure()
plt.imshow(img)
plt.show()
Keras 機器學習 深度學習
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。