|

【AI模型訓練】真假分不清!訓練假臉產生器

   
作者:曾成訓(CH.Tseng)

如果想虛擬出人類的五官,用大頭照是最適合的,這邊使用的訓練樣本是來自 Flickr 的資料庫—Flickr Faces HQ Dataset(FFHQ)。

  • 圖片數目:70,000 張
  • 圖片種類:PNG 圖檔
  • 圖片尺寸:共三種(裁切臉部的高品質 HQ、低解像 Thumbnail、in-the-wild 相片原圖各 70,000 張)
  • HQ:1024×1024 Thumbnail:128×128
  • 圖片特性:每一張圖片皆使用 Dlib 自動裁切臉部及校正對齊

這次所使用的程式,參考自周凡剛老師所開的課程並作了些許修改(如果您對 GAN 有興趣,相當推薦周老師的這個課程唷!)

Generator模型

主要修改基本的 MNIST DCGAN 架構,讓 Generator 能產生 56×56 的彩色 RGB 圖片。由於每次採樣會增加二倍尺寸,因此一開始的 7×7 需要三次的 up sampling 才會達到所需的 56×56(7x7x128 🡪 14x14x128 🡪 28x28x128 🡪 56×56×128 🡪 56x56x3),因此使用迴圈的方式會比較簡潔;另外,這邊使用的 128 是參考 VGG 的作法,這個數值可以更改。

def generator_model(dim, base_gen, rand_dim):

gen = Sequential()

gen.add(Dense(dim*dim*base_gen, input_dim=rand_dim, activation='relu'))

gen.add(Reshape((dim, dim, base_gen))) # 轉換成三維

for i in range(0, int(base_gen/28 + 1)):

# 上採樣, 長寬會變兩倍

gen.add(UpSampling2D(size=(2, 2)))

# (4, 4)卷積窗的卷積, DCGAN模型的標準用法。

gen.add(Conv2D(base_gen, kernel_size=(4, 4), activation='relu', padding='same'))

# 卷積層間使用BN來normalize,也是DCGAM的標準用法。

gen.add(BatchNormalization())

#因為是RGB圖片, 因此讓filter為3, 輸出channel為3的圖片

#一樣使用tanh作為激活

gen.add(Conv2D(3, kernel_size=(4, 4), activation='tanh', padding='same'))

return gen

Discriminator模型

基本上 Discriminator 方面,只要將輸入的部份修改成與 Generator 輸出的尺寸相同(56x56x3)即可。

def discriminator_model(in_shape, base_gen):

discriminator = Sequential()

#步長2卷積,這是 DCGAN的標準用法。

discriminator.add(Conv2D(int(base_gen/8), kernel_size=4,

strides=2,

input_shape=in_shape,

padding="same",

activation='relu'))

#DCGAN都會在兩層卷積中加入BN

discriminator.add(BatchNormalization())

discriminator.add(Conv2D(int(base_gen/4), kernel_size=4,

strides=2,

padding="same",

activation='relu'))

discriminator.add(BatchNormalization())

discriminator.add(Conv2D(int(base_gen/2), kernel_size=4,

strides=2,

padding="same",

activation='relu'))

discriminator.add(BatchNormalization())

discriminator.add(Conv2D(base_gen, kernel_size=4,

strides=2,

padding="same",

activation='relu'))

discriminator.add(BatchNormalization())

#全連接層(MLP)

discriminator.add(Flatten())

discriminator.add(Dense(base_gen*2, activation='relu'))

discriminator.add(Dropout(0.25))

discriminator.add(Dense(1, activation='sigmoid'))

return discriminator

DCGAN的訓練

在進行 GAN 訓練時,雖然 Discriminator 與 Generator 都包含在 GAN 模型中,但是由於 Discriminator 的 trainable 設定為 False,這表示在訓練 GAN 時,只有 Generator 被訓練到,此時 GAN 輸出的 loss 視為 Generator 的 loss;而 Discriminator 自己單獨進行訓練,它的 loss 是由兩種 loss 加總:判斷 Generator 產生的虛擬圖片、判斷 Dataset 中真實的圖片。

loss_file = os.path.join(output_folder, "loss_" + str(epoch_count) + "epochs.csv")

f = open(loss_file, 'w')

f.write("epoch,d_loss,g_loss \n")

f.close()

for epoch in range(0, epoch_count):

for batch_count in range(0, 100):

idx = np.random.randint(0, x_train.shape, batch_size)

imgs = x_train_shaped

valid = np.ones((batch_size, 1))

fake = np.zeros((batch_size, 1))

#Generator製造出虛擬圖片,這些圖片由Discriminator來判斷是否為False

noise = np.random.normal(0, 1, (batch_size, random_dim))

gen_imgs = generator.predict(noise)

discriminator.trainable = True

#由Discriminator判斷由Dataset來的圖片是否為True。

d_loss_real = discriminator.train_on_batch(imgs, valid)

#由Discriminator判斷由Generator產生的虛擬圖片。

d_loss_fake = discriminator.train_on_batch(gen_imgs, fake)

#兩者加總即為Discriminator的Loss

d_loss = (d_loss_real + d_loss_fake) / 2

discriminator.trainable = False

noise = np.random.normal(0, 1, (batch_size, random_dim))

#由於GAN模型中,Discriminator設為不可訓練,因此訓練GAN模型,可視為僅訓練Generator。

g_loss = gan.train_on_batch(noise, valid)

訓練時的資訊輸出

因為想要看看 Generator 在迭代訓練時,每批次訓練出來的臉孔圖片有否進步,同時也希望將模型的 loss值輸出以利統計,因此加入了下方的程式,這可以讓其在指定間隔的 epoch 次數達到時,輸出 Generator 的預測圖片、loss 值以及匯出模型的權重。

 out_path = output_folder +'/' + str(epoch+1) + '/'

if (epoch + 1) % epoch_interval_output_log == 0:

f = open(loss_file, 'a')

msg_line = "{} epoch Discriminator loss:{} Generator loss:{}".format(epoch + 1, d_loss, g_loss)

print(msg_line)

f.write( "{},{},{}\n".format(epoch+1,d_loss,g_loss))

f.close()

if (epoch + 1) % epoch_interval_output_model == 0:

if( not os.path.isdir(out_path)):

os.mkdir(out_path)

print("Output models.")

output_model(out_path, output_images_num, random_dim, epoch+1, generator, discriminator, gan)

if (epoch + 1) % epoch_interval_output_img == 0:

if( not os.path.isdir(out_path)):

os.mkdir(out_path)

print("Output images.")

output_img(out_path, output_images_num, random_dim, epoch+1, generator, discriminator, gan)

訓練結果

將 Generator 和 Discriminator 的 loss 輸出後,會看到它的訓練曲線圖如下,看起來並沒有明顯的下降趨勢,這是由於其 loss 是雙方相互拉距拔河的結果,Generator 強時 Discriminator 弱,反之亦同,因此就沒有一般深度學習模型訓練 loss 愈來愈低的情況。

(圖片來源:曾成訓提供)

另外值得一提的是,起初在訓練時,Generator 處於很吃虧的情況,因為要由無生有地產生以假亂真的圖片,一定不知道要如何產生,因此它產生的圖片很容易就會被 Discriminator 判定為假,造成 Generator  不知道要如何產生真的圖片而被迫提早放棄。

幸好本範例使用 56×56 的圖片還能訓練出來,但如果擴大一倍到 112×112,對 Generator 來說可能就很難訓練了。

下面是 Generator 各訓練階段的虛擬能力:

本文為會員限定文章

立即加入會員! 全站文章無限看~

                               

已經是會員? 按此登入

只需不到短短一分鐘...

輸入您的信箱與ID註冊即可享有一切福利!

會員福利
1

免費電子報

2

會員搶先看

3

主題訂閱

4

好文收藏

曾 成訓

Author: 曾 成訓

人到中年就像沒對準的描圖紙,一點一點的錯開,我只能當個Maker來使它復位。

Share This Post On

Submit a Comment

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *