深度學習中的生成模型技術近年來蓬勃發展,其中生成對抗網路(GAN)更是一大亮點。然而,傳統 GAN 無法控制生成影像的內容,這限制了其應用範圍。條件式生成對抗網路(cGAN)的出現解決了這個問題,它允許我們根據指定的條件,例如類別標籤,來生成影像。cGAN 的核心概念是在 GAN 的基礎上加入條件資訊,讓生成器和判別器都能根據條件進行學習。本文將進一步探討 cGAN 的原理、架構,並使用 Keras 框架實作一個 cGAN 模型,用於生成 MNIST 手寫數字影像。實作過程中,我們會將標籤資訊嵌入到生成器和判別器的輸入中,使模型能夠學習不同標籤對應的影像分佈,進而生成符合指定條件的影像。
條件式生成對抗網路(Conditional GANs)
在前面的章節中,我們討論了基本的生成對抗網路(GAN),它以完全無監督的方式在影像上進行訓練,以學習如何生成影像。潛在表示,如隨機噪聲向量,用於探索和取樣學習到的影像空間。一個簡單的增強方法是向輸入新增外部標籤。
無條件GAN的限制
考慮MNIST資料集,它由0到9的手寫數字組成。通常,GAN只是學習數字的分佈,當給生成器隨機噪聲向量時,它會生成不同的數字,如圖12-26所示。然而,無法控制生成的數字。
條件式GAN的工作原理
在訓練過程中,像MNIST這樣的資料集,我們可能知道每個影像的實際標籤或類別標籤。這些額外的資訊可以作為特徵包含在我們的GAN訓練中,然後在推理時使用。透過條件式GAN(cGANs),影像生成可以根據標籤進行條件化,因此我們能夠專注於特定數字的分佈。然後,在推理時,我們可以透過傳遞所需的標籤來建立特定數字的影像,而不是接收隨機數字,如圖12-27所示。
cGAN生成器的實作
我們需要對之前的普通GAN生成器程式碼進行一些更改,以便納入標籤。基本上,我們將把我們的潛在向量與標籤的向量表示進行拼接,如下面的程式碼所示:
# 建立生成器
def create_label_vectors(labels, num_classes, embedding_dim, dense_units):
embedded_labels = tf.keras.layers.Embedding(
input_dim=num_classes, output_dim=embedding_dim
)(labels)
label_vectors = tf.keras.layers.Dense(
units=dense_units
)(embedded_labels)
return label_vectors
內容解密:
create_label_vectors函式的作用是將整數標籤轉換為密集表示。- 使用
tf.keras.layers.Embedding層將整數標籤嵌入到一個密集向量空間中。 input_dim引數指定了輸入標籤的數量,output_dim引數指定了嵌入向量的維度。- 使用
tf.keras.layers.Dense層進一步混合嵌入向量的元件。
def standard_vanilla_generator(inputs, output_shape):
x = tf.keras.layers.Dense(units=64)(inputs)
x = tf.keras.layers.LeakyReLU(alpha=0.2)(x)
x = tf.keras.layers.Dense(units=128)(x)
x = tf.keras.layers.LeakyReLU(alpha=0.2)(x)
x = tf.keras.layers.Dense(units=256)(x)
x = tf.keras.layers.LeakyReLU(alpha=0.2)(x)
x = tf.keras.layers.Dense(
units=output_shape[0] * output_shape[1] * output_shape[2],
activation="tanh"
)(x)
outputs = tf.keras.layers.Reshape(target_shape=output_shape)(x)
return outputs
內容解密:
standard_vanilla_generator函式定義了一個標準的生成器網路。- 使用多個
tf.keras.layers.Dense層和tf.keras.layers.LeakyReLU啟用函式來轉換輸入。 - 最後一層使用
tanh啟用函式來輸出影像。 - 使用
tf.keras.layers.Reshape層將輸出重塑為所需的形狀。
def create_vanilla_generator(latent_dim, num_classes, output_shape):
latent_vector = tf.keras.Input(shape=(latent_dim,))
labels = tf.keras.Input(shape=())
label_vectors = create_label_vectors(
labels, num_classes, embedding_dim=50, dense_units=50
)
concatenated_inputs = tf.keras.layers.Concatenate(
axis=-1
)([latent_vector, label_vectors])
outputs = standard_vanilla_generator(
concatenated_inputs, output_shape=output_shape
)
return tf.keras.Model(
inputs=[latent_vector, labels],
outputs=outputs,
name="vanilla_generator"
)
內容解密:
create_vanilla_generator函式建立了一個普通的cGAN生成器。- 使用Keras Functional API來定義具有多個輸入的生成器模型。
- 第一個輸入是標準的潛在向量,第二個輸入是整數標籤。
- 將標籤向量與潛在向量拼接後傳遞給標準生成器網路。
條件式生成對抗網路(cGAN)的原理與實作
條件式生成對抗網路(Conditional Generative Adversarial Networks, cGAN)是一種特殊的生成對抗網路,它能夠根據特定的條件生成影像。這種條件可以是類別標籤、文字描述或其他型別的輸入。在本篇文章中,我們將探討cGAN的原理、架構以及如何使用Keras實作cGAN來生成手寫數字影像。
cGAN的原理
cGAN的核心思想是在生成對抗網路(GAN)的基礎上加入條件資訊,使得生成器和判別器都能夠根據這個條件進行學習和生成。在傳統的GAN中,生成器從一個隨機的隱向量(latent vector)中生成影像,而在cGAN中,這個隱向量會與條件向量(condition vector)結合,共同決定生成的影像。
cGAN生成器的實作
首先,我們來看看cGAN生成器的實作。生成器接收兩個輸入:一個是隨機的隱向量,另一個是類別標籤。類別標籤首先會被嵌入到一個高維空間中,然後透過一個全連線層(Dense layer)轉換成一個稠密向量。這個稠密向量會與隱向量拼接(concatenate)在一起,形成一個新的向量,這個新的向量包含了類別資訊和隨機隱向量。這個新的向量會被送入生成器的後續網路中,生成最終的影像。
def create_generator(latent_dim, num_classes):
# 定義輸入
latent_inputs = tf.keras.Input(shape=(latent_dim,))
label_inputs = tf.keras.Input(shape=(), dtype='int32')
# 將標籤嵌入到高維空間
embedded_labels = tf.keras.layers.Embedding(input_dim=num_classes, output_dim=50)(label_inputs)
dense_labels = tf.keras.layers.Dense(units=latent_dim)(embedded_labels)
# 拼接隱向量和標籤向量
concatenated_inputs = tf.keras.layers.Concatenate()([latent_inputs, dense_labels])
# 生成影像
x = tf.keras.layers.Dense(units=7*7*128)(concatenated_inputs)
x = tf.keras.layers.Reshape(target_shape=(7, 7, 128))(x)
x = tf.keras.layers.BatchNormalization()(x)
x = tf.keras.layers.LeakyReLU(alpha=0.2)(x)
# ... 其他層 ...
outputs = tf.keras.layers.Conv2DTranspose(filters=1, kernel_size=5, strides=2, padding='same', activation='tanh')(x)
return tf.keras.Model(inputs=[latent_inputs, label_inputs], outputs=outputs)
#### 內容解密:
- 輸入定義:定義了兩個輸入,一個是隨機的隱向量
latent_inputs,另一個是類別標籤label_inputs。 - 標籤嵌入:使用
Embedding層將類別標籤嵌入到高維空間中,然後透過Dense層轉換成稠密向量。 - 向量拼接:將隱向量和標籤稠密向量拼接在一起,形成新的輸入向量。
- 影像生成:透過一系列的轉置卷積層(Conv2DTranspose)、批次正規化(BatchNormalization)和啟用函式(LeakyReLU)生成影像。
cGAN判別器的實作
cGAN的判別器同樣接收兩個輸入:一個是影像,另一個是類別標籤。判別器首先將類別標籤轉換成一個與影像大小相同的灰階影像,然後將這個灰階影像與原始影像拼接在一起,形成一個新的輸入。這個新的輸入會被送入判別器的後續網路中,用於判斷影像是否為真實影像。
def create_discriminator(image_shape, num_classes):
# 定義輸入
image_inputs = tf.keras.Input(shape=image_shape)
label_inputs = tf.keras.Input(shape=(), dtype='int32')
# 將標籤轉換成灰階影像
label_image = create_label_images(label_inputs, num_classes, embedding_dim=50, image_shape=image_shape)
# 拼接影像和標籤灰階影像
concatenated_inputs = tf.keras.layers.Concatenate(axis=-1)([image_inputs, label_image])
# 判別真偽
x = tf.keras.layers.Flatten()(concatenated_inputs)
x = tf.keras.layers.Dense(units=128)(x)
x = tf.keras.layers.LeakyReLU(alpha=0.2)(x)
# ... 其他層 ...
outputs = tf.keras.layers.Dense(units=1, activation='sigmoid')(x)
return tf.keras.Model(inputs=[image_inputs, label_inputs], outputs=outputs)
def create_label_images(labels, num_classes, embedding_dim, image_shape):
embedded_labels = tf.keras.layers.Embedding(input_dim=num_classes, output_dim=embedding_dim)(labels)
num_pixels = image_shape[0] * image_shape[1]
dense_labels = tf.keras.layers.Dense(units=num_pixels)(embedded_labels)
label_image = tf.keras.layers.Reshape(target_shape=(image_shape[0], image_shape[1], 1))(dense_labels)
return label_image
#### 內容解密:
- 輸入定義:定義了兩個輸入,一個是影像
image_inputs,另一個是類別標籤label_inputs。 - 標籤轉換:使用
create_label_images函式將類別標籤轉換成灰階影像。 - 影像拼接:將原始影像和標籤灰階影像拼接在一起。
- 真偽判別:透過一系列的全連線層(Dense)和啟用函式(LeakyReLU)判別影像的真偽。
訓練cGAN
訓練cGAN的過程與訓練傳統GAN類別似,但需要同時提供類別標籤給生成器和判別器。在訓練過程中,生成器學習如何根據類別標籤生成特定的影像,而判別器學習如何根據類別標籤判斷影像的真偽。
結果展示
經過訓練後,cGAN能夠根據指定的類別標籤生成手寫數字影像。例如,當指定類別標籤為0時,生成器會生成類別似於0的手寫數字影像;當指定類別標籤為1時,生成器會生成類別似於1的手寫數字影像,以此類別推。
影像生成技術的進階應用:影像間轉換
生成對抗網路(GANs)是一種強大的資料生成工具,不僅限於影像生成,還能應用於表格、時間序列和音訊資料等。不過,GANs 的訓練過程較為複雜,需要多種技巧來提升其品質和穩定性。在本章中,我們將探討 GANs 的進階應用,特別是在影像間轉換(Image-to-Image Translation)領域的應用。
影像間轉換技術
影像間轉換是指將一張影像從一個來源域轉換到另一個目標域。例如,將馬的影像轉換成斑馬的影像,如圖 12-32 所示。這種技術在許多應用場景中都非常有用,例如將白天的影像轉換成夜晚的影像,或是將夏季的景色轉換成冬季的景色。
使用 CycleGAN 進行未配對影像的轉換
在大多數情況下,我們很難找到配對的影像(例如相同的場景在冬季和夏季的影像)。此時,我們可以使用 CycleGAN 這種模型架構來進行未配對影像的轉換。CycleGAN 包含兩個生成器和兩個判別器,它們互相迴圈,如圖 12-33 所示。
以馬和斑馬的影像轉換為例,生成器 G 將馬的影像(來源域 X)轉換成斑馬的影像(目標域 Y),而生成器 F 則將斑馬的影像轉換回馬的影像。判別器 D_X 和 D_Y 分別評估生成器 F 和 G 的輸出,確保它們足夠真實。
CycleGAN 的訓練過程
- 前向迴圈一致性損失:將馬的影像轉換成斑馬的影像,然後再轉換回馬的影像,並與原始的馬的影像進行比較。
- 後向迴圈一致性損失:將斑馬的影像轉換成馬的影像,然後再轉換回斑馬的影像,並與原始的斑馬的影像進行比較。
透過這種迴圈一致性損失,CycleGAN 能夠在未配對的影像上實作出色的轉換效果,如圖 12-34 所示。
使用 Pix2Pix 進行配對影像的轉換
如果我們擁有配對的影像,那麼就可以利用監督學習來實作更好的影像間轉換效果。例如,將城市的地圖檢視轉換成衛星檢視,如圖 12-35 所示。Pix2Pix 是一種使用配對影像進行訓練的模型架構,它包含一個 U-Net 生成器和一個 PatchGAN 判別器。
Pix2Pix 的訓練過程
- 生成器:使用 U-Net 將來源影像轉換成目標影像,並與真實的目標影像進行比較,使用 L1 損失函式。
- 判別器:使用 PatchGAN 對生成器輸出的區域性區域進行真實性評估。
透過這種方式,Pix2Pix 能夠實作高品質的影像間轉換,如地圖檢視到衛星檢視的轉換。
影像生成技術
隨著 GANs 和其他深度學習技術的發展,影像生成和轉換技術正變得越來越成熟。這些技術不僅能夠生成逼真的影像,還能夠實作複雜的影像間轉換任務,為許多應用領域帶來了新的可能性。
程式碼實作範例
以下是一個簡化的 CycleGAN 生成器的 PyTorch 程式碼範例:
import torch
import torch.nn as nn
class ResNetBlock(nn.Module):
def __init__(self, dim):
super(ResNetBlock, self).__init__()
self.conv_block = nn.Sequential(
nn.ReflectionPad2d(1),
nn.Conv2d(dim, dim, kernel_size=3),
nn.InstanceNorm2d(dim),
nn.ReLU(),
nn.ReflectionPad2d(1),
nn.Conv2d(dim, dim, kernel_size=3),
nn.InstanceNorm2d(dim)
)
def forward(self, x):
return x + self.conv_block(x)
class CycleGANGenerator(nn.Module):
def __init__(self, input_nc, output_nc, ngf=64, n_blocks=9):
super(CycleGANGenerator, self).__init__()
model = [nn.ReflectionPad2d(3),
nn.Conv2d(input_nc, ngf, kernel_size=7),
nn.InstanceNorm2d(ngf),
nn.ReLU()]
# 下取樣層
model += [nn.Conv2d(ngf, ngf * 2, kernel_size=3, stride=2, padding=1),
nn.InstanceNorm2d(ngf * 2),
nn.ReLU()]
model += [nn.Conv2d(ngf * 2, ngf * 4, kernel_size=3, stride=2, padding=1),
nn.InstanceNorm2d(ngf * 4),
nn.ReLU()]
# ResNet 層
for _ in range(n_blocks):
model += [ResNetBlock(ngf * 4)]
# 上取樣層
model += [nn.ConvTranspose2d(ngf * 4, ngf * 2, kernel_size=3, stride=2, padding=1, output_padding=1),
nn.InstanceNorm2d(ngf * 2),
nn.ReLU()]
model += [nn.ConvTranspose2d(ngf * 2, ngf, kernel_size=3, stride=2, padding=1, output_padding=1),
nn.InstanceNorm2d(ngf),
nn.ReLU()]
model += [nn.ReflectionPad2d(3),
nn.Conv2d(ngf, output_nc, kernel_size=7),
nn.Tanh()]
self.model = nn.Sequential(*model)
def forward(self, x):
return self.model(x)
#### 內容解密:
1. **ResNetBlock**:這是 CycleGAN 生成器中的基本建構塊,使用反射填充(Reflection Padding)和例項標準化(Instance Normalization)來穩定訓練過程。
2. **CycleGANGenerator**:這是 CycleGAN 的生成器模型,包含下取樣層、ResNet 層和上取樣層。下取樣層用於提取特徵,上取樣層用於重建影像。
3. **前向傳播**:輸入影像透過生成器的各個層,最終輸出轉換後的影像。