CycleGAN

這篇文章理解自知乎上兩篇文章:

GAN 補充

深度生成模型的分類樹如下:

可以根據(jù)極大似然原理學(xué)習(xí)的深度生成模型,根據(jù)如何表示或預(yù)估概率,可以分為顯式密度模型和隱式密度模型。顯式密度模型可以構(gòu)建一個明確的密度模型,p(x;θ),因此可以求得使可能性最大的 θ 值。顯式密度模型又分為易解決的和不易解決的(需要使用近似法求最大化可能性的 θ)。對于隱式密度模型,則沒有明確表示數(shù)據(jù)空間的概率分布,相反,該模型提供了一些與該概率分布間接相互作用的方式——生成樣本,即定義一種在沒有任何輸入的情況下,通過隨機轉(zhuǎn)換現(xiàn)有樣本,以便獲取另一個服從同一分布的樣本的方法。GAN 即屬于隱式密度模型,它直接從模型表示的分布中采樣,而非使用馬爾可夫鏈。

GAN 核心原理的數(shù)學(xué)描述為:

簡單分析一下這個公式:

  • 整個式子由兩項構(gòu)成。x 表示真實圖片,z 表示輸入 G 網(wǎng)絡(luò)的噪聲,而 G(z) 表示 G 網(wǎng)絡(luò)生成的圖片。
  • D(x) 表示 D 網(wǎng)絡(luò)判斷真實圖片是否真實的概率(因為x就是真實的,所以對于 D 來說,這個值越接近1越好)。而 D(G(z)) 是 D 網(wǎng)絡(luò)判斷 G 生成的圖片的是否真實的概率。
  • G 的目的:G 希望自己生成的圖片“越接近真實越好”。也就是說,G 希望 D(G(z)) 盡可能得大,這時 V(D, G) 會變小。因此式子對于 G 來說是求最小(min_G)。
  • D的目的:D 的能力越強,D(x) 應(yīng)該越大,D(G(x)) 應(yīng)該越小,這時 V(D,G) 會變大。因此式子對于 D 來說是求最大(max_D)。

用隨機梯度下降法訓(xùn)練 D 和 G 的算法為:

第一步訓(xùn)練 D,D 是希望 V(G, D) 越大越好,所以是加上梯度(ascending)。第二步訓(xùn)練 G 時,V(G, D) 越小越好,所以是減去梯度(descending)。整個訓(xùn)練過程交替進(jìn)行。

CycleGAN 原理

CycleGAN的原理可以概述為:將一類圖片轉(zhuǎn)換成另一類圖片。也就是說,現(xiàn)在有兩個樣本空間,X 和 Y,我們希望把 X 空間中的樣本轉(zhuǎn)換成 Y 空間中的樣本。因此,實際的目標(biāo)就是學(xué)習(xí)從 X 到 Y 的映射(設(shè)這個映射為 F),F(xiàn) 就對應(yīng)著 GAN 中的生成器,F(xiàn) 可以將 X 中的圖片 x 轉(zhuǎn)換為 Y 中的圖片 F(x)。對于生成的圖片,我們還需要 GAN 中的判別器來判別它是否為真實圖片,由此構(gòu)成對抗生成網(wǎng)絡(luò)。設(shè)這個判別器為 DY。這樣的話,根據(jù)這里的生成器和判別器,我們就可以構(gòu)造一個 GAN 損失,表達(dá)式為:

這個損失實際上和原始的 GAN 損失是一模一樣的,但單純的使用這一個損失是無法進(jìn)行訓(xùn)練的。原因在于,映射 F 完全可以將所有 x 都映射為 Y 空間中的同一張圖片,使損失無效化。對此,作者又提出了所謂的循環(huán)一致性損失(cycle consistency loss)。再假設(shè)一個映射 G,它可以將 Y 空間中的圖片 y 轉(zhuǎn)換為 X 中的圖片 G(y)。CycleGAN 同時學(xué)習(xí) F 和 G 兩個映射,并要求 F(G(y)) ≈ y,以及 G(F(x)) ≈ x。也就是說,將 X 的圖片轉(zhuǎn)換到 Y 空間后,應(yīng)該還可以轉(zhuǎn)換回來。這樣就杜絕模型把所有 X 的圖片都轉(zhuǎn)換為 Y 空間中的同一張圖片了。根據(jù) F(G(y)) ≈ y 和 G(F(x)) ≈ x,循環(huán)一致性損失就定義為:

同時,為 G 也引入一個判別器 DX,由此可以同樣定義一個 GAN 的損失 LGAN(G,DX,X,Y)。最終的損失就由三部分組成:

CycleGAN 的結(jié)構(gòu)示意圖如下:

從上圖可以了解 CycleGAN 的運作過程:兩個輸入被傳遞到對應(yīng)的鑒別器(一個是對應(yīng)于該域的原始圖像,另一個是通過生成器產(chǎn)生的圖像),并且鑒別器的任務(wù)是區(qū)分它們,識別出生成器輸出的生成圖像,并拒絕此生成圖像。生成器想要確保這些圖像被鑒別器接受,所以它將嘗試生成與 DB 類中原始圖像非常接近的新圖像。事實上,在生成器分布與所需分布相同時,生成器和鑒別器之間實現(xiàn)了納什均衡(Nash equilibrium)。

CycleGAN 的靈活性在于不需要提供從源域到目標(biāo)域的配對轉(zhuǎn)換例子就可以訓(xùn)練。比如,我們希望訓(xùn)練一個將白天的照片轉(zhuǎn)換為夜晚的模型。如果使用pix2pix模型,那么我們必須在搜集大量地點在白天和夜晚的兩張對應(yīng)圖片,而使用CycleGAN只需同時搜集白天的圖片和夜晚的圖片,不必滿足對應(yīng)關(guān)系。因此CycleGAN的用途要比pix2pix更廣泛,利用CycleGAN就可以做出更多有趣的應(yīng)用。

CycleGAN 實現(xiàn)

一、構(gòu)建生成器

生成器的結(jié)構(gòu)如下:

生成器由三部分組成:編碼器、轉(zhuǎn)換器、解碼器。

編碼
第一步是利用卷積網(wǎng)絡(luò)從輸入圖像中提取特征。整個編碼過程,將 DA 域中一個尺寸為 [256,256,3] 的圖像,輸入到設(shè)計的編碼器中,獲得了尺寸為 [64,64,256] 的輸出 OAenc。

轉(zhuǎn)換
這些網(wǎng)絡(luò)層的作用是組合圖像的不同相近特征,然后基于這些特征,確定如何將圖像的特征向量 OAenc 從 DA 域轉(zhuǎn)換為 DB 域的特征向量。因此,作者使用了 6 層 Resnet 模塊。OBenc 表示該層的最終輸出,尺寸為 [64,64,256],這可以看作是 DB 域中圖像的特征向量。

一個 Resnet 模塊是一個由兩個卷積層組成的神經(jīng)網(wǎng)絡(luò)層,其中部分輸入數(shù)據(jù)直接添加到輸出。這樣做是為了確保先前網(wǎng)絡(luò)層的輸入數(shù)據(jù)信息直接作用于后面的網(wǎng)絡(luò)層,使得相應(yīng)輸出與原始輸入的偏差縮小,否則原始圖像的特征將不會保留在輸出中且輸出結(jié)果會偏離目標(biāo)輪廓。這個任務(wù)的一個主要目標(biāo)是保留原始圖像的特征,如目標(biāo)的大小和形狀,因此殘差網(wǎng)絡(luò)非常適合完成這些轉(zhuǎn)換。Resnet 模塊的結(jié)構(gòu)如下所示:

解碼
解碼過程與編碼方式完全相反,從特征向量中還原出低級特征,這是利用了反卷積層(deconvolution)來完成的。最后,我們將這些低級特征轉(zhuǎn)換得到一張在DB域中的圖像,得到一個大小為 [256,256,3] 的生成圖像 genB。

二、構(gòu)建鑒別器

鑒別器將一張圖像作為輸入,并嘗試預(yù)測其為原始圖像或是生成器的輸出圖像。鑒別器的結(jié)構(gòu)如下所示:

鑒別器本身就屬于卷積網(wǎng)絡(luò),需要從圖像中提取特征;然后是確定這些特征是否屬于該特定類別,使用一個產(chǎn)生一維輸出的卷積層來完成這個任務(wù)。

至此,已經(jīng)完成該模型的兩個主要組成部分,即生成器和鑒別器。由于要使這個模型可以從 A→B 和 B→A 兩個方向工作,所以設(shè)置了兩個生成器,即生成器 A→B 和生成器 B→A,以及兩個鑒別器,即鑒別器 A 和鑒別器 B。

三、建立模型

在定義損失函數(shù)前,先定義基礎(chǔ)輸入變量,來構(gòu)建模型:

input_A = tf.placeholder(tf.float32, [batch_size, img_width, img_height, img_layer], name="input_A")
input_B = tf.placeholder(tf.float32, [batch_size, img_width, img_height, img_layer], name="input_B")

同時定義模型如下:

gen_B = build_generator(input_A, name="generator_AtoB")
gen_A = build_generator(input_B, name="generator_BtoA")
dec_A = build_discriminator(input_A, name="discriminator_A")
dec_B = build_discriminator(input_B, name="discriminator_B")

dec_gen_A = build_discriminator(gen_A, "discriminator_A")
dec_gen_B = build_discriminator(gen_B, "discriminator_B")
cyc_A = build_generator(gen_B, "generator_BtoA")
cyc_B = build_generator(gen_A, "generator_AtoB")

gen 表示使用相應(yīng)的生成器后生成的圖像,dec 表示在將相應(yīng)輸入傳遞到鑒別器后做出的判斷。因此:

  • gen_A 是生成器 B2A 根據(jù)真 B 生成的假 A,
    gen_B 是生成器 A2B 根據(jù)真 A 生成的假 B;
  • dec_A 是鑒別器 A 對真 A 的鑒別結(jié)果,
    dec_B 是鑒別器 B 對真 B 的鑒別結(jié)果;
  • dec_gen_A 是鑒別器 A 對 gen_A 的鑒別結(jié)果,
    dec_gen_B 是鑒別器 B 對 gen_B 的鑒別結(jié)果;
  • cyc_A 是生成器 B2A 根據(jù) gen_B 生成的假 A,
    cyc_B 是生成器 A2B 根據(jù) gen_A 生成的假 B.
四、損失函數(shù)

現(xiàn)在我們有兩個生成器和兩個鑒別器。我們要按照實際目的來設(shè)計損失函數(shù)。損失函數(shù)應(yīng)該包括如下四個部分:

  1. 鑒別器必須允許所有相應(yīng)類別的原始圖像,即對應(yīng)輸出置 1;
  2. 鑒別器必須拒絕所有想要愚弄過關(guān)的生成圖像,即對應(yīng)輸出置 0;
  3. 生成器必須使鑒別器允許通過所有的生成圖像,來實現(xiàn)愚弄操作;
  4. 所生成的圖像必須保留有原始圖像的特性,所以如果我們使用生成器 GeneratorA→B 生成一張假圖像,那么要能夠使用另一個生成器 GeneratorB→A 來努力恢復(fù)成原始圖像。此過程必須滿足循環(huán)一致性。

鑒別器損失
通過訓(xùn)練鑒別器 A,使其對真 A 的鑒別輸出接近于1,鑒別器 B 也是如此。因此,鑒別器 A 的訓(xùn)練目標(biāo)為最小化 (DiscriminatorA(a)?1)2 的值,鑒別器 B 也是如此。

另外,由于鑒別器應(yīng)該能夠區(qū)分生成圖像和原始圖像,所以在處理生成圖像時期望輸出為 0,即鑒別器 A 要最小化 (DiscriminatorA(GeneratorB→A(b)))2 的值。

d_loss_A_1 = tf.reduce_mean(tf.squared_difference(dec_A,1))
d_loss_B_1 = tf.reduce_mean(tf.squared_difference(dec_B,1))

d_loss_A_2 = tf.reduce_mean(tf.square(dec_gen_A))
d_loss_B_2 = tf.reduce_mean(tf.square(dec_gen_B))

d_loss_A = (d_loss_A_1 + d_loss_A_2) / 2
d_loss_B = (d_loss_B_1 + d_loss_B_2) / 2

生成器損失
最終生成器應(yīng)該使得鑒別器對生成圖像的輸出值盡可能接近 1。故生成器想要最小化 (DiscriminatorB(GeneratorA→B(a))?1)2。對應(yīng)代碼為:

g_loss_A_1 = tf.reduce_mean(tf.squared_difference(dec_gen_B,1))
g_loss_B_1 = tf.reduce_mean(tf.squared_difference(dec_gen_A,1))

循環(huán)損失
最后一個重要參數(shù)為循環(huán)丟失(cyclic loss),能判斷用另一個生成器得到的生成圖像與原始圖像的差別。因此原始圖像和循環(huán)圖像之間的差異應(yīng)該盡可能?。?/p>

cyc_loss = tf.reduce_mean(tf.abs(input_A - cyc_A)) + tf.reduce_mean(tf.abs(input_B - cyc_B))

所以完整的生成器損失為:

g_loss_A = g_loss_A_1 + 10 * cyc_loss
g_loss_B = g_loss_B_1 + 10 * cyc_loss

cyc_loss 的乘法因子設(shè)置為 10,說明循環(huán)損失比鑒別損失更重要。

五、訓(xùn)練模型

定義好損失函數(shù),接下來只需要訓(xùn)練模型來最小化損失函數(shù)。

d_A_trainer = optimizer.minimize(d_loss_A, var_list=d_A_vars)
d_B_trainer = optimizer.minimize(d_loss_B, var_list=d_B_vars)
g_A_trainer = optimizer.minimize(g_loss_A, var_list=g_A_vars)
g_B_trainer = optimizer.minimize(g_loss_B, var_list=g_B_vars)

訓(xùn)練過程如下:

for epoch in range(0,100):
    # Define the learning rate schedule. The learning rate is kept
    # constant upto 100 epochs and then slowly decayed
    if(epoch < 100) :
        curr_lr = 0.0002
    else:
        curr_lr = 0.0002 - 0.0002*(epoch-100)/100

    # Running the training loop for all batches
    for ptr in range(0,num_images):

        # Train generator G_A->B
        _, gen_B_temp = sess.run([g_A_trainer, gen_B],
                                 feed_dict={input_A:A_input[ptr], input_B:B_input[ptr], lr:curr_lr})

        # We need gen_B_temp because to calculate the error in training D_B
        _ = sess.run([d_B_trainer],
                     feed_dict={input_A:A_input[ptr], input_B:B_input[ptr], lr:curr_lr})

        # Same for G_B->A and D_A as follow
        _, gen_A_temp = sess.run([g_B_trainer, gen_A],
                                 feed_dict={input_A:A_input[ptr], input_B:B_input[ptr], lr:curr_lr})
        _ = sess.run([d_A_trainer],
                     feed_dict={input_A:A_input[ptr], input_B:B_input[ptr], lr:curr_lr})

在訓(xùn)練函數(shù)中可以看到,在訓(xùn)練時需要不斷調(diào)用不同鑒別器和生成器。為了訓(xùn)練模型,需要輸入訓(xùn)練圖像和選擇優(yōu)化器的學(xué)習(xí)率。由于 batch_size 設(shè)置為1,所以 num_batches 等于 num_images。

我們已經(jīng)完成了模型構(gòu)建,下面是模型中一些默認(rèn)超參數(shù)。

生成圖像庫
計算每個生成圖像的鑒別器損失是不可能的,因為會耗費大量的計算資源。為了加快訓(xùn)練,我們存儲了之前每個域的所有生成圖像,并且每次僅使用一張圖像來計算誤差。首先,逐個填充圖像庫使其完整,然后隨機將某個庫中的圖像替換為最新的生成圖像,并使用這個替換圖像來作為該步的訓(xùn)練。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容