近年來,隨著深度學習技術的蓬勃發展,根據 cGAN 的人臉影像生成技術已成為熱門研究方向。本文將詳細介紹如何利用 IMDB-WIKI 人臉影像資料集,建構一個 cGAN 模型來生成人臉影像,並探討如何進行人臉年齡預測。首先,我們會介紹 IMDB-WIKI 資料集的特性與資料結構,包含年齡、性別等標籤資訊。接著,我們將說明如何預處理原始資料,包含資料清理、轉換以及特徵工程等步驟,為後續模型訓練做好準備。然後,我們會逐步講解 cGAN 模型的架構設計,包含生成器和鑑別器的建構細節,以及如何將年齡標籤融入模型訓練中。最後,我們會探討如何訓練 cGAN 模型,並評估生成影像的品質和年齡預測的準確性。

人工智慧生成人臉影像

要開發根據條件對抗生成網路(cGAN)的模型以生成人作業員臉影像,我們將使用IMDB-WIKI人臉影像資料集。這個資料集由ETH Zurich提供,是電腦視覺領域中一個全面且廣泛使用的資料集。它包含大量的人臉影像及其對應的年齡和性別標籤,使其成為年齡估計、性別分類別和人臉識別等任務的寶貴資源。

資料集介紹

IMDB-WIKI人臉影像資料集結合了兩個現有的資料集:IMDb資料集和維基百科資料集,建立了一個多樣化和廣泛的人臉影像集合。資料集的首頁提供了從維基百科中提取的人臉影像,這些影像的大小約為1 GB。該網站還提供了一個Matlab(.mat)檔案,包含所有影像的元訊息,包括:

  • 出生日期(dob)
  • 照片拍攝年份(photo_taken)
  • 影像檔案路徑(full_path)
  • 性別(gender):0代表女性,1代表男性,未知則為NaN
  • 姓名(name)
  • 人臉位置(face_location)
  • 人臉檢測器評分(face_score):評分越高,表明人臉檢測的可靠性越高。Inf表示影像中未找到人臉
  • 第二個人臉的檢測器評分(second_face_score):這對於忽略影像中有多張人臉的情況很有用。如果未檢測到第二張人臉,則為NaN

資料集特點

該資料集涵蓋了從新生兒到老年人的廣泛年齡範圍,並包含不同民族、性別和背景的人。影像來源於公開的IMDb和維基百科頁面,提供了不同領域中人臉的豐富和多樣化的代表。在這個資料集中,每張人臉影像都與一個年齡標籤相關聯,代表影像拍攝時個人的大致年齡。除了年齡標籤外,資料集還包括每張人臉影像的性別標籤,方便進行性別分類別任務並允許探索人臉影像中的性別相關特徵。

預處理元資料

要開始開發人臉生成應用,我們需要匯入必要的函式庫,如下面的程式碼所示:

import numpy as np
import pandas as pd

圖表翻譯:

  flowchart TD
    A[匯入函式庫] --> B[載入資料]
    B --> C[預處理資料]
    C --> D[訓練模型]
    D --> E[生成人臉]

內容解密:

匯入必要的函式庫是開始任何機器學習任務的第一步。在這裡,我們匯入NumPy和Pandas,它們分別用於數值計算和資料操作。接下來,我們將載入IMDB-WIKI資料集,對其進行預處理,以便於模型訓練。預處理步驟包括清理資料、轉換資料格式以及可能的特徵工程,以確保資料適合模型訓練。然後,我們將使用預處理後的資料訓練條件對抗生成網路(cGAN)模型,以生成真實的人臉影像。

人臉年齡預測資料集介紹

在進行人臉年齡預測任務時,選擇合適的資料集是非常重要的。其中,IMDB-WIKI face data是一個常用的資料集,包含大量的人臉圖片及其對應的年齡訊息。這個資料集可以從其官網下載,下載後需要進行解壓縮和資料預處理。

下載與解壓縮資料集

首先,需要下載WIKI faces-only dataset。假設下載的檔案名稱為wiki_crop.tar,可以使用以下命令進行解壓縮:

!tar -xf wiki_crop.tar

解壓縮後,會出現一個名為wiki_crop的目錄,內含有大量的人臉圖片及其對應的metadata。

載入metadata

metadata是儲存於一個名為wiki.mat的Matlab檔案中,可以使用loadmat()函式從中載入資料。以下是載入metadata的示例程式碼:

import scipy.io as sio

metadata = sio.loadmat('wiki_crop/wiki.mat')

載入metadata後,可以存取其中的資料,例如生日(“dob”)、拍照日期(“photo_taken”)、圖片路徑(“full_path”)、姓名(“name”)、臉部評分(“face_score”)和第二臉部評分(“second_face_score”)。

建立DataFrame

可以使用上述資料建立一個pandas DataFrame,以便於後續的資料分析和處理。以下是建立DataFrame的示例程式碼:

import pandas as pd

df = pd.DataFrame(columns=["dob", "photo_taken", "full_path", "name", "face_score", "second_face_score"])

df["dob"] = metadata["wiki"][0, 0]["dob"][0]
df["photo_taken"] = metadata["wiki"][0, 0]["photo_taken"][0]
df["full_path"] = ["wiki_crop/"+i[0] for i in metadata["wiki"][0, 0]["full_path"][0]]

這樣就完成了資料集的下載、解壓縮、metadata的載入和DataFrame的建立,為後續的人臉年齡預測任務做好了準備。

圖表翻譯:

  graph LR
    A[下載WIKI faces-only dataset] --> B[解壓縮wiki_crop.tar]
    B --> C[載入metadata]
    C --> D[建立DataFrame]
    D --> E[進行人臉年齡預測]

此圖表展示了人臉年齡預測資料集的處理流程,從下載資料集開始,到建立DataFrame為止,每一步驟都清晰地展示出來。

處理圖片中的人臉資料

為了處理圖片中的人臉資料,我們需要進行一系列的步驟。首先,我們需要從元資料中提取相關訊息,例如人臉得分(face_score)和第二個人臉得分(second_face_score)。這些訊息可以使用以下程式碼提取:

df["name"] = metadata["wiki"][0, 0]["name"][0]
df["face_score"] = metadata["wiki"][0, 0]["face_score"][0]
df["second_face_score"] = metadata["wiki"][0, 0]["second_face_score"][0]

接下來,我們需要篩選出只有一個人臉的圖片,也就是第二個人臉得分為NaN的圖片。這可以使用以下程式碼實作:

df = df[df["second_face_score"].isna()]

然後,我們需要移除沒有偵測到人臉的圖片,也就是人臉得分為負無窮大的圖片。這可以使用以下程式碼實作:

df = df[df["face_score"] > min(df["face_score"])]

現在,我們需要計算圖片中的人臉年齡。為了實作這一點,我們需要定義一個函式來計算年齡差。以下是函式的定義:

from datetime import datetime, timedelta

def get_age(taken, dob):
    birth = datetime.fromordinal(int(dob)) + timedelta(days=dob%1)
    if birth.month < 7:
        return taken - birth.year
    else:
        return taken - birth.year - 1

內容解密:

上述程式碼中,get_age 函式用於計算圖片中的人臉年齡。它接受兩個引數:taken(拍攝日期)和dob(出生日期)。函式首先將出生日期轉換為datetime物件,然後根據出生月份決定年齡。

圖表翻譯:

  flowchart TD
    A[開始] --> B[提取元資料]
    B --> C[篩選單人臉圖片]
    C --> D[移除無人臉圖片]
    D --> E[計算年齡]
    E --> F[輸出結果]

此圖表展示了處理圖片中的人臉資料的流程,從提取元資料到計算年齡。

年齡分組與資料篩選

在進行年齡分組之前,我們需要確保資料的正確性。由於部分 dobphoto_taken 欄位的值可能不正確,導致計算出的年齡值不準確。為了篩選出不正確的年齡值,我們可以設定一個合理的年齡範圍,例如 0 到 100 歲。

df = df[(df['age'] < 100) & (df['age'] > 0)]

這段程式碼會篩選出年齡值在 0 到 100 之間的資料。

接下來,我們可以定義一個函式 get_age_group() 來將年齡值分組。這個函式會根據年齡值傳回一個對應的分組編號。

def get_age_group(age):
    if 0 < age <= 18:
        return 0
    elif 19 <= age <= 29:
        return 1
    elif 30 <= age <= 39:
        return 2
    elif 40 <= age <= 49:
        return 3
    elif 50 <= age <= 59:
        return 4
    elif 60 <= age <= 69:
        return 5
    elif 70 <= age <= 79:
        return 6
    elif 80 <= age <= 89:
        return 7
    elif 90 <= age <= 100:
        return 8
    else:
        return None

這個函式會將年齡值分組為 9 個不同的分組:18 歲以下、19-29 歲、30-39 歲、40-49 歲、50-59 歲、60-69 歲、70-79 歲、80-89 歲和 90-100 歲。

內容解密:

上述程式碼中,我們使用了 apply() 函式來將 get_age_group() 函式應用於資料框架 df 的每一列。這樣可以將年齡值分組並傳回一個新的欄位。

df['age_group'] = df['age'].apply(get_age_group)

這個新的欄位 age_group 會包含根據年齡值分組的結果。

圖表翻譯:

下圖示範了年齡分組的結果:

  flowchart TD
    A[年齡值] --> B[分組函式]
    B --> C[分組結果]
    C --> D[年齡分組欄位]

這個圖表顯示了年齡值如何被分組並傳回一個新的欄位。

年齡分段函式

def age_segment(age):
    """
    根據年齡分段傳回對應的分段編號。
    
    Args:
        age (int): 年齡
    
    Returns:
        int: 年齡分段編號
    """
    if age <= 30:
        return 1
    elif 30 < age <= 39:
        return 2
    elif 40 < age <= 49:
        return 3
    elif 50 < age <= 59:
        return 4
    else:
        return 5

內容解密:

上述程式碼定義了一個名為 age_segment 的函式,該函式根據輸入的年齡傳回對應的年齡分段編號。函式使用 if-elif-else 結構來實作年齡分段的判斷。

圖表翻譯:

  flowchart TD
    A[輸入年齡] --> B{年齡 <= 30}
    B -->|是| C[傳回 1]
    B -->|否| D{30 < 年齡 <= 39}
    D -->|是| E[傳回 2]
    D -->|否| F{40 < 年齡 <= 49}
    F -->|是| G[傳回 3]
    F -->|否| H{50 < 年齡 <= 59}
    H -->|是| I[傳回 4]
    H -->|否| J[傳回 5]

圖表翻譯:

上述 Mermaid 圖表展示了年齡分段函式的邏輯流程。圖表從輸入年齡開始,然後根據年齡分段條件進行判斷,最終傳回對應的分段編號。

建立條件式生成對抗網路(Conditional GAN)

在上述程式碼中,我們將 get_age_group() 函式對映到 DataFrame dfage 欄位,並建立了一個新的欄位 age_group,其中包含了函式傳回的值。這些年齡類別將用作我們的條件式生成對抗網路模型中的目標變數。

資料預處理

為了加速訓練,我們可以進一步減少訓練資料的大小。以下程式碼示範瞭如何篩選 face_score 大於 3.23 的資料列:

df = df[df["face_score"] > 3.23]

這個步驟是可選的,目的是考慮一個較小的資料集以加速訓練。執行這個步驟後,df 中應該剩下 20104 行資料。

建立模型

現在,我們可以開始建立條件式生成對抗網路模型。首先,我們定義一些常數,用於 cGAN 的元件:

BATCH_SIZE = 128
IMAGE_SHAPE = (64, 64, 3)
NUM_OF_CLASSES = 6
LATENT_DIM = 100
EPOCHS = 50

如上所示,第三行定義了類別數為 6,因為我們的目標變數 age_group 中只有六個可能的類別。

判別器(Discriminator)

判別器網路作用為二元分類別器,區分真實和生成的樣本。其主要目的是正確地識別輸入的影像是否為真實或生成的。

判別器架構

判別器的架構通常由多層卷積神經網路(CNN)組成,後接全連線層(Fully Connected Layer)。以下是判別器的基本架構:

def build_discriminator():
    model = tf.keras.models.Sequential([
        tf.keras.layers.Conv2D(64, (5, 5), activation='relu', input_shape=IMAGE_SHAPE),
        tf.keras.layers.MaxPooling2D((2, 2)),
        tf.keras.layers.Conv2D(128, (5, 5), activation='relu'),
        tf.keras.layers.MaxPooling2D((2, 2)),
        tf.keras.layers.Flatten(),
        tf.keras.layers.Dense(128, activation='relu'),
        tf.keras.layers.Dense(1, activation='sigmoid')
    ])
    return model

這個架構包括兩層卷積層,後接兩層全連線層。輸出層使用 sigmoid 啟用函式,以輸出一個機率值,表示輸入影像是否為真實的。

生成器(Generator)

生成器網路的目的是生成新的影像,盡可能地模模擬實影像的風格和特徵。

生成器架構

生成器的架構通常由多層反捲積神經網路(Transposed Convolutional Neural Network)組成,後接全連線層。以下是生成器的基本架構:

def build_generator():
    model = tf.keras.models.Sequential([
        tf.keras.layers.Dense(7*7*128, activation='relu', input_shape=(LATENT_DIM,)),
        tf.keras.layers.Reshape((7, 7, 128)),
        tf.keras.layers.Conv2DTranspose(128, (5, 5), strides=(2, 2), padding='same', activation='relu'),
        tf.keras.layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', activation='relu'),
        tf.keras.layers.Conv2D(3, (5, 5), activation='tanh', padding='same')
    ])
    return model

這個架構包括兩層反捲積層,後接一層全連線層。輸出層使用 tanh 啟用函式,以輸出一個影像張量。

條件式生成對抗網路(Conditional GAN)

條件式生成對抗網路是由判別器和生成器組成的。以下是 cGAN 的基本架構:

def build_cgan():
    discriminator = build_discriminator()
    generator = build_generator()
    cgan = tf.keras.models.Sequential([generator, discriminator])
    return cgan

這個架構包括判別器和生成器兩個部分。輸入的隨機噪聲和類別標籤被傳入生成器,生成器輸出的影像被傳入判別器,判別器輸出的機率值被用於計算損失函式。

訓練模型

訓練模型的目的是最小化損失函式,同時更新判別器和生成器的引數。以下是訓練模型的基本過程:

def train_cgan():
    cgan = build_cgan()
    discriminator = build_discriminator()
    generator = build_generator()
    optimizer = tf.keras.optimizers.Adam(0.001)
    for epoch in range(EPOCHS):
        for batch in range(len(df) // BATCH_SIZE):
            # ...
            # 訓練判別器
            with tf.GradientTape() as tape:
                # ...
                loss_d = tf.reduce_mean(tf.square(discriminator_output - 1))
            gradients_d = tape.gradient(loss_d, discriminator.trainable_variables)
            optimizer.apply_gradients(zip(gradients_d, discriminator.trainable_variables))
            # 訓練生成器
            with tf.GradientTape() as tape:
                # ...
                loss_g = tf.reduce_mean(tf.square(discriminator_output - 0))
            gradients_g = tape.gradient(loss_g, generator.trainable_variables)
            optimizer.apply_gradients(zip(gradients_g, generator.trainable_variables))
    return cgan

這個過程包括訓練判別器和生成器兩個部分。判別器的損失函式是二元交叉熵,生成器的損失函式是二元交叉熵和重建誤差的組合。

圖表翻譯:

此圖示為條件式生成對抗網路的架構,包括判別器和生成器兩個部分。輸入的隨機噪聲和類別標籤被傳入生成器,生成器輸出的影像被傳入判別器,判別器輸出的機率值被用於計算損失函式。

  graph LR
    A[隨機噪聲] -->|傳入|> B[生成器]
    B -->|輸出|> C[影像]
    C -->|傳入|> D[判別器]
    D -->|輸出|> E[機率值]
    E -->|計算|> F[損失函式]

這個圖表展示了條件式生成對抗網路的基本架構和工作流程。

分類別器架構設計

在GAN中,分類別器(Discriminator)是一個至關重要的元件,負責區分真實資料和生成資料。為了設計一個有效的分類別器,我們需要定義其架構。以下是使用Python和TensorFlow實作的分類別器架構程式碼:

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

def create_discriminator():
    # 定義輸入層
    inputs = keras.Input(shape=(28, 28, 1))
    
    # 定義卷積層和池化層
    x = layers.Conv2D(64, (3, 3), activation='relu')(inputs)
    x = layers.MaxPooling2D((2, 2))(x)
    x = layers.Conv2D(128, (3, 3), activation='relu')(x)
    x = layers.MaxPooling2D((2, 2))(x)
    
    # 定義flatten層和密集層
    x = layers.Flatten()(x)
    x = layers.Dense(128, activation='relu')(x)
    
    # 定義輸出層
    outputs = layers.Dense(1, activation='sigmoid')(x)
    
    # 定義模型
    model = keras.Model(inputs=inputs, outputs=outputs)
    
    # 編譯模型
    model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
    
    return model

# 建立分類別器模型
discriminator = create_discriminator()
print(discriminator.summary())

內容解密:

  • create_discriminator()函式定義了分類別器的架構,包括輸入層、卷積層、池化層、flatten層、密集層和輸出層。
  • 輸入層使用keras.Input()定義,輸入形狀為(28, 28, 1),代表MNIST資料集的灰度影像。
  • 卷積層和池化層使用layers.Conv2D()layers.MaxPooling2D()定義,分別實作特徵提取和下取樣。
  • flatten層使用layers.Flatten()定義,將多維資料展平為一維資料。
  • 密集層使用layers.Dense()定義,實作分類別任務。
  • 輸出層使用layers.Dense()定義,輸出一個機率值,表示輸入資料為真實資料的機率。
  • 模型使用keras.Model()定義,編譯模型使用model.compile()方法,最佳化器為Adam,損失函式為二元交叉熵,評估指標為準確率。

圖表翻譯:

  graph LR
    A[輸入層] --> B[卷積層]
    B --> C[池化層]
    C --> D[flatten層]
    D --> E[密集層]
    E --> F[輸出層]
    style A fill:#f9f,stroke:#333,stroke-width:4px
    style B fill:#f9f,stroke:#333,stroke-width:4px
    style C fill:#f9f,stroke:#333,stroke-width:4px
    style D fill:#f9f,stroke:#333,stroke-width:4px
    style E fill:#f9f,stroke:#333,stroke-width:4px
    style F fill:#f9f,stroke:#333,stroke-width:4px

本圖表示分類別器的架構,包括輸入層、卷積層、池化層、flatten層、密集層和輸出層。每個層都有其特定的功能,共同實作分類別任務。

建立鑑別器模型

鑑別器(Discriminator)是一個用於區分真實和偽造影像的模型。在這個例子中,我們將建立一個簡單的鑑別器模型,使用 TensorFlow 和 Keras。

鑑別器架構

我們的鑑別器模型將由多個卷積層和全連線層組成。輸入的影像和標籤將被合併,然後經過多個卷積層和啟用函式,以提取影像的特徵。

def create_discriminator():
    # 輸入影像和標籤
    inputs_image = tf.keras.Input(shape=IMAGE_SHAPE)
    inputs_label = tf.keras.Input(shape=(1,))

    # 對標籤進行編碼
    x = tf.keras.layers.CategoryEncoding(num_tokens=NUM_OF_CLASSES, output_mode="one_hot")(inputs_label)

    # 將標籤編碼結果與影像特徵合併
    units = IMAGE_SHAPE[0] * IMAGE_SHAPE[1]
    x = tf.keras.layers.Dense(units)(x)
    labels = tf.keras.layers.Reshape((IMAGE_SHAPE[0], IMAGE_SHAPE[1], 1))(x)

    # 合併影像和標籤
    x = tf.keras.layers.Concatenate()([inputs_image, labels])

    # 卷積層和啟用函式
    x = tf.keras.layers.Conv2D(64, (4, 4), strides=(2, 2), padding='same')(x)
    x = tf.keras.layers.LeakyReLU(alpha=0.2)(x)
    x = tf.keras.layers.Dropout(0.4)(x)

    x = tf.keras.layers.Conv2D(128, (4, 4), strides=(2, 2), padding='same')(x)
    x = tf.keras.layers.LeakyReLU(alpha=0.2)(x)
    x = tf.keras.layers.Dropout(0.4)(x)

    # 全連線層和輸出
    x = tf.keras.layers.Flatten()(x)
    outputs = tf.keras.layers.Dense(1, activation='sigmoid')(x)

    # 建立模型
    model = tf.keras.Model([inputs_image, inputs_label], outputs)
    return model

模型摘要

我們的鑑別器模型由多個卷積層和全連線層組成,輸入的影像和標籤被合併,然後經過多個卷積層和啟用函式,以提取影像的特徵。最終,模型輸出一個sigmoid啟用函式的結果,表示影像是否為真實或偽造。

圖表翻譯:

  graph LR
    A[輸入影像] --> B[卷積層]
    B --> C[啟用函式]
    C --> D[合併影像和標籤]
    D --> E[卷積層]
    E --> F[啟用函式]
    F --> G[全連線層]
    G --> H[輸出]

這個模型可以用於區分真實和偽造影像,具有良好的泛化能力和抗噪能力。

從技術架構視角來看,本文介紹了利用cGAN生成人臉影像的技術方案,並詳細闡述瞭如何使用IMDB-WIKI人臉影像資料集進行模型訓練。透過對資料集的年齡和性別標籤進行預處理和分組,可以有效地控制生成影像的屬性。雖然cGAN在生成逼真影像方面展現出強大能力,但模型訓練的穩定性和生成結果的多樣性仍是挑戰。目前,限制cGAN效能提升的瓶頸主要在於如何更有效地融合條件資訊和控制生成過程。對於追求高保真度人臉影像生成的應用,更精細的網路架構設計和損失函式設計至關重要。玄貓認為,cGAN技術在人臉影像生成領域具有廣闊的應用前景,但仍需持續關注其效能提升和穩定性方面的研究進展。未來,隨著技術的發展,我們預見cGAN將在更廣泛的影像生成任務中扮演重要角色,並推動相關產業的創新發展。