CNN on TensorFlow

CNN on TensorFlow

本文大部份內容均參考於:python

An Intuitive Explanation of Convolutional Neural Networks
知乎:「爲何 ReLU 要好過於 tanh 和 sigmoid function?」
Deep MNIST for Experts
TensorFlow Python API 「tf.nn」git

Build a Multilayer Convolutional Network

在 TensorFlow 官方的 tutorials 中,咱們使用 softmax 模型在 MNIST 數據集上獲得的結果只有 91% 的正確率,實在太糟糕。因此,咱們將使用一個稍微複雜的模型:CNN(卷積神經網絡)來改善實驗效果。在 CNN 主要有四個操做:github

  1. 卷積
  2. 非線性處理(ReLU)
  3. 池化或者亞採樣
  4. 分類(全鏈接層)

這些操做對於各個卷積神經網絡來講都是基本組件,所以理解它們的工做原理有助於充分了解卷積神經網絡。下面咱們將會嘗試理解各步操做背後的原理。web

What's CNN

Convolution

卷積的主要目的是爲了從輸入圖像中提取特徵。卷積能夠經過從輸入的一小塊數據中學到圖像的特徵,並能夠保留像素間的空間關係。讓咱們舉個例子來嘗試理解一下卷積是如何處理圖像的:算法

正如咱們上面所說,每張圖像均可以看做是像素值的矩陣。考慮一下一個 5 x 5 的圖像,它的像素值僅爲 0 或者 1(注意對於灰度圖像而言,像素值的範圍是 0 到 255,下面像素值爲 0 和 1 的綠色矩陣僅爲特例):api

同時,考慮下另外一個 3 x 3 的矩陣,以下所示:數組

接下來,5 x 5 的圖像和 3 x 3 的矩陣的卷積能夠按下圖所示的動畫同樣計算:網絡

如今停下來好好理解下上面的計算是怎麼完成的。咱們用橙色的矩陣在原始圖像(綠色)上滑動,每次滑動一個像素(也叫作「步長」),在每一個位置上,咱們計算對應元素的乘積(兩個矩陣間),並把乘積的和做爲最後的結果,獲得輸出矩陣(粉色)中的每個元素的值。注意,3 x 3 的矩陣每次步長中僅能夠看到輸入圖像的一部分。架構

在 CNN 的術語中,3x3 的矩陣叫作「濾波器」(filter) 或「核」(kernel) 或者 「特徵檢測器」(feature detector),經過在圖像上滑動濾波器並計算點乘獲得矩陣叫作「卷積特徵」(Convolved Feature) 或者 「激活圖」(Activation Map) 或者 「特徵圖」(Feature Map)。記住,濾波器在原始輸入圖像上的做用是特徵檢測器。dom

從上面圖中的動畫能夠看出,對於一樣的輸入圖像,不一樣值的濾波器將會生成不一樣的特徵圖。好比,對於下面這張輸入圖像:

在下表中,咱們能夠看到不一樣濾波器對上圖卷積的效果。正如表中所示,經過在卷積操做前修改濾波矩陣的數值,咱們能夠進行諸如邊緣檢測、銳化和模糊等操做 —— 這代表不一樣的濾波器能夠從圖中檢測到不一樣的特徵,好比邊緣、曲線等。

另外一個直觀理解卷積操做的好方法是看下面這張圖的動畫:

濾波器(紅色框)在輸入圖像滑過(卷積操做),生成一個特徵圖。另外一個濾波器(綠色框)在同一張圖像上卷積能夠獲得一個不一樣的特徵圖。注意卷積操做能夠從原圖上獲取局部依賴信息。同時注意這兩個不一樣的濾波器是如何從同一張圖像上生成不一樣的特徵圖。記住上面的圖像和兩個濾波器僅僅是咱們上面討論的數值矩陣。

在實踐中,CNN 會在訓練過程當中學習到這些濾波器的值(儘管咱們依然須要在訓練前指定諸如濾波器的個數、濾波器的大小、網絡架構等參數)。咱們使用的濾波器越多,提取到的圖像特徵就越多,網絡所能在未知圖像上識別的模式也就越好。

特徵圖的大小(卷積特徵)由下面三個參數控制,咱們須要在卷積前肯定它們:

  • 深度(Depth):深度對應的是卷積操做所需的濾波器個數。在下圖的網絡中,咱們使用三個不一樣的濾波器對原始圖像進行卷積操做,這樣就能夠生成三個不一樣的特徵圖。你能夠把這三個特徵圖看做是堆疊的 2d 矩陣,那麼,特徵圖的「深度」就是 3。

  • 步長(Stride):步長是咱們在輸入矩陣上滑動濾波矩陣的像素數。當步長爲 1 時,咱們每次移動濾波器一個像素的位置。當步長爲 2 時,咱們每次移動濾波器會跳過 2 個像素。步長越大,將會獲得更小的特徵圖。

  • 零填充(Zero-padding):有時,在輸入矩陣的邊緣使用零值進行填充,這樣咱們就能夠對輸入圖像矩陣的邊緣進行濾波。零填充的一大好處是可讓咱們控制特徵圖的大小。使用零填充的也叫作泛卷積,不適用零填充的叫作嚴格卷積。

ReLU

ReLU表示修正線性單元(Rectified Linear Unit),是一個非線性操做。

  1. 爲何要引入非線性激勵函數?

    若是不用激勵函數(其實至關於激勵函數是 $f(x) = x$ ),在這種狀況下你每一層輸出都是上層輸入的線性函數,很容易驗證,不管你神經網絡有多少層,輸出都是輸入的線性組合,與沒有隱層效果至關,這種狀況就是最原始的感知機(Perceptron)了。

    正由於上面的緣由,咱們決定引入非線性函數做爲激勵函數,這樣深層神經網絡就有意義了(再也不是輸入的線性組合,能夠逼近任意函數)。最先的想法是 sigmoid 函數或者 tanh 函數,輸出有界,很容易充當下一層輸入(以及一些人的生物解釋balabala)。

  2. 爲何要引入 ReLU 而不是其餘的非線性函數(例如 Sigmoid 函數)?

    • 採用 sigmoid 等函數,算激活函數時(指數運算),計算量大,反向傳播求偏差梯度時,求導涉及除法,計算量相對大,而採用Relu激活函數,整個過程的計算量節省不少。
    • 對於深層網絡,sigmoid 函數反向傳播時,很容易就會出現梯度消失的狀況(在sigmoid接近飽和區時,變換太緩慢,導數趨於0,這種狀況會形成信息丟失),從而沒法完成深層網絡的訓練。
    • Relu 會使一部分神經元的輸出爲 0,這樣就形成了網絡的稀疏性,而且減小了參數的相互依存關係,緩解了過擬合問題的發生(以及一些人的生物解釋balabala)。

    固然如今也有一些對 relu 的改進,好比 prelu,random relu等,在不一樣的數據集上會有一些訓練速度上或者準確率上的改進,具體的能夠找相關的paper看。

    (多加一句,如今主流的作法,會多作一步 batch normalization,儘量保證每一層網絡的輸入具備相同的分佈。而最新的 paper,他們在加入bypass connection 以後,發現改變 batch normalization 的位置會有更好的效果。)

  3. ReLU 的優勢與缺點?

    優勢:

    • 解決了gradient vanishing問題 (在正區間)
    • 計算速度很是快,只須要判斷輸入是否大於0
    • 收斂速度遠快於sigmoid和tanh

    缺點:

    • ReLU 的輸出不是 zero-centered
    • Dead ReLU Problem,指的是某些神經元可能永遠不會被激活,致使相應的參數永遠不能被更新。有兩個主要緣由可能致使這種狀況產生: (1) 很是不幸的參數初始化,這種狀況比較少見 (2) learning rate 過高致使在訓練過程當中參數更新太大,不幸使網絡進入這種狀態。解決方法是能夠採用 Xavier 初始化方法,以及避免將 learning rate 設置太大或使用 adagrad 等自動調節 learning rate 的算法。

幾十年的機器學習發展中,咱們造成了這樣一個概念:非線性激活函數要比線性激活函數更加先進。

尤爲是在佈滿 Sigmoid 函數的 BP 神經網絡,佈滿徑向基函數的 SVM 神經網絡中,每每有這樣的幻覺,非線性函數對非線性網絡貢獻巨大。

該幻覺在 SVM 中更加嚴重。核函數的形式並不是徹底是 SVM 可以處理非線性數據的主力功臣(支持向量充當着隱層角色)。

那麼在深度網絡中,對非線性的依賴程度就能夠縮一縮。另外,在上一部分提到,稀疏特徵並不須要網絡具備很強的處理線性不可分機制。

綜合以上兩點,在深度學習模型中,使用簡單、速度快的線性激活函數可能更爲合適。

ReLU 操做能夠從下面的圖中理解。這裏的輸出特徵圖也能夠看做是「修正」過的特徵圖。

所謂麻雀雖小,五臟俱全,ReLU雖小,但也是能夠改進的。

ReLU的種類

ReLU的區分主要在負數端,根據負數端斜率的不一樣來進行區分,大體以下圖所示。

普通的ReLU負數端斜率是0,Leaky ReLU則是負數端有一個比較小的斜率,而PReLU則是在後向傳播中學習到斜率。而Randomized Leaky ReLU則是使用一個均勻分佈在訓練的時候隨機生成斜率,在測試的時候使用均值斜率來計算。

效果

其中,NDSB 數據集是 Kaggle 的比賽,而 RReLU 正是在此次比賽中嶄露頭角的。

經過上述結果,能夠看到四點:

  • 對於 Leaky ReLU 來講,若是斜率很小,那麼與 ReLU 並無大的不一樣,當斜率大一些時,效果就好不少。
  • 在訓練集上,PReLU 每每能達到最小的錯誤率,說明 PReLU 容易過擬合。
  • 在 NSDB 數據集上 RReLU 的提高比 cifar10 和 cifar100 上的提高更加明顯,而 NSDB 數據集比較小,從而能夠說明,RReLU在與過擬合的對抗中更加有效。
  • 對於 RReLU 來講,還須要研究一下隨機化得斜率是怎樣影響訓練和測試過程的。

參考文獻

[1]. Xu B, Wang N, Chen T, et al. Empirical evaluation of rectified activations in convolutional network[J]. arXiv preprint arXiv:1505.00853, 2015.

Pooling

空間池化(Spatial Pooling)(也叫作亞採用或者下采樣)下降了各個特徵圖的維度,但能夠保持大部分重要的信息。空間池化有下面幾種方式:最大化、平均化、加和等等。

對於最大池化(Max Pooling),咱們定義一個空間鄰域(好比,2x2 的窗口),並從窗口內的修正特徵圖中取出最大的元素。除了取最大元素,咱們也能夠取平均(Average Pooling)或者對窗口內的元素求和。在實際中,最大池化被證實效果更好一些。

下面的圖展現了使用 2x2 窗口在修正特徵圖(在卷積 + ReLU 操做後獲得)使用最大池化的例子。

咱們以 2 個元素(也叫作「步長」)滑動咱們 2x2 的窗口,並在每一個區域內取最大值。如上圖所示,這樣操做能夠下降咱們特徵圖的維度。

在下圖展現的網絡中,池化操做是分開應用到各個特徵圖的(注意,由於這樣的操做,咱們能夠從三個輸入圖中獲得三個輸出圖)。

下圖展現了咱們在 ReLU 操做以後獲得的修正特徵圖的池化操做的效果:

池化函數能夠逐漸下降輸入表示的空間尺度。特別地,Pooling 的好處是:

  • 使輸入表示(特徵維度)變得更小,而且網絡中的參數和計算的數量更加可控的減少,所以,能夠控制過擬合。

  • 使網絡對於輸入圖像中更小的變化、冗餘和變換變得不變性(輸入的微小冗餘將不會改變池化的輸出——由於咱們在局部鄰域中使用了最大化/平均值的操做)。

  • 幫助咱們獲取圖像最大程度上的尺度不變性(準確的詞是「不變性」)。它很是的強大,由於咱們能夠檢測圖像中的物體,不管它們位置在哪裏。

到目前爲止咱們瞭解了卷積、ReLU 和池化是如何操做的。理解這些層是構建任意 CNN 的基礎是很重要的。正以下圖所示,咱們有兩組卷積、ReLU & 池化層 —— 第二組卷積層使用六個濾波器對第一組的池化層的輸出繼續卷積,獲得一共六個特徵圖。接下來對全部六個特徵圖應用 ReLU。接着咱們對六個修正特徵圖分別進行最大池化操做。

這些層一塊兒就能夠從圖像中提取有用的特徵,並在網絡中引入非線性,減小特徵維度,同時保持這些特徵具備某種程度上的尺度變化不變性。

第二組池化層的輸出做爲全鏈接層的輸入,接下來咱們將介紹全鏈接層。

Connect

全鏈接層是傳統的多層感知器,在輸出層使用的是 softmax 激活函數(也可使用其餘像 SVM 的分類器,但在本文中只使用 softmax)。「全鏈接」(Fully Connected) 這個詞代表前面層的全部神經元都與下一層的全部神經元鏈接。

卷積和池化層的輸出表示了輸入圖像的高級特徵。全鏈接層的目的是爲了使用這些特徵把輸入圖像基於訓練數據集進行分類。好比,在下面圖中咱們進行的圖像分類有四個可能的輸出結果(注意下圖並無顯示全鏈接層的節點鏈接)。

除了分類,添加一個全鏈接層也(通常)是學習這些特徵的非線性組合的簡單方法。從卷積和池化層獲得的大多數特徵可能對分類任務有效,但這些特徵的組合可能會更好。

從全鏈接層獲得的輸出機率和爲 1。這個能夠在輸出層使用 softmax 做爲激活函數進行保證。softmax 函數輸入一個任意大於 0 值的矢量,並把它們轉換爲零一之間的數值矢量,其和爲一。

Use Backpropagation to Train whole network

正如上面討論的,卷積 + 池化層的做用是從輸入圖像中提取特徵,而全鏈接層的做用是分類器。

注意在下面的圖中,由於輸入的圖像是船,對於船這一類的目標機率是 1,而其餘三類的目標機率是 0,即

  • 輸入圖像 = 船

  • 目標矢量 = [0, 0, 1, 0]

完整的卷積網絡的訓練過程能夠總結以下:

  • 第一步:咱們初始化全部的濾波器,使用隨機值設置參數/權重
  • 第二步:網絡接收一張訓練圖像做爲輸入,經過前向傳播過程(卷積、ReLU 和池化操做,以及全鏈接層的前向傳播),找到各個類的輸出機率
    • 咱們假設船這張圖像的輸出機率是 [0.2, 0.4, 0.1, 0.3]
    • 由於對於第一張訓練樣本的權重是隨機分配的,輸出的機率也是隨機的
  • 第三步:在輸出層計算總偏差(計算 4 類的和)
    • Total Error = ∑ ½ (target probability – output probability) ²
  • 第四步:使用反向傳播算法,根據網絡的權重計算偏差的梯度,並使用梯度降低算法更新全部濾波器的值/權重以及參數的值,使輸出偏差最小化
    • 權重的更新與它們對總偏差的佔比有關
    • 當一樣的圖像再次做爲輸入,這時的輸出機率可能會是 [0.1, 0.1, 0.7, 0.1],這就與目標矢量 [0, 0, 1, 0] 更接近了
    • 這代表網絡已經經過調節權重/濾波器,能夠正確對這張特定圖像的分類,這樣輸出的偏差就減少了
    • 像濾波器數量、濾波器大小、網絡結構等這樣的參數,在第一步前都是固定的,在訓練過程當中保持不變——僅僅是濾波器矩陣的值和鏈接權重在更新
  • 第五步:對訓練數據中全部的圖像重複步驟 1 ~ 4

上面的這些步驟能夠訓練 ConvNet —— 這本質上意味着對於訓練數據集中的圖像,ConvNet 在更新了全部權重和參數後,已經優化爲能夠對這些圖像進行正確分類。

當一張新的(未見過的)圖像做爲 ConvNet 的輸入,網絡將會再次進行前向傳播過程,並輸出各個類別的機率(對於新的圖像,輸出機率是使用已經在前面訓練樣本上優化分類的參數進行計算的)。若是咱們的訓練數據集很是的大,網絡將會(有但願)對新的圖像有很好的泛化,並把它們分到正確的類別中去。

注 1: 上面的步驟已經簡化,也避免了數學詳情,只爲提供訓練過程的直觀內容。

注 2:在上面的例子中咱們使用了兩組卷積和池化層。然而請記住,這些操做能夠在一個 ConvNet 中重複屢次。實際上,如今有些表現最好的 ConvNet 擁有多達十幾層的卷積和池化層!同時,每次卷積層後面不必定要有池化層。以下圖所示,咱們能夠在池化操做前連續使用多個卷積 + ReLU 操做。還有,請注意 ConvNet 的各層在下圖中是如何可視化的。

Visualization on CNN

通常而言,越多的卷積步驟,網絡能夠學到的識別特徵就越複雜。好比,ConvNet 的圖像分類可能在第一層從原始像素中檢測出邊緣,而後在第二層使用邊緣檢測簡單的形狀,接着使用這些形狀檢測更高級的特徵,好比更高層的人臉。下面的圖中展現了這些內容——咱們使用卷積深度置信網絡學習到的特徵,這張圖僅僅是用來證實上面的內容(這僅僅是一個例子:真正的卷積濾波器可能會檢測到對咱們毫無心義的物體)。

Adam Harley 建立了一個卷積神經網絡的可視化結果,使用的是 MNIST 手寫數字的訓練集。我強烈建議使用它來理解 CNN 的工做原理。

咱們能夠在下圖中看到網絡是如何識別輸入 「8」 的。注意下圖中的可視化並無單獨展現 ReLU 操做。

輸入圖像包含 1024 個像素(32 x 32 大小),第一個卷積層(卷積層 1)由六個獨特的 5x5 (步長爲 1)的濾波器組成。如圖可見,使用六個不一樣的濾波器獲得一個深度爲六的特徵圖。

卷積層 1 後面是池化層 1,在卷積層 1 獲得的六個特徵圖上分別進行 2x2 的最大池化(步長爲 2)的操做。你能夠在池化層上把鼠標移動到任意的像素上,觀察在前面卷積層(如上圖所示)獲得的 4x4 的小格。你會發現 4x4 小格中的最大值(最亮)的像素將會進入到池化層。

池化層 1 後面的是六個 5x5 (步長爲 1)的卷積濾波器,進行卷積操做。後面就是池化層 2,進行 2x2 的最大池化(步長爲 2)的操做。這兩層的概念和前面描述的同樣。

接下來咱們就到了三個全鏈接層。它們是:

  • 第一個全鏈接層有 120 個神經元
  • 第二層全鏈接層有 100 個神經元
  • 第三個全鏈接層有 10 個神經元,對應 10 個數字——也就作輸出層

注意在下圖中,輸出層中的 10 個節點的各個都與第二個全鏈接層的全部 100 個節點相連(所以叫作全鏈接)。

同時,注意在輸出層那個惟一的亮的節點是如何對應於數字 「8」 的——這代表網絡把咱們的手寫數字正確分類(越亮的節點代表從它獲得的輸出值越高,即,8 是全部數字中機率最高的)。

一樣的 3D 可視化能夠在這裏看到。

Other ConvNet

卷積神經網絡從上世紀 90 年代初期開始出現。咱們上面提到的 LeNet 是早期卷積神經網絡之一。其餘有必定影響力的架構以下所示:

  • LeNet (1990s): 本文已介紹。
  • 1990s to 2012:在上世紀 90 年代後期至 2010 年初期,卷積神經網絡進入孵化期。隨着數據量和計算能力的逐漸發展,卷積神經網絡能夠處理的問題變得愈來愈有趣。
  • AlexNet (2012) – 在 2012,Alex Krizhevsky (與其餘人)發佈了 AlexNet,它是比 LeNet 更深更寬的版本,並在 2012 年的 ImageNet 大規模視覺識別大賽(ImageNet Large Scale Visual Recognition Challenge,ILSVRC)中以巨大優點獲勝。這對於之前的方法具備巨大的突破,當前 CNN 大範圍的應用也是基於這個工做。
  • ZF Net (2013) – ILSVRC 2013 的獲勝者是來自 Matthew Zeiler 和 Rob Fergus 的卷積神經網絡。它以 ZFNet (Zeiler & Fergus Net 的縮寫)出名。它是在 AlexNet 架構超參數上進行調整獲得的效果提高。
  • GoogLeNet (2014) – ILSVRC 2014 的獲勝者是來自於 Google 的 Szegedy等人的卷積神經網絡。它的主要貢獻在於使用了一個 Inception 模塊,能夠大量減小網絡的參數個數(4M,AlexNet 有 60M 的參數)。
  • VGGNet (2014) – 在 ILSVRC 2014 的領先者中有一個 VGGNet 的網絡。它的主要貢獻是展現了網絡的深度(層數)對於性能具備很大的影響。
  • ResNets (2015) – 殘差網絡是何凱明(和其餘人)開發的,並贏得 ILSVRC 2015 的冠軍。ResNets 是當前卷積神經網絡中最好的模型,也是實踐中使用 ConvNet 的默認選擇(截至到 2016 年五月)。
  • DenseNet (2016 八月) – 近來由 Gao Huang (和其餘人)發表的,the Densely Connected Convolutional Network 的各層都直接於其餘層之前向的方式鏈接。DenseNet 在五種競爭積累的目標識別基準任務中,比之前最好的架構有顯著的提高。能夠在這裏看 Torch 實現。

CNN on TensorFlow

Tensorflow 在卷積和池化上有很強的靈活性。咱們改如何處理邊界?步長應該設多大?在這個實例裏,咱們會一直使用 vanilla 版本。咱們的卷積網絡選用步長(stride size)爲 1邊距(padding size)爲 0 的模板,保證輸出和輸入是同一個大小(嚴格卷積)。咱們的池化選用簡單傳統的 $2 \times 2$ 大小的模板做爲 max pooling(最大池化)。爲了使代碼更簡潔,咱們把這部分抽象成一個函數:

def conv2d(x, W):
    return tf.nn.conv2d(x, W, strides = [1, 1, 1, 1], padding = 'SAME')

def max_pool_2x2(x):
    return tf.nn.max_pool(x, ksize = [1, 2, 2, 1], strides = [1, 2, 2, 1], padding = 'SAME')

Convolution Layer on TensorFlow

卷積操做是使用一個二維的卷積核在一個批處理的圖片上進行不斷掃描。具體操做就是將一個卷積和在每張圖片上按照一個合適的尺寸在每一個通道上面進行掃描。爲了達到更好的卷積效率,須要在不一樣的通道和不一樣的卷積核之間進行權衡。

  • conv2d:任意的卷積核,能同時在不一樣的通道上面進行卷積操做。
  • depthwise_conv2d:卷積核能相互獨立地在本身的通道上面進行卷積操做。
  • separable_conv2d:在縱深卷積 depthwise filter 以後進行逐點卷積 separable filter

注意:雖然這些操做被稱之爲「卷積」操做,可是嚴格地來講,他們只是互相關,由於卷積核沒有作一個逆向的卷積過程。

卷積核的卷積過程是按照 strides 參數來肯定的,好比 strides = [1, 1, 1, 1] 表示卷積覈對每一個像素點進行卷積,即在二維屏幕上面,兩個軸方向的步長都是 1。strides = [1, 2, 2, 1]表示卷積覈對每隔一個像素點進行卷積,即在二維屏幕上面,兩個軸方向的步長都是 2。

若是咱們暫不考慮通道這個因素,那麼卷積操做的空間含義定義以下:若是輸入數據是一個四維的 input ,數據維度是[batch, in_height, in_width, ...],卷積核也是一個四維的卷積核,數據維度是[filter_height, filter_width, ...],那麼,對於輸出數據的維度 shape(output),這取決於填充參數padding 的設置:

  • padding = 'SAME':向下取捨,僅適用於全尺寸操做,即輸入數據維度和輸出數據維度相同。

    out_height = ceil(float(in_height) / float(strides[1]))
    out_width  = ceil(float(in_width) / float(strides[2]))
  • padding = 'VALID':向上取捨,適用於部分窗口,即輸入數據維度和輸出數據維度不一樣。

    out_height = ceil(float(in_height - filter_height + 1) / float(strides[1]))
    out_width  = ceil(float(in_width - filter_width + 1) / float(strides[2]))
output[b, i, j, :] = 
          sum_{di, dj} input[b, strides[1] * i + di, strides[2] * j + dj, ...] * 
                   filter[di, dj, ...]

由於,input數據是一個四維的,每個通道上面是一個向量input[b, i, j, :]。對於conv2d ,這些向量會被卷積核filter[di, dj, :, :]相乘而產生一個新的向量。對於depthwise_conv_2d,每一個標量份量input[b, i , j, k]將在 k 個通道上面獨立地被卷積核 filter[di, dj, k]進行卷積操做,而後把全部獲得的向量進行鏈接組合成一個新的向量。

tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, data_format=None, name=None)

這個函數的做用是對一個四維的輸入數據 input 和四維的卷積核 filter 進行操做,而後對輸入數據進行一個二維的卷積操做,最後獲得卷積以後的結果。

給定的輸入 tensor 的維度是 [batch, in_height, in_width, in_channels],卷積核 tensor 的維度是[filter_height, filter_width, in_channels, out_channels],具體卷積操做以下:

  • 將卷積核的維度轉換成一個二維的矩陣形狀[filter_height * filter_width* in_channels, output_channels]
  • 對於每一個批處理的圖片,咱們將輸入 tensor 轉換成一個臨時的數據維度 [batch, out_height, out_width, filter_height * filter_width * in_channels]
  • 對於每一個批處理的圖片,咱們右乘以卷積核,獲得最後的輸出結果。

更加具體的表示細節爲,若是採用默認的 NHWC data_format形式:

output[b, i, j, k] =
    sum_{di, dj, q} input[b, strides[1] * i + di, strides[2] * j + dj, q] *
                    filter[di, dj, q, k]

因此咱們注意到,必需要有strides[0] = strides[3] = 1。在大部分處理過程當中,卷積核的水平移動步數和垂直移動步數是相同的,即strides = [1, stride, stride, 1]

使用例子:

import numpy as np
import tensorflow as tf

input_data = tf.Variable(np.random.rand(10, 6, 6, 3), dtype = np.float32)
filter_data = tf.Variable(np.random.rand(2, 2, 3, 1), dtype = np.float32)

y = tf.nn.conv2d(input_data, filter_data, strides = [1, 1, 1, 1], padding = 'SAME')

with tf.Session() as sess:
    init = tf.initialize_all_variables()
    sess.run(init)
    print(sess.run(y))
    print(sess.run(tf.shape(y)))

輸入參數:

  • input: 一個 Tensor。數據類型必須是 float32 或者 float64
  • filter: 一個 Tensor。數據類型必須是input相同。
  • strides: 一個長度是 4 的一維整數類型數組,每一維度對應的是 input 中每一維的對應移動步數,好比,strides[1] 對應 input[1] 的移動步數。
  • padding: 一個字符串,取值爲 SAME 或者 VALID
  • use_cudnn_on_gpu: 一個可選布爾值,默認狀況下是 True
  • data_format:一個可選stringNHWC 或者NCHW。默認是用NHWC 。主要是規定了輸入 tensor 和輸出 tensor 的四維形式。若是使用 NHWC ,則數據以 [batch, in_height, in_width, in_channels]存儲;若是使用NCHW,則數據以[batch, in_channels, in_height, in_width]存儲。
  • name: (可選)爲這個操做取一個名字。

輸出參數:

  • 一個 Tensor,數據類型是 input 相同。

Pooling Layer on TensorFlow

池化操做是利用一個矩陣窗口在輸入張量上進行掃描,而且將每一個矩陣窗口中的值經過取最大值,平均值或者其餘方法來減小元素個數。每一個池化操做的矩陣窗口大小是由 ksize 來指定的,而且根據步長參數 strides 來決定移動步長。好比,若是 strides 中的值都是1,那麼每一個矩陣窗口都將被使用。若是 strides 中的值都是2,那麼每一維度上的矩陣窗口都是每隔一個被使用。以此類推。

更具體的輸出結果是:

output[i] = reduce( value[ strides * i: strides * i + ksize ] )

輸出數據維度是:

shape(output) = (shape(value) - ksize + 1) / strides

其中,取捨方向取決於參數 padding

  • padding = 'SAME': 向下取捨,僅適用於全尺寸操做,即輸入數據維度和輸出數據維度相同。
  • padding = 'VALID: 向上取捨,適用於部分窗口,即輸入數據維度和輸出數據維度不一樣。

tf.nn.avg_pool(value, ksize, strides, padding , data_format='NHWC', name=None)

這個函數的做用是計算池化區域中元素的平均值。

使用例子:

import numpy as np
import tensorflow as tf

input_data = tf.Variable( np.random.rand(10,6,6,3), dtype = np.float32 )
filter_data = tf.Variable( np.random.rand(2, 2, 3, 10), dtype = np.float32)

y = tf.nn.conv2d(input_data, filter_data, strides = [1, 1, 1, 1], padding = 'SAME')
output = tf.nn.avg_pool(value = y, ksize = [1, 2, 2, 1], strides = [1, 1, 1, 1], padding = 'SAME')

with tf.Session() as sess:
    init = tf.initialize_all_variables()
    sess.run(init)
    print(sess.run(output))
    print(sess.run(tf.shape(output)))

輸入參數:

  • value: 一個四維的Tensor。數據維度是 [batch, height, width, channels]。數據類型是float32float64qint8quint8qint32
  • ksize: 一個長度不小於 4 的整型數組。每一位上面的值對應於輸入數據張量中每一維的窗口對應值。
  • strides: 一個長度不小於 4 的整型數組。該參數指定滑動窗口在輸入數據張量每一維上面的步長。
  • padding: 一個字符串,取值爲 SAME 或者 VALID
  • data_format:一個可選stringNHWC 或者NCHW。默認是用NHWC 。主要是規定了輸入 tensor 和輸出 tensor 的四維形式。若是使用 NHWC ,則數據以 [batch, in_height, in_width, in_channels]存儲;若是使用NCHW,則數據以[batch, in_channels, in_height, in_width]存儲。
  • name: (可選)爲這個操做取一個名字。

輸出參數:

  • 一個Tensor,數據類型和value相同。

tf.nn.max_pool(value, ksize, strides, padding, data_format='NHWC', name=None)

這個函數的做用是計算 pooling 區域中元素的最大值。

tf.nn.max_pool_with_argmax(input, ksize, strides, padding, Targmax=None, name=None)

這個函數的做用是計算池化區域中元素的最大值和該最大值所在的位置。

由於在計算位置 argmax 的時候,咱們將 input 鋪平了進行計算,因此,若是 input = [b, y, x, c],那麼索引位置是 `( ( b * height + y ) * width + x ) * channels + c

查看源碼,該API只能在GPU環境下使用,因此我沒有測試下面的使用例子,若是你能夠測試,請告訴我程序是否能夠運行。

源碼展現:

REGISTER_KERNEL_BUILDER(Name("MaxPoolWithArgmax")
                            .Device(DEVICE_GPU)
                            .TypeConstraint<int64>("Targmax")
                            .TypeConstraint<float>("T"),
                        MaxPoolingWithArgmaxOp<Eigen::GpuDevice, float>);
REGISTER_KERNEL_BUILDER(Name("MaxPoolWithArgmax")
                            .Device(DEVICE_GPU)
                            .TypeConstraint<int64>("Targmax")
                            .TypeConstraint<Eigen::half>("T"),
                        MaxPoolingWithArgmaxOp<Eigen::GpuDevice, Eigen::half>);

使用例子:

import numpy as np
import tensorflow as tf

input_data = tf.Variable( np.random.rand(10,6,6,3), dtype = tf.float32 )
filter_data = tf.Variable( np.random.rand(2, 2, 3, 10), dtype = np.float32)

y = tf.nn.conv2d(input_data, filter_data, strides = [1, 1, 1, 1], padding = 'SAME')
output, argmax = tf.nn.max_pool_with_argmax(input = y, ksize = [1, 2, 2, 1], strides = [1, 1, 1, 1], padding = 'SAME')

with tf.Session() as sess:
    init = tf.initialize_all_variables()
    sess.run(init)
    print(sess.run(output))
    print(sess.run(tf.shape(output)))

輸入參數:

  • input: 一個四維的Tensor。數據維度是 [batch, height, width, channels]。數據類型是float32
  • ksize: 一個長度不小於 4 的整型數組。每一位上面的值對應於輸入數據張量中每一維的窗口對應值。
  • strides: 一個長度不小於 4 的整型數組。該參數指定滑動窗口在輸入數據張量每一維上面的步長。
  • padding: 一個字符串,取值爲 SAME 或者 VALID
  • Targmax: 一個可選的數據類型: tf.int32 或者 tf.int64。默認狀況下是 tf.int64
  • name: (可選)爲這個操做取一個名字。

輸出參數:

一個元祖張量 (output, argmax)

  • output: 一個Tensor,數據類型是float32。表示池化區域的最大值。
  • argmax: 一個Tensor,數據類型是Targmax。數據維度是四維的。

Weight Initialization

因此,爲了建立這個模型,咱們須要建立大量的權重和偏置項,這個模型中的權重在初始化的時候應該加入少許的噪聲來打破對稱性以及避免 0 梯度。因爲咱們使用的是 ReLU 神經元,所以比較好的作法是用一個較小的正數來初始化偏置項,以免神經元節點輸出恆爲 0 的問題(dead neurons)。爲了避免再創建模型的時候反覆作初始化操做,咱們定義兩個函數用於初始化。

def conv2d(x, W):
    return tf.nn.conv2d(x, W, strides = [1, 1, 1, 1], padding = 'SAME')

def max_pool_2x2(x):
    return tf.nn.max_pool(x, ksize = [1, 2, 2, 1], strides = [1, 2, 2, 1], padding = 'SAME')

def weight_variable(shape):
    initial = tf.truncated_normal(shape, stddev = 0.1)
    return tf.Variable(initial)
    
def bias_variable(shape):
    initial = tf.constant(0.1, shape = shape)
    return tf.Variable(initial)

第一層

接下來,咱們開始實現第一層。它由一個卷積層接一個 max_pooling 最大池化層完成。卷積在每一個 5x5 的 patch 中算出 32 個特徵。卷積的權重張量形狀是 [5, 5, 1, 32],前兩個維度是 patch 的大小,接着是輸入的通道數目,最後是輸出的通道數目。而對於每個輸出通道都有一個對應的偏置量。

W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])

爲了用這一層,咱們把 x 變成一個 4d 的向量,其第 二、第 3 維對應圖片的寬度、高度,最後一位表明圖片的顏色通道(由於是灰度圖,因此這裏的通道數爲 1,若是是 RBG 彩色圖,則爲 3)。

x_image = tf.reshape(x, [-1, 28, 28, 1])

以後,咱們把 x_image 和權值向量進行卷積,加上偏置項,而後應用 ReLU 激活函數,最後進行 max pooling。

h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)

第二層

爲了構建一個更深的網絡,咱們會把幾個相似的層堆疊起來。第二層中,每一個 5x5 的 patch 會獲得 64 個特徵。

W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])

h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)

密集鏈接層

如今,圖片尺寸減少到 7x7,咱們加入一個有 1024 個神經元的全鏈接層,用於處理整個圖片。咱們把池化層輸出的張量 reshape 成一些向量,乘上權重矩陣,加上偏置,而後對其使用 ReLU。

W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])

h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

Dropout

爲了減小過擬合,咱們在輸出層以前加入dropout。咱們用一個 placeholder 來表明一個神經元的輸出在 dropout 中保持不變的機率。這樣咱們能夠在訓練過程當中啓用 dropout,在測試過程當中關閉 dropout。 TensorFlow 的tf.nn.dropout 操做除了能夠屏蔽神經元的輸出外,還會自動處理神經元輸出值的 scale。因此用 dropout 的時候能夠不用考慮 scale。

keep_prob = tf.placeholder("float")
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

輸出層

最後咱們添加一個 softmax 層,就像前面的單層 softmax regression 同樣。

W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])

y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

訓練和評估模型

這個模型的效果如何呢?

爲了進行訓練和評估,咱們使用與以前簡單的單層 SoftMax 神經網絡模型幾乎相同的一套代碼,只是咱們會用更加複雜的 ADAM 優化器來作梯度最速降低,在 feed_dict 中加入額外的參數 keep_prob 來控制 dropout 比例。而後每 100 次迭代輸出一第二天志。

cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
sess.run(tf.initialize_all_variables())
for i in range(20000):
  batch = mnist.train.next_batch(50)
  if i%100 == 0:
    train_accuracy = accuracy.eval(feed_dict={
        x:batch[0], y_: batch[1], keep_prob: 1.0})
    print "step %d, training accuracy %g"%(i, train_accuracy)
  train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})

print "test accuracy %g"%accuracy.eval(feed_dict={
    x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0})




引用:https://www.jianshu.com/p/95c79381ab4f  

相關文章
相關標籤/搜索