變臉前奏:掌握可變編解碼器

現代人工智能技術能神乎其神的將一個人的臉嚴絲合縫的移植到另一個人的照片或視頻裏,類似於ZAO這類風靡一時的應用就能讓用戶將指定頭像切換到一段視頻中的對應角色裏,而且表情變化看不出任何違和感,我們本節提到的可變編解碼器就能實現類似功能。

前面章節我們創建的編碼器是將一張圖片映射爲二維空間中一個點,然後讓解碼器讀取該點後將圖片還原,它的問題在於如果我們將該點值稍微做一些更改,那麼解碼器將無法還原回原來圖片。可變編解碼器特點是將圖片映射到指定概率空間,這樣一來我們在該空間內無論取哪一點,解碼器都能把圖片還原回來,因此相較於原來編解碼器,可變編解碼器對輸入圖片的編碼特色如下圖所示:

屏幕快照 2019-11-12 下午10.39.42.png

通常情況下,我們會將圖片映射到正太分別函數對應的概率空間,上圖右邊的鐘型圖像就是二維正太分佈函數圖像,它就像一口倒掛的鐘,我們在這口鐘在二維平面上的投影圖像裏任取一點輸入到解碼器都可以把圖片還原回來。這種做法的好處在於我們能方便將各種要素進行組合,例如我們可以把雙眼皮和單臉皮兩種特性分別映射到空間[0,0.5]和[0.5, 1.0],這樣我們就可以通過使用一個隨機數來決定生成人臉的眼皮,如果生成的隨機數小於0.5,那網絡在生成人臉時使用單眼皮,要不然就使用雙眼皮。

在神經網絡開發中,我們時常要涉及到對高維數據的運算,例如我們要對人臉進行編碼時我們需要將它轉換爲含有d個數值的向量,其中每個數值用於表示人臉的某個特徵,同時我們讓每個數值按照單值正太概率函數圖形方式進行映射。單值正太分佈函數圖像如下:

屏幕快照 2019-11-12 下午10.57.00.png

它由兩個關鍵參數決定,一個是均值u,一個是方差𝞼,其中區間[u-𝞼,u+𝞼]佔據整個區間的60%以上,而[u-2𝞼,u+2𝞼]佔據整個區間90%以上,這樣網絡就可以把常見特徵映射到區間[u-𝞼,u+𝞼],少見的特徵映射到該區間之外,例如髮色,黑色白色最常見,那麼我們就使用區間[u-𝞼,u+𝞼]中的數組來表示,金色和綠色非常少見,那麼我們就使用[u-𝞼,u+𝞼]之外的數值來表示,這樣網絡在生成人臉時,60%以上的概率會讓人臉擁有黑色會白色頭髮,而不到30%的概率讓人臉擁有金色或綠色的頭髮,由此可見將某個特徵映射到連續區間有利於我們通過採樣以隨機的方式來決定特性的具體表現。

我們看看高維正太分佈函數的具體形式:
屏幕快照 2019-11-13 下午3.29.45.png
由於我們面對的是二維情況,因此上面公式中k=2,同時有:
屏幕快照 2019-11-13 下午3.33.59.png
上面兩個變量對正太分佈起到決定性作用,其中u1,𝞼1,u2,𝞼2分佈對應第一個和第二個變量的均值和方差,於是我們要訓練編碼器在接收圖片後輸出兩個向量分佈是u=[u1,u2]和𝞼 = [𝞼1,𝞼2],這兩個向量一旦確定,那麼相應的正太分佈概率函數就確定,接下來我們就要進行隨機採樣,具體做法是通過u=0,𝞼=1的正太分佈函數生成一個隨機值ε,然後計算z=u+ε*𝞼,該向量再輸入給解碼器,讓它生成輸入編碼器的圖片,這樣訓練出來的解碼器就能將滿足u=[u1,u2]和𝞼 = [𝞼1,𝞼2]二維正太分佈區間內任一點轉換爲給定圖片。

在實踐中我們通常讓編碼器生成𝞼’ = lg(𝞼 )=[log(𝞼1),log(𝞼2)],因此在採樣時計算要稍微做一些改變,那就是z = u + ε*exp(𝞼’ /2),接下來我們看看可變編解碼器的代碼實現:

class VariationalEncoder():
    ....
    def  _build(self):
        ....
        self.mu = Dense(self.z_dim, name = "mu")(x) #生成均值向量
        self.log_var = Dense(self.z_dim, name="log_var")(x) #生成方差向量
        self.encoder_mu_log_var = Model(encoder_input, (self.mu, self.log_var))
        def  sampling(args):#利用u=0,var=1的正太分佈函數生成一個隨機數以便進行採樣
            mu, log_var = args
            epsilon = K.random_normal(shape = K.shape(mu), mean = 0., stddev = 1.)
            encoder_output = self.mu + epsilon * K.exp(log_var / 2)
            return encoder_output
        encoder_output = Lambda(sampling, name = "encoder")([self.mu, self.log_var])
        self.encoder = Model(encoder_input, encoder_output)
        self.model.summary()
    def  compile(self, learning_rate, r_loss_factor):
        self.learning_rate = learning_rate
        optimizer = Adam(lr = learning_rate)
        def  vae_r_loss(y_true, y_pred): #輸出圖像與輸入圖像像素點差值的平方和越小表示網絡解碼後恢復的圖像越好
            return K.mean(K.square(y_true - y_pred), axis = [1,2,3]) * r_loss_factor
        def  vae_kl_loss(y_true, y_pred):
            kl_loss = -0.5 * K.sum(1 + self.log_var - K.square(self.mu) - K.exp(self.log_var), axis = 1)
            return kl_loss
        self.model.compile(optimizer = optimizer, loss = r_loss)
        ....  
    ....

上面顯示代碼是本節代碼與上一節不同之處,需要注意的是kl_loss,它使用信息論中的卡爾貝克-洛貝格公式來判斷編碼器生成的兩個向量所決定的概率函數是否與標準正太分佈函數足夠接近,此處我們暫時忽略掉它的數學原理,接着我們啓動網絡的訓練過程:

LEARNING_RATE = 0.0005
R_LOSS_FACTOR = 1000
vae.compile(LEARNING_RATE, R_LOSS_FACTOR)
BATCH_SIZE = 32
EPOCHS = 200
PRINT_EVERY_N_BATCHES = 100
INITIALIZE_EPOCH = 0
vae.train(x_train, batch_size = BATCH_SIZE, epochs = EPOCHS, 
         print_every_n_batches = PRINT_EVERY_N_BATCHES, 
         run_folder = 'C:\\Users\\cheny\\Desktop\\vae_encoder',
         initial_epoch = INITIALIZE_EPOCH)

在普通計算機上,訓練過程會持續一個較長時間,在給定文件夾下會產生網絡創建的數字圖片,我在自己機器上訓練將一輪就快一小時,在沒有GPU支持下要完成200輪將需要一週時間,因此我在訓練幾小時後停止,然後測試網絡的效果,我們依然像上節那樣將測試圖片讓編碼器識別後,將識別出來的二維向量繪製出來如下圖:

4.png

我們看到相同顏色的點對應相同數字圖片,而同色點恰好彙集在同一區域,這意味着編碼器確實可以將表示同一個數字的不同圖片映射到同一給定區域,下一節我們將看看如何使用該網絡實現如假包換的換臉效果。

更詳細的講解和代碼調試演示過程,請點擊鏈接

更多技術信息,包括操作系統,編譯器,面試算法,機器學習,人工智能,請關照我的公衆號:
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章