《TensorFlow:實戰Google深度學習框架(第二版)》筆記【1-6章】

本書PDF 密碼: uj6t
代碼:https://github.com/caicloud/tensorflow-tutorialnode

第一章:深度學習簡介

在大部分狀況下,在訓練數據達到必定數量以前,越多的訓練數據可使邏輯迴歸算法對未知郵件作出的判斷越精準。之因此說在大部分狀況下,是由於邏輯迴歸算法的效果除了依賴於訓練數據,也依賴於從數據中提取的特徵。假設從郵件中抽取的特徵只有郵件發送的時間,那麼即便有再多的訓練數據,邏輯迴歸算法也沒法很好地利用。這是由於郵件發送的時間和郵件是否爲垃圾郵件之間的關聯不大,而邏輯迴歸算法沒法從數據中習得更好的特徵表達。這也是不少傳統機器學習算法的一個共同問題。git

如何從實體中提取特徵,對於不少傳統機器學習算法的性能有巨大影響。下圖展現了一個簡單的例子。經過笛卡爾座標系和極角座標系表示數據。一樣的數據使用不一樣的表達方式會極大地影響解決問題的難度。一旦解決了數據表達和特徵提取,不少人工智能任務也就解決了90%。github

既然人工的方式沒法很好地抽取實體中的特徵,那麼是否有自動的方式呢?深度學習解決的核心問題之一就是自動的將簡單地特徵組合成更加複雜的特徵,並使用這些組合特徵解決問題。下圖展現了深度學習和傳統機器學習在流程上的差別。深度學習算法能夠從數據中學習更加複雜的特徵表達,使得最後一步權重學習變得更加簡單且有效。web

神經科學家們發現,若是將小白鼠的視覺神經鏈接到聽覺中樞,一段時間以後小白鼠能夠習得使用聽覺中樞「看」世界。這說明雖然哺乳動物大腦分爲了不少區域,但這些區域的學習機制倒是類似的。正則表達式

深度學習的發展歷程

第一個階段:模仿大腦的學習機理算法

感知機模型能夠根據樣例數據學習特徵權重。
不足:感知機沒法解決線性不可分問題,且計算能力不足以訓練多層神經網絡編程

第二個階段:分佈式知識表達和反向傳播算法
分佈式知識表達大大增強了模型的表達能力,讓神經網絡從寬度的方向走向了深度的方向。
反向傳播算法大幅下降了訓練神經網絡所須要的時間。
不足:計算資源不足以訓練深層神經網絡;數據量沒法知足訓練須要。json

第三個階段:雲計算、GPU和海量數據
解決了計算力和數據的問題,深度學習迎來高潮。數組

深度學習的應用

  1. 計算機視覺:分類,識別,無人駕駛,圖像搜索等
  2. 語音識別:siri,同聲傳譯等
  3. 天然語言處理:語言模型、機器翻譯、詞性標註、實體識別、情感分析等
    核心技術:單詞向量word embedding
  4. 人機博弈:AlphaGo系列

深度學習工具

第二章:TensorFlow環境搭建

  1. 依賴的工具包
  2. 安裝方式
  3. 樣例程序(略)

TensorFlow的主要依賴包

Protocol Buffer和Bazel瀏覽器

Protocol Buffer

Protocol Buffer的做用是將結構化的數據序列化,並從序列化以後的數據流中還原出原來的結構化數據。

與XML、Json等其餘工具相比:

  • 序列化爲二進制流,而不是字符串
  • XML、Json格式信息包含在數據流中,Protocol Buffer須要先定義數據的格式
  • Protocol Buffer數據流比XML小3-10倍,解析時間快20-100倍

結構化數據:

Protocol Buffer數據格式示例:

Protocol Buffer數據格式通常保存在.proto文件中。

TensorFlow中的數據基本都是經過Protocol Buffer來組織的。

Bazel

Bazel是從Google開源的自動化構建工具,相比傳統的makefile,Ant或者Maven,Bazel在速度、可伸縮性、靈活性以及對不一樣程序語言和平臺的支持上都要更加出色。Tensorflow自己以及Google給出的不少官方樣例都是經過Bazel來編譯的。

  • WORKSPACE文件:定義了對外部資源的依賴關係
  • BUILD文件:找到須要編譯的目標

在編譯出來的結果中,bazel-bin目錄下存放了編譯產生的二進制文件以及運行該二進制文件所須要的全部依賴關係。

Tensorflow安裝方式

  • Docker:可移植性最強,但對GPU支持有限,且對本地開發環境的支持不夠友好
  • Pip:最方便,但沒法修改Tensorflow自己
  • 源碼:最靈活,但比較繁瑣,通常只有修改tensorflow護着須要支持特殊GPU纔會被用到

第三章 Tensorflow入門

本章介紹Tensorflow的計算模型、數據模型和運行模型,以瞭解Tensorflow的工做原理。並用Tensorflow實現一個神經網絡的計算流程。

Tensorflow計算模型:計算圖

計算圖是Tensorflow中最基本的一個概念,Tensorflow中的全部計算都被被轉化爲計算圖上的節點。

Tensorflow是一個經過計算圖的形式來描述計算的編程系統。Tensor即張量,Flow指計算圖。Tensorflow中的每個計算都是計算圖上的一個節點,而節點之間的邊描述了計算之間的依賴關係。

爲了建模方便,tf將常量轉化成一種永遠輸出固定值的運算。

tf自動維護一個默認的計算圖,若是沒有特地指定,tf會將定義的計算自動轉化爲默認計算圖上的節點。

除了默認的計算圖,也能夠用tf.Graph生成新的計算圖,不一樣計算圖的張量和運算不會共享。

計算圖能夠經過tf.Graph.device函數指定運行計算的設備。

有效的整理tf程序中的資源也是計算圖的一個重要功能。能夠經過集合來管理不一樣類別的資源。體乳經過tf.add_to_collection將資源加入一個或多個集合中,而後經過tf.get_collection獲取一個集合裏面的全部資源。這裏的資源能夠是張量、變量或者運行tf程序所須要的隊列資源等。tf自動管理了一些最經常使用的集合:

Tensorflow數據模型:張量

張量是tf管理數據的形式。

tf全部的數據都經過張量的形式來表示,張量能夠簡單理解爲多維數組。但張量在tf的實現並非直接採用數組的形式,它只是對tf中運算結果的引用。在張量中並無真正保存數字,它保存的是如何獲得這些數字的計算過程。

執行上面的代碼,並不會獲得加法的結果,而是對結果的一個引用。
tf的計算均可以經過計算圖的模型來創建,而計算圖上的每個節點表明了一個計算,計算的結果保存在張量之中。張量對應了計算圖上節點表明的計算結果。

一個張量主要保存三個屬性:名字(name),維度(shape)和類型(type)。

  • 張量的命名形式能夠是「node:src_output"。其中node爲節點的名稱,src_output表示來自節點的第幾個輸出。
  • 維度是張量一個很重要的屬性,圍繞張量的維度tf給出了不少有用的運算。
  • 每一個張量都有一個惟一的類型,tf會對參與運算的全部張量進行類型檢查,當發現類型不匹配會報錯。如

報錯:

若是將a指定爲實數類型」a=tf.constant([1,2],name=「a」,dtype=tf.float32)",就不會報錯了。不指定類型,tf會給出默認的類型,容易致使類型不匹配問題。tf支持14中不一樣的類型,主要包括實數(tf.float32,tf.float64)、整數(tf.int8,tf.int16,tf.int64,tf.uinit8)、布爾型(tf.bool)和複數(tf.complex64/tf.conplex128)。

Tensorflow運行模型:會話

計算圖和張量分別組織運算和數據,而會話(session)用來執行定義好的運算。會話擁有並管理tf程序運行時的全部資源,當全部計算完成以後須要關閉會話幫助系統回收資源,不然可能出現資源泄露的問題。tf中使用會話的模式通常有兩種。

使用這種模式須要明確調用Session.close關閉會話並釋放資源。當程序由於異常而退出時,關閉函數可能不會執行致使資源泄露。

經過Python上下文管理器機制,退出時自動釋放全部資源。

會話和計算圖有相似的機制,但tf不會自動生成默認的會話,而是須要手動指定。默認的會話被指定以後能夠經過tf.Tensor.eval函數來計算一個張量的取值。

或者

在交互式環境(好比Python腳本或者Jupyter的編輯器)下,經過設置默認會話的方式來獲取張量的取值更加方便。因此tf提供了一種直接構建默認會話的函數tf.InteractiveSession。

不管使用哪一種方法均可以經過ConfigProto Protocol Buffer來配置須要生成的會話。

經過ConfigProto能夠配置相似並行的線程數、GPU分配策略,運算超時時間等參數。在這些參數中,最常使用的有兩個。

  • allow_soft_placement:默認值爲False。設爲True時,能夠在如下狀況發生時,把GPU的運算放到CPU上。
  1. 運算沒法在GPU上執行
  2. 沒有GPU資源(好比指定在第二個GPU,但只有一個GPU)
  3. 運算輸入包含對CPU結果的引用
  • log_device_placement:True時日誌中會記錄每一個節點被安排在了哪一個設備上以方便調試。在生產環境設爲False能夠減小日誌量。

Tensorflow實現神經網絡

Tensorflow遊樂場是一個經過網頁瀏覽器就能夠訓練的簡單神經網絡並實現了可視化訓練的工具。

使用這個工具能夠看到神經網絡解決分類問題主要分爲如下步驟:

  1. 提取問題中實體的特徵向量做爲神經網絡的輸入。
  2. 定義神經網絡的結構,並定義如何從輸入獲得輸出
  3. 經過訓練數據來調整神經網絡中參數的取值
  4. 使用訓練好的神經網絡預測未知的數據

前向傳播算法簡介

本小節用最簡單的全鏈接網絡(區別於卷積網絡、LSTM等結構)的前向傳播算法介紹。首先須要瞭解神經元(也稱爲節點)的結構。

一個節點有多個輸入和一個輸出,最簡單的節點的輸出就是對全部輸入的加權和,不一樣輸入的權重就是節點的參數。而神經網絡的優化過程就是優化節點中參數的值的過程(反向傳播算法)。下面給出了一個簡單的判斷零件是否合格的三層全鏈接神經網絡(全鏈接神經網絡是指相鄰兩層之間全部節點之間都有鏈接)。

計算神經網絡的前向傳播結果須要三部分信息。

  1. 神經網絡的輸入:從實體中提取的特徵向量。上圖中的零件長度x1和零件質量x2。
  2. 神經網絡的鏈接結構:神經網絡由節點組成,神經網絡的結構就是不一樣節點之間輸入輸出的鏈接關係。
  3. 神經元的參數:用W表示神經元的參數。W的上標代表神經網絡的層數,下標代表鏈接節點的編號。如W1,2(1)表示鏈接x1和a12節點的權重。

有了輸入、結構和參數,就能夠經過前向傳播算法計算神經網絡的輸出,如圖。

其中a11的計算過程

輸出y的計算過程

前向傳播算法能夠表示爲矩陣乘法,將輸入x1,x2表示爲1x2的矩陣x=[x1,x2],W(1)表示爲2x3的矩陣

這樣經過矩陣乘法就能夠獲得隱藏層三個節點的向量取值:

相似的輸出層:

tf中矩陣乘法的實現很是簡單。

以後的章節中會繼續介紹偏置(bias)、激活函數(activation function)等更復雜的神經元結構,還有卷積神經網絡、LSTM等更復雜的神經網絡結構。

神經網絡參數與Tensorflow變量

本小節介紹tf是如何組織、保存和使用神經網絡中的參數的。在tf中,變量的做用是保存和更新神經網絡中的參數。tf中的變量須要指定初始值,其中隨機初始值最多見。

這段代碼調用變量聲明函數tf.Variable,在函數中給出了初始化方法。初始值能夠是隨機數、常數或者經過其餘變量的初始值計算獲得。tf.random_normal([2,3],stddev=2)會產生一個2x3的矩陣,矩陣的元素是均值爲0,標準差爲2的隨機數。下表列出了tf目前支持的全部隨機數生成器。

常量聲明方法(好比偏置項:biases=tf.Variable(tf.zeros([3])))

經過其餘變量的初始值初始化

如下樣例介紹瞭如何經過變量實現神經網絡的參數並實現前向傳播的過程。

也可使用tf.initialize_all_variables函數實現初始化全部變量的過程。

tf中,變量聲明函數tf.Variable是一個運算,輸出結果是一個張量,因此變量只是一種特殊的張量。下面經過上個案例中計算圖中關於w1的操做的可視化結果說明tf.Variable操做在tf中底層是如何實現的。

能夠看到w1是一個Variable運算。w1經過一個read操做將值提供給了一個乘法運算(tf.matmul(x,w1)),Assign這個節點的輸入爲隨機數生成函數的輸出,輸出賦給了變量w1,這樣就完成了變量初始化。

全部的變量都會被自動的加入GraphKeys.VARIABLES這個集合,經過tf.all_variables函數能夠拿到當前計算圖上全部的變量。拿到全部變量有助於持久化計算圖的運行狀態。能夠經過變量聲明函數中的trainable參數來區分須要優化的參數(好比神經元參數)和其餘參數(好比迭代次數),若是trainable爲True,那麼這個變量會被加入到GraphKeys.TRAINABLE_VARIABLES集合。能夠經過tf.trainable_variables函數獲得全部須要優化的參數。tf提供的神經網絡優化算法會將GraphKeys.TRAINABLE_VARIABLES集合中的變量做爲默認的優化對象。

相似張量,維度和類型也是變量最重要的兩個屬性。變量類型是不能夠改變的,但維度是可能改變的,須要設置參數validate_shape=False,可是更改維度的作法在tf中比較罕見。

經過Tensorflow訓練神經網絡模型

在神經網絡優化算法中,最經常使用的方法是反向傳播算法,本小節主要介紹訓練神經網絡的總體流程以及Tensorflow對於這個流程的支持。

使用tf實現反向傳播算法的第一步是表達一個batch的數據。以前的例子中曾使用常量表達過一個樣本,但若是每次迭代都使用一個常量,那麼計算圖中的常量節點會很是多且利用率很低。爲了不這個問題,tf提供了placeholder機制用於提供輸入數據。placeholder至關於定義了一個位置,這個位置中的數據在程序運行時再指定。定義placeholder時,須要指定類型,可是不必定須要維度,由於能夠根據提供的數據推導出來。

batch數據例子:

獲得前向傳播結果後,經過定義好的損失函數計算損失並經過反向傳播算法更新神經網絡參數。損失函數和反向傳播算法將在第四章詳細介紹。

完整神經網絡樣例程序

ipynb github代碼

第四章 深層神經網絡

本章進一步介紹如何設計和優化神經網絡,內容包括深度學習的概念、損失函數、反向傳播算法、tf實現反向傳播的過程以及神經網絡優化中常見的問題。

深度學習與深層神經網絡

維基百科對深度學習的定義爲「一類經過多層非線性變換對高複雜性數據建模算法的合集」。基本上深度學習就是深層神經網絡的代名詞。

從定義中能夠看出深度學習有兩個很是重要的特徵:多層和非線性。

線性模型的侷限性

線性模型中,模型的輸出爲輸入的加權和。

前面介紹的前向傳播算法實現的就是一個線性模型。雖然那個神經網絡有兩層,可是和單層神經網絡的表達能力沒有區別,由於它們只有線性變換。然而線性模型解決的問題是有限的,也是爲何深度學習要強調非線性。

咱們使用Tensorflow遊樂場試驗一下,使用Activation爲Linear的線性模型獲得的效果是:

能夠看到模型不能很好的區分不一樣顏色的點,只能經過直線來劃分平面。

使用Activation爲Relu的非線性模型的效果是:

可見,對於複雜問題(沒法經過直線或者高維平面劃分的),非線性模型能夠很好的解決。在現實世界中,絕大部分問題都是非線性的。

對於線性可分問題,線性模型就能夠很好的區分:

激活函數實現非線性化

上一節提到了Activation(激活函數),若是將前面提到的加權和節點後經過一個非線性函數,那麼神經網絡模型就是非線性的了。這個非線性函數就是激活函數。

新的結構中加入了偏置項(輸出永遠爲1的節點)和非線性變換。

激活函數的圖像都不是一條直線:

新的神經網絡結構圖:

輸出層計算公式

目前tf提供7中不一樣的非線性激活函數,好比tf.nn.relu,tf.sigmoid和tf.tanh。tf也支持使用自定義的激活函數。如下代碼展現了tf實現上圖中的前向傳播算法:

多層網絡解決異或運算

神經網絡發展史上,一個重要的問題就是異或問題。感知機模型的提出從數學上完成了對神經網絡的建模,感知機能夠簡單的理解爲單層的神經網絡,其網絡結構就是圖4-5。然而感知機被證實沒法解決異或運算(若是兩個輸入的符號相同輸出0,不然輸出1):

加入隱藏層後,異或問題能夠很好地解決。

並且,隱藏層的四個節點中,每一個節點都有一個角是黑的。這個節點表明了從輸入特徵中抽取的更高維的特徵,好比第一個節點大體表明兩個輸入的邏輯與操做的結果。深度神經網絡的組合特徵提取的功能,對於解決不易提取特徵向量的問題(圖片識別、語音識別等)有很大幫助。

損失函數定義

神經網絡模型的效果以及優化的目標是經過損失函數來定義的,本節講解適用於分類和迴歸問題的經典損失函數、自定義損失函數。

經典損失函數

分類問題:交叉熵函數
經過神經網絡解決多分類問題最經常使用的方法是設置n個輸出節點,n是分類的個數。理想狀況下,若是一個樣本屬於類別k,那麼這個類別對應的輸出節點爲1,其餘節點爲0,好比[0,1,0,0,0,0]。怎麼判斷一個輸出向量和指望的向量有多接近呢?交叉熵(cross entropy)是經常使用的評判方法之一。交叉熵刻畫了兩個機率分佈之間的距離,它是分類問題中使用比較多的損失函數。

給定兩個機率分佈p和q,經過q來表示p的交叉熵爲

交叉熵刻畫的是兩個機率分佈的距離,然而神經網絡的輸出不必定是一個機率分佈(任意事件發生的機率都在0和1之間,且機率總和爲1)。Softmax迴歸是一個經常使用的將前向傳播結果變成機率分佈的方法。

交叉熵函數不是對稱的,H(p,q)!=H(q,p)

Softmax迴歸自己能夠做爲一個學習算法來優化分類結果,但在tf中,Softmax迴歸的參數被去掉了,只做爲一個額外的處理層,將輸出變成一個機率分佈。

交叉熵值越小,兩個機率分佈越接近。以前已經用tf實現過交叉熵的計算:

由於交叉熵通常與Softmax迴歸一塊兒使用,tf提供了tf.nn.softmax_cross_entropy_with_logits函數:

其中y表明輸出值,y_表明標準答案。
若是分類只有一個正確答案,還可使用tf.nn.sparse_softmax_cross_entropy_with_logits函數加速計算過程。

迴歸問題:均方偏差函數
迴歸問題須要預測的不是一個實現定義好的類別,而是一個任意實數,好比房價、銷量等。解決迴歸問題的神經網絡通常只有一個輸出節點,最經常使用的損失函數是均方偏差(MSE)

tf實現:

均方偏差也是分類問題經常使用的損失函數

自定義損失函數

爲了讓神經網絡優化的結果更加接近實際問題,咱們須要自定義損失函數。

好比商品銷量問題,若是預測值較大(大於真實銷量),商家損失生產商品的成本。若是預測值較小,損失商品的利潤。由於通常成本和利潤不相同,使用均方偏差不可以很好地最大化利潤。好比成本是1元,利潤是10元,那麼模型應該偏向預測值較大。下面的公式給出了預測值多於或少於真實值時有不一樣係數的損失函數,經過這個損失函數,模型可能最大化收益。

tf實現:

示例代碼github

神經網絡優化算法

本節介紹如何經過反向傳播算法和梯度降低算法調整神經網絡中參數的取值。梯度降低算法主要用於優化單個參數的取值,而反向傳播算法給出了一個高效的方式在全部參數上使用梯度降低算法。反向傳播算法是訓練神經網絡的核心算法,能夠根據定義好的損失函數優化參數的取值,從而使模型在訓練數據集上的損失函數達到一個較小值。神經網絡模型參數的優化過程直接決定了模型的質量。

本筆記不對梯度降低和反向傳播算法作記錄,詳情可參考原書或其餘資源。

幾個概念:

  • 學習率
  • 局部最優
  • 隨機梯度降低

在tf中實現神經網絡的訓練過程大體以下:

神經網絡進一步優化

介紹神經網絡優化過程當中可能遇到的一些問題,以及解決的經常使用方法。好比指數衰減學習率、過擬合和滑動平均模型。

學習率的設置

tf提供了一種靈活的學習率設置方法——指數衰減法,tf.train.exponential_decay。先使用較大的學習率快速獲得一個比較優的解,隨着迭代的繼續逐步減小學習率,使得模型在訓練後期更加穩定。它實現瞭如下代碼的功能:

decayed_learning_rate爲每一輪優化時使用的學習率,learning_rate爲設定的初始學習率,decay_rate爲衰減係數,decay_steps爲衰減速度。tf.train.exponential_decay能夠經過設置參數staircase選擇不一樣的衰減方式,默認爲False,此時學習率衰減趨勢以下圖灰色曲線。當設置爲True時,global_step/decay_steps會被轉化成整數,使得學習率成爲一個階梯函數,如黑色曲線。在這樣的設置下,decay_steps一般表明完整使用一遍訓練數據所須要的迭代輪數,也就是總訓練數據/一個batch的樣本數。這樣可使得總訓練數據中的每個batch都使用相同的學習率,對模型產生相等的做用。當完整過完一遍訓練數據時,學習率就減小一次。

tf代碼實現:

初始學習率、衰減係數和衰減速度都是根據經驗設置

示例代碼github

過擬合問題

正則化的思想是在損失函數中加入刻畫模型複雜程度的指標,通常來講模型複雜度只由權重w決定,和偏置b無關。經常使用的刻畫模型複雜度的函數R(w)有兩種,L1正則

和L2正則

L1正則化會讓參數變得更稀疏,而L2正則不會。其次,L1正則的計算公式不可導,L2正則可導。由於優化時須要計算損失函數的偏導數,因此對L2正則損失函數的優化要更加簡潔。優化L1正則更加複雜,並且優化方法也有不少種。也能夠將L1和L2一塊兒使用

tf實現帶L2正則的損失函數定義

相似的,L1爲

當網絡結構複雜 以後,定義網絡結構的部分和計算損失函數的部分可能不在同一個函數中,這樣經過變量這種方式計算損失函數就不方便了。爲了解決這個問題,咱們可使用tf的集合。

滑動平均模型

滑動平均模型是可讓模型在測試數據上更魯棒的方法。tf提供了tf.train.ExponentialMovingAverage來實現滑動平均模型。初始化ExponentialMovingAverage時,須要提供一個衰減率(decay)。ExponentialMovingAverage對每個變量都會維護一個影子變量(shadow variable),影子變量的初始值就是相應變量的值,而每次運行變量更新時,影子變量的值都會更新爲:

decay決定了模型更新的速度,decay越大模型越趨於穩定。實際應用中,decay通常設成很是接近1的數(好比0.999或0.9999)。爲了模型在訓練前期能夠更新的更快,ExponentialMovingAverage還提供了num_updates來動態設置decay的大小:

滑動平均實例代碼

第5章會給出在真實應用中使用滑動平均的樣例。

第五章 MNIST數字識別問題

第四章介紹了訓練神經網絡模型須要考慮的主要問題和經常使用解決方法,這一章將經過一個實際問題驗證。並介紹Tensorflow變量重用和命名空間、模型持久化問題。

MNIST數據處理

MNIST是一個很是有名的手寫體數字識別數據集。Tensorflow的封裝讓使用MNIST數據集變得更加方便,tf提供了一個類來處理MNIST數據,這個類會自動下載並轉化MNIST數據的格式,將數據從原始的數據包中解析成訓練和測試神經網絡時使用的格式。

神經網絡模型訓練及不一樣模型結果對比

首先給出一個tf程序解決MNIST問題,而後在驗證數據集上評價表現,使用第四章介紹的優化方法改進模型。

Tensorflow訓練神經網絡

使用第四章介紹的訓練和優化方法(指數衰減、正則、滑動平均)實現的代碼:

從上面的結果能夠看出,在訓練初期,模型在驗證數據集上的表現愈來愈好。從第4000輪開始,模型的表現開始波動,說明模型已經接近極小值。

程序開始設置了初始學習率、學習率衰減率、隱藏層節點數量、迭代輪數等7種不一樣的參數。通常從驗證數據集評判不一樣參數取值下模型的表現。交叉驗證的方式會花費大量時間,因此在海量數據的狀況下,通常會更多地採用驗證數據集的形式。

不一樣模型效果比較

本小節使用神經網絡模型在MNIST測試數據集上的正確率做爲評價不一樣優化方法的標準。

從上圖能夠看到,神經網絡的結構對模型的效果有本質性的影響。而滑動平均、指數衰減和正則化帶來的提高並非特別明顯。由於滑動平均和指數衰減在必定程度上都是限制神經網絡中參數更新的速度,而在MNSIT數據上,模型收斂的速度很快,因此這兩種優化影響不大。

能夠看到,從4000輪以後,由於梯度自己較小,參數的改變也就緩慢了。因而滑動平均或者指數衰減的做用沒有那麼突出了。然而,當問題更加複雜時,迭代不會那麼快收斂,好比Cifar-10數據集上,使用滑動平均模型能夠將錯誤率下降11%,使用指數衰減能夠將錯誤率下降7%。

相比滑動平均和指數衰減,正則化給模型帶來的提高相對顯著。下圖對比了兩個使用不一樣損失函數的神經網絡模型。實線給出了正確率的變化趨勢,虛線給出了當前訓練batch的交叉熵損失。

能夠看到,只優化交叉熵的模型在訓練數據上的交叉熵損失要比優化總損失的模型更小。然而在測試數據上,優化總損失的模型卻要好於只優化交叉熵的模型。這個緣由就是過擬合問題。下圖顯示了不一樣模型的損失函數的變化趨勢。

左側隨着迭代的進行,正則化損失是在不斷加大的,由於MNIST問題相對比較簡單,迭代後期的梯度很小,因此正則化損失的增加也不快。若是問題更復雜,總損失會呈現出一個U字型。右側的正則化損失也能夠隨着迭代的進行愈來愈小,從而使得總體的損失呈現一個逐步遞減的趨勢。

變量管理

在以前計算神經網絡前向傳播結果的過程抽象成了一個函數,經過這種方式在訓練和測試的過程當中能夠統一調用同一個函數來獲得模型的前向傳播結果。

def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):

這個函數的參數中包括了神經網絡中的全部參數,當神經網絡的結構更加複雜,參數更多時,就須要一個更好的方式來傳遞和管理神經網絡中的參數了。tf提供了經過變量名稱建立或者獲取一個變量的機制,在不一樣函數中能夠直接經過變量名字來使用變量,而不須要將變量經過參數的形式處處傳遞。

第四章中介紹了經過tf.Variable函數來建立一個變量。除了tf.Variable,tf還提供了tf.get_variable來建立或者獲取變量。tf.get_variable建立變量的功能和tf.Variable基本相同。

其中tf提供的initializer函數和第三章中介紹的隨機數以及常量生成函數大部分是一一對應的。

tf.get_variable的變量名稱參數是必須的,若是名稱已存在,建立變量的程序會報錯。

tf.get_variable也能夠獲取一個已經建立過的變量,須要經過tf.variable_scope生成一個上下文管理器。

當tf.variable_scope使用參數reuse=True時,這個上下文管理器內全部的tf.get_variable函數會直接獲取已經建立的變量。若是變量不存在則報錯。reuse=False或者None,tf.get_variable將建立新變量。tf.variable_scope是能夠嵌套的。

tf.variable_scope也會建立一個tf中的命名空間,在其中建立的變量都會帶上這個命名空間做爲前綴。

將前面的函數改進一下:

當神經網絡更加複雜時,使用這種變量管理方式將大大提升程序的可讀性。

Tensorflow模型持久化

持久化即保存訓練好的模型,並能夠從文件中還原。

持久化代碼實現

tf提供了一個很是簡單的API保存和還原一個神經網絡模型:tf.train.Saver類。

保存計算圖:

tf模型通常會存在後綴爲.ckpt的文件中,且目錄下會出現三個文件,由於tf會將計算圖的結構和圖上參數取值分開保存。model.ckpt.meta保存計算圖的結構,model.ckpt保存tf每個變量的取值,checkpoint保存目錄下全部的模型文件列表。三個文件的具體內容在下小節介紹。

加載模型:

加載模型和保存模型的代碼基本相同,惟一的不一樣是加載模型的代碼中沒有運行變量的初始化過程,而是將變量的值經過已經保存的模型加載進來。若是不但願重複定義圖上的運算,也能夠直接加載圖。

也能夠保存或加載部分變量,在聲明tf.train.Saver類時能夠提供一個列表來指定。好比saver=tf.train.Saver([v1]),只有v1被加載進來。另外還能夠在保存或加載時給變量重命名:

tf經過字典將模型保存時的變量名和須要加載的變量聯繫起來。

這樣作的主要目的之一是方便使用變量的滑動平均值。若是在加載模型時直接將影子變量映射到變量自身,那麼在使用訓練好的模型時就不須要再調用函數來獲取變量的滑動平均值了。

經過重命名讀取滑動平均值:

爲了方便加載時重命名滑動平均變量,tf.train.ExponentialMovingAverage提供了variables_to_restore函數來生成重命名字典。

使用tf.train.Saver會保存運行tf程序所須要的所有信息,然而在測試或者預測時,只須要知道前向傳播獲得輸出便可,不須要相似於變量初始化、模型保存等輔助節點的信息。tf提供了convert_variables_to_constants函數,將計算圖中的變量及其取值經過常量的方式保存,這樣整個計算圖能夠統一存放在同一個文件中。

張量的名稱後面有:0,表示某個計算節點的第一個輸出。而計算節點自己的名稱後是沒有:0的。

加載模型並直接獲得結果:

持久化原理及數據格式

上小節介紹了當調用saver.save函數時,tf會生成3個文件。這小節將詳細介紹這3個文件保存的內容和數據格式。

model.ckpt.meta
tf經過元圖(MetaGraph)保存計算圖中節點的信息以及元數據。元圖是由MetaGraphDef Protocol Buffer定義的。上小節保存的文件就是model.ckpt.meta。

元圖中主要記錄了5類信息,.meta文件是二進制,沒法直接查看。tf提供了export_meta_graph函數,以json格式導出MetaGraphDef Protocol Buffer。

  • meta_info_def
    meta_info_def是經過MetaInfoDef定義的,它記錄了tf計算圖中的元數據以及tf程序中全部使用到的運算方法的信息。

元數據包括計算圖的版本號以及用戶指定的一些標籤(tags),沒有特殊指定默認爲空。只有stripped_op_list屬性是不爲空的。stripped_op_list記錄了tf計算圖上使用到的全部運算方法的信息。每一個方法只會出現一次。
- OpList
stripped_op_list的類型是OpList,OpList是一個OpDef類型的列表。

name定義了運算的名稱,也是一個運算的惟一標識符。下面介紹的GraphDef將經過name來引用不一樣的運算。input_arg和output_arg定義了輸入和輸出列表(repeated),attr給出了其餘的運算參數信息。model.ckpt.meta.json文件中的一個Op爲:

名稱爲Add的運算,有2個輸入和1個輸出,輸入輸出都指定了type_attr,值爲T。在attr屬性中,必須出現name爲T的屬性。這個樣例中,這個屬性制定了運算輸入輸出容許的參數類型(allowed_values)。

  • graph_def
    graph_def記錄了tf計算圖的節點信息,每個節點對應了一個運算。meta_info_def包含了運算的具體信息,graph_def關注運算的鏈接結構。graph_def是經過GraphDef Protocol Buffer定義的,主要包含了一個NodeDef類型的列表。

versions屬性存儲了tf版本號。NodeDef名稱name是節點惟一標識符。tf程序中能夠經過節點的名稱來獲取相應的節點。op屬性是使用的運算方法的名稱,經過這個名稱在meta_info_def屬性中找到該運算。
input是字符串列表,取值格式爲node:src_output,當src_output爲0時可省略。好比node:0也能夠寫做node。(node的第一個輸出)
device指定了處理這個運算的設備。第10章具體介紹如何指定運行設備。device爲空時,tf會自動選取一個合適的設備。attr指定了和當前運算相關的配置信息。model.ckpt.meta.json文件一些計算節點以下:

第一個節點是變量定義的運算,名稱爲v1,運算方法爲Variable,attr屬性指定了變量的維度和類型。
第二個節點是加法運算。輸入爲v1/read,v2/read。v1/read(:0)表明的節點能夠讀取變量v1的值。save/control_dependency是系統在完成模型持久化過程當中自動生成的一個運算。versions是tf版本號。

  • saver_def
    saver_def記錄了持久化模型須要的一些參數,好比保存文件名、保存和加載操做的名稱以及保存頻率、清理歷史記錄等。saver_def類型爲SaverDef。

model.ckpt.meta.json文件的內容:

filename_tensor_name給出了保存文件名的張量名稱,就是節點save/Const的第一個輸出。save_tensor_name是持久化tf模型的運算對應的節點名稱,就是graph_def的save/control_dependency節點。加載tf模型的運算名稱是restore_op_name。max_to_keep和keep_checkpoint_every_n_hours屬性設定了tf.train.Saver類清理以前模型的策略。max_to_keep爲5的時候,第6次調用saver.save時,第一次保存的模型就會被刪除。keep_checkpoint_every_n_hours,每n小時能夠在max_to_keep的基礎上多保存一個模型。

  • collection_def
    tf計算圖能夠維護不一樣集合,底層實現就是collection_def屬性。collection_def是一個從集合名稱到集合內容的映射,名稱爲字符串,內容爲CollectionDef Protocol Buffer。

tf能夠維護4類不一樣的集合。NodeList維護節點集合,ByteList維護字符串或者序列化以後的Protocol Buffer集合,好比張量是Protocol Buffer表示,張量集合就是ByteList。Int64List維護整數集合,FloatList維護實數集合。model.ckpt.meta.json文件的內容:

樣例程序中維護了兩個集合,一個是全部變量的集合,一個是可訓練變量的集合。元素都是v1和v2。他們都是系統自動維護的。

model.ckpt

model.ckpt保存了全部變量的取值。這個文件是經過SSTable格式存儲的,能夠大體理解爲就是一個(key,value)列表。
model.ckpt列表的第一行描述了文件的元信息,好比這個文件中存儲的變量列表。剩下的每一行保存了一個變量的片斷,片斷信息經過SavedSlice Protocol Buffer定義,保存了變量名稱、當前片斷信息以及變量取值。tf提供了tf.train.NewCheckpointReader類查看model.ckpt文件。

checkpoint

這個文件是tf.train.Saver自動生成且維護的。checkpoint文件維護了全部tf模型文件的文件名,當某個模型被刪除時,也會從checkpoint文件中刪除。checkpoint內容格式爲CheckpointState Protocol Buffer。

model_checkpoint_path保存了最新的模型文件,all_model_checkpoint_paths列出了當前全部模型文件。
樣例文件:

Tensorflow最佳實踐樣例程序

本章前面已經給出了一個完整的tf程序解決MNIST問題,然而這個程序的可擴展性並很差。好比,前向傳播須要傳入全部變量,可讀性不好;沒有持久化訓練好的模型,且須要每隔一段時間保存一次模型訓練的中間結果(防止程序退出致使結果丟失)。

結合前面介紹的變量管理和持久化機制,本節介紹一個tf訓練神經網絡模型的最佳實踐。將訓練和測試分爲兩個獨立的程序,每個組件更加靈活。訓練神經網絡的程序能夠持續輸出訓練好的模型,而測試程序能夠每隔一段時間檢驗最新模型的正確率。將前向傳播的過程抽離成一個單獨的庫函數,方便且保持訓練和測試使用的前向傳播方法是一致的。

重構以後的代碼分爲三個程序,mnist_inference.py定義了前向傳播的過程以及神經網絡中的參數。mnist_train.py定義了神經網絡的訓練過程。mnist_eval.py定義了測試過程。

mnist_inference.py

mnist_train.py

運行程序獲得結果:

新的訓練代碼中,再也不將訓練和測試跑在一塊兒。訓練過程當中,沒1000輪輸出一次損失函數的大小評估訓練的效果。每1000輪保存一次訓練好的模型,這樣能夠經過一個單獨的測試程序,更加方便的在滑動平均模型上作測試。

mnist_eval.py

測試程序每10秒運行一次,每次運行都是讀取最新保存的模型,並在驗證集上計算正確率。運行程序獲得結果以下,注意由於訓練程序不必定每10秒輸出一個新模型,因此有些模型可能被測試屢次。通常解決真實問題時,不會這麼頻繁地運行評測程序。

github code

第六章 圖像識別與卷積神經網絡

卷積神經網絡(CNN)的應用很是普遍,在天然語言處理、醫藥發現、災難氣候發現甚至圍棋人工智能程序中都有應用。本章主要經過卷積神經網絡在圖像識別上的應用講解卷積神經網絡的基本原理以及如何使用Tensorflow實現。內容包括圖像識別領域解決的問題以及經典數據集、卷積神經網絡的主體架構、tf實現兩個經典的CNN模型以及實現CNN的遷移學習。

圖像識別問題簡介及經典數據集

圖像識別問題但願藉助計算機程序處理、分析和理解圖片中的內容,使得計算機能夠從圖片中自動識別不一樣模式的目標和對象。第五章介紹的MNIST數據集就是識別圖片中的手寫體數字。下圖顯示了圖像識別的主流技術在MNIST數據集上的錯誤率隨着年份的發展趨勢圖。

其餘經典數據集還有Cifar、ImageNet等。在這些數據集上,使用CNN的算法都超過了人類的表現,給圖像識別問題帶來了質的飛躍。

卷積神經網絡簡介

前面的章節介紹的神經網絡每兩層之間的全部節點都是有邊相連的,稱爲全鏈接神經網絡。卷積神經網絡、循環神經網絡等是非全鏈接神經網絡。

全鏈接神經網絡通常會將每一層的節點組織成一列,方便顯示鏈接結構。卷積神經網絡相鄰兩層之間只有部分節點相連,爲了展現每一層神經元的維度,通常會將每一層卷積層的節點組織成一個三維矩陣。

卷積神經網絡和全鏈接神經網絡的惟一區別在於神經網絡中相鄰兩層的鏈接方式,輸入輸出、損失函數和訓練流程基本一致。

全鏈接神經網絡沒法很好地處理圖像識別問題的主要緣由是參數太多,致使速度減慢,還很容易致使過擬合問題。而卷積神經網絡很好的避免了這個問題。

卷積神經網絡架構圖:

一個卷積神經網絡主要由如下5中結構組成:

  1. 輸入層
    在處理圖像的CNN中,它通常表明了一張圖片的像素矩陣,好比32323。從輸入層開始,CNN經過不一樣的神經網絡結構將上一層的三維矩陣轉化爲下一層的三維矩陣,直到最後的全鏈接層。
  2. 卷積層
    卷積層中每個節點的輸入只是上一層神經網絡的一小塊,這小塊經常使用的大小是33,55等。CNN將每一小塊進行深刻地分析從而獲得抽象程度更高的特徵。通常來講,經過卷積層處理過的節點矩陣會變得更深。
  3. 池化層
    池化層不會改變三維矩陣的額深度,可是能夠縮小矩陣的大小。能夠認爲是將一張高分辨率圖片轉化爲分辨率較低的圖片。能夠進一步減小參數的數量。
  4. 全鏈接層
    通過幾輪卷積層和池化層的處理以後,能夠認爲圖像中的信息已經被抽象成了信息含量更高的特徵。在特徵提取以後,仍然須要全鏈接層來完成分類任務。
  5. Softmax層
    和第四章介紹的做用相同,用於獲得不一樣種類的機率分佈。

下面將詳細介紹卷積神經網絡特殊的兩個結構:卷積層和池化層。

卷積神經網絡經常使用結構

卷積層

下圖是卷積層神經網絡結構中最重要的部分,稱爲過濾器(filter)或者核(kernel)。過濾器能夠將當前層神經網絡上的一個子節點矩陣轉化爲下一層神經網絡上的一個單位節點矩陣。單位節點矩陣指的是一個長和寬都是1,深度不限的節點矩陣。

過濾器處理的節點矩陣的尺寸也稱之爲過濾器的尺寸,其長和寬是人工指定的。經常使用的有33和55。由於過濾器處理的矩陣深度和當前神經網絡節點矩陣的深度一致,因此雖然節點矩陣是三維的,過濾器的尺寸只須要指定長和寬。過濾器中還要指定的一個維度是單位節點矩陣的深度,也稱爲過濾器的深度(或者過濾器的個數)。

下面展現過濾器的前向傳播過程,以一個223的節點矩陣變化爲一個115的單位節點矩陣爲例。總共須要223*5+5=65個參數,+5爲偏置項的個數。單位矩陣中第i個節點的取值g(i)爲:

其中,g(0)的計算過程:

卷積層結構的前向傳播過程就是經過一個過濾器從神經網絡當前層的左上角移動到右下角,而且在移動中計算每個對應的單位矩陣獲得的。

本筆記沒有完整記錄過濾器的前向傳播過程,具體參考原書或其餘資料。幾個概念:same padding,valid padding,步長(step)。

在卷積神經網絡中,每個卷積層中使用的過濾器中的參數都是同樣的。共享過濾器的參數可使得圖像上的內容不受位置的影響。同時大幅度減小參數數量。加入輸入層維度爲32×32×3,第一層卷積層使用55316的過濾器,那麼參數個數爲55316+16=1216個。而使用500個隱藏節點的全鏈接層有1.5百萬個參數。另外,卷積層的參數個數和圖片的大小無關,它只和過濾器的尺寸、深度以及當前層節點矩陣的深度有關,使得CNN能夠很好的擴展到更大的圖像數據上。

使用same padding、步長爲2的卷積層前向傳播流程:

左上角的格子計算過程爲:

tf對CNN提供了很是好的支持,下面的程序實現了一個卷積層的前向傳播過程。

池化層

和卷積層相似,池化層前向傳播的過程也是經過移動一個相似過濾器的結構完成的。過濾器中的計算不是節點的加權和,而是最大值或者平均值運算,分別稱爲 max pooling和average pooling。同時,池化層的過濾器也須要人工設定過濾器的尺寸、padding方式以及步長等,且這些設置的意義相同。和卷積層移動方式不一樣的是,池化層的過濾器隻影響一個深度上的節點,因此除了在長和寬方向上移動,還須要在深度這個方向上移動。如圖所示:

tf實現:

其中ksize(過濾器尺寸)和strides(步長)的數組的第一個和最後一個元素必須爲1,由於不能影響節點矩陣的深度。過濾器尺寸經常使用的有[1,2,2,1]和[1,3,3,1]。tf的另外一個池化方式tf.nn.avg_pool使用方式是一致的。

經典卷積網絡模型

經過上小節介紹的卷積層和池化層能夠組合任意結構的CNN。但怎樣結構的CNN才更好的解決真實圖像問題呢?本小節介紹一些經典的CNN網絡結構:LeNet-5和Inception。

LeNet-5模型

LeNet-5是Yann LeCun提出的,是第一個成功應用於數字識別問題的卷積神經網絡。總共有7層:

subsampling即pooling,池化

  • 第一層:卷積層
    輸入是原始的圖像像素,輸入層大小32321。第一個卷積層過濾器尺寸爲55,深度6。輸出2828*6的feature map。
  • 第二層:池化層
    過濾器大小爲2*2,輸出爲14×14×6。
  • 第三層:卷積層
    過濾器大小55,深度16,輸出1010*16
  • 第四層:池化層
    過濾器大小22,輸出55*16
  • 第五層:全鏈接層
    輸出節點120
  • 第六層:全鏈接層
    輸出節點84
  • 第七層:全鏈接層
    輸出節點10

tf實現LeNet-5解決MNIST問題:訓練過程能夠複用第五章中的mnist_train.py,不過由於CNN的輸入層是一個三維矩陣,因此須要調整一下輸入數據的格式:

修改mnist_inference.py實現LeNet-5模型的前向傳播過程:

幾個要點:

  1. 訓練過程當中加入dropout避免過擬合,dropout通常只在全鏈接層而不是卷積層或者池化層中使用
  2. 只有全鏈接層的權重須要加入正則化

運行mnist_train.py,獲得輸出:

相比第五章98.4%的準確率,使用CNN能夠達到99.4%。

一種CNN架構不能解決全部問題,好比LeNet-5就沒法很好地處理相似ImageNet這樣比較大的圖像數據集。那麼如何設計CNN的架構呢?能夠用下面的正則表達式公式總結經典的圖片分類問題的CNN架構:

輸入層 ->( 卷積層+ -> 池化層? )+ -> 全鏈接層+

「卷積層+」表示一層或多層卷積層,通常連續使用最多3層。「池化層?」表示沒有或者一層池化層,部分論文中發現能夠直接經過調整卷積層步長起到和池化層相同的效果。CNN輸出以前通常會通過1~2個全鏈接層。按照這個公式,LeNet-5的架構爲:

輸入層->卷積層->池化層->卷積層->池化層->全鏈接層->全鏈接層->輸出層

除此以外,AlexNet、ZF Net、VGGNet等都知足上面的公式,其中VGG論文中介紹的做者嘗試過的不一樣CNN架構以下:

convX-Y表示過濾器邊長爲X,深度爲Y。VGG中過濾器邊長通常爲3和1,每通過一次池化層以後,過濾器深度都會乘2。卷積層的步長通常爲1,最大池化比較經常使用,池化層的過濾器邊長通常爲2或者3,步長通常爲2或3.

Inception-v3模型

本小節介紹Inception結構以及Inception-v3卷積神經網絡模型。在LeNet-5中,不一樣卷積層經過串聯的方式鏈接在一塊兒,而Inception結構將不一樣卷積層並聯。

前面提到了一個卷積層可使用邊長爲一、3或5的過濾器,如何在這些邊長中選呢。Inception同時使用全部不一樣尺寸的過濾器,而後將獲得的輸出矩陣拼接起來。

上圖Inception使用了1/3/5三種過濾器,不一樣矩陣表明不一樣計算路徑。全部過濾器使用Same Padding且步長爲1,獲得的輸出矩陣大小一致,因此能夠拼接成更深的矩陣。Inception-v3架構以下:

詳細架構參考論文
Rethinking the Inception Architecture for Computer Vision

Inception-v3總共有46層,由11個Inception模塊組成,上圖中方框標註的就是一個Inception模塊。

Tensorflow-Slim是一個簡潔的實現卷積層的工具,相比直接使用tf,代碼量減小不少:

下面用tf-slim實現方框中的Inception模塊:

卷積神經網絡遷移學習

介紹遷移學習的概念以及如何經過tf實現。

遷移學習介紹

遷移學習是將一個問題上訓練好的模型經過簡單的調整使其適用於一個新的問題。根據論文DeCAF: A Deep Convolutional Activation Feature for Generic Visual Recognition
中的結論,能夠保留訓練好的Inception-v3模型中全部卷積層的參數,只是替換最後一層全鏈接層。最後這一層全鏈接層以前的網絡層稱之爲瓶頸層(bottleneck)。有理由認爲瓶頸層輸出的節點向量能夠被做爲任何圖像的一個更加精簡且表達能力更強的特徵向量。因而,在新數據集上,能夠直接利用這個訓練好的模型進行特徵提取,再將這個特徵向量做爲輸入來訓練一個新的單層全鏈接神經網絡處理新的分類問題。
通常來講,數據量足夠的狀況下,遷移學習的效果不如徹底從新訓練。可是遷移學習所須要的訓練時間和訓練樣本都遠遠小於完整訓練的模型。

Tensorflow實現遷移學習

首先下載數據集

解壓以後的文件夾包含5個子文件夾,每一個子文件夾的名稱爲一種花的名稱。

下載Google提供的Inception-v3模型

實現代碼:

運行以後的結果:

效果還不錯。