《基於Scikit-Learn、Keras和TensorFlow的機器學習實戰》:人工神經網絡介紹

2021-02-20 智能航空發動機
本文講解Aurélien Géron所著的《Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow》的第二部分(經網絡及深度學習)的第一章《人工神經網絡介紹》。該書的總目錄如下,分為兩部分:第一部分介紹機器學習的基礎,第二部分介紹神經網絡及深度學習。

鳥類啟發我們飛翔,牛蒡植物啟發了尼龍繩,大自然也激發了許多其他發明。因此,從大腦的結構中尋找如何構造智能機器的靈感似乎是合乎邏輯的。這是啟發人工神經網絡(ANN)的關鍵思想:ANN是一個受我們大腦中發現的生物神經元網絡啟發的機器學習模型。然而,儘管飛機受到鳥類的啟發,但它們不必拍動翅膀。同樣的,ANN 逐漸變得與生物神經元網絡有很大的不同。一些研究者甚至爭辯說,我們應該完全放棄與生物進行類比(例如,說「units」而不是「neurons」),以免我們把我們的創造力限制在生物學系統上。

人工神經網絡是深度學習的核心。它們具有通用性、強大性和可擴展性,使得它們能夠很好地解決大型和高度複雜的機器學習任務,例如分類數十億圖像(例如,谷歌圖像),強大的語音識別服務(例如,蘋果的 Siri),每天將最好的視頻推薦給數百萬的用戶(比如 YouTube),或者學會在Go遊戲中擊敗世界冠軍(DeepMind 的 AlgFaGo)。

本章的第一部分將介紹人工神經網絡,首先快速瀏覽最初的人工神經網絡架構,然後介紹如今被大量使用的多層感知機(mlp)(其他架構將在下一章中探討)。在第二部分中,我們將看看如何使用流行的Keras API實現神經網絡。這是一個設計精美、簡單的高級API,用於構建、訓練、評估和運行神經網絡。但不要被它的簡單性所迷惑:它的表達性和靈活性足以讓您構建各種各樣的神經網絡架構。事實上,對於您的大多數用例來說,它可能已經足夠了。如果您需要額外的靈活性,您總是可以使用它的低級API編寫自定義的Keras組件。但首先,讓我們回到過去看看人工神經網絡是如何誕生的!

1 From Biological to Artificial Neurons —— 從生物神經元到人工神經元

令人驚訝的是,人工神經網絡已經存在了相當長的一段時間:它們最初是由神經生理學家 Warren McCulloch 和數學家 Walter Pitts 在 1943 提出。McCulloch 和 Pitts 在其裡程碑式的論文<<A Logical Calculus of Ideas Immanent in Nervous Activity
>>中提出了一個簡化的計算模型,即動物大腦中的生物神經元如何協同工作,並用邏輯進行複雜的計算。這是第一個人工神經網絡架構。從那時起,正如我們將看到的,許多其他的架構被發明出來。

人工神經網絡的早期成功讓人們普遍相信,我們很快就會與真正的智能機器進行對話。到20世紀60年代,人們已經清楚地知道這一承諾無法實現(至少在相當長一段時間內),因此資金流向了其他地方,ANNs進入了一個漫長的冬天。20 世紀 80 年代初,隨著新的網絡架構的發明和更好的訓練技術的發展,人們對人工神經網絡的興趣也重新燃起。但是進展緩慢,到了 20 世紀 90 年代,其它的強大的機器學習技術的,如支持向量機受到大多數研究者的青睞,因為它們似乎比ANN提供了更好的結果和更強的理論基礎。因此,神經網絡的研究又一次地被推遲了。

我們現在目睹了另一股對 ANN 感興趣的浪潮。這波浪潮會像以前一樣消失嗎?有一些很好的理由相信,這一次的浪潮是不同的,將會對我們的生活產生更深遠的影響:

現在有大量的數據可用於訓練神經網絡,ANN 在許多非常大型、複雜的問題上經常優於其他 ML 技術。

自從 90 年代以來,計算能力的巨大增長使得在合理的時間內訓練大型神經網絡成為可能。這部分是由於摩爾定律(在過去的50年裡,集成電路中元件的數量大約每2年就翻一番),但也得益於遊戲產業,它已經產生了數以百萬計的強大的 GPU 顯卡。此外,雲平臺讓每個人都可以使用這種能力。

改進了訓練算法。公平地說,它們與上世紀 90 年代使用的只是稍微有些不同,但這些相對較小的調整產生了巨大的正面影響。

在實踐中,人工神經網絡的一些理論局限性是良性的。例如,許多人認為人工神經網絡訓練算法很可能陷入局部最優。但事實證明,這在實踐中是相當罕見的(或者如果它發生,它們也通常相當接近全局最優)。

ANN 似乎已經進入了資金和進展的良性循環。基於 ANN 的驚人產品定期成為頭條新聞,吸引了越來越多的關注和資金,導致越來越多的進步,甚至更驚人的產品。

1.1 Biological Neurons —— 生物神經元

在我們討論人工神經元之前,讓我們快速看一個生物神經元。

它是一種看起來異常的細胞,主要見於動物大腦皮層,由包含細胞核和大多數細胞複雜成分的細胞體,許多稱為樹突分支,加上一個稱為軸突的非常長的分支組成。軸突的長度可能只比細胞體長几倍,或長達幾萬倍。在它的末端附近,軸突分裂成許多稱為 telodendria 的分支,在這些分支的頂端是稱為突觸末端的微小結構,它們連接到其他神經元的樹突或細胞體。生物神經元產生稱為action potentials (APs, or just signals) 的短的電脈衝,通過這些突觸傳播,並在突觸末端釋放稱為neurotransmitters的化學物質。當神經元在幾毫秒內接收到來自其他神經元的足夠數量的信號時,它就發射出自己的信號。實際上,這取決於神經遞質,因為它們中的一些能抑制神經元發出信號。

因此,單個生物神經元似乎以一種相當簡單的方式運行,但是它們組織在一個巨大的數十億神經元的網絡中,每個神經元通常連接到數千個其他神經元。高度複雜的計算可以由相當簡單的神經元組成的巨大網絡來完成。生物神經網絡(BNN)的體系結構仍然是研究熱點,但是大腦的某些部分已經被解析出來,並且似乎神經元經常組織在連續的層中,尤其是在大腦皮層中的神經元,如下圖所示。

1.2 Logical Computations with Neurons — 神經元的邏輯計算Warren McCulloch 和 Pitts 提出一個非常簡單的生物神經元模型,後來作為人工神經元被眾所周知:它有一個或更多的二進位(ON/OFF)輸入和一個二進位輸出。當超過一定數量的輸入激活時,人工神經元會激活其輸出。McCulloch 和 Pitts 表明,即使用這樣一個簡化的模型,也有可能建立一個人工神經元網絡來計算任何你想要的邏輯命題。例如,讓我們構建一些執行各種邏輯計算的 ANN,假設當至少兩個輸入是激活的時候神經元被激活。

讓我們看看這些網絡都做了些什麼:

左邊的第一個網絡是恆等函數:如果神經元 A 被激活,那麼神經元 C 也被激活(因為它接收來自神經元 A 的兩個輸入信號),但是如果神經元 A 關閉,那麼神經元 C 也關閉。

第二網絡執行邏輯 AND:神經元 C 只有在神經元 A 和 B都被激活時才被激活(單個輸入信號不足以激活神經元 C)。

第三網絡執行邏輯 OR:如果神經元 A 或神經元 B 被激活(或兩者都被激活),神經元 C 被激活。

最後,如果我們假設輸入連接可以抑制神經元的激活(生物神經元就是這樣的),那麼第四個網絡計算一個稍微複雜的邏輯命題:只有當神經元 B 關閉,且神經元A是激活的時候,神經元 C 才被激活。如果神經元 A 始終是激活的,那麼你得到一個邏輯 NOT:神經元 C 在神經元 B 關閉時是激活的,反之亦然。

您可以很容易地想像如何將這些網絡組合起來計算複雜的邏輯表達式。

1.3 The Perceptron — 感知機

感知器是最簡單的人工神經網絡架構之一,由 Frank Rosenblatt 發明於 1957年。它基於一種稍微不同的人工神經元,稱為閾值邏輯單元(TLU),也稱為線性閾值單元(LTU):輸入和輸出是數字(而不是二進位值),並且每個輸入都與權重相連。

TLU計算其輸入的加權和(z = W1×X1 + W2×X2 + ... + + Wn×Xn),然後將階躍函數應用於該和,並輸出結果:H(x) = STEP(Z) = STEP(W^T·x)。

最常見的在感知器中使用的階躍函數是 Heaviside 階躍函數,有時使用符號函數代替。

單一的 TLU 可被用作簡單線性二分類。它計算輸入的線性組合,如果結果超過閾值,它輸出正類,否則輸出負類(就像一個邏輯回歸分類或線性 SVM)。例如,基於花瓣長度和寬度,你可以使用單一的 TLU 去分類鳶尾花(也可添加額外的偏置特徵x0=1)。在這個例子中,訓練一個TLU 意味著去尋找合適的W0,W1和W2值(訓練算法稍後提到)。

感知器簡單地由一層 TLUs 組成,每個TLU連接到所有輸入。當一層中的所有神經元都連接到前一層的每個神經元,該層就稱為全連接層,或稠密層。感知器的輸入被送到輸入神經元:它們的輸出等於輸入。此外,通常添加額外的偏置特徵(X0=1)。這種偏置特性通常用一種稱為偏置神經元的特殊類型的神經元來表示,它總是輸出 1。

下圖表示具有兩個輸入和三個輸出的感知器。該感知器可以將實例同時分類為三個不同的二進位類,這使得它是一個多輸出分類器。

由於線性代數的魔力,下式使得可以同時有效地計算多個實例的。

在這個方程中:
        和往常一樣,X表示輸入特徵矩陣。每行是一個實例,每列是一個特徵。

        權值矩陣W包含了除偏置神經元外的所有連接權值。

        偏置向量b包含了偏置神經元和人工神經元之間所有的連接權值。

        該函數稱為激活函數:當人工神經元是TLUs時,它是一個階躍函數。

那麼感知器是如何訓練的呢?Frank Rosenblatt 提出的感知器訓練算法在很大程度上受到 Hebb 規則的啟發。在 1949 出版的《The Organization of Behavior》一書中,Donald Hebb 提出,當一個生物神經元經常觸發另一個神經元時,這兩個神經元之間的聯繫就會變得更強。這個想法後來被 Siegrid Löwel 總結為一個吸引人的短語:「Cells that fire together, wire together」。也就是說,當兩個神經元同時激活時,它們之間的連接權值傾向於增加。這個規則後來被稱為 Hebb 規則(或 Hebbian 學習規則)。使用這個規則的變體來訓練感知器,該變體考慮了網絡進行預測時所犯的錯誤;它加強減少錯誤輸出的連接權重。更具體地,感知器一次被輸入一個訓練實例,並且對於每個實例,它進行預測。對於每一個產生錯誤預測的輸出神經元,它加強有助於正確預測的輸入的連接權重。該規則在下式示出。

每個輸出神經元的決策邊界是線性的,因此感知機不能學習複雜的模式(就像 Logistic 回歸分類器一樣)。然而,如果訓練實例是線性可分的,Rosenblatt 證明該算法將收斂到一個解。這被稱為感知器收斂定理。

sklearn 提供了一個 Perceptron 類,它實現了一個 LTU 網絡。它可以像你所期望的那樣使用,例如在 iris 數據集上:

import numpy as npfrom sklearn.datasets import load_irisfrom sklearn.linear_model import Perceptron
iris = load_iris()X = iris.data[:, (2, 3)] y = (iris.target == 0).astype(np.int)
per_clf = Perceptron()per_clf.fit(X, y)y_pred = per_clf.predict([[2, 0.5]])

您可能已經注意到,感知器學習算法類似於隨機梯度下降。事實上,sklearn 的Perceptron 類相當於使用具有以下超參數的SGDClassifier:loss="perceptron",learning_rate="constant",eta0=1(學習率),penalty=None(無正則化)。

注意,與邏輯斯蒂回歸分類器相反,感知機不輸出類概率,而是基於硬閾值進行預測。這是你更喜歡邏輯斯蒂回歸的一個理由。

在他們的 1969 年題為「Perceptrons」的專著中,Marvin Minsky 和 Seymour Papert 強調了感知機的許多嚴重缺陷,特別是它們不能解決一些瑣碎的問題(例如,異或(XOR)分類問題)。

當然,其他的線性分類模型(如 Logistic 回歸分類器)也都實現不了,但研究人員期望從感知器中得到更多,他們的失望是很大的,以至於許多研究人員放棄了聯結主義(即神經網絡的研究)。

然而,事實證明,感知器的一些局限性可以通過堆疊多個感知器來消除。由此產生的人工神經網絡被稱為多層感知器(MLP)。特別地,MLP 可以解決 XOR 問題,因為你可以通過計算上圖右側所示的 MLP 的輸出來驗證輸入的每一個組合:輸入(0, 0)或(1, 1),網絡輸出 0;輸入(0, 1)或(1, 0)它輸出 1。除了顯示的四個連接權重外,所有的連接權重都等於1。請嘗試驗證此網絡確實解決了XOR問題!

1.4 The Multilayer Perceptron and Backpropagation —— 多層感知器與反向傳播

MLP 由一個輸入層、一個或多個稱為隱藏層的 TLU 和一個稱為輸出層的 TLU組成 。

靠近輸入層的隱含層通常稱為下層,靠近輸出層的隱含層通常稱為上層。
除輸出層外,每一層都包括偏置神經元,並與下一層完全相連。除了輸出層之外的每一層都包括一個偏置神經元,並且全連接到下一層。信號只向一個方向流動(從輸入到輸出),因此這個架構是一個前饋神經網絡(FNN)的一個例子。

當人工神經網絡有兩個或多個隱含層時,稱為深度神經網絡(DNN)。深度學習領域研究DNN,或者更一般的,研究包含深層次計算的模型。即便如此,還是有很多人只要涉及到神經網絡(即使是淺層次的),都稱之為深度學習。

多年來,研究人員努力尋找一種訓練 MLP 的方法,但沒有成功。但在 1986,D. E. Rumelhart 等人提出了反向傳播訓練算法,該算法至今仍在使用。簡而言之,它就是使用一種高效方法自動計算梯度的梯度下降算法: 反向傳播算法只需通過網絡兩次(一次前向,一次後向),就能計算出網絡誤差關於每個模型參數的梯度。換句話說,它可以找出每個連接權重和每個偏置項應該如何調整,以減少誤差。一旦有了這些梯度,它只執行一個常規的梯度下降步驟,然後重複整個過程,直到網絡收斂。

自動計算梯度被稱為自動微分。有各種各樣的自動微分技術,有不同的優點和缺點。反向傳播使用一種被稱為反向模式自動微分的技術。它是快速和精確的,並且非常適合有很多變量(例如,連接權值)和很少輸出(例如,一個損失)的函數。

對於每個訓練實例,算法將其饋送到網絡並計算每個連續層中的每個神經元的輸出(這是向前傳遞,就像在進行預測時一樣)。然後,它測量網絡的輸出誤差(即,期望輸出和網絡實際輸出之間的差值),並且計算最後隱藏層中的每個神經元對每個輸出神經元的誤差貢獻多少。然後,繼續測量這些誤差貢獻有多少來自先前隱藏層中的每個神經元等等,直到算法到達輸入層。該反向通過有效地測量網絡中所有連接權重的誤差梯度,通過在網絡中向後傳播誤差梯度(也是該算法的名稱)。

讓我們更詳細地運行一下這個算法:

它一次處理一個小批量(例如,每個小批量包含32個實例),並且多次遍歷完整的訓練集。每一次遍歷稱為一個epoch。

每個小批量被傳遞到網絡的輸入層,輸入層將其發送到第一個隱藏層。然後,算法計算這一層中所有神經元的輸出(對於小批量中的每個實例)。結果被傳遞給下一層,其輸出被計算並傳遞給下一層,直到我們得到最後一層的輸出。這是正向傳遞:它與進行預測時完全一樣,只是保留了所有中間結果,因為後向傳遞需要它們。

接下來,算法測量網絡的輸出誤差(也就是說,它使用一個損失函數來比較網絡的期望輸出和實際輸出,並返回一些誤差的測量值)。

然後計算每個輸出連接對誤差的貢獻有多大。這是通過應用鏈式法則(也許是微積分中最基本的規則)來求解的,它使得這一步快速而精確。

然後,算法測量有多少誤差來自於下面層的每個連接,同樣使用鏈式法則,直到到達輸入層。如前所述,這種反向傳遞通過在網絡中後向傳播誤差梯度,有效地測量了網絡中所有連接權值的誤差梯度。

最後,該算法使用剛剛計算的誤差梯度,執行梯度下降步驟來調整網絡中的所有連接權值。

這個算法非常重要,值得再次總結一下:對於每個訓練實例,反向傳播算法首先做一個預測(forward pass)和測量誤差,然後反向遍歷每一層,測量每個連接的誤差貢獻(reverse pass),最後調整連接權值以減少誤差(Gradient Descent step)。

隨機初始化所有隱含層的連接權值是很重要的,否則訓練將失敗。例如,如果你初始化所有的權值和偏差為零,那麼層中的所有神經元將是完全相同的,因此反向傳播將以完全相同的方式影響它們,因此它們將保持相同。換句話說,儘管每一層有數百個神經元,您的模型將表現得好像每一層只有一個神經元一樣:它不會太聰明。相反,如果你隨機初始化權重,你打破了對稱性,並允許反向傳播訓練多樣化的神經元。

為了使算法能夠正常工作,作者對 MLP 的架構進行了一個關鍵性的改變:用 Logistic 函數代替了階躍函數,σ(z) = 1 / (1 + exp(–z))。這是必要的,因為階躍函數隻包含平坦的部分,因此沒有梯度(梯度下降不能在平面上移動),而 Logistic 函數處處都有一個非零導數,允許梯度下降算法在每一步上都取得一些進展。事實上,反向傳播算法可以與其他激活函數一起使用,而不僅僅是 Logistic 函數。另外兩個流行的激活函數是:

但是等等!為什麼我們需要激活函數呢?如果你把幾個線性變換串聯起來,你得到的就是一個線性變換。例如,如果f(x) = 2x + 3, g(x) = 5x - 1,那麼將這兩個線性函數串聯起來,就會得到另一個線性函數:f(g(x)) = 2(5x - 1) +3 = 10x + 1。因此,如果層與層之間沒有非線性,那麼即使是層的深度疊加也等同於單層,你無法用它來解決非常複雜的問題。相反,一個足夠大的具有非線性激活函數的DNN在理論上可以近似任何連續函數。

你已經知道神經網絡從何而來,它們的架構是什麼樣子的,以及如何計算它們的輸出。您還學習了反向傳播算法。但是你到底能用它們做什麼呢?

1.5 Regression MLPs  —— 回歸多層感知器

首先,MLP可用於回歸任務。如果你想要預測單個值(例如,房子的價格),那麼你只需要一個輸出神經元:它的輸出就是預測值。對於多元回歸(即,要一次性預測多個值),每個預測值都需要一個輸出神經元。例如,要在圖像中定位一個對象的中心,需要預測二維坐標,因此需要兩個輸出神經元。如果你還想在對象周圍放置一個邊界框,那麼你還需要兩個數字:對象的寬度和高度。所以,你最終有四個輸出神經元。

通常,當構建用於回歸的MLP時,您不希望輸出神經元使用任何激活函數,因此它們可以自由地輸出任何範圍的值。如果您希望保證輸出總是正的,那麼您可以在輸出層使用ReLU激活函數。另外,你也可以使用softplus激活函數,它是ReLU的平滑變體: softplus(z) = log(1 + exp(z)). 當z為負數時,它接近於0,當z為正數時,它接近於z。最後,如果你想保證預測值落在一個給定範圍內,那麼您可以使用logistic或雙曲正切函數:logistic函數的範圍為0到1,雙曲正切函數的範圍為-1到1。

在訓練期間使用的損失函數通常是均方誤差,但如果在訓練集中有大量的離群值,您可能更喜歡使用平均絕對誤差。或者,您可以使用Huber損失,這是兩者的組合。

當誤差小於閾值δ(通常是1)時,Huber損失是二次的;誤差大於δ時為線性。線性部分相比於均方誤差對離群值不那麼敏感,而二次部分比平均絕對誤差收斂更快、更精確。

下表總結了回歸MLP的典型架構。

1.6 Classification MLPs —— 分類多層感知器

MLP還可以用於分類任務。對於一個二元分類問題,你只需要一個使用logistic激活函數的輸出神經元:輸出將是一個0到1之間的數字,你可以將其解釋為正類的估計概率。負類的估計概率等於1減去那個數。

MLPs還可以輕鬆地處理多標籤二分類任務。例如,你有一個電子郵件分類系統,它可以預測每封收到的電子郵件是否為垃圾郵件,同時還可以預測它是否為緊急郵件。在這種情況下,您需要兩個輸出神經元,它們都使用logistic激活函數:第一個輸出電子郵件是垃圾郵件的概率,第二個輸出電子郵件是緊急郵件的概率。更一般地說,你應該為每個正類指定一個輸出神經元。請注意,此時輸出概率加起來並不一定等於1。這允許模型輸出任意標籤組合:您可以擁有非緊急垃圾郵件、緊急垃圾郵件、非緊急垃圾郵件,甚至可能是緊急垃圾郵件(儘管這可能是一個錯誤)。

如果每個實例只能屬於一個類(例如,數字圖像分類),那麼每個類都需要一個輸出神經元,你應該為整個輸出層使用softmax激活函數。softmax函數
將確保所有的估計概率都在0和1之間,並且它們加起來等於1。這就叫做多分類問題。

關於損失函數,由於我們預測的是概率分布,交叉熵損失(也稱為對數損失)通常是一個很好的選擇。

下表總結了分類MLP的典型架構。

現在,您已經掌握了用Keras實現MLP所需的所有概念 !

2 Implementing MLPs with Keras

Keras是一個高級的深度學習API,它可以讓你輕鬆地構建、訓練、評估和執行各種神經網絡。它的文檔(或規範)可以在https://keras.io/找到。Keras是Francois Chollet作為一個研究項目的一部分開發的,並於2015年3月作為一個開源項目發布。由於它的易用性、靈活性和美觀的設計,它很快就受到了歡迎。為了執行神經網絡所需的大量計算,這個參考實現依賴於計算後端。目前,有三種流行的開源深度學習庫可供選擇:TensorFlow、Microsoft Cognitive Toolkit(CNTK)和Theano。因此,為了避免混淆,我們將此參考實現稱為多後端Keras。

自2016年底以來,已經發布了其他實現。你現在可以在Apache MXNet、蘋果的Core ML,JavaScript或TypeScript以及PlaidML(它可以在各種GPU設備上運行,而不僅僅是英偉達)上運行Keras。此外,TensorFlow本身現在捆綁了自己的Keras實現,tf.keras。它只支持TensorFlow作為後端,但它的優勢是提供了一些非常有用的額外特性: 例如,它支持TensorFlow的數據API,這使得它很容易有效地加載和預處理數據。因此,在這本書裡,我們將使用tf.keras。然而,在本章中,我們不會使用任何與TensorFlow相關的具體特性,因此代碼應該也能在其他Keras實現上運行良好(至少在Python中),只需做一些小的修改,比如更改導入包。

在Keras和TensorFlow之後,最流行的深度學習庫是Facebook的PyTorch庫。好消息是它的API非常類似於Keras的(部分原因是這兩個api都是受到Scikit-Learn和Chainer的啟發),所以一旦你了解Keras,如果你想轉換到PyTorch並不難。PyTorch的受歡迎程度在2018年呈指數級增長,這主要得益於它的簡單性和出色的文檔,而這些都不是TensorFlow1.x的主要優勢。然而,TensorFlow 2可以說和PyTorch一樣簡單,因為它採用Keras作為官方的高級API,它的開發人員極大地簡化和清理了API的其餘部分。文檔也已經完全重新組織,現在更容易找到您需要的內容。類似地,PyTorch的主要弱點(例如,有限的可移植性和沒有計算圖分析)在PyTorch 1.0中也得到了很大的解決。健康的競爭對每個人都有益。

好了,開始編寫代碼了! 由於tf.keras與TensorFlow綁定,讓我們從安裝TensorFlow開始。

2.1 Installing TensorFlow 2 

假設你按照第二章的安裝說明安裝了Jupyter和Scikit-Learn,使用pip安裝TensorFlow。如果你使用virtualenv創建了一個隔離環境,你首先需要激活它:

$ cd $ML_PATH                $ source my_env/bin/activate $ .\my_env\Scripts\activate  

接下來,安裝TensorFlow 2(如果你不使用virtualenv,你將需要管理員權限,或添加 --user選項):

對於GPU支持,在寫這篇文章的時候,你需要安裝tensorflow-GPU而不是tensorflow,但tensorflow團隊正在研究一個單獨的庫,它將同時支持僅有cpu和配備GPU的系統。你仍然需要安裝額外的GPU支持庫(詳情請參閱https://tensorflow.org/install)。我們將在第19章中更深入地討論gpu。

要測試安裝,打開Python shell或Jupyter筆記本,然後導入TensorFlow和tf.keras,並列印他們的版本:

>>> import tensorflow as tf>>> from tensorflow import keras>>> tf.__version__'2.0.0'>>> keras.__version__'2.2.4-tf'

第二個版本是由tf.keras實現的Keras API版本。注意,它以-tf結尾,強調了tf.keras實現了Keras API,加上一些tensorflow特有的額外特性。

現在讓我們使用tf.keras!我們將從構建一個簡單的圖像分類器開始。

2.2 Building an Image Classifier Using the Sequential API

首先,我們需要加載一個數據集。本章我們將討論Fashion MNIST,臨時替代MNIST數據集。它有與MNIST數據集一樣的格式(70000×28像素的灰度圖像,一共10類),但圖像代表時尚物品而不是手寫數字,所以每個類更加多樣化,問題明顯比MNIST更具挑戰性。例如,一個簡單的線性模型在MNIST上的準確率約為92%,但在Fashion MNIST上的準確率僅為83%。

2.2.1 Using Keras to load the dataset 

Keras提供了一些實用函數來獲取和加載常見數據集,包括MNIST、Fashion MNIST和我們在第2章中使用的加州住房數據集。讓我們加載Fashion MNIST:

fashion_mnist = keras.datasets.fashion_mnist(X_train_full, y_train_full), (X_test, y_test) = fashion_mnist.load_data()


當使用Keras而不是Scikit-Learn加載MNIST或Fashion MNIST時,一個重要的區別是,每個圖像都表示為一個28×28數組,而不是大小為784的1D數組。此外,像素強度表示為整數(從0到255),而不是浮點數(從0.0到255.0)。讓我們看一下訓練集的形狀和數據類型:

>>> X_train_full.shape(60000, 28, 28)>>> X_train_full.dtypedtype('uint8')


注意,數據集已經被劃分為訓練集和測試集,但是沒有驗證集,所以我們現在將創建一個驗證集。此外,由於我們要使用梯度下降來訓練神經網絡,我們必須縮放輸入特徵。為了簡單起見,我們將像素強度除以255.0,從而縮小到0-1範圍內(這也會將它們轉換為浮點數):

X_valid, X_train = X_train_full[:5000] / 255.0, X_train_full[5000:] / 255.0y_valid, y_train = y_train_full[:5000], y_train_full[5000:]


對於MNIST數據集,當標籤等於5時,意味著圖像表示手寫數字5。然而,對於Fashion MNIST,我們需要類名列表來知道我們正在處理什麼:

class_names = ["T-shirt/top", "Trouser", "Pullover", "Dress", "Coat",               "Sandal", "Shirt", "Sneaker", "Bag", "Ankle boot"]


例如,訓練集中的第一張圖像代表一件外套:

>>> class_names[y_train[0]]'Coat'


下圖顯示了Fashion MNIST數據集的一些樣本。


2.2.2 Creating the model using the Sequential API

現在讓我們構建神經網絡!這裡是一個有兩個隱藏層的分類MLP:

model = keras.models.Sequential()model.add(keras.layers.Flatten(input_shape=[28, 28]))model.add(keras.layers.Dense(300, activation="relu"))model.add(keras.layers.Dense(100, activation="relu"))model.add(keras.layers.Dense(10, activation="softmax"))

讓我們逐行瀏覽這段代碼:

第一行創建了一個Sequential模型。這是最簡單的一種Keras神經網絡模型,它由一層一層順序連接而成的,稱為順序API。

接下來,我們構建第一層並將其添加到模型中。它是一個Flatten層,其作用是將每個輸入圖像轉換為一維數組:如果它接收到輸入數據X,它計算X. shape(- 1,1)。此層沒有任何參數;它只是做一些簡單的預處理。由於它是模型的第一層,您應該指定input_shape,它不包括批量大小,只包括實例的形狀。或者,你也可以添加一個keras.layers.InputLayer作為第一層,設置input_shape=[28, 28]。

接下來,我們添加一個有300個神經元的稠密隱藏層。它將使用ReLU激活函數。每個稠密層管理著自己的權值矩陣,其中包含了神經元與其輸入之間的所有連接權值。它還管理一個偏置項向量(每個神經元一個)。

然後我們添加了第2個稠密隱藏層,包含100個神經元,同樣使用ReLU激活函數。

最後,使用softmax激活函數,我們添加了一個有10個神經元的稠密輸出層(每個類一個)。

指定activation="relu"等價於指定activation=keras.activation.relu。keras.activations包中還有其他激活功能,我們將在本書中使用它們中的許多。參見https://keras。io/activations/獲得完整的列表。

在創建順序模型時,你可以傳遞一個層的列表,而不是像我們剛才那樣一個一個地添加層:

model = keras.models.Sequential([    keras.layers.Flatten(input_shape=[28, 28]),    keras.layers.Dense(300, activation="relu"),    keras.layers.Dense(100, activation="relu"),    keras.layers.Dense(10, activation="softmax")])

使用來自KERAS.IO的代碼示例

keras上記錄的代碼示例在tf.keras上可以很好地運行,但您需要更改導入。例如,考慮下面這個keras.io代碼:

from keras.layers import Denseoutput_layer = Dense(10)

你必須像這樣改變導入:

from tensorflow.keras.layers import Denseoutput_layer = Dense(10)

如果你喜歡,也可以直接使用完整路徑:

from tensorflow import kerasoutput_layer = keras.layers.Dense(10)

這種方法比較冗長,但是我在本書中使用它,這樣您就可以很容易地看到要使用哪些包,並避免標準類和自定義類之間的混淆。在產品代碼中,我更喜歡前面的方法。很多人也使用from tensorflow.keras import layers後,然後使用layers.Dense(10).

模型的summary()方法顯示模型的所有層,包括每一層的名稱(除非在創建層時設置它,否則它是自動生成的)、它的輸出形狀(None意味著批量大小可以是任何東西)和它的參數數量。模型整體信息的最後是參數總數,包括可訓練參數和不可訓練參數。這裡我們只有可訓練的參數:

>>> model.summary()Model: "sequential"

請注意,稠密層通常有很多參數。例如,第一個隱層有784×300的連接權值,加上300個偏差項,總共有235,500個參數!這為模型擬合訓練數據提供了很大的靈活性,但這也意味著模型有過擬合的風險,特別是在沒有大量訓練數據的情況下。

你可以很容易地獲取一個模型的層列表,通過它的索引獲取一個層,或者你可以通過名字獲取層:

>>> model.layers[<tensorflow.python.keras.layers.core.Flatten at 0x132414e48>,<tensorflow.python.keras.layers.core.Dense at 0x1324149b0>,<tensorflow.python.keras.layers.core.Dense at 0x1356ba8d0>,<tensorflow.python.keras.layers.core.Dense at 0x13240d240>]>>> hidden1 = model.layers[1]>>> hidden1.name'dense'>>> model.get_layer('dense') is hidden1True

可以使用層的get_weights()和set_weights()方法訪問層的所有參數。對於稠密層,參數包括連接權值和偏差項:

>>> weights, biases = hidden1.get_weights()>>> weightsarray([[0.02448617, -0.00877795, -0.02189048, ..., -0.02766046,        0.03859074, -0.06889391],        ...,        [-0.06022581, 0.01577859, -0.02585464, ..., -0.00527829,        0.00272203, -0.06793761]], dtype=float32)>>> weights.shape(784, 300)>>> biasesarray([0., 0., 0., 0., 0., 0., 0., 0., 0., ..., 0., 0., 0.],dtype=float32)>>> biases.shape(300,)

請注意,稠密層隨機初始化了連接權值(這是打破對稱性所必需的),偏差被初始化為0。如果您想使用不同的初始化方法,您可以在創建層時設置kernel_initializer (kernel是連接權重矩陣的另一個名稱)或bias_initializer。我們將在第十一章進一步討論初始化,但如果你想要完整的列表,請參閱https://keras.io/initializers/。

權矩陣的形狀取決於輸入的數量。這就是為什麼建議在創建順序模型的第一層時指定input_shape。然而,如果您沒有指定輸入形狀,也是可以的: Keras一直等待,直到它知道輸入形狀,它才會真正地構建模型。當您向它提供實際數據時(例如,訓練期間),或者當您調用它的build()方法時,都會發生這種情況。在模型真正建立之前,這些層將沒有任何權重,並且您將無法執行某些操作(例如列印模型整體信息或保存模型)。因此,如果你在創建模型時知道輸入形狀,最好指定它。

2.2.3 Compiling the model

在創建模型之後,必須調用它的compile()方法來指定loss函數和要使用的優化器。可選地,您可以指定在訓練和評估期間需要計算的額外指標列表:

model.compile(loss="sparse_categorical_crossentropy",              optimizer="sgd",              metrics=["accuracy"])

使用loss="sparse_categorical_crossentropy"等價於使用loss=keras.loss .sparse_categorical_crossentropy。類似地,指定optimizer="sgd"等價於指定optimizer=keras.optimizer.sgd(),而metrics=["accuracy"]等價於metrics=[keras.metrics.sparse_categorical_accuracy](在使用此丟失時)。我們將在本書中使用許多其他損失函數、優化器和指標;完整名單見https://keras.io/loss,https://keras.io/optimizer,https://keras.io/metrics。

這段代碼需要一些解釋。首先,我們使用"sparse_categorical_crossentropy"損失,是因為我們有稀疏的標籤(例如,對於每個實例,只有一個類標籤,在本例中是從0到9),並且這些類是互斥的。如果我們為每個實例的每個類設置一個目標概率(例如一個獨熱向量,例如[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]表示第3類),則需要使用「categorical_crossentropy」損失。如果我們正在進行二分類(使用一個或多個二進位標籤),那麼我們將在輸出層使用「sigmoid」(即logistic)激活函數而不是「softmax」激活函數,並且我們會使用「binary_crossentropy」損失。

如果您想將稀疏標籤(即類索引)轉換為獨熱向量標籤,請使用keras.utils.to_categorical()函數。反過來,可以使用axis=1的np.argmax()函數。

對於優化器,「sgd」意味著我們將使用簡單的隨機梯度下降法來訓練模型。換句話說,Keras將執行前面所述的反向傳播算法(即反向模式的autodiff加上梯度下降法)。我們將在第十一章討論更有效的優化器(它們改善梯度下降部分,而不是autodiff部分)。


在使用SGD優化器時,重要的是要調優學習率。因此,您通常希望使用optimizer=keras.optimizer . sgd (lr=??)來設置學習率,而不是使用optimizer="sgd",後者默認為lr=0.01。

最後,因為這是一個分類器,所以在訓練和評估期間測量它的「準確性」是有用的。

2.2.4 Training and evaluating the model

現在可以訓練模型了。為此,我們只需要調用它的fit()方法:

>>> history = model.fit(X_train, y_train, epochs=30,... validation_data=(X_valid, y_valid))...Train on 55000 samples, validate on 5000 samplesEpoch 1/3055000/55000 [                                      - val_loss: 0.4973 - val_accuracy: 0.8366Epoch 2/3055000/55000 [                                      - val_loss: 0.4456 - val_accuracy: 0.8480[...]Epoch 30/3055000/55000 [                                      -val_loss: 0.2999 - val_accuracy: 0.8926

我們向它傳遞輸入特徵(X_train)和目標類(y_train),以及要訓練的epoch數(否則,它將默認為僅1,這肯定不足以收斂到一個好的解決方案)。我們還傳遞了一個驗證集(這是可選的)。Keras會在每個epoch結束時測量驗證集的損失和額外指標,這對於查看模型的實際執行情況非常有用。如果訓練集上的性能比驗證集上的性能好得多,那麼您的模型可能過度擬合了訓練集(或者存在bug,比如訓練集和驗證集之間的數據不匹配)。

神經網絡已經訓練好了。在訓練的每個epoch,Keras顯示到目前為止處理的實例數量(以及進度條)、每個樣本的平均訓練時間,以及訓練集和驗證集上的損失和準確性(或您要求的任何其他額外指標)。你可以看到訓練的損失下降了,這是一個好跡象,驗證集的準確性在30個epoch後達到了89.26%。這與訓練精度相差不大,所以似乎沒有太多的過擬合。

不使用validation_data參數傳遞驗證集,您可以將validation_split設置為您希望Keras用於驗證的訓練集的比例。例如,validation_split=0.1告訴Keras使用最後10%的數據(在打亂順序之前)進行驗證。

如果訓練集非常傾斜,有些類過多,而另一些類不足,那麼在調用fit()方法時設置class_weight參數會很有用,因為fit()方法會賦予更大的權重給不足的類,賦予更低的權重給過多的類。Keras在計算損失時將使用這些權重。如果需要每個實例的權重,設置sample_weight參數(它取代class_weight)。如果某些實例是由專家標記的,而其他實例是使用眾包平臺標記的,那麼使用每個實例的權重可能會有用:您可能想給前者賦予更多的權重。您還可以為驗證集提供樣本權重(而不是類權重),方法是將它們作為validation_data元組中的第三項添加進去。

fit()方法返回一個History對象,包含訓練參數(history.params)、它所經歷的epoch的列表(history.epoch)和一個字典(history.history),其中包含在每個epoch結束時它在訓練集和驗證集(如果有的話)上計算的損失和額外指標。如果你使用這個字典創建一個pandas的DataFrame並調用它的plot()方法,你會得到下圖所示的學習曲線:

import pandas as pdimport matplotlib.pyplot as pltpd.DataFrame(history.history).plot(figsize=(8, 5))plt.grid(True)plt.gca().set_ylim(0, 1) plt.show()

可以看到,訓練集的準確性和驗證集的準確性都在訓練過程中穩步增加,而訓練損失和驗證損失都在減小。驗證曲線與訓練曲線非常接近,意味著不存在太多過擬合。在這個例子中,在訓練開始階段,模型在驗證集上的表現似乎比在訓練集上的表現要好。但事實並非如此:事實上,驗證誤差是在每個epoch結束時計算的,而訓練誤差是在每個epoch使用一個移動平均值計算的。所以訓練曲線應該向左移動半個epoch。如果這樣做,您將看到訓練曲線和驗證曲線在訓練開始時幾乎完全重合。

註:在繪製訓練曲線時,它應向左移動半個epoch。

通常情況下,如果訓練時間足夠長的話,訓練集的性能最終會超過驗證性能。您可以看出,模型還沒有完全收斂,因為驗證損失仍在下降,因此您可能應該繼續訓練。只需要再次調用fit()方法即可,因為Keras只是在它停止的地方繼續訓練(您應該能夠達到接近89%的驗證精度)。

如果您對模型的性能不滿意,那麼您應該調優超參數。首先要檢查的是學習速率。如果這沒有幫助,嘗試另一個優化器(在更改任何超參數後總是需要重新調優學習率)。如果性能仍然不是很好,那麼嘗試調優模型超參數,比如層數、每層神經元的數量,以及為每個隱藏層使用的激活函數的類型。您還可以嘗試調優其他超參數,比如批量大小(可以在fit()方法中使用batch_size參數設置,默認值為32)。我們將在本章的最後回到超參數調優。

一旦您對模型的驗證準確性感到滿意,在將模型部署到生產環境之前,您應該在測試集中評估它,以估計泛化誤差。您可以使用evaluate()方法輕鬆做到這一點(它還支持其他幾個參數,如batch_size和sample_weight;請查看文檔了解更多細節):

>>> model.evaluate(X_test, y_test)10000/10000 [==========] - 0s 29us/sample - loss: 0.3340 - accuracy: 0.8851[0.3339798209667206, 0.8851]

正如我們在第二章看到的,在測試集上的性能通常略低於驗證集上的性能,因為hyperparameters是在驗證集上調優的,而不是測試集(然而,在這個例子中,我們沒有做任何hyperparameter調優,所以低精度只能歸結為運氣不好)。記住,一定要抵制在測試集上調優超參數的誘惑,否則您對泛化錯誤的估計將過於樂觀。

2.2.5 Using the model to make predictions

接下來,我們可以使用模型的predict()方法對新實例進行預測。由於我們沒有實際的新實例,我們將只使用測試集的前三個實例:

>>> X_new = X_test[:3]>>> y_proba = model.predict(X_new)>>> y_proba.round(2)array([[0. , 0. , 0. , 0. , 0. , 0.03, 0. , 0.01, 0. , 0.96],       [0. , 0. , 0.98, 0. , 0.02, 0. , 0. , 0. , 0. , 0. ],       [0. , 1. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ]],       dtype=float32)

如您所見,對於每個實例,模型為從類0到類9的每個類都估計一個概率。例如,對於第一張圖片,它估計第9類(踝靴)的概率是96%,第5類(涼鞋)的概率是3%, class 7 (sneaker)的概率為1%,其他類別的概率可以忽略。換句話說,它「相信」第一個圖像是鞋子,最有可能是短靴,但也可能是涼鞋或運動鞋。如果您只關心具有最高估計概率的類(即使該概率非常低),那麼您可以使用predict_classes()方法:

>>> y_pred = model.predict_classes(X_new)>>> y_predarray([9, 2, 1])>>> np.array(class_names)[y_pred]array(['Ankle boot', 'Pullover', 'Trouser'], dtype='<U11')

在這裡,分類器實際上正確地分類了這三幅圖像:

>>> y_new = y_test[:3]>>> y_newarray([9, 2, 1])

現在您知道了如何使用順序API來構建、訓練、評估和使用分類MLP。但是回歸呢?

2.3 Building a Regression MLP Using the Sequential API

讓我們切換到加州的住房問題並使用回歸神經網絡來解決它。為簡單起見,我們將使用Scikit-Learn的fetch_california_housing()函數來加載數據。這個數據集比我們在第2章中使用的數據集要簡單,因為它只包含數字特徵
(沒有ocean_proximity特徵),也沒有丟失值。加載數據後,我們將其分為訓練集、驗證集和測試集,並縮放所有特徵:

from sklearn.datasets import fetch_california_housingfrom sklearn.model_selection import train_test_splitfrom sklearn.preprocessing import StandardScalerhousing = fetch_california_housing()X_train_full, X_test, y_train_full, y_test = train_test_split(housing.data, housing.target)X_train, X_valid, y_train, y_valid = train_test_split(X_train_full, y_train_full)
scaler = StandardScaler()X_train = scaler.fit_transform(X_train)X_valid = scaler.transform(X_valid)X_test = scaler.transform(X_test)

使用順序API來構建、訓練、評估和使用回歸MLP來進行預測與我們對分類任務所做的非常相似。主要的區別是輸出層只有一個神經元(因為我們只想預測一個值),並且沒有使用激活函數,而損失函數是均方誤差。由於數據集非常嘈雜,為了避免過擬合,我們只使用了一個神經元較少的隱含層:

model = keras.models.Sequential([    keras.layers.Dense(30, activation="relu",    input_shape=X_train.shape[1:]),    keras.layers.Dense(1)])model.compile(loss="mean_squared_error", optimizer="sgd")history = model.fit(X_train, y_train, epochs=20,                    validation_data=(X_valid, y_valid))mse_test = model.evaluate(X_test, y_test)X_new = X_test[:3] y_pred = model.predict(X_new)

如您所見,順序API非常容易使用。然而,儘管順序模型是非常常見的,它有時用於構建具有更複雜拓撲或具有多個輸入或輸出的神經網絡。為此目的,Keras提供了函數式API。

2.4 Building Complex Models Using the Functional API

非順序神經網絡的一個例子是寬深度神經網絡。這種神經網絡架構在2016年程恆策等的一篇論文中被介紹。它將所有或部分輸入直接連接到輸出層,如下圖所示。


這種結構使得神經網絡既可以學習深度模式(使用深度路徑),也可以學習簡單規則(通過短路徑)。相比之下,一個常規的MLP強制所有的數據流經整個層的堆疊。因此,數據中的簡單模式最終可能會被這一系列轉換所扭曲。


讓我們構建這樣一個神經網絡來解決加州的住房問題:

input_ = keras.layers.Input(shape=X_train.shape[1:])hidden1 = keras.layers.Dense(30, activation="relu")(input_)hidden2 = keras.layers.Dense(30, activation="relu")(hidden1)concat = keras.layers.Concatenate()([input_, hidden2])output = keras.layers.Dense(1)(concat)model = keras.Model(inputs=[input_], outputs=[output])


讓我們看一下這段代碼的每一行:

首先,我們需要創建一個輸入對象。這是模型將獲得的輸入的規範,包括它的形狀和數據類型。一個模型實際上可能有多個輸入。

接下來,我們使用ReLU激活函數創建一個有30個神經元的緻密層。一旦它被創建,請注意我們像調用函數一樣調用它,傳遞給它輸入。這就是為什麼它被稱為函數式API。注意,我們只是告訴Keras它應該如何將圖層連接在一起;目前還沒有實際的數據被處理。

然後我們創建第二個隱藏層,再次將其作為函數使用。注意,我們將第一個隱藏層的輸出傳遞給它。

接下來,我們創建一個Concatenate層。再一次,我們立即像使用函數一樣使用它,來連接輸入和第二個隱藏層的輸出。您可能更喜歡keras.layers.concatenate()函數,它創建一個連接層,並立即用給定的輸入調用它。

然後我們創建輸出層,只有一個神經元,沒有激活函數,我們像調用函數一樣調用它,將連接層的結果傳遞給它。

最後,我們創建一個Keras模型,指定要使用哪些輸入和輸出。

一旦您構建了Keras模型,一切都與前面完全一樣,因此沒有必要在這裡重複:您必須編譯模型、訓練它、評估它,並使用它進行預測。

但是,如果您想一個特徵子集發送給寬路徑,並將另一個不同的特徵子集發送給深路徑(可能重疊),該怎麼辦呢?

在這種情況下,一種解決方案是使用多個輸入。例如,假設我們想要通過寬路徑發送5個特徵(特徵0到4),並通過深路徑發送6個特徵(特徵2到7):

input_A = keras.layers.Input(shape=[5], name="wide_input")input_B = keras.layers.Input(shape=[6], name="deep_input")hidden1 = keras.layers.Dense(30, activation="relu")(input_B)hidden2 = keras.layers.Dense(30, activation="relu")(hidden1)concat = keras.layers.concatenate([input_A, hidden2])output = keras.layers.Dense(1, name="output")(concat)model = keras.Model(inputs=[input_A, input_B], outputs=[output])

代碼是很容易解釋。你至少應該命名最重要的層,特別是當模型變得有點複雜的時候。注意,我們在創建模型時指定了inputs=[input_A, input_B]。現在我們可以像往常一樣編譯模型,但是當我們調用fit()方法時,不是傳遞單個輸入矩陣X_train,而是必須傳遞一對矩陣(X_train_A, X_train_B)作為輸入。當你調用evaluate()或predict()時,X_valid也是這樣,X_test和X_new也是這樣:

model.compile(loss="mse", optimizer=keras.optimizers.SGD(lr=1e-3))X_train_A, X_train_B = X_train[:, :5], X_train[:, 2:]X_valid_A, X_valid_B = X_valid[:, :5], X_valid[:, 2:]X_test_A, X_test_B = X_test[:, :5], X_test[:, 2:]X_new_A, X_new_B = X_test_A[:3], X_test_B[:3]
history = model.fit((X_train_A, X_train_B), y_train, epochs=20,validation_data=((X_valid_A, X_valid_B), y_valid))mse_test = model.evaluate((X_test_A, X_test_B), y_test)y_pred = model.predict((X_new_A, X_new_B))

在許多情況下,你可能想要有多個輸出:

任務可能會要求這樣做。例如,您可能想定位和分類圖片中的主要對象。這是一個回歸任務(找到對象中心的坐標,以及它的寬度和高度)和一個分類任務。

類似地,您可能有多個基於相同數據的獨立任務。當然,您可以為每個任務訓練一個神經網絡。但在許多情況下,通過訓練為每個任務分配一個輸出神經元的單個神經網絡,您將在所有任務上獲得更好的結果。這是因為神經網絡可以學習數據中有用的跨任務特徵。例如,您可以對人臉圖片執行多任務分類,使用一個輸出來分類人的面部表情(微笑、驚訝等),另一個輸出來識別他們是否戴眼鏡。

另一個用例是作為一種正則化技術(即,一個訓練約束,其目標是減少過擬合,從而提高模型的泛化能力)。例如,您可能希望在神經網絡架構中添加一些輔助輸出,以確保網絡的底層部分能夠自行學習一些有用的東西,而不依賴於網絡的其餘部分。


添加額外的輸出非常簡單:只需將它們連接到適當的層,並將它們添加到模型的輸出列表中。例如,下面的代碼構建了上圖所示的網絡:

[...] output = keras.layers.Dense(1, name="main_output")(concat)aux_output = keras.layers.Dense(1, name="aux_output")(hidden2)model = keras.Model(inputs=[input_A, input_B], outputs=[output, aux_output])

每個輸出都需要有它自己的損失函數。因此,當我們編譯模型時,我們應該傳遞一個損失列表(如果我們傳遞一個損失,Keras將假定所有輸出必須使用相同的損失)。默認情況下,Keras會計算所有這些損失,然後簡單地把它們加起來,得到用於訓練的最終損失。我們更關心主輸出,而不是輔助輸出(因為它只是用於正則化),所以我們想給主輸出的損失一個更大的權重。幸運的是,當編譯模型時,可以設置所有的損失權重:

model.compile(loss=["mse", "mse"], loss_weights=[0.9, 0.1], optimizer="sgd")

現在,當我們訓練模型時,我們需要為每個輸出提供標籤。在這個例子中,主輸出和輔助輸出應該嘗試預測相同的東西,因此它們應該使用相同的標籤。所以我們不需要傳遞y_train,而需要傳遞(y_train, y_train)(對於y_valid和y_test也是一樣的):

history = model.fit(    [X_train_A, X_train_B], [y_train, y_train], epochs=20,    validation_data=([X_valid_A, X_valid_B], [y_valid, y_valid]))

當我們評估模型時,Keras將返回總損失,以及所有單個輸出的損失:

total_loss, main_loss, aux_loss = model.evaluate(    [X_test_A, X_test_B], [y_test, y_test])

類似地,predict()方法將返回每個輸出的預測結果:

y_pred_main, y_pred_aux = model.predict([X_new_A, X_new_B])

如您所見,您可以使用Functional API輕鬆地構建任何類型的架構。讓我們看看構建Keras模型的最後一種方法。

2.5 Using the Subclassing API to Build Dynamic Models

順序API和函數API都是聲明式的:首先聲明想要使用哪些層以及它們應該如何連接,然後才可以開始向模型提供一些數據,以便進行訓練或推斷。這有許多優點:模型可以很容易地保存、克隆和共享;可以顯示和分析它的結構;框架可以推斷形狀和檢查類型,因此可以及早發現錯誤(即,在任何數據進入模型之前)。它也相當容易調試,因為整個模型是關於層的靜態圖。值得注意的是,它是靜態的。有些模型涉及循環、形狀發生變化、條件分支和其他動態行為。對於這種情況,或者如果您更喜歡命令式的編程風格,那麼Subclassing API非常適合您。

只需將Model類子類化,在構造器中創建所需的層,並使用它們在call()方法中執行所需的計算。例如,創建以下WideAndDeepModel類的實例,將為我們提供一個與我們剛剛用Functional API構建的模型等價的模型。然後你可以編譯它,評估它,用它來做預測,就像我們剛才做的一樣:

class WideAndDeepModel(keras.Model):    def __init__(self, units=30, activation="relu", **kwargs):        super().__init__(**kwargs)         self.hidden1 = keras.layers.Dense(units, activation=activation)        self.hidden2 = keras.layers.Dense(units, activation=activation)        self.main_output = keras.layers.Dense(1)        self.aux_output = keras.layers.Dense(1)        def call(self, inputs):        input_A, input_B = inputs        hidden1 = self.hidden1(input_B)        hidden2 = self.hidden2(hidden1)        concat = keras.layers.concatenate([input_A, hidden2])        main_output = self.main_output(concat)        aux_output = self.aux_output(hidden2)        return main_output, aux_output    model = WideAndDeepModel()

 這個例子看起來非常像函數式API,除了我們不需要創建輸入;我們只對call()方法使用輸入參數,並將構造函數中的層創建過程與call()方法中的層使用過程分離開來。最大的區別是,你可以在call()方法中做幾乎任何你想做的事情:for循環,if語句,低級TensorFlow操作——你的想像力是有限的!這使它成為研究人員試驗新想法的一個非常偉大的API。

這種額外的靈活性確實是有代價的:您的模型的架構隱藏在call()方法中,所以Keras不能輕易地檢查它;它不能保存或克隆它;當您調用summary()方法時,您只會得到層的列表,而不會得到關於層之間如何連接的任何信息。此外,
Keras不能提前檢查類型和形狀,更容易出錯。因此,除非您真的需要額外的靈活性,否則您可能應該堅持使用順序API或函數API。

Keras模型可以像常規層一樣使用,因此您可以輕鬆地將它們組合起來構建複雜的架構。

現在您已經知道如何使用Keras構建和訓練神經網絡,您將希望保存它們!

2.6 Saving and Restoring a Model 

當使用順序API或函數API時,保存經過訓練的Keras模型非常簡單:

model = keras.layers.Sequential([...]) model.compile([...])model.fit([...])model.save("my_keras_model.h5")

Keras將使用HDF5格式來保存模型的架構(包括每一層的超參數)和每一層的所有模型參數的值(例如,連接權值和偏差)。它還保存了優化器(包括它的超參數和它可能擁有的任何狀態)。

您通常會有一個訓練模型並保存它的腳本,以及一個或多個加載模型並使用它進行預測的腳本(或web服務)。加載模型也很簡單:

model = keras.models.load_model("my_keras_model.h5")


但是如果訓練持續幾個小時呢?這是很常見的,特別是在大型數據集上進行訓練時。在這種情況下,您不僅應該在訓練結束時保存您的模型,而且還應該在訓練期間定期保存魔心,以避免計算機崩潰時丟失所有東西。但是您如何告訴fit()方法來保存魔心呢?使用回調。

2.7 Using Callbacks

fit()方法接受一個callback參數,該參數允許您指定一個對象列表,Keras將在訓練開始和結束時,在每個epoch開始和結束時,甚至在處理每個批量樣本之前和之後調用這些對象。例如,ModelCheckpoint回調函數在訓練期間定期保存模型,默認情況下是在每個epoch結束時:

[...] checkpoint_cb = keras.callbacks.ModelCheckpoint("my_keras_model.h5")history = model.fit(X_train, y_train, epochs=10, callbacks=[checkpoint_cb])

此外,如果您在訓練期間使用驗證集,那麼您可以在創建ModelCheckpoint
時設置save_best_only=True。在這種情況下,只有當驗證集上的性能是迄今為止最好的時候,它才會保存模型。通過這種方式,你不需要擔心訓練太久和過度擬合訓練集:只要恢復訓練後的模型即可,這將是在驗證集上效果最好的模型。下面的代碼是一個簡單的方法來實現早停:

checkpoint_cb = keras.callbacks.ModelCheckpoint("my_keras_model.h5", save_best_only=True)history = model.fit(X_train, y_train, epochs=10,validation_data=(X_valid, y_valid),callbacks=[checkpoint_cb])model = keras.models.load_model("my_keras_model.h5") 


實現早停的另一種方法是使用EarlyStopping回調。當它監測到在許多epoch(由patience參數定義)後的驗證集上都沒有進展時,它將中斷訓練,並且它將有選擇地回滾到最佳模型。你可以結合這兩個回調來保存你的模型,以防你的計算機崩潰,以及在沒有更多進展時儘早中斷訓練(以避免浪費時間和資源):

early_stopping_cb = keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)history = model.fit(X_train, y_train, epochs=100,                    validation_data=(X_valid, y_valid),                    callbacks=[checkpoint_cb, early_stopping_cb])

可以將epoch的數量設置為一個大的值,因為當沒有進一步的進展時,訓練將自動停止。在這種情況下,不需要恢復保存的最佳模型,因為EarlyStopping
回調將跟蹤最佳權重,並在訓練結束時恢復它們。

在keras.callbacks package中還有許多其他可用的回調函數。

如果需要額外的控制,您可以輕鬆編寫自定義回調。作為如何做到這一點的一個例子,下面的自定義回調將顯示在訓練期間驗證損失和訓練損失之間的比率(例如,用於檢測過擬合):

class PrintValTrainRatioCallback(keras.callbacks.Callback):    def on_epoch_end(self, epoch, logs):        print("\nval/train: {:.2f}".format(logs["val_loss"] / logs["loss"]))

如您所料,您可以實現on_train_begin()、on_train_end()、on_epoch_begin()、on_epoch_end()、on_batch_begin()和on_batch_end()。回調還可以在評估和預測期間使用,如果您需要它們(例如,用於調試)。對於評估期間,你應該實現on_test_begin(), on_test_end(), on_test_batch_end(),或on_test_batch_end()(由evaluate()調用)。對於預測期間,你應該實現on_predict_begin(), on_predict_batch_begin(),或on_predict_batch_end()(由predict()調用)。

現在讓我們看一看另外一個工具: TensorBoard。

2.8 Using TensorBoard for Visualization

TensorBoard是一個偉大的交互式可視化工具,您可以使用它來查看訓練期間的學習曲線,比較多次運行的學習曲線,可視化計算圖,分析訓練數據,查看由你的模型生成的圖像,可視化複雜多維數據(將其投影到三維空間,並自動聚類)以及更多!當你安裝TensorFlow時,這個工具會自動安裝,所以你已經有了它。

要使用它,您必須修改您的程序,使其將您想要可視化的數據輸出到稱為事件文件的特殊二進位日誌文件中。每條二進位數據記錄稱為摘要。TensorBoard伺服器將監視日誌目錄,它將自動獲取更改並更新可視化:這允許您可視化實時數據(有短延遲),例如訓練期間的學習曲線。通常,您希望將TensorBoard伺服器指向一個根日誌目錄,並配置您的程序,使它在每次運行時都寫入不同的子目錄。通過這種方式,同一個TensorBoard伺服器實例將允許您可視化和比較程序多次運行的數據,而不會把一切都搞混。

讓我們首先定義用於TensorBoard日誌的根日誌目錄,再加上一個小函數,該函數將根據當前日期和時間生成一個子目錄路徑,以便每次運行時都不同。您可能希望在日誌目錄名中包含額外的信息,例如您正在測試的超參數值,以便更容易地在TensorBoard中了解您正在查看的內容:

import osroot_logdir = os.path.join(os.curdir, "my_logs")
def get_run_logdir(): import time run_id = time.strftime("run_%Y_%m_%d-%H_%M_%S") return os.path.join(root_logdir, run_id)run_logdir = get_run_logdir()

好消息是Keras提供了一個很好的TensorBoard()回調函數:

[...] tensorboard_cb = keras.callbacks.TensorBoard(run_logdir)history = model.fit(X_train, y_train, epochs=30,                    validation_data=(X_valid, y_valid),                    callbacks=[tensorboard_cb])

就是這樣!用起來再簡單不過了。如果運行此代碼,則TensorBoard()回調函數將為您創建日誌目錄(如果需要,還可以與父目錄一起使用),在訓練期間,它將創建事件文件並向它們寫入摘要。在第二次運行程序(可能更改了一些超參數值)之後,您將得到一個類似於下面的目錄結構:

每次運行都有一個目錄,每個目錄包含一個子目錄用於訓練日誌,一個子目錄用於驗證日誌。兩者都包含事件文件,但訓練日誌還包括分析跟蹤:這允許TensorBoard向您顯示模型在所有設備上的每個部分分別花費了多少時間,這對於定位性能瓶頸是很好的。

接下來,您需要啟動TensorBoard伺服器。一種方法是在終端上運行命令。如果你在virtualenv中安裝了TensorFlow,你應該激活它。接下來,在項目的根目錄下運行以下命令(或者從其他任何地方,只要你指向適當的日誌目錄):

$ tensorboard --logdir=./my_logs --port=6006TensorBoard 2.0.0 at http://mycomputer.local:6006/ (Press CTRL+C to quit)

如果您的shell無法找到tensorboard腳本,那麼您必須更新PATH環境變量,使其包含安裝腳本的目錄(或者,您可以直接在命令行中用python3 -m tensorboard.main替換tensorboard)。伺服器啟動後,您可以打開web瀏覽器並訪問http://localhost:6006。

或者,您可以通過運行以下命令直接在jupiter中使用TensorBoard。第一行加載TensorBoard擴展,第二行在埠6006上啟動TensorBoard伺服器(除非它已經啟動)並連接到它:

%load_ext tensorboard%tensorboard --logdir=./my_logs --port=6006

無論哪種方式,您都應該看到TensorBoard的web界面。單擊SCALARS可以查看學習曲線,如下圖所示。在左下角,選擇您想要可視化的日誌(例如,第一次和第二次運行的訓練日誌),並單擊epoch_loss。請注意,兩次運行的訓練損失都很好地減少了,但是第二次運行的損失更快。實際上,我們使用的學習率是0.05 (optimizer=keras.optimers.sgd (lr=0.05)),而不是0.001。

您還可以可視化整個圖、學習到的權值(投影到3D)或分析跟蹤。TensorBoard()回調也有記錄額外數據的選項,比如嵌入。

此外,TensorFlow在tf.summary包中提供了一個低級API。下面的代碼使用create_file_writer()函數創建了一個SummaryWriter,它使用這個writer作為上下文來記錄標量、直方圖、圖像、音頻和文本,所有這些都可以使用TensorBoard進行可視化(嘗試一下!):

test_logdir = get_run_logdir()writer = tf.summary.create_file_writer(test_logdir)with writer.as_default():    for step in range(1, 1000 + 1):        tf.summary.scalar("my_scalar", np.sin(step / 10), step=step)        data = (np.random.randn(100) + 2) * step / 100         tf.summary.histogram("my_hist", data, buckets=50, step=step)        images = np.random.rand(2, 32, 32, 3)         tf.summary.image("my_images", images * step / 1000, step=step)texts = ["The step is " + str(step), "Its square is " + str(step**2)]        tf.summary.text("my_text", texts, step=step)        sine_wave = tf.math.sin(tf.range(12000) / 48000 * 2 * np.pi * step)        audio = tf.reshape(tf.cast(sine_wave, tf.float32), [1, -1, 1])        tf.summary.audio("my_audio", audio, sample_rate=48000, step=step)

這實際上是一個很有用的可視化工具,甚至超過了TensorFlow或深度學習。

讓我們總結一下到目前為止你在這一章中學到的東西:我們看到了神經網絡從哪裡來,MLP是什麼,以及如何使用它進行分類和回歸,如何使用tf.keras的順序API來構建mlp,以及如何使用函數式API或子類API來構建更複雜的模型架構。您了解了如何保存和恢復模型,以及如何使用回調來隨時保存模型、早停等等。最後,您學習了如何使用TensorBoard進行可視化。您可以繼續使用神經網絡來解決許多問題!然而,你可能想知道如何選擇隱藏層的數量,網絡中的神經元的數量,以及所有其他超參數。

3 Fine-Tuning Neural Network Hyperparameters

神經網絡的靈活性也是其主要缺點之一:有許多超參數需要調整。您不僅可以使用任何可以想像到的網絡架構,而且即使在一個簡單的MLP中,您也可以更改層的數量,每層的神經元數量,每層使用的激活函數類型,權值初始化邏輯,等等。您如何知道哪種超參數組合最適合您的任務?

一種選擇是簡單地嘗試多種超參數組合,看看哪種組合最適合驗證集(或使用K-fold交叉驗證)。例如,我們可以使用GridSearchCV或RandomizedSearchCV來探索超參數空間,就像我們在第2章中所做的那樣。為此,我們需要將Keras模型包裝在對象中,該對象就類似於常規scikit-learn的回歸器。第一步是創建一個函數來構建和編譯一個Keras模型,給定一組超參數:

def build_model(n_hidden=1, n_neurons=30, learning_rate=3e-3, input_shape=[8]):    model = keras.models.Sequential()    model.add(keras.layers.InputLayer(input_shape=input_shape))    for layer in range(n_hidden):        model.add(keras.layers.Dense(n_neurons, activation="relu"))    model.add(keras.layers.Dense(1))    optimizer = keras.optimizers.SGD(lr=learning_rate)    model.compile(loss="mse", optimizer=optimizer)    return model

該函數為單變量回歸問題創建了一個簡單的序列模型(只有一個輸出神經元),具有給定的輸入形狀和給定的隱藏層數和神經元,並使用配置了指定學習率的SGD優化器編譯它。就像Scikit-Learn所做的那樣,最好為儘可能多的超參數提供合理的默認值。

接下來,讓我們基於build_model()函數創建一個KerasRegressor:

keras_reg = keras.wrappers.scikit_learn.KerasRegressor(build_model)

KerasRegressor對象是使用build_model()構建的Keras模型的薄包裝。因為在創建它時沒有指定任何超參數,所以它將使用在build_model()中定義的默認超參數。現在我們可以像使用常規的Scikit-Learn回歸器一樣使用這個對象:我們可以使用它的fit()方法訓練它,然後使用它的score()方法評估它,並使用它的predict()方法進行預測,如下代碼所示:

keras_reg.fit(X_train, y_train, epochs=100,              validation_data=(X_valid, y_valid),              callbacks=[keras.callbacks.EarlyStopping(patience=10)])mse_test = keras_reg.score(X_test, y_test)y_pred = keras_reg.predict(X_new)

請注意,傳遞給fit()方法的任何額外參數都將傳遞給底層的Keras模型。還要注意,分數與MSE損失是相反的。因為Scikit-Learn想要的是分數,而不是損失(也就是說,越高越好)。

我們不想訓練和評估這樣的單個模型,儘管我們想訓練數百個變體,看看哪一個在驗證集中表現最好。因為存在許多超參數,所以最好使用隨機搜索而不是網格搜索。讓我們嘗試探索隱藏層的數量、神經元的數量和學習速率:

from scipy.stats import reciprocalfrom sklearn.model_selection import RandomizedSearchCV
param_distribs = { "n_hidden": [0, 1, 2, 3], "n_neurons": np.arange(1, 100), "learning_rate": reciprocal(3e-4, 3e-2),} 
rnd_search_cv = RandomizedSearchCV(keras_reg, param_distribs, n_iter=10, cv=3)rnd_search_cv.fit(X_train, y_train, epochs=100, validation_data=(X_valid, y_valid), callbacks=[keras.callbacks.EarlyStopping(patience=10)])

這與我們在第2章中所做的是相同的,除了這裡我們將額外的參數傳遞給fit()方法,它們將被轉發給底層的Keras模型。注意,RandomizedSearchCV使用K-fold交叉驗證,所以它不使用X_valid和y_valid,這兩個參數只用於早停。

根據硬體、數據集的大小、模型的複雜性以及n_iter和cv的值,探索超參數的過程可能會持續幾個小時。當它結束時,你可以按照如下代碼,訪問找到的最好的參數,最好的分數,和訓練好的Keras模型:

>>> rnd_search_cv.best_params_{'learning_rate': 0.0033625641252688094, 'n_hidden': 2, 'n_neurons': 42}>>> rnd_search_cv.best_score_-0.3189529188278931>>> model = rnd_search_cv.best_estimator_.model

您現在可以保存這個模型,在測試集中評估它,如果您對它的性能感到滿意,那麼就將它部署到生產環境中。使用隨機搜索並不難,它可以很好地解決許多相當簡單的問題。然而,當訓練緩慢時(例如,對於使用更大數據集的更複雜的問題),這種方法將只探索超參數空間的一小部分。您可以通過手動協助搜索過程來部分地緩解這個問題:首先使用大範圍的超參數值運行一個快速隨機搜索,然後使用以第一次運行中找到的最佳值為中心的較小範圍的值運行另一次搜索,等等。這種方法有望放大一組良好的超參數。然而,這是非常耗費時間的,並且可能不是你利用時間的最好方法。

幸運的是,有很多技術可以比隨機搜索更有效地探索搜索空間。他們的核心理念很簡單:當一個空間區域被證明是好的,它就應該被更多地探索。這些技術可以幫助你處理「縮放」過程,並在更短的時間內產生更好的解決方案。下面是一些你可以用來優化超參數的Python庫:

此外,許多公司提供超參數優化服務。我們將討論谷歌雲人工智慧平臺的超參數調優服務。

超參數調優仍然是一個活躍的研究領域,進化算法正在捲土重來。例如,DeepMind 2017年的優秀論文中,作者共同優化了一組模型及其超參數。谷歌還使用了一種進化的方法,不僅搜索超參數,而且為問題尋找最佳的神經網絡結構;他們的AutoML套件已經作為雲服務提供了。也許人工構建神經網絡的日子很快就會結束?查看谷歌關於這個話題的帖子。事實上,進化算法已經成功地用來訓練個體神經網絡,取代普遍存在的梯度下降!例如,請參見2017年,優步發布了一篇文章,作者介紹了他們的深度神經進化技術。

但是,儘管有這些令人興奮的進展和所有這些工具和服務,了解每個超參數的合理值,以便您可以建立一個快速原型並限制搜索空間這一點是很有用的。下面的章節提供了在MLP中選擇隱藏層和神經元的數量,以及為一些主要的超參數選擇好的值的指導方針。

3.1 Number of Hidden Layers

對於許多問題,您可以從單個隱藏層開始並得到合理的結果。只要有足夠的神經元,只含一個隱含層的MLP在理論上甚至可以對最複雜的功能進行建模。但對於複雜的問題: 深度網絡的參數效率比淺層網絡高得多,它們可以用指數級較少的神經元來建模複雜的函數,這使得它們在同等數量的訓練數據下達到更好的性能。


為了理解其中的原因,假設你被要求用一些繪圖軟體畫一個森林,但是你被禁止複製和粘貼任何東西。這需要花費大量的時間:你必須把每棵樹都單獨畫出來,一根樹枝一根樹枝,一片葉子一片葉子。如果你可以只畫一片葉子,複製並粘貼它來畫一個樹枝,然後複製並粘貼那個樹枝來創建一個樹,最後複製並粘貼這棵樹來創建一個森林,你很快就可以完成了。現實世界的數據通常是以這種分層的方式構建的,深度神經網絡自動利用了這一事實:較低的隱藏層建模較低層次的結構(例如,各種形狀和方向的線段),中級隱藏層結合這些較低層次的結構建模中級層次的結構(例如,正方形,圓形),最高的隱藏層和輸出層結合這些中級結構建模高級結構(例如,人臉)。

這種層次結構不僅有助於DNNs更快地收斂到一個好的解,而且還提高了它們對新數據集的泛化能力。例如,如果你已經訓練了一個模型來識別圖片中的人臉,現在你想訓練一個新的神經網絡來識別髮型,你可以通過重用第一個網絡的較低的層來啟動訓練。不要隨機初始化新神經網絡的前幾層的權值和偏差,你可以初始化它們為第一個網絡的較低層的權值和偏差值。這樣,網絡就不必從頭開始學習大多數圖片中出現的低級結構;它只需要學習更高層次的結構即可(例如,髮型)。這叫做遷移學習。

總之,對於許多問題,你可以從一個或兩個隱藏層開始,神經網絡就可以很好地工作。例如,你可以很容易地在MNIST數據集上達到97%以上的準確率,使用一個包含幾百個神經元的隱藏層,使用兩個具有相同神經元總數的隱藏層,在大致相同的訓練時間內達到98%以上的準確率。對於更複雜的問題,您可以增加隱藏層的數量,直到開始過度擬合訓練集。非常複雜的任務,如大型圖像分類或語音識別,通常需要幾十層的網絡(甚至數百層,但不是完全連接的),並且他們需要大量的訓練數據。您很少需要從頭訓練這樣的網絡:更常見的做法是重用預先訓練過的、最先進的網絡的某些部分來執行類似的任務。訓練將會更快,並且需要更少的數據。

3.2 Number of Neurons per Hidden Layer

輸入和輸出層的神經元數量取決於你的任務需要的輸入和輸出類型。例如,MNIST任務需要28×28 = 784個輸入神經元和10個輸出神經元。

對於隱藏層,通常是將它們的大小調整成一個金字塔,每一層上的神經元越來越少——其原理是許多低級特徵可以合併成更少的高級特徵。MNIST的典型神經網絡可能有3個隱藏層,第一層有300個神經元,第二層有200個,第三層有100個。然而,這種做法基本上已經被放棄了,因為似乎在所有隱藏層中使用相同數量的神經元在大多數情況下表現得一樣好,甚至更好。此外,只有一個超參數可調,而不是每層一個。也就是說,根據數據集的不同,它有時使第一個隱藏層比其他層更大會更有幫助。

就像層數一樣,你可以嘗試逐漸增加神經元的數量,直到網絡開始過度擬合。但是在實踐中,選擇一個比你實際需要的層次和神經元更多的模型,然後使用早停和其他正則化技術來防止過擬合通常更簡單和更有效。谷歌的科學家文森特·萬霍克(Vincent Vanhoucke)將這種方法稱為「彈力褲」方法:與其浪費時間去尋找與你的尺寸完全匹配的褲子,不如使用會縮小到合適尺寸的大彈力褲。通過這種方法,您可以避免可能破壞模型的瓶頸層。另一方面,如果一個層的神經元太少,它就沒有足夠的表徵能力來保存輸入的所有有用信息(例如,一個兩個神經元的層只能輸出2D數據,所以如果它處理3D數據,一些信息就會丟失)。不管網絡的其他部分有多龐大和強大,這些信息永遠都不會被恢復。


一般來說,增加層數比增加每層的神經元數更能讓你的錢花得更有價值。

3.3 Learning Rate, Batch Size, and Other Hyperparameters

隱藏層和神經元的數量並不是你能在MLP中調整的唯一超參數。以下是一些最重要的參數,以及如何設置它們:

學習速率
學習速率可以說是最重要的超參數。一般來說,最優學習率大約是最大學習率的一半(高於該學習率算法便會發散)。找到一個好的學習率的一種方法是進行幾百次迭代的模型訓練,從一個非常低的學習率(例如,10^-5)開始,然後逐漸增加到一個非常大的值(例如,10)。這是通過在每次迭代中將學習速率乘以一個常數因子來實現的(例如,通過exp(log(10)/500)將學習速率在500次迭代後從10^-5提高到10)。如果你將損失繪製成學習速率的函數(使用學習速率的對數尺度),你應該首先看到它的下降。但過了一段時間,學習速率會太大,所以損失會反彈:最佳學習速率會略低於損失開始上升的點(通常比轉折點低10倍左右)。然後,您可以重新初始化您的模型,並使用這個良好的學習率正常地訓練它。我們將在第11章看到更多的學習速率技巧。

優化器
選擇一個比普通的小批量梯度下降更好的優化器(並調優其超參數)也非常重要。我們在第11章會看到一些高級優化器。

批量大小
批量的大小對模型的性能和訓練時間有很大的影響。使用大批量的主要好處是,像gpu這樣的硬體加速器可以有效地處理它們(參見第19章),所以訓練算法每秒會看到更多的實例。因此,許多研究人員和實踐者建議使用GPU RAM能夠容納的最大批量大小。但是,這裡有一個問題:在實踐中,大批量常常會導致訓練的不穩定性,特別是在訓練的開始階段,並且生成的模型可能不能很好地像用小批量訓練的模型那樣泛化。2018年4月,Yann LeCun甚至發推文稱:「朋友不會讓朋友使用大於32個的小批量。多米尼克·馬斯特斯(Dominic Masters)和卡洛·盧斯奇(Carlo Luschi) 2018年的一篇論文總結稱,使用小批量(從2個到32個)更合適,因為小批量可以在更少的訓練時間內產生更好的模型。然而,其他論文卻指向了相反的方向。2017年,Elad Hoffer等人和Priya Goyal等人的論文表明,可以藉助各種技術使用非常大的批量(多達8192個),如預熱學習速率(即,用一個小的學習率開始訓練,然後再逐漸增大)。這種方法也導致了非常短的訓練時間,且沒有任何泛化差距。因此,一種策略是嘗試使用大批量,使用學習率預熱,如果訓練不穩定或最終表現令人失望,那麼嘗試使用小批量。

激活函數
我們在本章前面討論了如何選擇激活函數:一般來說,ReLU激活函數將是所有隱藏層的默認值。對於輸出層,它實際上取決於您的任務。

迭代次數
在大多數情況下,訓練迭代的次數實際上不需要調整:只需使用早停即可。因此,可以將迭代次數設置得儘可能得大。

最優學習率取決於其他超參數,尤其是批量大小。因此,如果您修改任何超參數,請確保同時更新學習率。

有關調整神經網絡超參數的更多最佳實踐,請參閱Leslie Smith 2018年的優秀論文。

我們對人工神經網絡及其Keras實現的介紹到此結束。在接下來的幾章中,我們將討論訓練深層網絡的技術。我們還將探索如何使用TensorFlow的底層API自定義模型,以及如何有效地使用數據API加載和預處理數據。我們將深入研究其他流行的神經網絡架構: 用於圖像處理的卷積神經網絡,用於序列數據的遞歸神經網絡,用於表示學習的自編碼器,以及用於建模和生成數據的生成對抗網絡。

相關焦點

  • 開源下載 | 基於Scikit-learn、Keras和TensorFlow的機器學習實戰
    今天要給大家分享的是機器學習領域的一本經典之作:《基於Scikit-learn、Keras
  • 推薦:《機器學習實戰:基於Scikit-Learn和TensorFlow》中文翻譯和代碼下載
    本文提供機器學習入門的好書《機器學習實戰:基於Scikit-Learn和TensorFlow》的中文翻譯文件和原始碼下載。一、前言推薦一本機器學習入門的好書:《機器學習實戰:基於Scikit-Learn和TensorFlow》。
  • Hands-on Machine Learning with Scikit-Learn and TensorFlow 學習筆記
    今天收到 Github 用戶 Baymax(https://github.com/DeqianBai) 的投稿,他目前在哈工程讀研,研究方向是強化學習,利用業餘時間學習了《Hands-on Machine Learning with Scikit-Learn and TensorFlow》這本書並做了Jupyter notebook 筆記,這本書的中文版上市不久,譯名為《機器學習實戰
  • scikit-learn和tensorflow到底有什麼本質區別?
    功能不同Scikit-learn(sklearn)的定位是通用機器學習庫,而TensorFlow(tf)的定位主要是深度學習庫。一個顯而易見的不同:tf並未提供sklearn那種強大的特徵工程,如維度壓縮、特徵選擇等。
  • 使用tensorflow和Keras的初級教程
    介紹人工神經網絡(ANNs)是機器學習技術的高級版本,是深度學習的核心。人工神經網絡涉及以下概念。輸入輸出層、隱藏層、隱藏層下的神經元、正向傳播和反向傳播。簡單地說,輸入層是一組自變量,輸出層代表最終的輸出(因變量),隱藏層由神經元組成,在那裡應用方程和激活函數。
  • Keras和TensorFlow究竟哪個會更好?
    我會使用基於 TensorFlow 的標準 keras 模塊和 tf.keras 模塊,來實現一個卷積神經網絡(CNN)。然後,基於一個示例數據集,來訓練這些 CNN,然後檢查所得結果,你會發現,Keras 和 TensorFlow 是可以和諧共處的。
  • 100天搞定機器學習|day39 Tensorflow Keras手寫數字識別
    實戰」,「置頂」公眾號重磅乾貨,第一時間送達提示:建議先看day36-38的內容TensorFlow™ 是一個採用數據流圖(data flow graphs),用於數值計算的開源軟體庫。TensorFlow 最初由Google大腦小組(隸屬於Google機器智能研究機構)的研究員和工程師們開發出來,用於機器學習和深度神經網絡方面的研究,但這個系統的通用性使其也可廣泛用於其他計算領域。1、安裝庫tensorflow有些教程會推薦安裝nightly,它適用於在一個全新的環境下進行TensorFlow的安裝,默認會把需要依賴的庫也一起裝上。
  • 機器學習 | 四大常用機器學習Python庫介紹
    Python中常用的機器學習庫(機器學習、深度學習啥的,小編還是建議使用Python進行建模編寫哈),也算是本公號機器學習的第一篇推文,主要內容如下:深度學習常用四大Python庫這一部分我們簡單介紹下Python中的常用的機器學習庫,算是比較入門的介紹哈,具體包括Scikit-learn、Keras、TensorFlow和PyTorch
  • 資料|《機器學習實戰:基於 Scikit-Learn、Keras 和 TensorFlow(第...
    資料 |《機器學習實戰:基於 Scikit-Learn、Keras 和 TensorFlow(第二版)》
  • 《機器學習實戰:基於Scikit-Learn和TensorFlow》中文版
    機器學習的核心是「使用算法解析數據,從中學習,然後對世界上的某件事情做出決定或預測」。這意味著,與其顯式地編寫程序來執行某些任務,不如教計算機如何開發一個算法來完成任務。有三種主要類型的機器學習:監督學習、非監督學習和強化學習,所有這些都有其特定的優點和缺點。
  • 【下載】豆瓣評分8.1,《機器學習實戰:基於Scikit-Learn和TensorFlow》
    AI算法工程   公眾號:datayx《機器學習實戰:基於Scikit-Learn和TensorFlow》本書主要分為兩個部分。第一部分為第1章到第8章,涵蓋機器學習的基礎理論知識和基本算法——從線性回歸到隨機森林等,幫助讀者掌握Scikit-Learn的常用方法;第二部分為第9章到第16章,探討深度學習和常用框架TensorFlow,一步一個腳印地帶領讀者使用TensorFlow搭建和訓練深度神經網絡,以及卷積神經網絡。
  • 一文上手Tensorflow2.0之tf.keras|三
    Tensorflow2.0 的安裝(CPU和GPU)「tf.keras」API3 TensorFlow2.0使用3.2 「tf.keras」APIKeras是一個基於Python編寫的高層神經網絡API,Keras強調用戶友好性、模塊化以及易擴展等,其後端可以採用TensorFlow、Theano以及CNTK,目前大多是以TensorFlow作為後端引擎。
  • 基於TensorFlow的深度學習實戰
    毫不誇張得說,TensorFlow的流行讓深度學習門檻變得越來越低,只要你有Python和機器學習基礎,入門和使用神經網絡模型變得非常簡單。TensorFlow簡介如前所述,TensorFlow是一個深度學習庫,使用這一框架,可以用來構建和測試深度神經網絡。深度學習讓我們能夠以極高的準確性構建複雜的應用程式。
  • 使用TensorFlow編寫您的第一個神經網絡
    介紹神經網絡是受生物神經網絡啟發而產生的一套特殊的機器學習算法,它們徹底改變了機器學習。簡單地說,它們是通用的函數近似,可以應用於幾乎任何關於學習從輸入到輸出空間的複雜映射的機器學習問題。神經網絡可能相當複雜,但在這篇文章中,我將解釋如何編寫您的第一個嘗試學習x和y之間的線性關係的神經網絡。y = 2 * x - 1上面的方程是一個簡單的線性方程,我們不一定需要神經網絡來學習這種關係。
  • TensorFlow發布JavaScript開發者的機器學習框架TensorFlow.js
    在下文中,機器之心對 TensorFlow.js 做了細緻介紹:在大會的 Keynote 中,TensorFlow 團隊表示基於網頁的 JavaScript 庫 TensorFlow.js 現在已經能訓練並部署機器學習模型。我們可以使用神經網絡的層級 API 構建模型,並在瀏覽器中使用 WebGL 創建複雜的數據可視化應用。
  • MIT 深度學習基礎教程:七個基本框架TensorFlow代碼實戰
    【導讀】麻省理工學院發布一系列深度學習視頻課和代碼實戰,今天給大家介紹的是研究科學家Lex Fridman整理的常用的深度學習七個基本框架實戰,
  • Tensorflow.keras筆記-卷積神經網絡
    Tensorflow.keras筆記-卷積神經網絡cifar10數據集    1.
  • Keras結合Keras後端搭建個性化神經網絡模型(不用原生Tensorflow)
    Keras是基於Tensorflow等底層張量處理庫的高級API庫。它幫我們實現了一系列經典的神經網絡層(全連接層、卷積層、循環層等),以及簡潔的迭代模型的接口,讓我們能在模型層面寫代碼,從而不用仔細考慮模型各層張量之間的數據流動。
  • 初學AI神經網絡應該選擇Keras或是Pytorch框架?
    對於許多開發者來說,TensorFlow是他們接觸的第一個機器學習框架。TensorFlow框架儘管意義非凡,引起極大關注和神經網絡學習風潮,但對一般開發者用戶太不友好。軟體開發者畢竟不是科學家,很多時候簡單易學易用是程式設計師選擇的第一要素。
  • 基於Tensorflow\keras銀行卡識別
    本文轉載自【微信公眾號:機器學習算法與Python精研 ,ID:AITop100】,經微信公眾號授權轉載,如需轉載原文作者聯繫來自:GitHub測試環境Ubuntu18.04python 3.6.7numpy 1.16.4tensorflow-gpu 1.13.1 或者是cpu版本keras 2.2.4opencv-python 4.1.0.25PyQt5 5.12.2CUDA