CHAPTER 11-Training Deep Neural Nets-part4

本篇文章是個人翻譯的,如有商業用途,請通知本人謝謝.

Avoiding Overftting Through Regularization

有四個參數,我可以fit一個大象,五個我可以讓他擺動他的象鼻。

—John von Neumann,cited by Enrico Fermi in Nature 427

深度神經網絡通常具有數以萬計的參數,有時甚至是數百萬。 有了這麼多的參數,網絡擁有難以置信的自由度,可以適應各種複雜的數據集。 但是這個很大的靈活性也意味着它很容易過度訓練集。有了數以百萬計的參數,你可以適應整個動物園。 在本節中,我們將介紹一些最流行的神經網絡正則化技術,以及如何用TensorFlow實現它們:早期停止,l1和l2正則化,drop out,最大範數正則化和數據增強。


Early Stopping
爲避免過度擬合訓練集,一個很好的解決方案就是儘早停止訓練(在第4章中介紹):只要在訓練集的性能開始下降時中斷訓練。

與TensorFlow實現方法之一是評估其對設置定期(例如,每50步)驗證模型,並保存一個“winner”的快照,如果它優於以前“winner”的快照。 計算自上次“winner”快照保存以來的步數,並在達到某個限制時(例如2000步)中斷訓練。 然後恢復最後的“winner”快照。

雖然早期停止在實踐中運行良好,但是通過將其與其他正則化技術相結合,您通常可以在網絡中獲得更高的性能。後恢復最後的“winner”快照。


1and ℓ2Regularization 

就像你在第4章中對簡單線性模型所做的那樣,你可以使用l1和l2正則化約束一個神經網絡的連接權重(但通常不是它的偏置)。
使用TensorFlow做到這一點的一種方法是簡單地將適當的正則化術語添加到您的成本函數中。 例如,假設您只有一個權重爲weight1的隱藏層和一個權重爲weight2的輸出層,那麼您可以像這樣應用l1正則化:

我們可以將正則化函數傳遞給tf.layers.dense()函數,該函數將使用它來創建計算正則化損失的操作,並將這些操作添加到正則化損失集合中。 開始和上面一樣:

n_inputs = 28 * 28  # MNIST
n_hidden1 = 300
n_hidden2 = 50
n_outputs = 10

X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int64, shape=(None), name="y")
接下來,我們將使用Python partial()函數來避免一遍又一遍地重複相同的參數。 請注意,我們設置了內核正則化參數(正則化函數有l1_regularizer(),l2_regularizer(), andl1_l2_regularizer() 

scale = 0.001
my_dense_layer = partial(
    tf.layers.dense, activation=tf.nn.relu,
    kernel_regularizer=tf.contrib.layers.l1_regularizer(scale))

with tf.name_scope("dnn"):
    hidden1 = my_dense_layer(X, n_hidden1, name="hidden1")
    hidden2 = my_dense_layer(hidden1, n_hidden2, name="hidden2")
    logits = my_dense_layer(hidden2, n_outputs, activation=None,
                            name="outputs")
該代碼創建了一個具有兩個隱藏層和一個輸出層的神經網絡,並且還在圖中創建節點以計算與每個層的權重相對應的l1正則化損失。 TensorFlow會自動將這些節點添加到包含所有正則化損失的特殊集合中。 您只需要將這些正則化損失添加到您的整體損失中,如下所示:

接下來,我們必須將正則化損失加到基本損失上:

with tf.name_scope("loss"):                                     # not shown in the book
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(  # not shown
        labels=y, logits=logits)                                # not shown
    base_loss = tf.reduce_mean(xentropy, name="avg_xentropy")   # not shown
    reg_losses = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
    loss = tf.add_n([base_loss] + reg_losses, name="loss")
其餘的和往常一樣:

with tf.name_scope("eval"):
    correct = tf.nn.in_top_k(logits, y, 1)
    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name="accuracy")

learning_rate = 0.01

with tf.name_scope("train"):
    optimizer = tf.train.GradientDescentOptimizer(learning_rate)
    training_op = optimizer.minimize(loss)

init = tf.global_variables_initializer()
saver = tf.train.Saver()

n_epochs = 20
batch_size = 200

with tf.Session() as sess:
    init.run()
    for epoch in range(n_epochs):
        for iteration in range(mnist.train.num_examples // batch_size):
            X_batch, y_batch = mnist.train.next_batch(batch_size)
            sess.run(training_op, feed_dict={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)

    save_path = saver.save(sess, "./my_model_final.ckpt")
不要忘記把正常化的損失加在你的整體損失上,否則就會被忽略。


Dropout
深度神經網絡最流行的正則化技術可以說是drop out。 它由GE Hinton於2012年提出,並在Nitish Srivastava等人的論文中進一步詳細描述,並且已被證明是非常成功的:即使是現有技術的神經網絡,通過添加drop out。 這聽起來可能不是很多,但是當一個模型已經具有95%的準確率時,獲得2%的準確度提升意味着將誤差率降低近40%(從5%誤差降至大約3%)。

這是一個相當簡單的算法:在每個訓練步驟中,每個神經元(包括輸入神經元,但不包括輸出神經元)都有一個暫時“退出”的概率p,這意味着在這個訓練步驟中它將被完全忽略, 在下一步可能會激活(見圖11-9)。 超參數p稱爲丟失率,通常設爲50%。 訓練後,神經元不會再下降。 這就是全部(除了我們將要討論的技術細節)。


一開始這個技術是相當粗魯,這是相當令人驚訝的。如果一個公司的員工每天早上被告知要擲硬幣來決定是否上班,公司的表現會不會更好呢?那麼,誰知道;也許會!公司顯然將被迫適應這樣的組織構架;它不能依靠任何一個人填寫咖啡機或執行任何其他關鍵任務,所以這個專業知識將不得不分散在幾個人身上。員工必須學會與其他的許多同事合作,而不僅僅是其中的一小部分。該公司將變得更有彈性。如果一個人放棄了,那就沒有什麼區別了。目前還不清楚這個想法是否真的可以在公司實行,但它確實對於神經網絡是可以的。神經元退化訓練不能與其相鄰的神經元共同適應;他們必須儘可能讓自己變得有用。他們也不能過分依賴一些輸入神經元;他們必須注意他們的每個輸入神經元。他們最終對輸入的微小變化會不太敏感。最後,你會得到一個更強大的網絡,更好地推廣。

瞭解dropout的另一種方法是認識到每個訓練步驟都會產生一個獨特的神經網絡。 由於每個神經元可以存在或不存在,總共有2 ^ N個可能的網絡(其中N是可丟棄神經元的總數)。 這是一個巨大的數字,實際上不可能對同一個神經網絡進行兩次採樣。 一旦你運行了10,000個訓練步驟,你基本上已經訓練了10,000個不同的神經網絡(每個神經網絡只有一個訓練實例)。 這些神經網絡顯然不是獨立的,因爲它們共享許多權重,但是它們都是不同的。 由此產生的神經網絡可以看作是所有這些較小的神經網絡的平均集合。

有一個小而重要的技術細節。 假設p = 50,在這種情況下,在測試期間,在訓練期間神經元將被連接到兩倍於(平均)的輸入神經元。 爲了彌補這個事實,我們需要在訓練之後將每個神經元的輸入連接權重乘以0.5。 如果我們不這樣做,每個神經元的總輸入信號大概是網絡訓練的兩倍,而且不太可能表現良好。 更一般地說,我們需要將每個輸入連接權重乘以訓練後的保持概率(1-p)。 或者,我們可以在訓練過程中將每個神經元的輸出除以保持概率(這些替代方案並不完全等價,但它們工作得同樣好)。

要使用TensorFlow實現壓縮,可以簡單地將dropout()函數應用於輸入層和每個隱藏層的輸出。 在訓練過程中,這個功能隨機丟棄一些項目(將它們設置爲0),並用保留概率來劃分剩餘項目。 訓練結束後,這個功能什麼都不做。 下面的代碼將丟失正則化應用於我們的三層神經網絡: 

注意:本書使用tf.contrib.layers.dropout()而不是tf.layers.dropout()(本章寫作時不存在)。 現在最好使用tf.layers.dropout(),因爲contrib模塊中的任何內容都可能會改變或被刪除,恕不另行通知。 tf.layers.dropout()函數幾乎與tf.contrib.layers.dropout()函數相同,只是有一些細微差別。 最重要的是:

  • 您必須指定丟失率(率)而不是保持概率(keep_prob),其中rate簡單地等於1 - keep_prob
  • is_training參數被重命名爲training。

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

dropout_rate = 0.5  # == 1 - keep_prob
X_drop = tf.layers.dropout(X, dropout_rate, training=training)

with tf.name_scope("dnn"):
    hidden1 = tf.layers.dense(X_drop, n_hidden1, activation=tf.nn.relu,
                              name="hidden1")
    hidden1_drop = tf.layers.dropout(hidden1, dropout_rate, training=training)
    hidden2 = tf.layers.dense(hidden1_drop, n_hidden2, activation=tf.nn.relu,
                              name="hidden2")
    hidden2_drop = tf.layers.dropout(hidden2, dropout_rate, training=training)
    logits = tf.layers.dense(hidden2_drop, n_outputs, name="outputs")

with tf.name_scope("loss"):
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
    loss = tf.reduce_mean(xentropy, name="loss")

with tf.name_scope("train"):
    optimizer = tf.train.MomentumOptimizer(learning_rate, momentum=0.9)
    training_op = optimizer.minimize(loss)    

with tf.name_scope("eval"):
    correct = tf.nn.in_top_k(logits, y, 1)
    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))
    
init = tf.global_variables_initializer()
saver = tf.train.Saver()

n_epochs = 20
batch_size = 50

with tf.Session() as sess:
    init.run()
    for epoch in range(n_epochs):
        for iteration in range(mnist.train.num_examples // batch_size):
            X_batch, y_batch = mnist.train.next_batch(batch_size)
            sess.run(training_op, feed_dict={training: True, X: X_batch, y: y_batch})
        acc_test = accuracy.eval(feed_dict={X: mnist.test.images, y: mnist.test.labels})
        print(epoch, "Test accuracy:", acc_test)

    save_path = saver.save(sess, "./my_model_final.ckpt")
你想在tensorflow.contrib.layers中使用dropout()函數,而不是tensorflow.nn中的那個。 第一個在不訓練的時候關掉(沒有操作),這是你想要的,而第二個不是

如果觀察到模型過度擬合,則可以增加dropout率(即,減少keep_prob超參數)。 相反,如果模型不適合訓練集,則應嘗試降低dropout率(即增加keep_prob)。 它也可以幫助增加大層的dropout率,並減少小層的dropout率。

dropout傾向於顯着減緩收斂,但通常會導致一個好得多的模型,在適當調整之後。 所以,這通常是值得去付出額外的時間和精力的。


Max-Norm Regularization
另一種在神經網絡中非常流行的正則化技術被稱爲最大範數正則化:對於每個神經元,它約束輸入連接的權重w,使得,其中r是最大範數超參數,是l2範數。

我們通常通過在每個訓練步驟之後計算來實現這個約束,並且如果需要的話可以剪切W.

減少r增加了正則化的數量,並有助於減少過度配合。 Maxnorm正則化還可以幫助減輕消失/爆炸梯度問題(如果您不使用批量標準化)。

讓我們回到MNIST的簡單而簡單的神經網絡,只有兩個隱藏層:

n_inputs = 28 * 28
n_hidden1 = 300
n_hidden2 = 50
n_outputs = 10

learning_rate = 0.01
momentum = 0.9

X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int64, shape=(None), name="y")

with tf.name_scope("dnn"):
    hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu, name="hidden1")
    hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=tf.nn.relu, name="hidden2")
    logits = tf.layers.dense(hidden2, n_outputs, name="outputs")

with tf.name_scope("loss"):
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
    loss = tf.reduce_mean(xentropy, name="loss")

with tf.name_scope("train"):
    optimizer = tf.train.MomentumOptimizer(learning_rate, momentum)
    training_op = optimizer.minimize(loss)    

with tf.name_scope("eval"):
    correct = tf.nn.in_top_k(logits, y, 1)
    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))
接下來,讓我們來處理第一個隱藏層的權重,並創建一個操作,使用clip_by_norm()函數計算剪切後的權重。 然後我們創建一個賦值操作來將權值賦給權值變量:

threshold = 1.0
weights = tf.get_default_graph().get_tensor_by_name("hidden1/kernel:0")
clipped_weights = tf.clip_by_norm(weights, clip_norm=threshold, axes=1)
clip_weights = tf.assign(weights, clipped_weights)
我們也可以爲第二個隱藏層做到這一點:

weights2 = tf.get_default_graph().get_tensor_by_name("hidden2/kernel:0")
clipped_weights2 = tf.clip_by_norm(weights2, clip_norm=threshold, axes=1)
clip_weights2 = tf.assign(weights2, clipped_weights2)

讓我們添加一個初始化器和一個保存器:

init = tf.global_variables_initializer()
saver = tf.train.Saver()

現在我們可以訓練模型。 與往常一樣,除了在運行training_op之後,我們運行clip_weights和clip_weights2操作:

n_epochs = 20
batch_size = 50

with tf.Session() as sess:                                              # not shown in the book
    init.run()                                                          # not shown
    for epoch in range(n_epochs):                                       # not shown
        for iteration in range(mnist.train.num_examples // batch_size):  # not shown
            X_batch, y_batch = mnist.train.next_batch(batch_size)       # not shown
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
            clip_weights.eval()
            clip_weights2.eval()                                        # not shown
        acc_test = accuracy.eval(feed_dict={X: mnist.test.images,       # not shown
                                            y: mnist.test.labels})      # not shown
        print(epoch, "Test accuracy:", acc_test)                        # not shown

    save_path = saver.save(sess, "./my_model_final.ckpt")               # not shown

上面的實現很簡單,工作正常,但有點麻煩。 更好的方法是定義一個max_norm_regularizer()函數:

def max_norm_regularizer(threshold, axes=1, name="max_norm",
                         collection="max_norm"):
    def max_norm(weights):
        clipped = tf.clip_by_norm(weights, clip_norm=threshold, axes=axes)
        clip_weights = tf.assign(weights, clipped, name=name)
        tf.add_to_collection(collection, clip_weights)
        return None # there is no regularization loss term
    return max_norm

然後你可以調用這個函數來得到一個最大規範調節器(與你想要的閾值)。 當你創建一個隱藏層時,你可以將這個正規化器傳遞給kernel_regularizer參數:

n_inputs = 28 * 28
n_hidden1 = 300
n_hidden2 = 50
n_outputs = 10

learning_rate = 0.01
momentum = 0.9

X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int64, shape=(None), name="y")


max_norm_reg = max_norm_regularizer(threshold=1.0)
with tf.name_scope("dnn"):
    hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu,
                              kernel_regularizer=max_norm_reg, name="hidden1")
    hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=tf.nn.relu,
                              kernel_regularizer=max_norm_reg, name="hidden2")
    logits = tf.layers.dense(hidden2, n_outputs, name="outputs")

with tf.name_scope("loss"):
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
    loss = tf.reduce_mean(xentropy, name="loss")

with tf.name_scope("train"):
    optimizer = tf.train.MomentumOptimizer(learning_rate, momentum)
    training_op = optimizer.minimize(loss)    

with tf.name_scope("eval"):
    correct = tf.nn.in_top_k(logits, y, 1)
    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))

init = tf.global_variables_initializer()
saver = tf.train.Saver()

訓練與往常一樣,除了每次訓練後必須運行重量裁剪操作:

請注意,最大範數正則化不需要在整體損失函數中添加正則化損失項,所以max_norm()函數返回None。 但是,在每個訓練步驟之後,仍需要運行clip_weights操作,因此您需要能夠掌握它。 這就是爲什麼max_norm()函數將clip_weights節點添加到max-norm剪裁操作的集合中的原因。您需要獲取這些裁剪操作並在每個訓練步驟後運行它們:

n_epochs = 20
batch_size = 50

clip_all_weights = tf.get_collection("max_norm")

with tf.Session() as sess:
    init.run()
    for epoch in range(n_epochs):
        for iteration in range(mnist.train.num_examples // batch_size):
            X_batch, y_batch = mnist.train.next_batch(batch_size)
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
            sess.run(clip_all_weights)
        acc_test = accuracy.eval(feed_dict={X: mnist.test.images,     # not shown in the book
                                            y: mnist.test.labels})    # not shown
        print(epoch, "Test accuracy:", acc_test)                      # not shown

    save_path = saver.save(sess, "./my_model_final.ckpt")             # not shown

Data Augmentation (數據擴張)
最後一個正規化技術,數據增強,包括從現有的訓練實例中產生新的訓練實例,人爲地增加了訓練集的大小。 這將減少過度擬合,使之成爲正規化技術。 訣竅是生成逼真的訓練實例; 理想情況下,一個人不應該能夠分辨出哪些是生成的,哪些不是生成的。 而且,簡單地加白噪聲也無濟於事。 你應用的修改應該是可以學習的(白噪聲不是)。

例如,如果您的模型是爲了分類蘑菇圖片,您可以稍微移動,旋轉和調整訓練集中的每個圖片的大小,並將結果圖片添加到訓練集(見圖11-10)。 這迫使模型更能容忍圖片中蘑菇的位置,方向和大小。 如果您希望模型對光照條件更加寬容,則可以類似地生成具有各種對比度的許多圖像。 假設蘑菇是對稱的,你也可以水平翻轉圖片。 通過結合這些轉換,可以大大增加訓練集的大小。


在培訓期間通常優先生成訓練實例,而不是浪費存儲空間和網絡帶寬。 TensorFlow提供了多種圖像處理操作,例如移調(shift),旋轉,調整大小,翻轉和裁剪,以及調整亮度,對比度,飽和度和色調(請參閱API文檔以獲取更多詳細信息)。 這可以很容易地爲圖像數據集實現數據增強。

訓練非常深的神經網絡的另一個強大的技術是添加跳過連接(跳過連接是將層的輸入添加到更高層的輸出時)。 當我們談論深度殘差網絡時,我們將在第13章中探討這個想法。


Practical Guidelines (實際指導)
在本章中,我們已經涵蓋了很多技術,你可能想知道應該使用哪些技術。 表11-2中的配置在大多數情況下都能正常工作。


當然,如果你能找到解決類似問題的方法,你應該嘗試重用預訓練的神經網絡的一部分。

這個默認配置可能需要調整:

  • 如果你找不到一個好的學習速度(收斂速度太慢,所以你增加了訓練速度,現在收斂速度很快,但是網絡的準確性不是最理想的),那麼你可以嘗試添加一個學習計劃,如指數衰減。
  • 如果你的訓練集太小,你可以實現數據增強。
  • 如果你需要一個稀疏的模型,你可以添加一個正則化到混合(並可以選擇在訓練後將微小的權重歸零)。 如果您需要更稀疏的模型,您可以嘗試使用FTRL而不是Adam優化以及l1正則化。
  • 如果在運行時需要快速模型,則可能需要刪除批處理標準化,並可能用leakyReLU替換ELU激活函數。 有一個稀疏的模型也將有所幫助。

有了這些指導方針,你現在已經準備好訓練非常深的網絡 - 好吧,如果你非常有耐心的話,那就是! 如果使用單臺機器,則可能需要等待幾天甚至幾個月才能完成培訓。 在下一章中,我們將討論如何使用分佈式TensorFlow在許多服務器和GPU上訓練和運行模型。








發佈了77 篇原創文章 · 獲贊 25 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章