GAN 生成對抗網路-使用捲積神經網路(DCGAN)
使用 GAN 生成動漫美少女圖片的實驗與改進
GAN 生成對抗網路-使用捲積神經網路(DCGAN)
使用 GAN 生成圖片的實驗與改進
在先前的實作中,我們使用了 GAN(生成對抗網路) 來生成動漫美少女圖片。當時的 生成器(Generator) 和 鑑別器(Discriminator) 都僅使用了全連接層(Fully Connected Layers)。然而,實驗結果顯示,單純依賴全連接層,想讓 GAN 生成高品質的圖像需要極長的訓練週期(epochs),甚至會遇到 梯度消失 的問題,導致模型難以收斂,影響訓練品質。
引入 CNN 提升 GAN 生成效果
在影像處理領域,卷積神經網路(CNN, Convolutional Neural Network) 以其卓越的表現廣泛應用於圖像辨識,因此我們考慮將 CNN 加入 GAN 的生成器與鑑別器,以提升圖像生成品質並降低訓練難度。
CNN 在 GAN 中的應用可分為兩個部分:
1. 鑑別器(Discriminator)引入 CNN
鑑別器使用 CNN 來判別圖像真偽,其原理與傳統 CNN 用於影像辨識的方式相同。透過 堆疊卷積層與過濾器(Filters),可以大幅提升鑑別器對圖像的判別能力,使其能夠更準確地區分真實與生成的圖片。
2. 生成器(Generator)引入 CNN:逆卷積(Transpose Convolution)
相較於傳統 CNN 是將輸入圖像透過卷積層拆解並提取特徵,生成器的目標則是從隨機噪聲中構建影像,這與普通 CNN 的作用相反。因此,我們需要使用 逆卷積(Transpose Convolution),又稱為轉置卷積,來逐步恢復影像。
逆卷積(Transpose Convolution)原理
逆卷積的概念可以視為 CNN 的 反向運算。在標準卷積中,影像會根據捲積核(Kernel)大小進行降維與特徵提取,而在逆卷積中,則是透過 反向擴展(Upsampling) 來重建影像。每經過一層逆卷積,影像的解析度會根據步長(Stride)與過濾器數量(Filters)發生變化。
舉例來說,假設輸入張量的形狀為 (7,7,128)(寬×高×通道數),經過不同參數的逆卷積層後的變化如下:
- 步長(Stride)= 1,過濾器(Filter)= 64 輸出張量形狀:(7,7,64)
- 步長(Stride)= 2,過濾器(Filter)= 64 輸出張量形狀:(14,14,64)
以 MNIST 手寫數字資料集為例
在 MNIST(圖像尺寸 28×28×1)的生成過程中,我們可以透過多層逆卷積來逐步擴展張量,直到達到最終的影像尺寸:
- 輸入張量 (7,7,128)
- Stride = 2, Filter = 64 → (14,14,64)
- Stride = 1, Filter = 32 → (14,14,32)
- Stride = 2, Filter = 8 → (28,28,8)
- Stride = 1, Filter = 1 → (28,28,1)(最終輸出影像)

透過這種方式,生成器能夠逐步將低維度的潛在空間轉換為完整的圖像,並使輸出結果與原始影像形狀一致。
全連接層 GAN vs. 使用 CNN 的 DCGAN
為了驗證 CNN 在 GAN 中的效果,我們將比較以下兩種架構:
- 傳統 GAN(僅使用全連接層)
- DCGAN(深度卷積生成對抗網路)(引入 CNN)
透過這項實驗,我們可以觀察 CNN 是否能有效提升生成影像的品質,並減少訓練難度。
首先準備mnist資料集
import matplotlib.pyplot as plt
import numpy as np
from keras.datasets import mnist
(x_train, _), (_, _) = mnist.load_data()
print("original x_train:", x_train.shape)
img_row = x_train.shape[1]
img_col = x_train.shape[2]
channel = 1
x_train = x_train.reshape(x_train.shape[0], img_row, img_col, channel)/127.5 - 1.0
img_shape = x_train[0].shape
print("reshape:",x_train.shape)
print("img_shape:",img_shape)
print()
# print("x_train[0]:")
# print(x_train[0])
plt.imshow(x_train[0, :, :, 0], cmap='gray')
plt.show()

接下來分別製作兩種生成器(全連接層生成器 & 捲積層生成器)
from keras.models import Sequential
from keras.layers import Dense, LeakyReLU, Reshape, Input, BatchNormalization, Activation
from keras.layers import Conv2D, Conv2DTranspose
z_dim = 100 # random signal vector
# 捲積生成器
def build_generator_cnn(img_shape, z_dim):
model = Sequential()
model.add(Input(shape=(z_dim,)))
model.add(Dense(128 * 7 * 7))
model.add(LeakyReLU(alpha=0.2))
model.add(BatchNormalization())
model.add(Reshape((7, 7, 128)))
model.add(Conv2DTranspose(128, kernel_size=3, strides=2, padding='same')) # (14,14,128)
model.add(LeakyReLU(alpha=0.2))
model.add(BatchNormalization())
model.add(Conv2DTranspose(64, kernel_size=3, strides=1, padding='same')) # (14,14,64)
model.add(LeakyReLU(alpha=0.2))
model.add(BatchNormalization())
model.add(Conv2DTranspose(32, kernel_size=3, strides=2, padding='same')) # (28,28,32)
model.add(LeakyReLU(alpha=0.2))
model.add(BatchNormalization())
model.add(Conv2DTranspose(16, kernel_size=3, strides=1, padding='same')) # (28,28,16)
model.add(LeakyReLU(alpha=0.2))
model.add(BatchNormalization())
model.add(Conv2DTranspose(1, kernel_size=3, strides=1, padding='same')) # (28,28,1)
model.add(Activation('tanh'))
return model
# 全連接層生成器
def build_generator(img_shape, z_dim):
model = Sequential()
model.add(Input(shape=(z_dim,)))
model.add(Dense(128))
model.add(LeakyReLU(alpha=0.2))
model.add(BatchNormalization(momentum=0.8))
model.add(Dense(256))
model.add(LeakyReLU(alpha=0.2))
model.add(BatchNormalization(momentum=0.8))
model.add(Dense(512))
model.add(LeakyReLU(alpha=0.2))
model.add(BatchNormalization(momentum=0.8))
model.add(Dense(28*28, activation='tanh'))
model.add(Reshape(img_shape))
return model
同上製作兩種鑑別器(全連接層鑑別器 & 捲積層鑑別器)
from keras.models import Sequential
from keras.layers import Dense, LeakyReLU, Flatten, Input, BatchNormalization, LeakyReLU, Activation
from keras.layers import Conv2D
#捲積層鑑別器
def build_discriminator_cnn(img_shape):
model = Sequential()
model.add(Input(shape=img_shape))
model.add(Conv2D(64, kernel_size=3, strides=(2,2), padding='same'))
model.add(LeakyReLU(alpha=0.2))
model.add(Conv2D(128, kernel_size=3, strides=(2,2), padding='same'))
model.add(LeakyReLU(alpha=0.2))
model.add(Conv2D(256, kernel_size=3, strides=(2,2), padding='same'))
model.add(LeakyReLU(alpha=0.2))
model.add(Flatten())
model.add(Dense(64, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
return model
#全連接層鑑別器
def build_discriminator(img_shape):
model = Sequential()
model.add(Input(shape=img_shape))
model.add(Flatten())
model.add(Dense(512))
model.add(LeakyReLU(alpha=0.2))
model.add(Dense(256))
model.add(LeakyReLU(alpha=0.2))
model.add(Dense(1, activation='sigmoid'))
return model
建立GAN模型
from keras.optimizers import Adam,SGD
def build_gan(generator, discriminator):
model = Sequential()
model.add(generator)
model.add(discriminator)
return model
optimizer_d = Adam(0.0002, 0.5)
optimizer_g = Adam(0.0002, 0.5)
#鑑別器
discriminator = build_discriminator_cnn(img_shape) #可以選擇全連接或是CNN鑑別器
discriminator.compile(loss='binary_crossentropy', optimizer=optimizer_d, metrics=['acc'])
#GAN生成器
generator = build_generator_cnn(img_shape, z_dim) #可以選擇全連接或是CNN生成器
gan = build_gan(generator, discriminator)
gan.compile(loss='binary_crossentropy', optimizer=optimizer_g)
完成模型建立後,接著是定義訓練方法,記得訓練鑑別器的時候,要設定鑑別器權重參數可以更新
discriminator.trainable = True
當訓練生成器時(GAN),要鎖住鑑別器權重參數
discriminator.trainable = False
import numpy as np
loss = []
accuracy_list = []
#產生影像
def sample_images(generator, image_row=4, image_col=4):
z = np.random.normal(0,1, (image_row*image_col, z_dim))
gen_imgs = generator.predict(z)
fig, axs = plt.subplots(image_row, image_col, figsize=(4,4), sharey=True, sharex=True)
cnt = 0
for i in range(image_row):
for j in range(image_col):
axs[i,j].imshow(gen_imgs[cnt, :,:,0], cmap='gray')
axs[i,j].axis('off')
cnt +=1
plt.show()
#定義訓練方法
def train(epochs, batch_size):
batch_count = x_train.shape[0] // batch_size
real_label = np.ones((batch_size, 1))
fake_label = np.zeros((batch_size, 1))
for epoch in range(epochs):
# print("Epoch: {}".format(epoch + 1))
for iteration in range(batch_count):
idx = np.random.randint(0, x_train.shape[0], batch_size)
imgs = x_train[idx] # 隨機挑選batch_size圖片
z = np.random.normal(0,1, (batch_size, z_dim))
gen_imgs = generator.predict(z)
discriminator.trainable = True #解開鑑別器參數
d_loss_real = discriminator.train_on_batch(imgs, real_label,) # 對真的圖片判定為真的訓練
d_loss_fake = discriminator.train_on_batch(gen_imgs, fake_label) # 對假的圖片判定為假的訓練
d_loss, accuracy = np.add(d_loss_fake,d_loss_real)*0.5
z = np.random.normal(0,1, (batch_size, z_dim))
discriminator.trainable = False #鎖住鑑別器參數
g_loss = gan.train_on_batch(z, real_label) # gan模型中,鑑別器用real_label去判定生成器的圖片真或假
print("\r epoch:%d [D loss: %f, acc.:%.2f] [G loss: %f]" % (epoch+1, d_loss, 100.0*accuracy, g_loss),end="", flush=True)
loss.append([d_loss, g_loss])
accuracy_list.append(100.0*accuracy)
sample_images(generator)
以下是實驗的結果

左邊是全連接層GAN,右邊是捲積層DCGAN在訓練epoch=1時產生的圖片
在 epoch = 1 時,普通 GAN(全連接層)與 DCGAN(捲積層)在生成圖像上呈現出明顯差異:
- 普通 GAN 生成的圖像雜訊較多,雖然可以觀察到部分圖像聚集的趨勢,但整體畫面模糊且呈現擴散狀態,無法清晰識別特徵。
- DCGAN 則能展現出 反捲積(Transpose Convolution) 生成圖像的優勢。透過反捲積過濾器聚焦於影像的局部特徵,分解並重建影像,使得即使在第一個訓練週期(epoch)時,生成的圖像背景已無明顯雜訊,且影像邊緣較為清晰,不會出現模糊擴散的現象。
這說明 DCGAN 能夠更有效地捕捉圖像結構,使得早期訓練階段的影像品質就優於普通 GAN。

左邊是全連接層GAN,右邊是捲積層DCGAN在訓練epoch=10時產生的圖片
當訓練進行到 epoch = 10 時,比較普通 GAN(全連接層)與 DCGAN(捲積層)的生成圖像品質,可以發現兩者皆已能產生可辨識的手寫數字,但影像品質存在明顯差異:
- 普通 GAN:生成的影像邊緣模糊,圖像輪廓擴散,背景仍殘留較多雜訊,且字體結構不夠穩定,呈現出斷裂或不連續的現象。
- DCGAN:生成的影像邊緣更加銳利,背景乾淨無雜訊,字體結構完整且清晰,能夠更準確地還原手寫數字的形狀。
由此可見,引入捲積層的 DCGAN 在影像生成方面表現更佳,能有效提升圖像品質並減少雜訊影響。
總結來說,DCGAN 相較於普通 GAN,在影像生成的品質上有顯著提升。普通 GAN 生成的圖像容易出現模糊、邊緣擴散及背景雜訊,而 DCGAN 透過捲積層的特性,能夠更準確地重建圖像,使生成的數字邊緣銳利、背景乾淨且字體清晰。這表明引入捲積層能有效改善 GAN 的訓練效果,提升影像的可辨識度與真實感。
메타데이터
- post_id
- 3f9dbe3a908c
- slug
- gan-生成對抗網路-使用捲積神經網路-dcgan-3f9dbe3a908c
- url
- https://medium.com/@wkol1126/gan-%E7%94%9F%E6%88%90%E5%B0%8D%E6%8A%97%E7%B6%B2%E8%B7%AF-%E4%BD%BF%E7%94%A8%E6%8D%B2%E7%A9%8D%E7%A5%9E%E7%B6%93%E7%B6%B2%E8%B7%AF-dcgan-3f9dbe3a908c
- canonical_url
- https://medium.com/@wkol1126/gan-%E7%94%9F%E6%88%90%E5%B0%8D%E6%8A%97%E7%B6%B2%E8%B7%AF-%E4%BD%BF%E7%94%A8%E6%8D%B2%E7%A9%8D%E7%A5%9E%E7%B6%93%E7%B6%B2%E8%B7%AF-dcgan-3f9dbe3a908c
- author_url
- https://medium.com/@wkol1126
- status
- ok
- fetched_at
- 2026-06-26 03:39:16