Ask me anything
Ithy Logo
Sign in to save

一個基於 Python TensorFlow 的 1D Deep Convolutional GAN 實作

以類別結構實現一維卷積生成對抗網路的完整範例與詳細解析

1d signal waveform

主要重點

  • 使用類別組織網絡架構:將生成器、判別器以及 GAN 模型分別封裝於各自的類別中,實現模組化與易讀性。
  • 利用 1D 卷積與反捲積層:透過 Conv1D、Conv1DTranspose 以及 UpSampling1D 建構深度卷積網路,專門處理一維數據。
  • 定義自訂訓練步驟:使用 tf.GradientTape 計算梯度與更新參數,並示範如何對生成器與判別器進行交替訓練。

實作概述與設計思路

生成對抗網路(GAN)已成為生成模型領域中的一個熱門方法,其中 1D Deep Convolutional GAN 則主要針對一維數據(例如音訊信號、時間序列資料)進行建模。這篇文章將深入介紹如何利用 Python 與 TensorFlow 實現一個 1D Deep Convolutional GAN,並以類別化的方式封裝生成器(Generator)、判別器(Discriminator)以及整體的 GAN 模型。藉由詳細的註解與步驟說明,可以幫助讀者理解如何構建與訓練這個模型,以及如何根據實際應用場景調整網路結構與超參數。


架構組成與關鍵元件

生成器(Generator)

生成器的主要任務是從隨機噪聲空間中生成看似真實的一維數據。在本實作中,生成器會從一個固定維度的潛在向量作為輸入,經由一個全連接層映射到一個初始的特徵張量,隨後透過數層 1D 反卷積(Conv1DTranspose)或 UpSampling1D 搭配 Conv1D 層實現上採樣。為確保生成的數據品質,通常會加入批量標準化(BatchNormalization)與 LeakyReLU 激活函數調整非線性特性,最終層使用 tanh 作為激活函數以將數據範圍限制在 [-1, 1]。

生成器設計亮點

  • 類別化封裝,使得模型更易於管理與調試。
  • 使用 Dense與 Reshape 將一維噪聲轉換成合適形狀,再利用卷積轉置進行空間上採樣。
  • 適當添加批量標準化與 LeakyReLU,使模型收斂更平穩。

判別器(Discriminator)

判別器的目標是對輸入數據判斷其真實性。其架構通常包括一系列的 1D 卷積層,其作用在於從數據中提取潛在特徵。經過卷積運算後,透過 LeakyReLU 激活函數增加非線性表達能力,並適量使用 dropout 防止過度擬合,最後用一個全連接層輸出一個標量,並用 sigmoid 激活函數將結果映射到 [0,1] 的範圍內,代表數據的真實概率。

判別器設計亮點

  • 利用 Conv1D 層進行特徵提取,確保對一維數據的有效辨識。
  • 批量標準化與 LeakyReLU 激活函數有助於深度網絡的穩定訓練。
  • 在輸出層中使用 sigmoid 將預測值約束在合理範圍內,以便後續使用交叉熵損失計算。

GAN 總體訓練流程

GAN 模型利用生成器與判別器互相博弈的思想進行訓練。生成器試圖騙過判別器,而判別器則努力區分真實數據與生成數據。透過交替訓練兩個網路,我們可以看到雙方逐步逼近最優解。整體流程包含以下步驟:

  1. 首先使用隨機噪聲向量通過生成器生成假數據。
  2. 然後將真實數據與假數據同時輸入判別器獲得預測結果。
  3. 計算生成器與判別器各自的損失:生成器損失期望產生被認定為真實的數據,判別器損失則需正確區分真與假。
  4. 運用 tf.GradientTape 計算梯度,並使用優化器(如 Adam)更新兩個網絡的參數。

本實作重點在於如何利用類別來簡潔地實現上述步驟,同時展示如何使用 TensorFlow 的動態圖特性(@tf.function 裝飾器)來加速訓練。


完整程式碼實作

以下提供一個完整的 Python 程式碼範例,其中詳細說明了每個步驟與類別的功能。此程式碼使用 TensorFlow 2.x 版本,並透過類別封裝生成器、判別器以及 GAN 模型,方便用戶在真實數據上進行模型訓練、調試及修改。

程式碼詳細解說

# -*- coding: utf-8 -*-
# -------------- 匯入必要的函式庫 --------------
import tensorflow as tf
from tensorflow.keras import layers
import numpy as np
import matplotlib.pyplot as plt

# 設定隨機種子以便重現結果
tf.random.set_seed(42)
np.random.seed(42)

# -------------- 生成器類別 --------------
class Generator(tf.keras.Model):
    def __init__(self, noise_dim=100, output_length=64, initial_filters=128):
        super(Generator, self).__init__()
        self.noise_dim = noise_dim
        self.output_length = output_length

        # 使用一個全連接層將噪聲轉換成初始特徵表示
        self.fc = layers.Dense((output_length // 4) * initial_filters, activation='relu')
        # 將全連接層的結果 reshape 成 (batch_size, steps, features)
        self.reshape_layer = layers.Reshape((output_length // 4, initial_filters))

        # 第一階段上採樣及卷積 (使用 UpSampling1D 來模擬 Conv1DTranspose 效果)
        self.upsample1 = layers.UpSampling1D(size=2)
        self.conv1 = layers.Conv1D(filters=initial_filters // 2, kernel_size=5, padding='same', activation='relu')
        self.bn1 = layers.BatchNormalization()

        # 第二階段上採樣及卷積
        self.upsample2 = layers.UpSampling1D(size=2)
        self.conv2 = layers.Conv1D(filters=initial_filters // 4, kernel_size=5, padding='same', activation='relu')
        self.bn2 = layers.BatchNormalization()

        # 輸出層,使用 tanh 將數據值約束在 -1 到 1 之間
        self.conv_out = layers.Conv1D(filters=1, kernel_size=5, padding='same', activation='tanh')

    def call(self, inputs, training=False):
        """ 將噪聲向量轉換為一維生成數據 """
        x = self.fc(inputs)
        x = self.reshape_layer(x)
        x = self.upsample1(x)
        x = self.conv1(x)
        x = self.bn1(x, training=training)
        x = self.upsample2(x)
        x = self.conv2(x)
        x = self.bn2(x, training=training)
        x = self.conv_out(x)
        return x

# -------------- 判別器類別 --------------
class Discriminator(tf.keras.Model):
    def __init__(self, input_length=64):
        super(Discriminator, self).__init__()
        # 第一卷積層:提取基本特徵
        self.conv1 = layers.Conv1D(filters=32, kernel_size=5, strides=2, padding='same', activation='relu', input_shape=(input_length, 1))
        self.bn1 = layers.BatchNormalization()
        # 第二卷積層:進一步抽取特徵
        self.conv2 = layers.Conv1D(filters=64, kernel_size=5, strides=2, padding='same', activation='relu')
        self.bn2 = layers.BatchNormalization()
        # 將卷積層後的特徵展平,再用全連接層輸出預測結果
        self.flatten = layers.Flatten()
        self.fc = layers.Dense(1, activation='sigmoid')

    def call(self, inputs, training=False):
        """ 判斷數據的真實性,輸出介於 0 和 1 之間的分數 """
        x = self.conv1(inputs)
        x = self.bn1(x, training=training)
        x = self.conv2(x)
        x = self.bn2(x, training=training)
        x = self.flatten(x)
        x = self.fc(x)
        return x

# -------------- GAN 總體類別 --------------
class DCGAN1D:
    def __init__(self, noise_dim=100, output_length=64):
        self.noise_dim = noise_dim
        self.output_length = output_length
        # 初始化生成器與判別器
        self.generator = Generator(noise_dim=noise_dim, output_length=output_length)
        self.discriminator = Discriminator(input_length=output_length)
        # 使用 Binary Crossentropy 作為損失函數
        self.cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=False)
        # 初始化優化器
        self.gen_optimizer = tf.keras.optimizers.Adam(1e-4)
        self.disc_optimizer = tf.keras.optimizers.Adam(1e-4)

    def generator_loss(self, fake_output):
        # 生成器期望生成的假數據能被判別器認為是真實的 (標籤設定為 1)
        return self.cross_entropy(tf.ones_like(fake_output), fake_output)

    def discriminator_loss(self, real_output, fake_output):
        # 判別器的損失為對真實數據與假數據的預測誤差之和
        real_loss = self.cross_entropy(tf.ones_like(real_output), real_output)
        fake_loss = self.cross_entropy(tf.zeros_like(fake_output), fake_output)
        return real_loss + fake_loss

    @tf.function
    def train_step(self, real_data, batch_size):
        # 從 noise 空間產生隨機噪聲
        noise = tf.random.normal([batch_size, self.noise_dim])
        with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
            generated_data = self.generator(noise, training=True)
            # 注意:real_data 須具有形狀 (batch_size, output_length, 1)
            real_output = self.discriminator(real_data, training=True)
            fake_output = self.discriminator(generated_data, training=True)
            gen_loss = self.generator_loss(fake_output)
            disc_loss = self.discriminator_loss(real_output, fake_output)
        # 計算生成器與判別器的梯度
        gradients_of_generator = gen_tape.gradient(gen_loss, self.generator.trainable_variables)
        gradients_of_discriminator = disc_tape.gradient(disc_loss, self.discriminator.trainable_variables)
        # 更新網絡參數
        self.gen_optimizer.apply_gradients(zip(gradients_of_generator, self.generator.trainable_variables))
        self.disc_optimizer.apply_gradients(zip(gradients_of_discriminator, self.discriminator.trainable_variables))
        return gen_loss, disc_loss

    def train(self, dataset, epochs=100, batch_size=32):
        for epoch in range(epochs):
            print(f"Epoch {epoch+1}/{epochs}")
            for batch, real_data in enumerate(dataset):
                gen_loss, disc_loss = self.train_step(real_data, batch_size)
                # 每處理 100 個批次輸出一次狀態
                if batch % 100 == 0:
                    print(f" Batch {batch}: Generator Loss: {gen_loss:.4f}, Discriminator Loss: {disc_loss:.4f}")
  # 測試生成效果,利用固定 noise 來觀察生成數據變化
    def generate_and_plot(self, num_samples=1):
        noise = tf.random.normal([num_samples, self.noise_dim])
        generated_samples = self.generator(noise, training=False)
        generated_samples = generated_samples.numpy().squeeze()
        for i in range(num_samples):
            plt.figure()
            plt.plot(generated_samples[i])
            plt.title("Generated 1D Signal")
            plt.xlabel("Time Steps")
            plt.ylabel("Amplitude")
            plt.show()

# ------------------------------------
# 模擬一個真實數據集 (例如: 正弦波加上一些雜訊)
def get_dataset(batch_size=32, sequence_length=64, total_samples=1024):
    x = np.linspace(0, 2 * np.pi, sequence_length)
    data = []
    for _ in range(total_samples):
        # 隨機選擇正弦波的頻率與位移
        freq = np.random.uniform(0.8, 1.2)
        phase = np.random.uniform(0, 2 * np.pi)
        signal = np.sin(freq * x + phase)
        # 增加少量高斯雜訊
        signal += np.random.normal(scale=0.1, size=signal.shape)
        # 擴展維度以符合 (sequence_length, 1)
        signal = np.expand_dims(signal, axis=-1)
        data.append(signal)
    data = np.array(data, dtype='float32')
    dataset = tf.data.Dataset.from_tensor_slices(data)
    return dataset.shuffle(buffer_size=1024).batch(batch_size)

if __name__ == "__main__":
    # 設定模型超參數
    noise_dim = 100
    sequence_length = 64
    batch_size = 32
    epochs = 100

    # 準備數據集
    dataset = get_dataset(batch_size=batch_size, sequence_length=sequence_length, total_samples=2048)

    # 建立與訓練 1D DCGAN 模型
    gan = DCGAN1D(noise_dim=noise_dim, output_length=sequence_length)
    gan.train(dataset, epochs=epochs, batch_size=batch_size)

    # 產生新樣本並顯示圖形
    gan.generate_and_plot(num_samples=3)

程式碼說明

上述程式碼展示了如何分別定義生成器與判別器的類別,以及如何在 DCGAN1D 總體類別中:

  • 利用全連接層與 reshape 將噪聲轉換為初始的特徵張量。
  • 通過兩階段的上採樣與卷積操作,將特徵圖轉換為一維信號。
  • 利用 tf.GradientTape 同時計算生成器及判別器的梯度,並採用 Adam 優化器更新網絡參數。
  • 訓練過程中印出損失函數值,以便隨時監控模型表現。

模型超參數與性能調整

在實際應用中,不同的資料特性與生成任務可能需要調整模型的結構與訓練超參數。下列表格列出一些常見的參數以及其在此實作中的預設值,作為參考:

參數名稱 預設值 說明
noise_dim 100 產生器輸入噪聲向量的維度
output_length 64 產生的 1D 信號長度
batch_size 32 每次訓練迭代所使用的批次大小
epochs 100 整個訓練集重複訓練的次數
學習率 1e-4 Adam 優化器的學習率

根據數據集的複雜程度與目標生成效果,可以調整這些參數以取得最佳效果。當遇到訓練不穩定或收斂過慢問題時,可考慮調整學習率、增減卷積層數或修改激活函數。

擴充與未來應用方向

擴充模型架構

除了本文介紹的基本結構外,您可以根據具體應用需求進行以下擴展:

  • 增加模型深度:通過增加卷積層或升採樣層,提高生成數據的細節呈現,但要注意防止過擬合。
  • 調整激活函數及正則化機制:根據不同數據特性,選擇適合的激活函數(例如 LeakyReLU、ELU 等),並加入 dropout 或 L2 正則化降低過擬合風險。
  • 使用條件 GAN:若黃需求增加數據生成的多樣性,可以在生成器與判別器中加入條件變量,使模型在特定條件下生成相關數據。
  • 採用不同的損失函數:如 Wasserstein GAN 損失函數(WGAN)可用以改進訓練穩定性。

應用場景

此 1D DCGAN 模型在很多一維數據生成的場景中具有廣泛應用。例如:

  • 音訊訊號生成:生成逼真的音訊波形,用於音樂創作或語音合成。
  • 時間序列數據生成:生成金融市場、氣象資料等時間序列數據,以幫助模擬或預測實際情況。
  • 生理訊號模擬:如心電圖(ECG)數據生成,可用於醫學研究與檢測模型的魯棒性。

結論

本文以繁體中文詳細介紹了如何利用 Python 與 TensorFlow 架構一個 1D Deep Convolutional GAN。透過類別封裝生成器、判別器以及整體 GAN 模型的方式,不僅使程式碼結構清晰,也方便進一步修改與調整。從數據預處理、模型建構到訓練流程的每個步驟,都已進行充分說明,而提供的完整程式碼範例亦可直接運用於實際項目中。希望這篇文章能夠幫助你理解 GAN 的基本原理及應用技巧,並為你未來在一維數據生成相關的研究或專案中提供有價值的參考。


參考資料


更多相關查詢


Last updated February 18, 2025

AI-assisted research. Sources, methodology and corrections · Source-checked guides and tools · Explore published answers

Ask Ithy AI