天天看點

【翻譯】Sklearn與TensorFlow機器學習實用指南 —— 第15章 自編碼器(中)

關聯權重

當自編碼器整齊地對稱時,就像我們剛剛建構的那樣,一種常用技術是将解碼器層的權重與編碼器層的權重相關聯。 這樣減少了模型中的權重數量,加快了訓練速度,并限制了過度拟合的風險。

【翻譯】Sklearn與TensorFlow機器學習實用指南 —— 第15章 自編碼器(中)
不幸的是,使用fully_connected()函數在 TensorFlow 中實作相關權重有點麻煩;手動定義層實際上更容易。 代碼結尾明顯更加冗長:

activation = tf.nn.elu
regularizer = tf.contrib.layers.l2_regularizer(l2_reg)
initializer = tf.contrib.layers.variance_scaling_initializer()

X = tf.placeholder(tf.float32, shape=[None, n_inputs])

weights1_init = initializer([n_inputs, n_hidden1])
weights2_init = initializer([n_hidden1, n_hidden2])

weights1 = tf.Variable(weights1_init, dtype=tf.float32, name="weights1")
weights2 = tf.Variable(weights2_init, dtype=tf.float32, name="weights2")
weights3 = tf.transpose(weights2, name="weights3") # tied weights
weights4 = tf.transpose(weights1, name="weights4") # tied weights

biases1 = tf.Variable(tf.zeros(n_hidden1), name="biases1")
biases2 = tf.Variable(tf.zeros(n_hidden2), name="biases2")
biases3 = tf.Variable(tf.zeros(n_hidden3), name="biases3")
biases4 = tf.Variable(tf.zeros(n_outputs), name="biases4")

hidden1 = activation(tf.matmul(X, weights1) + biases1)
hidden2 = activation(tf.matmul(hidden1, weights2) + biases2)
hidden3 = activation(tf.matmul(hidden2, weights3) + biases3)
outputs = tf.matmul(hidden3, weights4) + biases4

reconstruction_loss = tf.reduce_mean(tf.square(outputs - X))
reg_loss = regularizer(weights1) + regularizer(weights2)
loss = reconstruction_loss + reg_loss

optimizer = tf.train.AdamOptimizer(learning_rate)
training_op = optimizer.minimize(loss)

init = tf.global_variables_initializer()
           

這段代碼非常簡單,但有幾件重要的事情需要注意:

首先,權重 3 和權重 4 不是變量,它們分别是權重 2 和權重 1 的轉置(它們與它們“綁定”)。

其次,由于它們不是變量,是以規範它們是沒有用的:我們隻調整權重 1 和權重 2。

第三,偏置永遠不會被束縛,并且永遠不會正規化。

一次訓練一個自編碼器

我們不是一次完成整個棧式自編碼器的訓練,而是一次訓練一個淺自編碼器,然後将所有這些自編碼器堆疊到一個棧式自編碼器(是以名稱)中,通常要快得多,如圖 15-4 所示。 這對于非常深的自編碼器特别有用。

【翻譯】Sklearn與TensorFlow機器學習實用指南 —— 第15章 自編碼器(中)

在訓練的第一階段,第一個自編碼器學習重構輸入。 在第二階段,第二個自編碼器學習重構第一個自編碼器隐藏層的輸出。 最後,您隻需使用所有這些自編碼器來建構一個大三明治,如圖 15-4 所示(即,您首先将每個自編碼器的隐藏層,然後按相反順序堆疊輸出層)。 這給你最後的棧式自編碼器。 您可以用這種方式輕松地訓練更多的自編碼器,建構一個非常深的棧式自編碼器。

為了實作這種多階段訓練算法,最簡單的方法是對每個階段使用不同的 TensorFlow 圖。 訓練完一個自編碼器後,您隻需通過它運作訓練集并捕獲隐藏層的輸出。 這個輸出作為下一個自編碼器的訓練集。 一旦所有自編碼器都以這種方式進行了訓練,您隻需複制每個自編碼器的權重和偏置,然後使用它們來建構堆疊的自編碼器。 實作這種方法非常簡單,是以我們不在這裡詳細說明,但請查閱 Jupyter notebooks 中的代碼作為示例。

另一種方法是使用包含整個棧式自編碼器的單個圖,以及執行每個訓練階段的一些額外操作,如圖 15-5 所示。

【翻譯】Sklearn與TensorFlow機器學習實用指南 —— 第15章 自編碼器(中)

這值得解釋一下:

圖中的中央列是完整的棧式自編碼器。這部分可以在訓練後使用。

左列是運作第一階段訓練所需的一系列操作。它建立一個繞過隐藏層 2 和 3 的輸出層。該輸出層與堆疊的自編碼器的輸出層共享相同的權重和偏置。此外還有旨在使輸出盡可能接近輸入的訓練操作。是以,該階段将訓練隐藏層1和輸出層(即,第一自編碼器)的權重和偏置。

圖中的右列是運作第二階段訓練所需的一組操作。它增加了訓練操作,目的是使隐藏層 3 的輸出盡可能接近隐藏層 1 的輸出。注意,我們必須在運作階段 2 時當機隐藏層 1。此階段将訓練隐藏層 2 和 3 的權重和偏置(即第二自編碼器)。

TensorFlow 代碼如下所示:

[...] # Build the whole stacked autoencoder normally.
# In this example, the weights are not tied.
optimizer = tf.train.AdamOptimizer(learning_rate)

with tf.name_scope("phase1"):
    phase1_outputs = tf.matmul(hidden1, weights4) + biases4
    phase1_reconstruction_loss = tf.reduce_mean(tf.square(phase1_outputs - X))
    phase1_reg_loss = regularizer(weights1) + regularizer(weights4)
    phase1_loss = phase1_reconstruction_loss + phase1_reg_loss
    phase1_training_op = optimizer.minimize(phase1_loss)

with tf.name_scope("phase2"):
    phase2_reconstruction_loss = tf.reduce_mean(tf.square(hidden3 - hidden1))
    phase2_reg_loss = regularizer(weights2) + regularizer(weights3)
    phase2_loss = phase2_reconstruction_loss + phase2_reg_loss
    train_vars = [weights2, biases2, weights3, biases3]
    phase2_training_op = optimizer.minimize(phase2_loss, var_list=train_vars)
           

第一階段比較簡單:我們隻建立一個跳過隐藏層 2 和 3 的輸出層,然後建構訓練操作以最小化輸出和輸入之間的距離(加上一些正則化)。

第二階段隻是增加了将隐藏層 3 和隐藏層 1 的輸出之間的距離最小化的操作(還有一些正則化)。 最重要的是,我們向minim()方法提供可訓練變量的清單,確定省略權重 1 和偏差 1;這有效地當機了階段 2 期間的隐藏層 1。

在執行階段,你需要做的就是為階段 1 一些疊代進行訓練操作,然後階段 2 訓練運作更多的疊代。

由于隐藏層 1 在階段 2 期間被當機,是以對于任何給定的訓練執行個體其輸出将總是相同的。 為了避免在每個時期重新計算隐藏層1的輸出,您可以在階段 1 結束時為整個訓練集計算它,然後直接在階段 2 中輸入隐藏層 1 的緩存輸出。這可以得到一個不錯的性能上的提升。

可視化重建

確定自編碼器得到适當訓練的一種方法是比較輸入和輸出。 它們必須非常相似,差異應該是不重要的細節。 我們來繪制兩個随機數字及其重建:

n_test_digits = 2
X_test = mnist.test.images[:n_test_digits]

with tf.Session() as sess:
    [...] # Train the Autoencoder
    outputs_val = outputs.eval(feed_dict={X: X_test})

def plot_image(image, shape=[28, 28]):
    plt.imshow(image.reshape(shape), cmap="Greys", interpolation="nearest")
    plt.axis("off")

for digit_index in range(n_test_digits):
    plt.subplot(n_test_digits, 2, digit_index * 2 + 1)
    plot_image(X_test[digit_index])
    plt.subplot(n_test_digits, 2, digit_index * 2 + 2)
    plot_image(outputs_val[digit_index])
           
【翻譯】Sklearn與TensorFlow機器學習實用指南 —— 第15章 自編碼器(中)

看起來夠接近。 是以自編碼器已經适當地學會了重制它,但是它學到了有用的特性? 讓我們來看看。

可視化功能

一旦你的自編碼器學習了一些功能,你可能想看看它們。 有各種各樣的技術。 可以說最簡單的技術是在每個隐藏層中考慮每個神經元,并找到最能激活它的訓練執行個體。 這對頂層隐藏層特别有用,因為它們通常會捕獲相對較大的功能,您可以在包含它們的一組訓練執行個體中輕松找到這些功能。 例如,如果神經元在圖檔中看到一隻貓時強烈激活,那麼激活它的圖檔最顯眼的地方都會包含貓。 然而,對于較低層,這種技術并不能很好地工作,因為這些特征更小,更抽象,是以很難準确了解神經元正在為什麼而興奮。

讓我們看看另一種技術。 對于第一個隐藏層中的每個神經元,您可以建立一個圖像,其中像素的強度對應于給定神經元的連接配接權重。 例如,以下代碼繪制了第一個隐藏層中五個神經元學習的特征:

with tf.Session() as sess:
    [...] # train autoencoder
    weights1_val = weights1.eval()

for i in range(5):
    plt.subplot(1, 5, i + 1)
    plot_image(weights1_val.T[i])
           

您可能會得到如圖 15-7 所示的低級功能。

【翻譯】Sklearn與TensorFlow機器學習實用指南 —— 第15章 自編碼器(中)

前四個特征似乎對應于小塊,而第五個特征似乎尋找垂直筆劃(請注意,這些特征來自堆疊去噪自編碼器,我們将在後面讨論)。

另一種技術是給自編碼器提供一個随機輸入圖像,測量您感興趣的神經元的激活,然後執行反向傳播來調整圖像,使神經元激活得更多。 如果疊代數次(執行漸變上升),圖像将逐漸變成最令人興奮的圖像(用于神經元)。 這是一種有用的技術,用于可視化神經元正在尋找的輸入類型。

最後,如果使用自編碼器執行無監督預訓練(例如,對于分類任務),驗證自編碼器學習的特征是否有用的一種簡單方法是測量分類器的性能。

無監督預訓練使用棧式自編碼器

正如我們在第 11 章中讨論的那樣,如果您正在處理複雜的監督任務,但您沒有大量标記的訓練資料,則一種解決方案是找到執行類似任務的神經網絡,然後重新使用其較低層。 這樣就可以僅使用很少的訓練資料來訓練高性能模型,因為您的神經網絡不必學習所有的低級特征;它将重新使用現有網絡學習的特征檢測器。

同樣,如果您有一個大型資料集,但大多數資料集未标記,您可以先使用所有資料訓練棧式自編碼器,然後重新使用較低層為實際任務建立一個神經網絡,并使用标記資料對其進行訓練。 例如,圖 15-8 顯示了如何使用棧式自編碼器為分類神經網絡執行無監督預訓練。 正如前面讨論過的,棧式自編碼器本身通常每次都會訓練一個自編碼器。 在訓練分類器時,如果您确實沒有太多标記的訓練資料,則可能需要當機預訓練層(至少是較低層)。

【翻譯】Sklearn與TensorFlow機器學習實用指南 —— 第15章 自編碼器(中)

這種情況實際上很常見,因為建構一個大型的無标簽資料集通常很便宜(例如,一個簡單的腳本可以從網際網路上下載下傳數百萬張圖像),但隻能由人類可靠地标記它們(例如,将圖像分類為可愛或不可愛)。 标記執行個體是耗時且昂貴的,是以隻有幾千個标記執行個體是很常見的。

正如我們前面所讨論的那樣,目前深度學習海嘯的觸發因素之一是 Geoffrey Hinton 等人在 2006 年的發現,深度神經網絡可以以無監督的方式進行預訓練。 他們使用受限玻爾茲曼機器(見附錄 E),但在 2007 年 Yoshua Bengio 等人表明自編碼器也起作用。

TensorFlow 的實作沒有什麼特别之處:隻需使用所有訓練資料訓練自編碼器,然後重用其編碼器層以建立一個新的神經網絡(有關如何重用預訓練層的更多詳細資訊,請參閱第 11 章或檢視 Jupyte notebooks 中的代碼示例)。

到目前為止,為了強制自編碼器學習有趣的特性,我們限制了編碼層的大小,使其不夠完善。 實際上可以使用許多其他類型的限制,包括允許編碼層與輸入一樣大或甚至更大的限制,導緻過度完成的自編碼器。 現在我們來看看其中的一些方法。

降噪自編碼(DAE)

另一種強制自編碼器學習有用功能的方法是為其輸入添加噪聲,對其進行訓練以恢複原始的無噪聲輸入。 這可以防止自編碼器将其輸入複制到其輸出,是以最終不得不在資料中查找模式。

自 20 世紀 80 年代以來,使用自編碼器消除噪音的想法已經出現(例如,在 Yann LeCun 的 1987 年碩士論文中提到過)。 在 2008 年的一篇論文中,帕斯卡爾文森特等人。 表明自編碼器也可用于特征提取。 在 2010 年的一篇文章中 Vincent 等人引入堆疊降噪自編碼器。

噪聲可以是純粹的高斯噪聲添加到輸入,或者它可以随機關閉輸入,就像 drop out(在第 11 章介紹)。 圖 15-9 顯示了這兩個選項。

【翻譯】Sklearn與TensorFlow機器學習實用指南 —— 第15章 自編碼器(中)

TensorFlow 實作

在 TensorFlow 中實作去噪自編碼器并不難。 我們從高斯噪聲開始。 這實際上就像訓練一個正常的自編碼器一樣,除了給輸入添加噪聲外,重建損耗是根據原始輸入計算的:

X = tf.placeholder(tf.float32, shape=[None, n_inputs])
X_noisy = X + tf.random_normal(tf.shape(X))
[...]
hidden1 = activation(tf.matmul(X_noisy, weights1) + biases1)
[...]
reconstruction_loss = tf.reduce_mean(tf.square(outputs - X)) # MSE
[...]
           

由于X的形狀隻是在構造階段部分定義的,我們不能預先知道我們必須添加到X中的噪聲的形狀。我們不能調用X.get_shape(),因為這隻會傳回部分定義的X的形狀 ([None,n_inputs])和random_normal()需要一個完全定義的形狀,是以會引發異常。 相反,我們調用tf.shape(X),它将建立一個操作,該操作将在運作時傳回X的形狀,該操作将在此時完全定義。

實施更普遍的 dropout 版本,而且這個版本并不困難:

from tensorflow.contrib.layers import dropout

keep_prob = 0.7

is_training = tf.placeholder_with_default(False, shape=(), name='is_training')
X = tf.placeholder(tf.float32, shape=[None, n_inputs])
X_drop = dropout(X, keep_prob, is_training=is_training)
[...]
hidden1 = activation(tf.matmul(X_drop, weights1) + biases1)
[...]
reconstruction_loss = tf.reduce_mean(tf.square(outputs - X)) # MSE
[...]
           

在訓練期間,我們必須使用feed_dict将is_training設定為True(如第 11 章所述):

sess.run(training_op, feed_dict={X: X_batch, is_training: True})
           

但是,在測試期間,不需要将is_training設定為False,因為我們将其設定為對placeholder_with_default()函數調用的預設值。

原文釋出時間為:2018-07-05

本文作者:ApacheCN【翻譯】

本文來自雲栖社群合作夥伴“

Python愛好者社群

”,了解相關資訊可以關注“

繼續閱讀