原文作者:Steven Wang

前言
兩兄弟N.Coder 和D.Coder 經營著一家藝術畫廊。一周末,他們舉辦了一場特別奇怪的展覽,因為它只有一面牆,沒有實體藝術品。當他們收到一幅新畫時,N.Coder 在牆上選擇一個點作為標記來代表這幅畫,然後扔掉原來的藝術品。當顧客要求觀看這幅畫時,D.Coder 嘗試僅使用牆上相關標記的坐標來重新創作這件藝術品。
展牆如下圖所示,每個黑點是N.Coder 放置的一個標記,代表一幅畫。在牆上坐標[– 3.5, 1 ] 處的那幅原圖(original) 數字6 的畫N.Coder 對其進行了重建(reconstruction)。
下圖展示了更多例子,頂行的數字是原圖,中行的坐標是N.Coder 將圖掛在牆上的坐標,底行是D.Coder 根據坐標重建的作品。
問題來了,N.Coder 如何決定每幅畫在展牆上對應的坐標,而使得D.Coder 僅用它就能重建原圖的?原來是兩兄弟在放置標記和重建作品的過程中,仔細監控售票處因顧客因重建質量不佳而要求退款而造成的收入損失,他們經過多年的“訓練”逐漸“精通”標記放置和作品重建,而最大限度地減少這種收入損失。從上圖對比原圖和重建可以看出,兩兄弟之間的磨合效果還不錯。來參觀藝術品的顧客很少抱怨D.Coder 重新創作的畫作與他們來參觀的原始作品有很大的不同。
有一天,N.Coder 望著展牆,有了一個大膽的想法,對於那些牆上當前沒有標記的部分,如果讓D.Coder 來重建能創作出什麼樣的作品?如果成功的話,那麼他們就可以舉辦自己100% 原創的畫展了。想想就興奮,於是D.Coder 隨機選取了之前沒有標記的坐標 (紅點) 來重建,結果如下圖所示。
正如你所看到的,重建效果較差,有些圖甚至都分辨不出是什麼數字。那麼到底出了什麼問題,Coder 兩兄弟該如何改進他們的方案呢?
1.自動編碼器
前言的故事其實就是類比自動編碼器(autoencoder),D.Coder 音譯為encoder,即編碼器,做的事情就是將圖片轉成坐標,而N.Coder 音譯為decoder,即解碼器,做的事情就是將坐標還原成圖片。上節的兩兄弟監控的收入損失其實就是模型訓練時用的損失函數。
故事歸故事,讓我們看看自動編碼器的嚴謹描述,它本質上就是一個神經網絡,包含:
一個編碼器(encoder):用來把高維數據壓縮成低維表徵向量。
一個解碼器(decoder):用來將低維表徵向量還原成高維數據。
該流程如下圖所示,original input data 是高維圖片數據,圖片包含很多像素因此是高維的,而representation vector 是低維表徵向量,本例用的二維向量[-2.0, -0.5 ] 是低維的。

該網絡經過訓練,可以找到編碼器和解碼器的權重,最小化原始輸入與輸入通過編碼器和解碼器後的重建之間的損失。表徵向量是將原始圖像壓縮到較低維的潛空間。通過選擇潛空間(latent space) 中的任何點,我們應該能夠通過將該點傳遞給解碼器來生成新的圖像,因為解碼器已經學會瞭如何將潛空間中的點轉換為可看的圖像。
在前言描述中,N.Coder 和D.Coder 使用表示二維潛空間(牆壁) 內的向量對每個圖像進行編碼。之所以用二維是為了可視化潛空間,在實踐中,潛空間通常高過兩維,以便更自由地捕獲圖像中更大的細微差別。
2.模型解析
2.1 初次見面
一般來說,最好用單獨的文件來創建模型的類,比如下面的Autoencoder class。這樣其他項目可以靈活調用此類。下面代碼首先展示了Autoencoder 的框架,__init__() 是構造函數,通過調用_build() 來創建模型,compile() 函數用於設定優化器,save() 函數用於保存模型,load_weights() 函數用於下次使用模型時加載權重,train() 函數用於訓練模型。

構建函數包含8 個必需參數和2 個默認參數,input_dim 是圖片的維度,z_dim 是潛空間的維度,剩下的6 個必需參數分別是編碼器和解碼器的濾波器個數(filters)、濾波器大小(kernel_size)、步長大小(strides)。

用構建函數創建自動編碼器,命名為AE。輸入數據是黑白圖片,其維度是( 28, 28, 1),潛空間用的2D 平面,因此z_dim = 2 。此外六個參數的值都是一個大小為4 的列表,那麼編碼模型和解碼模型都含有4 層。

在AutoEncoder 類裡面定義_build() 函數,構建編碼器和解碼器並將兩者相連,代碼框架如下(後三小節會逐個分析):

接下兩小節我們來一一剖析自動編碼器中的編碼模型和解碼模型。
2.2 編碼模型
編碼器的任務是將輸入圖片轉換成潛空間的一個點,編碼模型在_build() 函數裡面的具體實現如下:

代碼解釋如下:
第2-3 行將圖片定義為encoder 的輸入。
第5-17 行按順序將捲積層堆起來。
第19 行記錄x 的形狀,K.int_shape 的返回是一個元組(None, 7, 7, 64),第0 個元素是樣本大小,用[ 1:] 返回除樣本大小的數據形狀( 7, 7, 64)。
第20 行將最後的捲積層打平成為一個1D 向量。
第21 行的稠密層將該向量轉成另一個大小為z_dim 的1D 向量。
第22 行構建encoder 模型,分別在Model() 函數確定入參encoder_input 和encoder_output。
用summary() 函數打印出編碼模型的信息,用來描述每層的名稱類型(layer (type))、輸出形狀(Output Shape) 和參數個數(Param #)。


2.3 解碼模型
解碼器是編碼器的鏡像,只不過不是使用卷積層,而是使用卷積轉置層(convolutional transpose layers) 來構建。當步長設為2 ,卷積層每次將圖片的高和寬減半,而卷積轉置層將圖片的高和寬翻倍。具體操作見下圖。

解碼器在_build() 函數裡面的具體實現如下:

代碼解釋如下:
第1 行將encoder 的輸出定義為decoder 的輸入。
第2-3 行將1D 向量重塑成形狀為( 7, 7, 64) 的張量。
第6-15 行按順序將捲積轉置層堆起來。
第7-22 行:
如果是最後一層,用sigmoid 函數轉換,得到的結果在0-1 之間當成像素
如果不是最後一層,用leaky relu 函數轉換,並加上批歸一化(batch normalization) 和隨機失活(dropout) 的處理。
第24-25 行構建decoder 模型,分別在Model() 函數確定入參decoder_input 和decoder_output,前者是encoder 的輸出,即潛空間的點,而後者是重建的圖片。
用summary() 函數打印出解碼模型的信息。


2.4 串連起來
為了能同時訓練編碼器和解碼器,我們需要將兩者連在一起,

代碼解釋如下:
第1 行將encoder_input 作為整體模型的輸入model_input (中間產物encoder_output 是編碼器的輸出)。
第2 行將解碼器的輸出作為整體模型的輸出model_output (解碼器的輸入就是編碼器的輸出)。
第3 行構建autoencoder 模型,分別在Model() 函數確定入參model_input 和model_output。
一圖胜千言。

2.5 訓練模型
構建好模型之後,只需要定義損失函數和編譯優化器。損失函數通常選擇均方誤差(RMSE)。編譯complie() 函數的實現如下,用的是Adam 優化器,學習率設為0.0005 :


訓練模型用fit() 函數,批大小設為32 ,epoch 設為200 ,代碼如下:


在測試集上隨機選10 個看看效果:


10 張圖中只有4 張重建效果還行。
3.三大缺陷
模型訓練之後,我們可以可視化圖片在潛空間的情況。通過模型中的encoder 在測試集生成坐標在2D 散點圖中顯示。


圖中有三個現象值得注意:
有些數字的佔地區域很小,比如紅色的9 ,有些數字的佔地區域很大,比如紫色的0 。
圖中的點對於( 0, 0) 不對稱,比如x 軸上負值的點比正值的點會多很多,有些點甚至到了x =-15 處。
顏色之間有很大的間隙,其中包含很少的點,如上圖左上角。
上述三大缺陷使我們從潛空間中採樣非常困難:
對於缺陷1 , 由於數字9 比0 的佔地區域大,那麼我們更容易採樣到9 。
對於缺陷2 ,從技術上講,我們可以採樣平面上任何點。但每個數字的分佈是不確定的,如果分佈不是對稱的話,那麼隨機採樣的會很難操作。
對於缺陷3 ,從下圖可看出從潛空間中的空白處有的根本重構不出像樣的數字。

缺陷3 空白出重構不出數字還好理解,但下圖兩條紅線表示的重構就讓人擔憂了。這兩個點都不在空白處,但是還是無法解碼成像樣的數字。根本原因就是自動編碼器並沒有強制確保生成的潛空間是連續的,例如,即便( 2,-2) 能夠生成令人滿意的數字4 ,但該模型沒有一個機制來確保點( 2.1, – 2.1 ) 也能產生令人滿意的數字4 。

總結
自動編碼器只需要特徵不需要標籤,是一種無監督學習的模型,用於重建數據。該模型是一個生成模型,但從上節提到的三大缺陷,該生成模型對於低維黑白數字的效果都不好,那麼對於高維彩色人臉的效果會更差。
這個自編碼器框架是好的,那麼我們應該如何解決這三個缺陷能生成一個強大的自動編碼器。這個就是下篇的內容,變分自動編碼器 (Variational AutoEncoder, VAE)。

您可在ChatGPT 4.0 的Plugin Store 搜索SignalPlus,獲取實時加密資訊。如果想即時收到我們的更新,歡迎關注我們的推特賬號@SignalPlus_Web 3 ,或者加入我們的微信群(添加小助手微信:SignalPlus 123)、Telegram 群以及Discord 社群,和更多朋友一起交流互動。
SignalPlus Official Website:https://www.signalplus.com


