本文大部份內容均參考於:python
An Intuitive Explanation of Convolutional Neural Networks
知乎:「爲何 ReLU 要好過於 tanh 和 sigmoid function?」
Deep MNIST for Experts
TensorFlow Python API 「tf.nn」git
在 TensorFlow 官方的 tutorials 中,咱們使用 softmax 模型在 MNIST 數據集上獲得的結果只有 91% 的正確率,實在太糟糕。因此,咱們將使用一個稍微複雜的模型:CNN(卷積神經網絡)來改善實驗效果。在 CNN 主要有四個操做:github
這些操做對於各個卷積神經網絡來講都是基本組件,所以理解它們的工做原理有助於充分了解卷積神經網絡。下面咱們將會嘗試理解各步操做背後的原理。web
卷積的主要目的是爲了從輸入圖像中提取特徵。卷積能夠經過從輸入的一小塊數據中學到圖像的特徵,並能夠保留像素間的空間關係。讓咱們舉個例子來嘗試理解一下卷積是如何處理圖像的:算法
正如咱們上面所說,每張圖像均可以看做是像素值的矩陣。考慮一下一個 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表示修正線性單元(Rectified Linear Unit),是一個非線性操做。
爲何要引入非線性激勵函數?
若是不用激勵函數(其實至關於激勵函數是 $f(x) = x$ ),在這種狀況下你每一層輸出都是上層輸入的線性函數,很容易驗證,不管你神經網絡有多少層,輸出都是輸入的線性組合,與沒有隱層效果至關,這種狀況就是最原始的感知機(Perceptron)了。
正由於上面的緣由,咱們決定引入非線性函數做爲激勵函數,這樣深層神經網絡就有意義了(再也不是輸入的線性組合,能夠逼近任意函數)。最先的想法是 sigmoid 函數或者 tanh 函數,輸出有界,很容易充當下一層輸入(以及一些人的生物解釋balabala)。
爲何要引入 ReLU 而不是其餘的非線性函數(例如 Sigmoid 函數)?
固然如今也有一些對 relu 的改進,好比 prelu,random relu等,在不一樣的數據集上會有一些訓練速度上或者準確率上的改進,具體的能夠找相關的paper看。
(多加一句,如今主流的作法,會多作一步 batch normalization,儘量保證每一層網絡的輸入具備相同的分佈。而最新的 paper,他們在加入bypass connection 以後,發現改變 batch normalization 的位置會有更好的效果。)
ReLU 的優勢與缺點?
優勢:
缺點:
幾十年的機器學習發展中,咱們造成了這樣一個概念:非線性激活函數要比線性激活函數更加先進。
尤爲是在佈滿 Sigmoid 函數的 BP 神經網絡,佈滿徑向基函數的 SVM 神經網絡中,每每有這樣的幻覺,非線性函數對非線性網絡貢獻巨大。
該幻覺在 SVM 中更加嚴重。核函數的形式並不是徹底是 SVM 可以處理非線性數據的主力功臣(支持向量充當着隱層角色)。
那麼在深度網絡中,對非線性的依賴程度就能夠縮一縮。另外,在上一部分提到,稀疏特徵並不須要網絡具備很強的處理線性不可分機制。
綜合以上兩點,在深度學習模型中,使用簡單、速度快的線性激活函數可能更爲合適。
ReLU 操做能夠從下面的圖中理解。這裏的輸出特徵圖也能夠看做是「修正」過的特徵圖。
所謂麻雀雖小,五臟俱全,ReLU雖小,但也是能夠改進的。
ReLU的區分主要在負數端,根據負數端斜率的不一樣來進行區分,大體以下圖所示。
普通的ReLU負數端斜率是0,Leaky ReLU則是負數端有一個比較小的斜率,而PReLU則是在後向傳播中學習到斜率。而Randomized Leaky ReLU則是使用一個均勻分佈在訓練的時候隨機生成斜率,在測試的時候使用均值斜率來計算。
其中,NDSB 數據集是 Kaggle 的比賽,而 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.
空間池化(Spatial Pooling)(也叫作亞採用或者下采樣)下降了各個特徵圖的維度,但能夠保持大部分重要的信息。空間池化有下面幾種方式:最大化、平均化、加和等等。
對於最大池化(Max Pooling),咱們定義一個空間鄰域(好比,2x2 的窗口),並從窗口內的修正特徵圖中取出最大的元素。除了取最大元素,咱們也能夠取平均(Average Pooling)或者對窗口內的元素求和。在實際中,最大池化被證實效果更好一些。
下面的圖展現了使用 2x2 窗口在修正特徵圖(在卷積 + ReLU 操做後獲得)使用最大池化的例子。
咱們以 2 個元素(也叫作「步長」)滑動咱們 2x2 的窗口,並在每一個區域內取最大值。如上圖所示,這樣操做能夠下降咱們特徵圖的維度。
在下圖展現的網絡中,池化操做是分開應用到各個特徵圖的(注意,由於這樣的操做,咱們能夠從三個輸入圖中獲得三個輸出圖)。
下圖展現了咱們在 ReLU 操做以後獲得的修正特徵圖的池化操做的效果:
池化函數能夠逐漸下降輸入表示的空間尺度。特別地,Pooling 的好處是:
使輸入表示(特徵維度)變得更小,而且網絡中的參數和計算的數量更加可控的減少,所以,能夠控制過擬合。
使網絡對於輸入圖像中更小的變化、冗餘和變換變得不變性(輸入的微小冗餘將不會改變池化的輸出——由於咱們在局部鄰域中使用了最大化/平均值的操做)。
幫助咱們獲取圖像最大程度上的尺度不變性(準確的詞是「不變性」)。它很是的強大,由於咱們能夠檢測圖像中的物體,不管它們位置在哪裏。
到目前爲止咱們瞭解了卷積、ReLU 和池化是如何操做的。理解這些層是構建任意 CNN 的基礎是很重要的。正以下圖所示,咱們有兩組卷積、ReLU & 池化層 —— 第二組卷積層使用六個濾波器對第一組的池化層的輸出繼續卷積,獲得一共六個特徵圖。接下來對全部六個特徵圖應用 ReLU。接着咱們對六個修正特徵圖分別進行最大池化操做。
這些層一塊兒就能夠從圖像中提取有用的特徵,並在網絡中引入非線性,減小特徵維度,同時保持這些特徵具備某種程度上的尺度變化不變性。
第二組池化層的輸出做爲全鏈接層的輸入,接下來咱們將介紹全鏈接層。
全鏈接層是傳統的多層感知器,在輸出層使用的是 softmax 激活函數(也可使用其餘像 SVM 的分類器,但在本文中只使用 softmax)。「全鏈接」(Fully Connected) 這個詞代表前面層的全部神經元都與下一層的全部神經元鏈接。
卷積和池化層的輸出表示了輸入圖像的高級特徵。全鏈接層的目的是爲了使用這些特徵把輸入圖像基於訓練數據集進行分類。好比,在下面圖中咱們進行的圖像分類有四個可能的輸出結果(注意下圖並無顯示全鏈接層的節點鏈接)。
除了分類,添加一個全鏈接層也(通常)是學習這些特徵的非線性組合的簡單方法。從卷積和池化層獲得的大多數特徵可能對分類任務有效,但這些特徵的組合可能會更好。
從全鏈接層獲得的輸出機率和爲 1。這個能夠在輸出層使用 softmax 做爲激活函數進行保證。softmax 函數輸入一個任意大於 0 值的矢量,並把它們轉換爲零一之間的數值矢量,其和爲一。
正如上面討論的,卷積 + 池化層的做用是從輸入圖像中提取特徵,而全鏈接層的做用是分類器。
注意在下面的圖中,由於輸入的圖像是船,對於船這一類的目標機率是 1,而其餘三類的目標機率是 0,即
輸入圖像 = 船
目標矢量 = [0, 0, 1, 0]
完整的卷積網絡的訓練過程能夠總結以下:
上面的這些步驟能夠訓練 ConvNet —— 這本質上意味着對於訓練數據集中的圖像,ConvNet 在更新了全部權重和參數後,已經優化爲能夠對這些圖像進行正確分類。
當一張新的(未見過的)圖像做爲 ConvNet 的輸入,網絡將會再次進行前向傳播過程,並輸出各個類別的機率(對於新的圖像,輸出機率是使用已經在前面訓練樣本上優化分類的參數進行計算的)。若是咱們的訓練數據集很是的大,網絡將會(有但願)對新的圖像有很好的泛化,並把它們分到正確的類別中去。
注 1: 上面的步驟已經簡化,也避免了數學詳情,只爲提供訓練過程的直觀內容。
注 2:在上面的例子中咱們使用了兩組卷積和池化層。然而請記住,這些操做能夠在一個 ConvNet 中重複屢次。實際上,如今有些表現最好的 ConvNet 擁有多達十幾層的卷積和池化層!同時,每次卷積層後面不必定要有池化層。以下圖所示,咱們能夠在池化操做前連續使用多個卷積 + ReLU 操做。還有,請注意 ConvNet 的各層在下圖中是如何可視化的。
通常而言,越多的卷積步驟,網絡能夠學到的識別特徵就越複雜。好比,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)的操做。這兩層的概念和前面描述的同樣。
接下來咱們就到了三個全鏈接層。它們是:
注意在下圖中,輸出層中的 10 個節點的各個都與第二個全鏈接層的全部 100 個節點相連(所以叫作全鏈接)。
同時,注意在輸出層那個惟一的亮的節點是如何對應於數字 「8」 的——這代表網絡把咱們的手寫數字正確分類(越亮的節點代表從它獲得的輸出值越高,即,8 是全部數字中機率最高的)。
一樣的 3D 可視化能夠在這裏看到。
卷積神經網絡從上世紀 90 年代初期開始出現。咱們上面提到的 LeNet 是早期卷積神經網絡之一。其餘有必定影響力的架構以下所示:
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')
卷積操做是使用一個二維的卷積核在一個批處理的圖片上進行不斷掃描。具體操做就是將一個卷積和在每張圖片上按照一個合適的尺寸在每一個通道上面進行掃描。爲了達到更好的卷積效率,須要在不一樣的通道和不一樣的卷積核之間進行權衡。
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]
[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
:一個可選string
,NHWC
或者NCHW
。默認是用NHWC
。主要是規定了輸入 tensor 和輸出 tensor 的四維形式。若是使用 NHWC
,則數據以 [batch, in_height, in_width, in_channels]
存儲;若是使用NCHW
,則數據以[batch, in_channels, in_height, in_width]
存儲。name
: (可選)爲這個操做取一個名字。輸出參數:
Tensor
,數據類型是 input
相同。池化操做是利用一個矩陣窗口在輸入張量上進行掃描,而且將每一個矩陣窗口中的值經過取最大值,平均值或者其餘方法來減小元素個數。每一個池化操做的矩陣窗口大小是由 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]
。數據類型是float32
,float64
,qint8
,quint8
,qint32
。ksize
: 一個長度不小於 4 的整型數組。每一位上面的值對應於輸入數據張量中每一維的窗口對應值。strides
: 一個長度不小於 4 的整型數組。該參數指定滑動窗口在輸入數據張量每一維上面的步長。padding
: 一個字符串,取值爲 SAME
或者 VALID
。data_format
:一個可選string
,NHWC
或者NCHW
。默認是用NHWC
。主要是規定了輸入 tensor 和輸出 tensor 的四維形式。若是使用 NHWC
,則數據以 [batch, in_height, in_width, in_channels]
存儲;若是使用NCHW
,則數據以[batch, in_channels, in_height, in_width]
存儲。name
: (可選)爲這個操做取一個名字。輸出參數:
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
。數據維度是四維的。因此,爲了建立這個模型,咱們須要建立大量的權重和偏置項,這個模型中的權重在初始化的時候應該加入少許的噪聲來打破對稱性以及避免 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。咱們用一個 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})