自編碼器是一種能夠通過無監督學習,學到輸入數據高效表示的人工神經網絡。輸入數據的這一高效表示稱為編碼(codings),其維度一般遠小於輸入數據,使得自編碼器可用於降維(查看第八章)。更重要的是,自編碼器可作為強大的特征檢測器(feature detectors),應用於深度神經網絡的預訓練(查看第十一章)。此外,自編碼器還可以隨機生成與訓練數據類似的數據,這被稱作生成模型(generative model)。比如,可以用人臉圖片訓練一個自編碼器,它可以生成新的圖片。
自編碼器通過簡單地學習將輸入復制到輸出來工作。這一任務(就是輸入訓練數據, 再輸出訓練數據的任務)聽起來似乎微不足道,但通過不同方式對神經網絡增加約束,可以使這一任務變得極其困難。比如,可以限制內部表示的尺寸(這就實現降維了),或者對訓練數據增加噪聲並訓練自編碼器使其能恢復原有。這些限制條件防止自編碼器機械地將輸入復制到輸出,並強制它學習數據的高效表示。簡而言之,編碼(就是輸入數據的高效表示)是自編碼器在一些限制條件下學習恆等函數(identity function)的副產品。(這句話有點抽象,不過看完15.1就明白了)
15.1 高效的數據表示
下面有兩組數字,哪組更容易記憶呢?
- 40, 27, 25, 36, 81, 57, 10, 73, 19, 68
- 50, 25, 76, 38, 19, 58, 29, 88, 44, 22, 11, 34, 17, 52, 26, 13, 40, 20
乍一看可能覺得第一行數字更容易記憶,畢竟更短。但仔細觀察就會發現,第二組數字是有規律的:偶數后面是其二分之一,奇數后面是其三倍加一(這就是著名的hailstone sequence)。如果識別出了這一模式,第二組數據只需要記住這兩個規則、第一個數字、以及序列長度。如果你的記憶能力超強,可以記住很長的隨機數字序列,那你可能就不會去關心一組數字是否存在規律了。所以我們要對自編碼器增加約束來強制它去探索數據中的模式。
記憶(memory)、感知(perception)、和模式匹配(pattern matching)的關系在1970s早期就被William Chase和Herbert Simon研究過。他們發現國際象棋大師觀察棋盤5秒,就能記住所有棋子的位置,而常人是無法辦到的。但棋子的擺放必須是實戰中的棋局(也就是棋子存在規則,就像第二組數字),棋子隨機擺放可不行(就像第一組數字)。象棋大師並不是記憶力優於我們,而是經驗豐富,很擅於識別象棋模式,從而高效地記憶棋局。
和棋手的記憶模式類似,一個自編碼器接收輸入,將其轉換成高效的內部表示,然后再輸出輸入數據的類似物。自編碼器通常包括兩部分:encoder(也稱為識別網絡)將輸入轉換成內部表示,decoder(也稱為生成網絡)將內部表示轉換成輸出。(如圖15-1)
圖15-1 象棋大師的記憶模式(左)和一個簡單的自編碼器
正如上圖所示,自編碼器的結構和多層感知機(查看第十章)類似,除了輸入神經元和輸出神經元的個數相等。在上圖的例子中,自編碼器只有一個包含兩個神經元的隱層(encoder),以及包含3個神經元的輸出層(decoder)。輸出是在設法重建輸入,損失函數是重建損失(reconstruction loss)。
由於內部表示(也就是隱層的輸出)的維度小於輸入數據(用2D取代了原來的3D), 這稱為不完備自編碼器(undercomplete autoencoder)。
undercomplete應該是個數學概率,不用深究了,畢竟在Wikipedia上面的解釋只有一句話:Describing a frame (in linear algebra) having a set of functions less than a basis。
15.2 不完備線性自編碼器實現PCA(Performing PCA with an Undercomplete Linear Autoencoder)
如果自編碼器使用線性激活函數並且損失函數是均方差(Mean Squared Error,MSE),那它就可以用來實現主成分分析(查看第八章)。
下面的代碼實現了一個簡單的線性自編碼器,將3D數據投影為2D:
import tensorflow as tf from tensorflow.contrib.layers import fully_connected n_inputs = 3 # 3D inputs n_hidden = 2 # 2D codings n_outputs = n_inputs learning_rate = 0.01 X = tf.placeholder(tf.float32, shape=[None, n_inputs]) hidden = fully_connected(X, n_hidden, activation_fn=None) outputs = fully_connected(hidden, n_outputs, activation_fn=None) reconstruction_loss = tf.reduce_mean(tf.square(outputs - X)) # MSE optimizer = tf.train.AdamOptimizer(learning_rate) training_op = optimizer.minimize(reconstruction_loss) init = tf.global_variables_initializer()
然后載入數據集,在訓練集上訓練模型,並對測試集進行編碼(也就是投影為2D):
X_train, X_test = [...] # load the dataset n_iterations = 1000 codings = hidden # the output of the hidden layer provides the codings with tf.Session() as sess: init.run() for iteration in range(n_iterations): training_op.run(feed_dict={X: X_train}) # no labels (unsupervised) codings_val = codings.eval(feed_dict={X: X_test})
15.3 棧式自編碼器(Stacked Autoencoders)
和其他的神經網絡一樣,自編碼器可以有多個隱層,這被稱作棧式自編碼器(或者深度自編碼器)。增加隱層可以學到更復雜的編碼,但千萬不能使自編碼器過於強大。想象一下,一個encoder過於強大,它僅僅是學習將輸入映射為任意數(然后decoder學習其逆映射)。很明顯這一自編碼器可以很好的重建數據,但它並沒有在這一過程中學到有用的數據表示。(而且也不能推廣到新的實例)
棧式自編碼器的架構一般是關於中間隱層對稱的,如圖15-3所示。
圖15-3 棧式自編碼器
15.3.1 TensorFlow 實現
參考:本書代碼
15.3.2 捆綁權重
如果一個自編碼器的層次是嚴格軸對稱的(如圖15-3),一個常用的技術是將decoder層的權重捆綁到encoder層。這使得模型參數減半,加快了訓練速度並降低了過擬合風險。具體的,假設自編碼器一共有$N$層(不算輸入層),$W_L$表示第$L$層的權重(例如,第一層是第一個隱層,第$\frac{2}{N}$層是編碼層,第$N$層是輸出層),那么decoder層的權重可以表示為$W_{N-L+1} = W_L^T \ , L = 1,2,\cdots ,\frac{N}{2}$。
不過偏置項不會捆綁。
15.3.3 一次訓練一個自編碼器
與之前訓練整個棧式自編碼器不同,可以訓練多個淺層的自編碼器,然后再將它們合並為一體,這樣要快得多。如圖15-4
圖15-4 一次訓練一個淺層自編碼器
首先,第一個自編碼器學習去重建輸入。然后,第二個自編碼器學習去重建第一個自編碼器隱層的輸出。最后,這兩個自編碼器被整合到一起,如圖15-4。可以使用這種方式,創建一個很深的棧式自編碼器。
另一個實現方法首先創建一個包含完整棧式編碼器的圖,然后再每一個訓練時期增加額外的操作,如圖15-5:
圖15-5
其中,
- 中間的一列是完整的棧式編碼器,這部分在訓練完成之后可以使用。
- 左側一列是最先需要訓練的,它跳過第二和第三個隱層,直接創建一個輸出層。這個輸出層與棧式自編碼器的輸出層共享同樣的權重和偏置。
- 隨后是右側一列的訓練。它使得第三個隱層的輸出與第一個隱層的輸出盡可能的接近。
15.4 使用棧式自編碼器進行無監督預訓練
正如第十一章所討論的,如果我們要處理一個復雜的有監督學習問題又沒有足夠的標注數據,一個解決方案是找到一個解決類似任務的訓練好的模型,復用低層。類似的,如果有一個很大的數據集但絕大部分是未標注數據,可以使用所有的數據先訓練一個棧式自編碼器,然后復用低層來完成真正的任務。如圖15-8所示:
圖15-8 使用自編碼器進行無監督預訓練
15.5 去噪自編碼器
另一種強制自編碼器學習有用特征的方式是最輸入增加噪聲,通過訓練之后得到無噪聲的輸出。這防止了自編碼器簡單的將輸入復制到輸出,從而提取出數據中有用的模式。如圖15-9左側所示。
使用自編碼器去燥的思想在1980s提出(比如,在1987年Yann LeCun的碩士論文中有所提及)。在一篇2008年的論文中,Pascal Vincent等人表明自編碼器可用於特征提取。在一篇2010年的論文中,Vincent等人提出棧式去燥自編碼器(stacked denoising autoencoders)。
噪聲可以是添加到輸入的純高斯噪聲,也可以是隨機丟棄輸入層的某個特征,類似於dropout。如圖15-9右側所示。
圖15-9 圖中自編碼器,通過高斯噪聲(左)或者Dropout(右)