自編碼器是一種很好的降維技術,它可以學習到數據中非常有用的信息。而收縮自編碼器作為正則自編碼器的一種,其非線性降維效果非常好,並且它的過程可以通過流形知識來解釋。
基礎知識
1、自編碼器
自編碼器是一種降維的技術,由編碼器和解碼器兩部分組成,架構圖如下。編碼器 \(f\) 用來輸出降維后的表示 \(h\),而解碼器 \(g\) 則通過最小化代價函數從編碼器的輸出 \(h\) 來重構原始的輸入 \(x\),輸出 \(r\)。

編碼器 \(f\) 和解碼器 \(g\) 的內部結構是一個仿射函數(線性組合)再加一個(線性或非線性)激活函數,形式如下:
其中,\(s\) 是激活函數,\(W\) 是權重矩陣,\(b\) 是偏置向量。為了防止自編碼器學習到整體收縮再放大的無用映射,一般 \(W'=W ^{T}\)。
詳細的自編碼器介紹可以參考Introduction to autoencoders.
2、流形切平面
流形的一個重要特征是切平面的集合。\(d\) 維流形上的一點 \(x\),切平面由能張成流形上允許變動的局部方向的 \(d\) 維基向量給出。這是《Deep Learning》上的定義,其實切平面就是切線、切面拓展到高維的情況,類似於超平面的概念。
3、Keras
Keras 是一個用 Python 編寫的高級神經網絡 API,它能夠以 TensorFlow、CNTK 或者 Theano 作為后端運行。Keras 的開發重點是支持快速的實驗。能夠以最小的時延把你的想法轉換為實驗結果,是做好研究的關鍵。
使用 Keras 前一定要安裝 TensorFlow、CNTK 和 Theano 三個框架中的任一一個,並且要注意每個框架適用的 Python 版本,提前配置好相應的環境。
收縮自編碼器(CAE)
1、定義
為了提高對訓練集數據點周圍小擾動的魯棒性,收縮自編碼器在基礎自編碼器上添加了正則項,其形式為編碼器的映射 \(f\) 關於輸入 \(x\) 的 \(Jacobian\) 矩陣的 \(Frobenius\) 范數(具體形式如下),目的是迫使其學習在訓練樣本上有更強收縮作用的映射。
假設訓練集為 \(D _{n}\),我們通過最小化重構誤差以及對梯度的懲罰來學習自編碼器的參數,完整的代價函數如下:
其中 \(L\) 是重構誤差,形式為平方誤差(線性自編碼器)或者交叉熵損失(非線性誤差),而\(\lambda \) 則是控制正則化強度的超參數。
2、解釋
從代價函數來看,收縮自編碼器通過兩種相反的推動力學習有用信息--重構誤差和收縮懲罰(正則項)。收縮懲罰迫使自編碼器學習到的所有映射對於輸入的梯度都很小,即把輸入都降維到一個很小的區域(點附近),而重構誤差迫使自編碼器學習一個恆等映射,保留完整的信息。兩種推動力沖擊下,使得大部分映射對於輸入的梯度都很小,而只有少部分的大梯度。這樣在輸入具有小擾動時,小梯度會減小這些擾動,達到增加自編碼器對輸入附近小擾動的魯棒性。
3、學習流形
學習流形的介紹可以看我以前的博客“學習流形”的初認識。
從流行角度來進一步探索,訓練數據是位於一個低維流形上的。數據中的變化對應於流形上的局部變化(沿着切平面的方向),而數據中的不變方向是對應於正交於流形的方向。只要我們學習到數據中的變化和不變方向,那么流形的結構也就被捕捉到了。
回頭再看收縮自編碼器學習的兩種推動力,收縮懲罰想要使學習到的特征在所有方向上不變(對所有方向都有收縮作用),而重構誤差則想要能將學習到的特征重構回輸入。所以在學習的過程中,重構誤差的推動力使數據中的變化方向(即流形切平面的方向)能夠抵抗收縮作用,體現在其對應的 \(Jacobian\) 矩陣中的奇異值很大;而抵抗不了收縮作用的方向則對應於數據中不變的方向(正交於流形的方向),其在 \(Jacobian\) 矩陣中的梯度則會變得很小。
由此可以看出收縮自編碼器可以很好地捕捉流形結構。
代碼實現
下面收縮自編碼器的實現是基於 Keras 框架的。
導入數據:
from tensorflow.examples.tutorials.mnist import input_data
from keras.layers import Input, Dense
from keras.models import Model
import numpy as np
import matplotlib.pyplot as plt
import keras.backend as K
導入 MNISR 數據:
mnist = input_data.read_data_sets('../data/MNIST_data', one_hot=True)
X_train, y_train = mnist.train.images, mnist.train.labels
X_val, y_val = mnist.validation.images, mnist.validation.labels
X_test, y_test = mnist.test.images, mnist.test.labels
定義收縮自編碼器:
def contractive_autoencoder(X, lam=1e-3):
X = X.reshape(X.shape[0], -1)
M, N = X.shape
N_hidden = 64
N_batch = 100
inputs = Input(shape=(N,))
encoded = Dense(N_hidden, activation='sigmoid', name='encoded')(inputs)
outputs = Dense(N, activation='linear')(encoded)
model = Model(input=inputs, output=outputs)
def contractive_loss(y_pred, y_true):
mse = K.mean(K.square(y_true - y_pred), axis=1)
W = K.variable(value=model.get_layer('encoded').get_weights()[0]) # N x N_hidden
W = K.transpose(W) # N_hidden x N
h = model.get_layer('encoded').output
dh = h * (1 - h) # N_batch x N_hidden
# N_batch x N_hidden * N_hidden x 1 = N_batch x 1
contractive = lam * K.sum(dh**2 * K.sum(W**2, axis=1), axis=1)
return mse + contractive
model.compile(optimizer='adam', loss=contractive_loss)
model.fit(X, X, batch_size=N_batch, nb_epoch=3)
return model, Model(input=inputs, output=encoded)
訓練模型並測試,畫出重構后的圖形:
model, representation = contractive_autoencoder(X_train)
idxs = np.random.randint(0, X_test.shape[0], size=5)
X_recons = model.predict(X_test[idxs])
for X_recon in X_recons:
plt.imshow(X_recon.reshape(28, 28), cmap='Greys_r')
plt.show()
代碼主要參考了Deriving Contractive Autoencoder and Implementing it in Keras,其中還詳細推導了收縮正則項的計算形式,有興趣的可以看一下。