自編碼器是什麼?有什麼用?這裡有一份入門指南(附代碼)

作者 Nathan Hubens

王小新 編譯自 Towards Data Science

自編碼器是什麼?有什麼用?這裡有一份入門指南(附代碼)

自編碼器(Autoencoder,AE),是一種利用反向傳播算法使得輸出值等於輸入值的神經網絡,它先將輸入壓縮成潛在空間表徵,然後通過這種表徵來重構輸出。

自編碼器由兩部分組成:

編碼器:這部分能將輸入壓縮成潛在空間表徵,可以用編碼函數h=f(x)表示。

解碼器:這部分能重構來自潛在空間表徵的輸入,可以用解碼函數r=g(h)表示。

自編碼器是什麼?有什麼用?這裡有一份入門指南(附代碼)

自編碼器結構

因此,整個自編碼器可以用函數g(f(x)) = r來描述,其中輸出r與原始輸入x相近。

為何要用輸入來重構輸出?

如果自編碼器的唯一目的是讓輸出值等於輸入值,那這個算法將毫無用處。事實上,我們希望通過訓練輸出值等於輸入值的自編碼器,讓潛在表徵h將具有價值屬性

這可通過在重構任務中構建約束來實現。

從自編碼器獲得有用特徵的一種方法是,限制h的維度使其小於輸入x,這種情況下稱作有損自編碼器。通過訓練有損表徵,使得自編碼器能學習到數據中最重要的特徵。

如果自編碼器的容量過大,它無需提取關於數據分佈的任何有用信息,即可較好地執行重構任務。

如果潛在表徵的維度與輸入相同,或是在過完備案例中潛在表徵的維度大於輸入,上述結果也會出現。

在這些情況下,即使只使用線性編碼器和線性解碼器,也能很好地利用輸入重構輸出,且無需瞭解有關數據分佈的任何有用信息。

在理想情況下,根據要分配的數據複雜度,來準確選擇編碼器和解碼器的編碼維數和容量,就可以成功地訓練出任何所需的自編碼器結構。

自編碼器是什麼?有什麼用?這裡有一份入門指南(附代碼)

自編碼器用來幹什麼?

目前,自編碼器的應用主要有兩個方面,第一是數據去噪,第二是為進行可視化而降維。設置合適的維度和稀疏約束,自編碼器可以學習到比PCA等技術更有意思的數據投影。

自編碼器能從數據樣本中進行無監督學習,這意味著可將這個算法應用到某個數據集中,來取得良好的性能,且不需要任何新的特徵工程,只需要適當地訓練數據。

但是,自編碼器在圖像壓縮方面表現得不好。由於在某個給定數據集上訓練自編碼器,因此它在處理與訓練集相類似的數據時可達到合理的壓縮結果,但是在壓縮差異較大的其他圖像時效果不佳。這裡,像JPEG這樣的壓縮技術在通用圖像壓縮方面會表現得更好。

訓練自編碼器,可以使輸入通過編碼器和解碼器後,保留儘可能多的信息,但也可以訓練自編碼器來使新表徵具有多種不同的屬性。不同類型的自編碼器旨在實現不同類型的屬性。下面將重點介紹四種不同的自編碼器。

四種不同的自編碼器

本文將介紹以下四種不同的自編碼器:

  1. 香草自編碼器

  2. 多層自編碼器

  3. 卷積自編碼器

  4. 正則自編碼器

為了說明不同類型的自編碼器,我使用Keras框架和MNIST數據集對每個類型分別創建了示例。每種自編碼器的代碼都可在Github上找到:

https://github.com/Yaka12/Autoencoders

香草自編碼器

在這種自編碼器的最簡單結構中,只有三個網絡層,即只有一個隱藏層的神經網絡。它的輸入和輸出是相同的,可通過使用Adam優化器和均方誤差損失函數,來學習如何重構輸入。

在這裡,如果隱含層維數(64)小於輸入維數(784),則稱這個編碼器是有損的。通過這個約束,來迫使神經網絡來學習數據的壓縮表徵。

input_size = 784hidden_size = 64output_size = 784x = Input(shape=(input_size,))# Encoderh = Dense(hidden_size, activation='relu')(x)# Decoderr = Dense(output_size, activation='sigmoid')(h)autoencoder = Model(input=x, output=r)autoencoder.compile(optimizer='adam', loss='mse')

多層自編碼器

如果一個隱含層還不夠,顯然可以將自動編碼器的隱含層數目進一步提高。

在這裡,實現中使用了3個隱含層,而不是隻有一個。任意一個隱含層都可以作為特徵表徵,但是為了使網絡對稱,我們使用了最中間的網絡層。

input_size = 784hidden_size = 128code_size = 64x = Input(shape=(input_size,))# Encoderhidden_1 = Dense(hidden_size, activation='relu')(x)h = Dense(code_size, activation='relu')(hidden_1)# Decoderhidden_2 = Dense(hidden_size, activation='relu')(h)r = Dense(input_size, activation='sigmoid')(hidden_2)autoencoder = Model(input=x, output=r)autoencoder.compile(optimizer='adam', loss='mse')

卷積自編碼器

你可能有個疑問,除了全連接層,自編碼器應用到卷積層嗎?

答案是肯定的,原理是一樣的,但是要使用3D矢量(如圖像)而不是展平後的一維矢量。對輸入圖像進行下采樣,以提供較小維度的潛在表徵,來迫使自編碼器從壓縮後的數據進行學習。

x = Input(shape=(28, 28,1))# Encoderconv1_1 = Conv2D(16, (3, 3), activation='relu', padding='same')(x)pool1 = MaxPooling2D((2, 2), padding='same')(conv1_1)conv1_2 = Conv2D(8, (3, 3), activation='relu', padding='same')(pool1)pool2 = MaxPooling2D((2, 2), padding='same')(conv1_2)conv1_3 = Conv2D(8, (3, 3), activation='relu', padding='same')(pool2)h = MaxPooling2D((2, 2), padding='same')(conv1_3)# Decoderconv2_1 = Conv2D(8, (3, 3), activation='relu', padding='same')(h)up1 = UpSampling2D((2, 2))(conv2_1)conv2_2 = Conv2D(8, (3, 3), activation='relu', padding='same')(up1)up2 = UpSampling2D((2, 2))(conv2_2)conv2_3 = Conv2D(16, (3, 3), activation='relu')(up2)up3 = UpSampling2D((2, 2))(conv2_3)r = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(up3)autoencoder = Model(input=x, output=r)autoencoder.compile(optimizer='adam', loss='mse')

正則自編碼器

除了施加一個比輸入維度小的隱含層,一些其他方法也可用來約束自編碼器重構,如正則自編碼器。

正則自編碼器不需要使用淺層的編碼器和解碼器以及小的編碼維數來限制模型容量,而是使用損失函數來鼓勵模型學習其他特性(除了將輸入複製到輸出)。這些特性包括稀疏表徵、小導數表徵、以及對噪聲或輸入缺失的魯棒性。

即使模型容量大到足以學習一個無意義的恆等函數,非線性且過完備的正則自編碼器仍然能夠從數據中學到一些關於數據分佈的有用信息。

在實際應用中,常用到兩種正則自編碼器,分別是稀疏自編碼器和降噪自編碼器。

稀疏自編碼器

一般用來學習特徵,以便用於像分類這樣的任務。稀疏正則化的自編碼器必須反映訓練數據集的獨特統計特徵,而不是簡單地充當恆等函數。以這種方式訓練,執行附帶稀疏懲罰的復現任務可以得到能學習有用特徵的模型。

還有一種用來約束自動編碼器重構的方法,是對其損失函數施加約束。比如,可對損失函數添加一個正則化約束,這樣能使自編碼器學習到數據的稀疏表徵。

要注意,在隱含層中,我們還加入了L1正則化,作為優化階段中損失函數的懲罰項。與香草自編碼器相比,這樣操作後的數據表徵更為稀疏。

input_size = 784hidden_size = 64output_size = 784x = Input(shape=(input_size,))# Encoderh = Dense(hidden_size, activation='relu', activity_regularizer=regularizers.l1(10e-5))(x)# Decoderr = Dense(output_size, activation='sigmoid')(h)autoencoder = Model(input=x, output=r)autoencoder.compile(optimizer='adam', loss='mse')

降噪自編碼器

這裡不是通過對損失函數施加懲罰項,而是通過改變損失函數的重構誤差項來學習一些有用信息。

向訓練數據加入噪聲,並使自編碼器學會去除這種噪聲來獲得沒有被噪聲汙染過的真實輸入。因此,這就迫使編碼器學習提取最重要的特徵並學習輸入數據中更加魯棒的表徵,這也是它的泛化能力比一般編碼器強的原因。

這種結構可以通過梯度下降算法來訓練。

x = Input(shape=(28, 28, 1))# Encoderconv1_1 = Conv2D(32, (3, 3), activation='relu', padding='same')(x)pool1 = MaxPooling2D((2, 2), padding='same')(conv1_1)conv1_2 = Conv2D(32, (3, 3), activation='relu', padding='same')(pool1)h = MaxPooling2D((2, 2), padding='same')(conv1_2)# Decoderconv2_1 = Conv2D(32, (3, 3), activation='relu', padding='same')(h)up1 = UpSampling2D((2, 2))(conv2_1)conv2_2 = Conv2D(32, (3, 3), activation='relu', padding='same')(up1)up2 = UpSampling2D((2, 2))(conv2_2)r = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(up2)autoencoder = Model(input=x, output=r)autoencoder.compile(optimizer='adam', loss='mse')

總結

本文先介紹了自編碼器的基本結構,還研究了許多不同類型的自編碼器,如香草、多層、卷積和正則化,通過施加不同約束,包括縮小隱含層的維度和加入懲罰項,使每種自編碼器都具有不同屬性。

希望這篇文章能讓深度學習初學者對自編碼器有個很好的認識,有問題歡迎加入量子位的機器學習專業群一起討論

誠摯招聘

վ'ᴗ' ի 追蹤AI技術和產品新動態


分享到:


相關文章: