若是你須要解決很是複雜的問題,例如檢測高分辨率圖像中的數百種類型的對象,該怎麼辦? 你可能須要訓練更深的 DNN,也許有 10 層,每層包含數百個神經元,經過數十萬個鏈接來鏈接。 這不會是閒庭信步:python
首先,你將面臨棘手的梯度消失問題(或相關的梯度爆炸問題),這會影響深度神經網絡,並使較低層難以訓練。在本章中,咱們將依次討論這些問題,並提出解決問題的技巧。 咱們將從解釋梯度消失問題開始,並探討解決這個問題的一些最流行的解決方案。 接下來咱們將看看各類優化器,與普通梯度降低相比,它們能夠加速大型模型的訓練。 最後,咱們將瀏覽一些流行的大型神經網絡正則化技術。git
使用這些工具,你將可以訓練很是深的網絡:歡迎來到深度學習的世界!算法
正如咱們在第 10 章中所討論的那樣,反向傳播算法的工做原理是從輸出層到輸入層,傳播偏差的梯度。 一旦該算法已經計算了網絡中每一個參數的損失函數的梯度,它就使用這些梯度來用梯度降低步驟來更新每一個參數。bash
不幸的是,梯度每每變得愈來愈小,隨着算法進展到較低層。 結果,梯度降低更新使得低層鏈接權重實際上保持不變,而且訓練永遠不會收斂到良好的解決方案。 這被稱爲梯度消失問題。 在某些狀況下,可能會發生相反的狀況:梯度可能變得愈來愈大,許多層獲得了很是大的權重更新,算法發散。這是梯度爆炸的問題,在循環神經網絡中最爲常見(見第 14 章)。 更通常地說,深度神經網絡受梯度不穩定之苦; 不一樣的層次可能以很是不一樣的速度學習。網絡
雖然這種不幸的行爲已經通過了至關長的一段時間的實驗觀察(這是形成深度神經網絡大部分時間都被拋棄的緣由之一),但直到 2010 年左右,人們纔有了明顯的進步。 Xavier Glorot 和 Yoshua Bengio 發表的題爲《Understanding the Difficulty of Training Deep Feedforward Neural Networks》的論文發現了一些疑問,包括流行的 sigmoid 激活函數和當時最受歡迎的權重初始化技術的組合,即隨機初始化時使用平均值爲 0,標準差爲 1 的正態分佈。簡而言之,他們代表,用這個激活函數和這個初始化方案,每層輸出的方差遠大於其輸入的方差。網絡正向,每層的方差持續增長,直到激活函數在頂層飽和。這其實是由於logistic函數的平均值爲 0.5 而不是 0(雙曲正切函數的平均值爲 0,表現略好於深層網絡中的logistic函數)函數
看一下logistic 激活函數(參見圖 11-1),能夠看到當輸入變大(負或正)時,函數飽和在 0 或 1,導數很是接近 0。所以,當反向傳播開始時, 它幾乎沒有梯度經過網絡傳播回來,並且因爲反向傳播經過頂層向下傳遞,因此存在的小梯度不斷地被稀釋,所以較低層確實沒有任何東西可用。工具
Glorot 和 Bengio 在他們的論文中提出了一種顯著緩解這個問題的方法。 咱們須要信號在兩個方向上正確地流動:在進行預測時是正向的,在反向傳播梯度時是反向的。 咱們不但願信號消失,也不但願它爆炸並飽和。 爲了使信號正確流動,做者認爲,咱們須要每層輸出的方差等於其輸入的方差。(這裏有一個比喻:若是將麥克風放大器的旋鈕設置得太接近於零,人們聽不到聲音,可是若是將麥克風放大器設置得太大,聲音就會飽和,人們就會聽不懂你在說什麼。 如今想象一下這樣一個放大器的鏈條:它們都須要正確設置,以便在鏈條的末端響亮而清晰地發出聲音。 你的聲音必須以每一個放大器的振幅相同的幅度出來。)並且咱們也須要梯度在相反方向上流過一層以前和以後有相同的方差(若是您對數學細節感興趣,請查閱論文)。實際上不可能保證二者都是同樣的,除非這個層具備相同數量的輸入和輸出鏈接,可是他們提出了一個很好的折衷辦法,在實踐中證實這個折中辦法很是好:隨機初始化鏈接權重必須如公式 11-1 所描述的那樣。其中n_inputs
和n_outputs
是權重正在被初始化的層(也稱爲扇入和扇出)的輸入和輸出鏈接的數量。 這種初始化策略一般被稱爲Xavier初始化(在做者的名字以後),或者有時是 Glorot 初始化。性能
當輸入鏈接的數量大體等於輸出鏈接的數量時,能夠獲得更簡單的等式 學習
咱們在第 10 章中使用了這個簡化的策略測試
使用 Xavier 初始化策略能夠大大加快訓練速度,這是致使深度學習目前取得成功的技巧之一。 最近的一些論文針對不一樣的激活函數提供了相似的策略,如表 11-1 所示。 ReLU 激活函數(及其變體,包括簡稱 ELU 激活)的初始化策略有時稱爲 He 初始化(在其做者的姓氏以後)。
默認狀況下,fully_connected()
函數(在第 10 章中介紹)使用 Xavier 初始化(具備統一的分佈)。 你能夠經過使用以下所示的variance_scaling_initializer()
函數來將其更改成 He 初始化:
注意:本書使用tensorflow.contrib.layers.fully_connected()
而不是tf.layers.dense()
(本章編寫時不存在)。 如今最好使用tf.layers.dense()
,由於contrib
模塊中的任何內容可能會更改或刪除,恕不另行通知。 dense()
函數幾乎與fully_connected()
函數徹底相同。 與本章有關的主要差別是:
幾個參數被從新命名:範圍變成名字,activation_fn
變成激活(相似地,_fn
後綴從諸如normalizer_fn
之類的其餘參數中移除),weights_initializer
變成kernel_initializer
等等。默認激活如今是None
,而不是tf.nn.relu
。 它不支持tensorflow.contrib.framework.arg_scope()
(稍後在第 11 章中介紹)。 它不支持正則化的參數(稍後在第 11 章介紹)。
he_init = tf.contrib.layers.variance_scaling_initializer()
hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu,
kernel_initializer=he_init, name="hidden1")複製代碼
He 初始化只考慮了扇入,而不是像 Xavier 初始化那樣扇入和扇出之間的平均值。 這也是variance_scaling_initializer()
函數的默認值,但您能夠經過設置參數mode ="FAN_AVG"
來更改它。
Glorot 和 Bengio 在 2010 年的論文中的一個看法是,消失/爆炸的梯度問題部分是因爲激活函數的選擇很差形成的。 在那以前,大多數人都認爲,若是大天然選擇在生物神經元中使用 sigmoid 激活函數,它們一定是一個很好的選擇。 但事實證實,其餘激活函數在深度神經網絡中表現得更好,特別是 ReLU 激活函數,主要是由於它對正值不會飽和(也由於它的計算速度很快)。
不幸的是,ReLU激活功能並不完美。 它有一個被稱爲 「ReLU 死區」 的問題:在訓練過程當中,一些神經元有效地死亡,意味着它們中止輸出 0 之外的任何東西。在某些狀況下,你可能會發現你網絡的一半神經元已經死亡,特別是若是你使用大學習率。 在訓練期間,若是神經元的權重獲得更新,使得神經元輸入的加權和爲負,則它將開始輸出 0 。當這種狀況發生時,因爲當輸入爲負時,ReLU函數的梯度爲0,神經元不可能恢復生機。
爲了解決這個問題,你可能須要使用 ReLU 函數的一個變體,好比 leaky ReLU。這個函數定義爲LeakyReLUα(z)= max(αz,z)
(見圖 11-2)。超參數α
定義了函數「leaks」的程度:它是z < 0
時函數的斜率,一般設置爲 0.01。這個小斜坡確保 leaky ReLU 永不死亡;他們可能會長期昏迷,但他們有機會最終醒來。最近的一篇論文比較了幾種 ReLU 激活功能的變體,其中一個結論是 leaky Relu 老是優於嚴格的 ReLU 激活函數。事實上,設定α= 0.2
(巨大 leak)彷佛致使比α= 0.01
(小 leak)更好的性能。他們還評估了隨機化 leaky ReLU(RReLU),其中α
在訓練期間在給定範圍內隨機挑選,並在測試期間固定爲平均值。它表現至關好,彷佛是一個正則項(減小訓練集的過擬合風險)。最後,他們還評估了參數 leaky ReLU(PReLU),其中α
被受權在訓練期間被學習(而不是超參數,它變成能夠像任何其餘參數同樣被反向傳播修改的參數)。據報道這在大型圖像數據集上的表現強於 ReLU,可是對於較小的數據集,其具備過分擬合訓練集的風險。
最後,Djork-Arné Clevert 等人在 2015 年的一篇論文中提出了一種稱爲指數線性單元(exponential linear unit,ELU)的新的激活函數,在他們的實驗中表現優於全部的 ReLU 變體:訓練時間減小,神經網絡在測試集上表現的更好。 如圖 11-3 所示,公式 11-2 給出了它的定義。
它看起來很像 ReLU 函數,但有一些區別,主要區別在於:
首先它在z < 0
時取負值,這使得該單元的平均輸出接近於 0。這有助於減輕梯度消失問題,如前所述。 超參數
α
定義爲當
z
是一個大的負數時,ELU 函數接近的值。它一般設置爲 1,可是若是你願意,你能夠像調整其餘超參數同樣調整它。
z < 0
有一個非零的梯度,避免了神經元死亡的問題。
z = 0
左右,這有助於加速梯度降低,由於它不會彈回
z = 0
的左側和右側。
ELU 激活函數的主要缺點是計算速度慢於 ReLU 及其變體(因爲使用指數函數),可是在訓練過程當中,這是經過更快的收斂速度來補償的。 然而,在測試時間,ELU 網絡將比 ReLU 網絡慢。
那麼你應該使用哪一個激活函數來處理深層神經網絡的隱藏層? 雖然你的里程會有所不一樣,通常 ELU > leaky ReLU(及其變體)> ReLU > tanh > sigmoid。 若是您關心運行時性能,那麼您可能喜歡 leaky ReLU超過ELU。 若是你不想調整另外一個超參數,你可使用前面提到的默認的α
值(leaky ReLU 爲 0.01,ELU 爲 1)。 若是您有充足的時間和計算能力,您可使用交叉驗證來評估其餘激活函數,特別是若是您的神經網絡過擬合,則爲RReLU; 若是您擁有龐大的訓練數據集,則爲 PReLU。
TensorFlow 提供了一個能夠用來創建神經網絡的elu()
函數。 調用fully_connected()
函數時,只需設置activation_fn
參數便可:
hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.elu, name="hidden1")
TensorFlow 沒有針對 leaky ReLU 的預約義函數,可是很容易定義:
def leaky_relu(z, name=None):
return tf.maximum(0.01 * z, z, name=name)
hidden1 = tf.layers.dense(X, n_hidden1, activation=leaky_relu, name="hidden1")複製代碼
儘管使用 He初始化和 ELU(或任何 ReLU 變體)能夠顯著減小訓練開始階段的梯度消失/爆炸問題,但不保證在訓練期間問題不會回來。
在 2015 年的一篇論文中,Sergey Ioffe 和 Christian Szegedy 提出了一種稱爲批量標準化(Batch Normalization,BN)的技術來解決梯度消失/爆炸問題,每層輸入的分佈在訓練期間改變的問題,更廣泛的問題是當前一層的參數改變,每層輸入的分佈會在訓練過程當中發生變化(他們稱之爲內部協變量偏移問題)。
該技術包括在每層的激活函數以前在模型中添加操做,簡單地對輸入進行zero-centering和規範化,而後每層使用兩個新參數(一個用於尺度變換,另外一個用於偏移)對結果進行尺度變換和偏移。 換句話說,這個操做可讓模型學習到每層輸入值的最佳尺度,均值。爲了對輸入進行歸零和歸一化,算法須要估計輸入的均值和標準差。 它經過評估當前小批量輸入的均值和標準差(所以命名爲「批量標準化」)來實現。 整個操做在方程 11-3 中。
在測試時,沒有小批量計算經驗均值和標準差,因此您只需使用整個訓練集的均值和標準差。 這些一般在訓練期間使用移動平均值進行有效計算。 所以,總的來講,每一個批次標準化的層次都學習了四個參數:γ
(標度),β
(偏移),μ
(平均值)和σ
(標準差)。
做者證實,這項技術大大改善了他們試驗的全部深度神經網絡。梯度消失問題大大減小了,他們可使用飽和激活函數,如 tanh 甚至 sigmoid 激活函數。網絡對權重初始化也不那麼敏感。他們可以使用更大的學習率,顯著加快了學習過程。具體地,他們指出,「應用於最早進的圖像分類模型,批標準化用少了 14 倍的訓練步驟實現了相同的精度,以顯著的優點擊敗了原始模型。[...] 使用批量標準化的網絡集合,咱們改進了 ImageNet 分類上的最佳公佈結果:達到4.9% 的前5個驗證錯誤(和 4.8% 的測試錯誤),超出了人類評估者的準確性。批量標準化也像一個正則化項同樣,減小了對其餘正則化技術的需求(如本章稍後描述的 dropout).
然而,批量標準化的確會增長模型的複雜性(儘管它不須要對輸入數據進行標準化,由於第一個隱藏層會照顧到這一點,只要它是批量標準化的)。 此外,還存在運行時間的損失:因爲每層所需的額外計算,神經網絡的預測速度較慢。 因此,若是你須要預測閃電般快速,你可能想要檢查普通ELU + He初始化執行以前如何執行批量標準化。
您可能會發現,訓練起初至關緩慢,而漸變降低正在尋找每層的最佳尺度和偏移量,但一旦找到合理的好值,它就會加速。
使用 TensorFlow 實現批量標準化
TensorFlow 提供了一個batch_normalization()
函數,它簡單地對輸入進行居中和標準化,可是您必須本身計算平均值和標準差(基於訓練期間的小批量數據或測試過程當中的完整數據集) 做爲這個函數的參數,而且還必須處理縮放和偏移量參數的建立(並將它們傳遞給此函數)。 這是可行的,但不是最方便的方法。 相反,你應該使用batch_norm()
函數,它爲你處理全部這些。 您能夠直接調用它,或者告訴fully_connected()
函數使用它,以下面的代碼所示:
注意:本書使用tensorflow.contrib.layers.batch_norm()
而不是tf.layers.batch_normalization()
(本章寫做時不存在)。 如今最好使用tf.layers.batch_normalization()
,由於contrib
模塊中的任何內容均可能會改變或被刪除,恕不另行通知。 咱們如今不使用batch_norm()
函數做爲fully_connected()
函數的正則化參數,而是使用batch_normalization()
,並明確地建立一個不一樣的層。 參數有些不一樣,特別是:
decay
改名爲
momentum
is_training
被重命名爲
training
updates_collections
被刪除:批量標準化所需的更新操做被添加到
UPDATE_OPS
集合中,而且您須要在訓練期間明確地運行這些操做(請參閱下面的執行階段)
scale = True
,由於這是默認值。
還要注意,爲了在每一個隱藏層激活函數以前運行批量標準化,咱們手動應用 RELU 激活函數,在批量規範層以後。注意:因爲tf.layers.dense()
函數與本書中使用的tf.contrib.layers.arg_scope()
不兼容,咱們如今使用 python 的functools.partial()
函數。 它能夠很容易地建立一個my_dense_layer()
函數,只需調用tf.layers.dense()
,並自動設置所需的參數(除非在調用my_dense_layer()
時覆蓋它們)。 如您所見,代碼保持很是類似。
import tensorflow as tf
n_inputs = 28 * 28
n_hidden1 = 300
n_hidden2 = 100
n_outputs = 10
X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
training = tf.placeholder_with_default(False, shape=(), name='training')
hidden1 = tf.layers.dense(X, n_hidden1, name="hidden1")
bn1 = tf.layers.batch_normalization(hidden1, training=training, momentum=0.9)
bn1_act = tf.nn.elu(bn1)
hidden2 = tf.layers.dense(bn1_act, n_hidden2, name="hidden2")
bn2 = tf.layers.batch_normalization(hidden2, training=training, momentum=0.9)
bn2_act = tf.nn.elu(bn2)
logits_before_bn = tf.layers.dense(bn2_act, n_outputs, name="outputs")
logits = tf.layers.batch_normalization(logits_before_bn, training=training,
momentum=0.9)
X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
training = tf.placeholder_with_default(False, shape=(), name='training')複製代碼
爲了不一遍又一遍重複相同的參數,咱們可使用 Python 的partial()
函數:
from functools import partial
my_batch_norm_layer = partial(tf.layers.batch_normalization,
training=training, momentum=0.9)
hidden1 = tf.layers.dense(X, n_hidden1, name="hidden1")
bn1 = my_batch_norm_layer(hidden1)
bn1_act = tf.nn.elu(bn1)
hidden2 = tf.layers.dense(bn1_act, n_hidden2, name="hidden2")
bn2 = my_batch_norm_layer(hidden2)
bn2_act = tf.nn.elu(bn2)
logits_before_bn = tf.layers.dense(bn2_act, n_outputs, name="outputs")
logits = my_batch_norm_layer(logits_before_bn)複製代碼
完整代碼
from functools import partial
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
if __name__ == '__main__':
n_inputs = 28 * 28
n_hidden1 = 300
n_hidden2 = 100
n_outputs = 10
mnist = input_data.read_data_sets("/tmp/data/")
batch_norm_momentum = 0.9
learning_rate = 0.01
X = tf.placeholder(tf.float32, shape=(None, n_inputs), name = 'X')
y = tf.placeholder(tf.int64, shape=None, name = 'y')
training = tf.placeholder_with_default(False, shape=(), name = 'training')#給Batch norm加一個placeholder
with tf.name_scope("dnn"):
he_init = tf.contrib.layers.variance_scaling_initializer()
#對權重的初始化
my_batch_norm_layer = partial(
tf.layers.batch_normalization,
training = training,
momentum = batch_norm_momentum
)
my_dense_layer = partial(
tf.layers.dense,
kernel_initializer = he_init
)
hidden1 = my_dense_layer(X ,n_hidden1 ,name = 'hidden1')
bn1 = tf.nn.elu(my_batch_norm_layer(hidden1))
hidden2 = my_dense_layer(bn1, n_hidden2, name = 'hidden2')
bn2 = tf.nn.elu(my_batch_norm_layer(hidden2))
logists_before_bn = my_dense_layer(bn2, n_outputs, name = 'outputs')
logists = my_batch_norm_layer(logists_before_bn)
with tf.name_scope('loss'):
xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels = y, logits= logists)
loss = tf.reduce_mean(xentropy, name = 'loss')
with tf.name_scope('train'):
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
training_op = optimizer.minimize(loss)
with tf.name_scope("eval"):
correct = tf.nn.in_top_k(logists, y, 1)
accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))
init = tf.global_variables_initializer()
saver = tf.train.Saver()
n_epoches = 20
batch_size = 200
# 注意:因爲咱們使用的是 tf.layers.batch_normalization() 而不是 tf.contrib.layers.batch_norm()(如本書所述),
# 因此咱們須要明確運行批量規範化所需的額外更新操做(sess.run([ training_op,extra_update_ops], ...)。
extra_update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.Session() as sess:
init.run()
for epoch in range(n_epoches):
for iteraton in range(mnist.train.num_examples//batch_size):
X_batch, y_batch = mnist.train.next_batch(batch_size)
sess.run([training_op,extra_update_ops],
feed_dict={training:True, X:X_batch, y:y_batch})
accuracy_val = accuracy.eval(feed_dict= {X:mnist.test.images,
y:mnist.test.labels})
print(epoch, 'Test accuracy:', accuracy_val)複製代碼
什麼!? 這對 MNIST 來講不是一個很好的準確性。 固然,若是你訓練的時間越長,準確性就越好,可是因爲這樣一個淺的網絡,批量範數和 ELU 不太可能產生很是積極的影響:它們大部分都是爲了更深的網絡而發光。請注意,您還能夠訓練操做取決於更新操做:
with tf.name_scope("train"):
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
extra_update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(extra_update_ops):
training_op = optimizer.minimize(loss)複製代碼
這樣,你只須要在訓練過程當中評估training_op,TensorFlow也會自動運行更新操做:
sess.run(training_op, feed_dict={training: True, X: X_batch, y: y_batch})複製代碼
本文來自阿里雲開發者社區
原文連接:https://developer.aliyun.com/article/603625?utm_content=g_1000073407