CHAPTER 10-Introduction to Artifcial Neural Networks

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

使用普通TensorFlow訓練DNN

如果您想要更好地控制網絡架構,您可能更喜歡使用TensorFlow的較低級別的Python API(在第9章中介紹)。 在本節中,我們將使用與之前的API相同的模型,我們將實施Minibatch 梯度下降來在MNIST數據集上進行訓練。 第一步是建設階段,構建TensorFlow圖。 第二步是執行階段,您實際運行計算圖譜來訓練模型。


Construction Phase
開始吧。 首先我們需要導入tensorflow庫。 然後我們必須指定輸入和輸出的數量,並設置每個層中隱藏的神經元數量:

import tensorflow as tf
n_inputs = 28*28 # MNIST
n_hidden1 = 300
n_hidden2 = 100
n_outputs = 10

接下來,與第9章一樣,您可以使用佔位符節點來表示訓練數據和目標。 X的形狀僅有部分被定義。 我們知道它將是一個2D張量(即一個矩陣),沿着第一個維度的實例和第二個維度的特徵,我們知道特徵的數量將是28×28(每像素一個特徵) 但是我們不知道每個訓練批次將包含多少個實例。 所以X的形狀是(None,n_inputs)。 同樣,我們知道y將是一個1D張量,每個實例有一個入口,但是我們再次不知道在這一點上訓練批次的大小,所以形狀(None)。

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

現在讓我們創建一個實際的神經網絡。 佔位符X將作爲輸入層; 在執行階段,它將一次更換一個訓練批次(注意訓練批中的所有實例將由神經網絡同時處理)。 現在您需要創建兩個隱藏層和輸出層。 兩個隱藏的層幾乎相同:它們只是它們所連接的輸入和它們包含的神經元的數量不同。 輸出層也非常相似,但它使用softmax激活功能而不是ReLU激活功能。 所以讓我們創建一個neuron_layer()  函數,我們將一次創建一個圖層。 它將需要參數來指定輸入,神經元數量,激活功能和圖層的名稱:


def neuron_layer(X, n_neurons, name, activation=None):
    with tf.name_scope(name):
        n_inputs = int(X.get_shape()[1])
        stddev = 2 / np.sqrt(n_inputs)
        init = tf.truncated_normal((n_inputs, n_neurons), stddev=stddev)
        W = tf.Variable(init, name="weights")
        b = tf.Variable(tf.zeros([n_neurons]), name="biases")
        z = tf.matmul(X, W) + b
        if activation == "relu":
            return tf.nn.relu(z)
        else:
            return z


我們逐行瀏覽這個代碼:
1.首先,我們使用名稱範圍來創建每層的名稱:它將包含該神經元層的所有計算節點。 這是可選的,但如果節點組織良好,則TensorBoard圖形將會更加出色。
2.接下來,我們通過查找輸入矩陣的形狀並獲得第二個維度的大小來獲得輸入數量(第一個維度用於實例)。

3. 接下來的三行創建一個保存權重矩陣的W變量。 它將是包含每個輸入和每個神經元之間的所有連接權重的2D張量;因此,它的形狀將是(n_inputs,n_neurons)。它將被隨機初始化,使用具有標準差爲的截斷的正態(高斯)分佈(使用截斷的正態分佈而不是常規正態分佈確保不會有任何大的權重,這可能會減慢培訓。).使用這個特定的標準差有助於算法的收斂速度更快(我們將在第11章中進一步討論這一點),這是對神經網絡的微小調整之一,對它們的效率產生了巨大的影響)。 重要的是爲所有隱藏層隨機初始化連接權重,以避免梯度下降算法無法中斷的任何對稱性。

例如,如果將所有權重設置爲0,則所有神經元將輸出0,並且給定隱藏層中的所有神經元的誤差梯度將相同。 然後,梯度下降步驟將在每個層中以相同的方式更新所有權重,因此它們將保持相等。 換句話說,儘管每層有數百個神經元,你的模型就像每層只有一個神經元一樣。 

4. 下一行創建一個偏差的b變量,初始化爲0(在這種情況下無對稱問題),每個神經元有一個偏置參數。

5. 然後我們創建一個子圖來計算z = X·W + b。 該向量化實現將有效地計算輸入的加權和加上層中每個神經元的偏置,對於批次中的所有實例,僅需一次.

6.最後,如果激活參數設置爲“relu”,則代碼返回relu(z)(即max(0,z)),否則它只返回z。


好了,現在你有一個很好的函數來創建一個神經元層。 讓我們用它來創建深層神經網絡! 第一個隱藏層以X爲輸入。 第二個將第一個隱藏層的輸出作爲其輸入。 最後,輸出層將第二個隱藏層的輸出作爲其輸入。

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

請注意,爲了清楚起見,我們再次使用名稱範圍。 還要注意,logit是在通過softmax激活函數之前神經網絡的輸出:爲了優化,我們稍後將處理softmax計算。

正如你所期望的,TensorFlow有許多方便的功能來創建標準的神經網絡層,所以通常不需要像我們剛纔那樣定義你自己的neuron_layer()函數。 例如,TensorFlow的fully_connected()函數創建一個完全連接的層,其中所有輸入都連接到圖層中的所有神經元。 它使用正確的初始化策略來負責創建權重和偏置變量,並且默認情況下使用ReLU激活功能(我們可以使用activate_fn參數來更改它)。 正如我們將在第11章中看到的,它還支持正則化和歸一化參數。 我們來調整上面的代碼來使用fully_connected()函數,而不是我們的neuron_layer()函數。 只需導入該功能,並使用以下代碼替換dnn構建部分:

    from tensorflow.contrib.layers import fully_connected
    with tf.name_scope("dnn"):
        hidden1 = fully_connected(X, n_hidden1, scope="hidden1")
        hidden2 = fully_connected(hidden1, n_hidden2, scope="hidden2")
        logits = fully_connected(hidden2, n_outputs, scope="outputs",
                                    activation_fn=None)

tensorflow.contrib包包含許多有用的功能,但它是一個尚未分級成爲主要TensorFlow API一部分的實驗代碼的地方。 因此,full_connected()函數(和任何其他contrib代碼)可能會在將來更改或移動。


使用dense()代替neuron_layer()

注意:本書使用tensorflow.contrib.layers.fully_connected()而不是tf.layers.dense()(本章編寫時不存在)。 

現在最好使用tf.layers.dense(),因爲contrib模塊中的任何內容可能會更改或刪除,恕不另行通知。 dense()函數與fully_connected()函數幾乎相同,除了一些細微的差別:
幾個參數被重命名:scope變爲名稱,activation_fn變爲激活(同樣_fn後綴從其他參數(如normalizer_fn)中刪除),weights_initializer成爲kernel_initializer等。默認激活現在是無,而不是tf.nn.relu。 第11章還介紹了更多的差異。

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

現在我們已經有了神經網絡模型,我們需要定義我們用來訓練的損失函數。 正如我們在第4章中對Softmax迴歸所做的那樣,我們將使用交叉熵。 正如我們之前討論的,交叉熵將懲罰估計目標類的概率較低的模型。 TensorFlow提供了幾種計算交叉熵的功能。 我們將使用sparse_softmax_cross_entropy_with_logits():它根據“logit”計算交叉熵(即,在通過softmax激活函數之前的網絡輸出),並且期望以0到-1數量的整數形式的標籤(在我們的例子中,從0到9)。 這將給我們一個包含每個實例的交叉熵的1D張量。 然後,我們可以使用TensorFlow的reduce_mean()函數來計算所有實例的平均交叉熵。

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

該sparse_softmax_cross_entropy_with_logits()函數等同於應用SOFTMAX激活功能,然後計算交叉熵,但它更高效,它妥善照顧的角落情況下,比如logits等於0,這就是爲什麼我們沒有較早的應用SOFTMAX激活函數。 還有稱爲softmax_cross_entropy_with_logits()的另一個函數,該函數在標籤one-hot載體的形式(而不是整數0至類的數目減1)。

我們有神經網絡模型,我們有成本函數,現在我們需要定義一個GradientDescentOptimizer來調整模型參數以最小化損失函數。 沒什麼新鮮的; 就像我們在第9章中所做的那樣:

learning_rate = 0.01

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


建模階段的最後一個重要步驟是指定如何評估模型。 我們將簡單地將精度用作我們的績效指標。 首先,對於每個實例,通過檢查最高logit是否對應於目標類別來確定神經網絡的預測是否正確。 爲此,您可以使用in_top_k()函數。 這返回一個充滿布爾值的1D張量,因此我們需要將這些布爾值轉換爲浮點數,然後計算平均值。 這將給我們網絡的整體準確性.

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

而且,像往常一樣,我們需要創建一個初始化所有變量的節點,我們還將創建一個Saver來將我們訓練有素的模型參數保存到磁盤中:

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

唷! 建模階段結束。 這是不到40行代碼,但相當激烈:我們爲輸入和目標創建佔位符,我們創建了一個構建神經元層的函數,我們用它來創建DNN,我們定義了成本函數,我們 創建了一個優化器,最後定義了性能指標。 現在到執行階段。

Execution Phase 

這部分要短得多,更簡單。 首先,我們加載MNIST。 我們可以像之前的章節那樣使用ScikitLearn,但是TensorFlow提供了自己的幫助器來獲取數據,將其縮放(0到1之間),將它洗牌,並提供一個簡單的功能來一次加載一個小批量:

from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets("/tmp/data/")
現在我們定義我們要運行的迭代數,以及小批量的大小:

n_epochs = 10001
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={X: X_batch, y: y_batch})
        acc_train = accuracy.eval(feed_dict={X: X_batch, y: y_batch})
        acc_test = accuracy.eval(feed_dict={X: mnist.test.images, y: mnist.test.labels})
        print(epoch, "Train accuracy:", acc_train, "Test accuracy:", acc_test)

    save_path = saver.save(sess, "./my_model_final.ckpt")
該代碼打開一個TensorFlow會話,並運行初始化所有變量的init節點。 然後它運行的主要訓練循環:在每個時期,通過一些小批次的對應於訓練集的大小的代碼進行迭代。 每個小批量通過next_batch()方法獲取,然後代碼簡單地運行訓練操作,爲當前的小批量輸入數據和目標提供。 接下來,在每個時期結束時,代碼評估最後一個小批量和完整訓練集上的模型,並打印出結果。 最後,模型參數保存到磁盤。

Using the Neural Network
現在神經網絡被訓練了,你可以用它進行預測。 爲此,您可以重複使用相同的建模階段,但是更改執行階段,如下所示:

with tf.Session() as sess:
    saver.restore(sess, "./my_model_final.ckpt") # or better, use save_path
    X_new_scaled = mnist.test.images[:20]
    Z = logits.eval(feed_dict={X: X_new_scaled})
    y_pred = np.argmax(Z, axis=1)
首先,代碼從磁盤加載模型參數。 然後加載一些您想要分類的新圖像。 記住應用與訓練數據相同的特徵縮放(在這種情況下,將其從0縮放到1)。 然後代碼評估對數點節點。 如果您想知道所有估計的類概率,則需要將softmax()函數應用於對數,但如果您只想預測一個類,則可以簡單地選擇具有最高logit值的類(使用 argmax()函數做的伎倆)。

Fine-Tuning Neural Network Hyperparameters


神經網絡的靈活性也是其主要缺點之一:有很多超參數要進行調整。 不僅可以使用任何可想象的網絡拓撲(如何神經元互連),而且即使在簡單的MLP中,您可以更改層數,每層神經元數,每層使用的激活函數類型,weights 初始化邏輯等等。 你怎麼知道什麼組合的超參數是最適合你的任務?

當然,您可以使用具有交叉驗證的網格搜索來查找正確的超參數,就像您在前幾章中所做的那樣,但是由於要調整許多超參數,並且由於在大型數據集上訓練神經網絡需要很多時間, 您只能在合理的時間內探索超參數空間的一小部分。 正如我們在第2章中討論的那樣,使用隨機搜索 http://http://www.jmlr.org/papers/volume13/bergstra12a/bergstra12a.pdf 要好得多。另一個選擇是使用諸如Oscar之類的工具,它可以實現更復雜的算法,以幫助您快速找到一組好的超參數.

它有助於瞭解每個超級參數的值是合理的,因此您可以限制搜索空間。 我們從隱藏層數開始。


Number of Hidden Layers 


對於許多問題,您只需從單個隱藏層開始,您將獲得合理的結果。 實際上已經表明,只有一個隱藏層的MLP可以建模甚至最複雜的功能,只要它具有足夠的神經元。 長期以來,這些事實說服了研究人員,沒有必要調查任何更深層次的神經網絡。 但是他們忽略了這樣一個事實:深層網絡具有比淺層網絡更高的參數效率:他們可以使用比淺網格更少的神經元來建模複雜的函數,使得訓練更快。

要了解爲什麼,假設您被要求使用一些繪圖軟件繪製一個森林,但是您被禁止使用複製/粘貼。 你必須單獨繪製每棵樹,每枝分枝,每葉葉。 如果你可以畫一個葉,複製/粘貼它來繪製一個分支,然後複製/粘貼該分支來創建一個樹,最後複製/粘貼這個樹來製作一個林,你將很快完成。 現實世界的數據通常以這樣一種分層的方式進行結構化,DNN自動利用這一事實:較低的隱藏層模擬低級結構(例如,各種形狀和方向的線段),中間隱藏層將這些低級結構組合到 模型中級結構(例如,正方形,圓形)和最高隱藏層和輸出層將這些中間結構組合在一起,以模擬高級結構(如面)。

這種分層架構不僅可以幫助DNN更快地融合到一個很好的解決方案,而且還可以提高其將其推廣到新數據集的能力。 例如,如果您已經訓練了模型以識別圖片中的臉部,並且您現在想要訓練一個新的神經網絡來識別髮型,那麼您可以通過重新使用第一個網絡的較低層次來啓動訓練。 而不是隨機初始化新神經網絡的前幾層的權重和偏置,您可以將其初始化爲第一個網絡的較低層的權重和偏置的值。這樣,網絡將不必從大多數圖片中低結構中從頭學習; 它只需要學習更高層次的結構(例如髮型)。

總而言之,對於許多問題,您可以從一個或兩個隱藏層開始,它可以正常工作(例如,您可以使用只有一個隱藏層和幾百個神經元,在MNIST數據集上容易達到97%以上的準確度使用兩個具有相同總神經元數量的隱藏層,在大致相同的訓練時間量中精確度爲98%)。對於更復雜的問題,您可以逐漸增加隱藏層的數量,直到您開始覆蓋訓練集。非常複雜的任務,例如大型圖像分類或語音識別,通常需要具有數十個層(或甚至數百個但不完全相連的網絡)的網絡,正如我們將在第13章中看到的那樣),並且需要大量的訓練數據。但是,您將很少從頭開始訓練這樣的網絡:重用預先訓練的最先進的網絡執行類似任務的部分更爲常見。訓練將會更快,需要更少的數據(我們將在第11章中進行討論)


Number of Neurons per Hidden Layer


顯然,輸入和輸出層中神經元的數量由您的任務需要的輸入和輸出類型決定。例如,MNIST任務需要28×28 = 784個輸入神經元和10個輸出神經元。對於隱藏的層次來說,通常的做法是將其設置爲形成一個漏斗,每個層面上的神經元越來越少,原因在於許多低級別功能可以合併成更少的高級功能。例如,MNIST的典型神經網絡可能具有兩個隱藏層,第一個具有300個神經元,第二個具有100個。但是,這種做法現在並不常見,您可以爲所有隱藏層使用相同的大小 - 例如,所有隱藏的層與150個神經元:這樣只用調整一次超參數而不是每層都需要調整(因爲如果每層一樣,比如150,之後調就每層都調成160)。就像層數一樣,您可以嘗試逐漸增加神經元的數量,直到網絡開始過度擬合。一般來說,通過增加每層的神經元數量,可以增加層數,從而獲得更多的消耗。不幸的是,正如你所看到的,找到完美的神經元數量仍然是黑色的藝術.

一個更簡單的方法是選擇一個具有比實際需要的更多層次和神經元的模型,然後使用early stopping 來防止它過度擬合(以及其他正則化技術,特別是drop out,我們將在第11章中看到)。 這被稱爲“拉伸褲”的方法:而不是浪費時間尋找完美匹配您的大小的褲子,只需使用大型伸縮褲,縮小到合適的尺寸。


Activation Functions

在大多數情況下,您可以在隱藏層中使用ReLU激活功能(或其中一個變體,我們將在第11章中看到)。 與其他激活功能相比,計算速度要快一些,而梯度下降在局部最高點上並不會被卡住,因爲它不會對大的輸入值飽和(與邏輯函數或雙曲正切函數相反, 他們容易在1飽和)

對於輸出層,softmax激活函數通常是分類任務的良好選擇(當這些類是互斥的時)。 對於迴歸任務,您完全可以不使用激活函數。

這就是人造神經網絡的這個介紹。 在接下來的章節中,我們將討論訓練非常深的網絡的技術,並分發多個服務器和GPU的培訓。 然後我們將探討一些其他流行的神經網絡架構:卷積神經網絡,復發神經網絡和自動編碼器。


完整代碼

from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
from sklearn.metrics import accuracy_score
import numpy as np



if __name__ == '__main__':
    n_inputs = 28 * 28
    n_hidden1 = 300
    n_hidden2 = 100
    n_outputs = 10


    mnist = input_data.read_data_sets("/tmp/data/")

    X_train = mnist.train.images
    X_test = mnist.test.images
    y_train = mnist.train.labels.astype("int")
    y_test = mnist.test.labels.astype("int")


    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, name='hidden2',
                                  activation= tf.nn.relu)

        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')#所有值求平均

    learning_rate = 0.01

    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(logits ,y ,1)#是否與真值一致 返回布爾值
        accuracy = tf.reduce_mean(tf.cast(correct, tf.float32)) #tf.cast將數據轉化爲0,1序列

    init = tf.global_variables_initializer()


    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={X:X_batch,
                                                y: y_batch})
            acc_train = accuracy.eval(feed_dict={X:X_batch,
                                                y: y_batch})
            acc_test = accuracy.eval(feed_dict={X: mnist.test.images,
                                                y: mnist.test.labels})
            print(epoch, "Train accuracy:", acc_train, "Test accuracy:", acc_test)




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