1. 程式人生 > 其它 >【學術】一文搞懂自編碼器及其用途(含程式碼示例)

【學術】一文搞懂自編碼器及其用途(含程式碼示例)

自編碼器(Autoencoder)是一種旨在將它們的輸入複製到的輸出的神經網路。他們通過將輸入壓縮成一種隱藏空間表示(latent-space representation),然後這種重構這種表示的輸出進行工作。

這種網路由兩部分組成:

1. 編碼器:將輸入壓縮為潛在空間表示。可以用編碼函式h = f(x)表示。

2. 解碼器:這部分旨在重構來自隱藏空間表示的輸入。可以用解碼函式r = g(h)表示。

自編碼器架構

因此自編碼器的整體可以用函式g(f(x))= r來描述,其中我們想要得到的r與原始輸入x相近。

為什麼要將輸入複製給輸出?

很明顯,如果自編碼器的只是單純的將輸入複製到輸出中,那麼它沒有用處。所以實際上,我們希望通過訓練自編碼器將輸入複製到輸出中,使隱藏表示的h擁有有用的屬性。

通過在複製任務上建立約束條件來實現這點。想要從自編碼器中獲得有用特徵,一種方法是約束h的維度小於x,在這種情況下,自編碼器被稱為欠完備(undercomplete)。通過訓練不完整的表示,我們迫使自編碼器學習訓練資料的最有代表性(顯著的)的特徵。如果給自編碼器的容量過大,則它可以學習執行復制任務而不去提取關於資料分佈的有用資訊。如果隱藏表示的維度與輸入相同,並且處於過完備的情況下潛在表示的維度大於輸入。在這些情況下,即使線性編碼器和線性解碼器也可以學習將輸入複製到輸出,而無需學習有關資料分佈的有用資訊。理論上,可以成功地訓練任何自編碼器架構,根據要分配的複雜度來選擇編碼器和解碼器的程式碼維數和容量然後建模。

自編碼器的用途

如今,資料視覺化的資料降噪和降維被認為是自編碼器的兩個主要的實際應用。使用適當的維度和稀疏性約束,自編碼器可以得到比PCA或其他類似技術更好的資料投影。

自編碼器通過資料示例自動學習。這意味著很容易訓練在特定型別的輸入中表現良好的演算法的特定例項,並且不需要任何新的結構,只需適當的訓練資料即可。

但是,自編碼器在影象壓縮方面做得並不好。由於自編碼器是在給定的一組資料上進行訓練的,因此它將得到與所用訓練集資料相似的壓縮結果,但對通用的影象壓縮器來說效果並不好。至少不如像JPEG這樣的壓縮技術。

自編碼器被訓練成,可以在輸入通過編碼器和解碼器後保留儘可能多的資訊,但也會被訓練成,使新的表示具有各種不錯的屬性。不同型別的自編碼器旨在實現不同型別的屬性。

我們將重點介紹四種類型的自編碼器。

自編碼器的型別

在本文中,將介紹以下四種類型的自編碼器:

1. 普通的自編碼器

2. 多層自編碼器

3. 卷積自編碼器

4. 正則化自編碼器

為了說明不同型別的自編碼器,我們使用Keras框架和MNIST資料集建立了每個型別的示例。

  • 示例連結: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')

卷積自編碼器

那麼,自編碼器可以用卷積代替完全連線層嗎?

答案當然是肯定的,原理是一樣的,但是使用象徵(3維的向量)代替一維向量。對輸入的象徵進行降取樣以提供較小維度潛在表示,並強制自編碼器學習象徵的壓縮版本。

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')

正則化的自編碼器

除強加一個比輸入更低維度的隱藏層外,還有其他一些方法可以限制自編碼器的重構。正則化自編碼器不需要通過保持編碼器和解碼器的淺層和程式的小體量來限制模型容量,而是使用損失函式來鼓勵模型取得除了將輸入複製到其輸出之外的其他屬性。在實踐中,我們通常會使用兩種正則化自編碼器:稀疏自編碼器和降噪自編碼器。

稀疏自編碼器:稀疏自編碼器通常用於學習諸如分類等任務的特徵。已被正則化為稀疏的自編碼器必須響應訓練後資料集的唯一統計特徵,而不僅僅是作為標識函式。通過這種方式,訓練執行帶有稀疏懲罰的複製任務可以產生一個學習有用特徵作為副產(byproduct)的模型。

我們可以限制自編碼器重構的另一種方式是對其損失施加約束。例如,我們可以在損失函式中新增一個正則化項。這樣做可以會使我們的自編碼器學習資料的稀疏表示。

注意在的隱藏層中,我們添加了一個l1啟用值正則化矩陣(activity regularizer),它將在優化階段對損失函式施加一個懲罰,與普通的自編碼器相比,現在的表示方式更加稀疏。

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')

總結

在本文中,我們介紹了自編碼器的基本架構。我們還研究了許多不同型別的自編碼器。這些自編碼器根據不同的約束,有著不同的屬性。