← Back to list

GAN 生成對抗網路-使用捲積神經網路(DCGAN)

使用 GAN 生成動漫美少女圖片的實驗與改進

KL Wu · 2025-04-01 05:04 · 0 claps · 15.1 min read
#genrative-ai #生成式ai #dcgan #cnn #卷積神經網路
Open on Medium ↗

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)的生成過程中,我們可以透過多層逆卷積來逐步擴展張量,直到達到最終的影像尺寸:

  1. 輸入張量 (7,7,128)
  2. Stride = 2, Filter = 64(14,14,64)
  3. Stride = 1, Filter = 32(14,14,32)
  4. Stride = 2, Filter = 8(28,28,8)
  5. Stride = 1, Filter = 1(28,28,1)(最終輸出影像)

透過這種方式,生成器能夠逐步將低維度的潛在空間轉換為完整的圖像,並使輸出結果與原始影像形狀一致。

全連接層 GAN vs. 使用 CNN 的 DCGAN

為了驗證 CNN 在 GAN 中的效果,我們將比較以下兩種架構:

  1. 傳統 GAN(僅使用全連接層)
  2. 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時產生的圖片

左邊是全連接層GAN,右邊是捲積層DCGAN在訓練epoch=1時產生的圖片

epoch = 1 時,普通 GAN(全連接層)與 DCGAN(捲積層)在生成圖像上呈現出明顯差異:

  • 普通 GAN 生成的圖像雜訊較多,雖然可以觀察到部分圖像聚集的趨勢,但整體畫面模糊且呈現擴散狀態,無法清晰識別特徵。
  • DCGAN 則能展現出 反捲積(Transpose Convolution) 生成圖像的優勢。透過反捲積過濾器聚焦於影像的局部特徵,分解並重建影像,使得即使在第一個訓練週期(epoch)時,生成的圖像背景已無明顯雜訊,且影像邊緣較為清晰,不會出現模糊擴散的現象。

這說明 DCGAN 能夠更有效地捕捉圖像結構,使得早期訓練階段的影像品質就優於普通 GAN。

左邊是全連接層GAN,右邊是捲積層DCGAN在訓練epoch=10時產生的圖片

左邊是全連接層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