1. 程式人生 > 其它 >自編碼器是什麼?有什麼用?這裡有一份入門指南(附程式碼)

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

自編碼器(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 = 784
hidden_size = 64
output_size = 784

x = Input(shape=(input_size,))

# Encoder
h = Dense(hidden_size, activation='relu')(x)

# Decoder
r = Dense(output_size, activation='sigmoid')(h)

autoencoder = Model(input=x, output=r)
autoencoder.compile(optimizer='adam', loss='mse')

多層自編碼器

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

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

input_size = 784
hidden_size = 128
code_size = 64

x = Input(shape=(input_size,))

# Encoder
hidden_1 = Dense(hidden_size, activation='relu')(x)
h = Dense(code_size, activation='relu')(hidden_1)

# Decoder
hidden_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)) 

# Encoder
conv1_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)

# Decoder
conv2_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 = 784
hidden_size = 64
output_size = 784

x = Input(shape=(input_size,))

# Encoder
h = Dense(hidden_size, activation='relu', activity_regularizer=regularizers.l1(10e-5))(x)

# Decoder
r = Dense(output_size, activation='sigmoid')(h)

autoencoder = Model(input=x, output=r)
autoencoder.compile(optimizer='adam', loss='mse')

降噪自編碼器

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

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

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

x = Input(shape=(28, 28, 1))

# Encoder
conv1_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)

# Decoder
conv2_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')

總結

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

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