Tensorflow之基於MNIST手寫識別的入門介紹

Tensorflow是當下AI熱潮下,最爲受歡迎的開源框架。不管是從Github上的fork數量仍是star數量,仍是從支持的語音,開發資料,社區活躍度等多方面,他當之爲superstar。python

 

在前面介紹瞭如何搭建Tensorflow的運行環境後(包括CPU和GPU的),今天就從MNIST手寫識別的源碼上分析一下,tensorflow的工做原理,重點是介紹CNN的一些基本理論,做爲掃盲入門,也做爲本身的handbook吧。編程

 

Architecture數組

首先,簡單的說下,tensorflow的基本架構。網絡

使用 TensorFlow, 你必須明白 TensorFlow:session

  • 使用圖 (graph) 來表示計算任務.
  • 在被稱之爲 會話 (Session) 的上下文 (context) 中執行圖.
  • 使用 tensor 表示數據.
  • 經過 變量 (Variable) 維護狀態.
  • 使用 feed 和 fetch 能夠爲任意的操做(arbitrary operation) 賦值或者從其中獲取數據.

 

Tensor數據結構

TensorFlow 是一個編程系統, 使用圖來表示計算任務. 圖中的節點被稱之爲 op (operation 的縮寫). 一個 op 得到 0 個或多個 Tensor, 執行計算, 產生 0 個或多個 Tensor. 每一個 Tensor 是一個類型化的多維數組. 例如, 你能夠將一小組圖像集表示爲一個四維浮點數數組, 這四個維度分別是 [batch, height, width, channels].架構

一個 TensorFlow 圖描述了計算的過程. 爲了進行計算, 圖必須在 會話 裏被啓動. 會話 將圖的 op 分發到諸如 CPU 或 GPU 之類的 設備 上, 同時提供執行 op 的方法. 這些方法執行後, 將產生的 tensor 返回. 在 Python 語言中, 返回的 tensor 是 numpy ndarray 對象; 在 C 和 C++ 語言中, 返回的 tensor 是tensorflow::Tensor 實例.框架

 

Tensor是tensorflow中很是重要且很是基礎的概念,能夠說數據的呈現形式都是用tensor表示的。輸入輸出都是tensor,tensor的中文含義,就是張量,能夠簡單的理解爲線性代數裏面的向量或者矩陣。ide

 

Graph函數

TensorFlow 程序一般被組織成一個構建階段和一個執行階段. 在構建階段, op 的執行步驟 被描述成一個圖. 在執行階段, 使用會話執行執行圖中的 op.

例如, 一般在構建階段建立一個圖來表示和訓練神經網絡, 而後在執行階段反覆執行圖中的訓練 op. 下面這個圖,就是一個比較形象的說明,圖中的每個節點,就是一個op,各個op透過tensor數據流向造成邊的鏈接,構成了一個圖。

構建圖的第一步, 是建立源 op (source op). 源 op 不須要任何輸入, 例如 常量 (Constant). 源 op 的輸出被傳遞給其它 op 作運算. Python 庫中, op 構造器的返回值表明被構造出的 op 的輸出, 這些返回值能夠傳遞給其它 op 構造器做爲輸入.

TensorFlow Python 庫有一個默認圖 (default graph), op 構造器能夠爲其增長節點. 這個默認圖對 許多程序來講已經足夠用了.

 

Session

當圖構建好後,須要建立一個Session來運行構建好的圖,來實現邏輯,建立session的時候,若無任何參數,tensorflow將啓用默認的session。session.run(xxx)是比較典型的使用方案, session運行結束後,返回值是一個tensor。

tensorflow中的session,有兩大類,一種就是普通的session,即tensorflow.Session(),還有一種是交互式session,即tensorflow.InteractiveSession(). 使用Tensor.eval() 和Operation.run()方法代替Session.run(). 這樣能夠避免使用一個變量來持有會話, 爲程序架構的設計添加了靈活性.

 

到此,親,你是否已經感受到tensorflow有個特色,處理業務邏輯上,很像Mapreduce架構裏面,先將數據按照必定的數據結構組織好,將邏輯單位進行排布,也是分階段的,也是側重數據集中型的業務。

 

數據載體

Tensorflow體系下,變量(Variable)是用來維護圖計算過程當中的中間狀態信息,是一種常見高頻使用的數據載體,還有一種特殊的數據載體,那就是常量(Constant),主要是用做圖處理過程的輸入量。這些數據載體,也都是以Tensor的形式體現。變量定義和常量定義上,比較好理解:

# 建立一個變量, 初始化爲標量0.沒有指定數據類型(dtype) state = tf.Variable(0, name="counter") # 建立一個常量,其值爲1,沒有指定數據類型(dtype) one = tf.constant(1)

針對上面的變量和常量,看看Tensorflow裏面的函數定義:

class Variable(object): 
  def __init__(self,
    initial_value=None,
    trainable=True,
    collections=None,
    validate_shape=True,
    caching_device=None,
    name=None,
    variable_def=None,
    dtype=None,
    expected_shape=None,
    import_scope=None):

 

def constant(value, dtype=None, shape=None, name="Const", verify_shape=False):

從上面的源碼能夠看出,定義變量,其實就是定義了一個Variable的實例,而定義常量,其實就是調用了一下常量函數,建立了一個常量Tensor。

 

還有一個很重要的概念,那就是佔位符placeholder,這個在Tensorflow中進行Feed數據灌入時,頗有用。所謂的數據灌入,指的是在建立Tensorflow的圖時,節點的輸入部分,就是一個placeholder,後續在執行session操做的前,將實際數據Feed到圖中,進行執行便可。

input1 = tf.placeholder(tf.types.float32)
input2 = tf.placeholder(tf.types.float32)
output = tf.mul(input1, input2)

with tf.Session() as sess: print sess.run([output], feed_dict={input1:[7.], input2:[2.]}) # 輸出: # [array([ 14.], dtype=float32)]

佔位符的定義原型,也是一個函數:

def placeholder(dtype, shape=None, name=None):

 

到此,Tensorflow的入門級的基本知識介紹完了。下面,將結合一個MNIST的手寫識別的例子,從代碼上簡單分析一下,下面是源代碼:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import tensorflow as tf

#加載測試數據的讀寫工具包,加載測試手寫數據,目錄MNIST_data是用來存放下載網絡上的訓練和測試數據的。 #這裏,參考我前面的博文,因爲網絡緣由,測試數據,我單獨下載後,放在當前目錄的MNIST_data目錄了。
import tensorflow.examples.tutorials.mnist.input_data as input_data
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)

#建立一個交互式的Session。
sess = tf.InteractiveSession()

#建立兩個佔位符,數據類型是float。x佔位符的形狀是[None,784],即用來存放圖像數據的變量,圖像有多少張
#是不關注的。可是圖像的數據維度有784圍。怎麼來的,由於MNIST處理的圖片都是28*28的大小,將一個二維圖像
#展平後,放入一個長度爲784的數組中。
#y_佔位符的形狀相似x,只是維度只有10,由於輸出結果是0-9的數字,因此只有10種結構。
x = tf.placeholder("float", shape=[None, 784])
y_ = tf.placeholder("float", shape=[None, 10])

#經過函數的形式定義權重變量。變量的初始值,來自於截取正態分佈中的數據。
def weight_variable(shape):
  initial = tf.truncated_normal(shape, stddev=0.1)
  return tf.Variable(initial)

#經過函數的形式定義偏置量變量,偏置的初始值都是0.1,形狀由shape定義。
def bias_variable(shape):
  initial = tf.constant(0.1, shape=shape)
  return tf.Variable(initial)

#定義卷積函數,其中x是輸入,W是權重,也能夠理解成卷積核,strides表示步長,或者說是滑動速率,包含長寬方向 #的步長。padding表示補齊數據。 目前有兩種補齊方式,一種是SAME,表示補齊操做後(在原始圖像周圍補充0),實 #際卷積中,參與計算的原始圖像數據都會參與。一種是VALID,補齊操做後,進行卷積過程當中,原始圖片中右邊或者底部 #的像素數據可能出現丟棄的狀況。
def conv2d(x, W):
  return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

#這步定義函數進行池化操做,在卷積運算中,是一種數據下采樣的操做,下降數據量,聚類數據的有效手段。常見的 #池化操做包含最大值池化和均值池化。這裏的2*2池化,就是每4個值中取一個,池化操做的數據區域邊緣不重疊。
#函數原型:def max_pool(value, ksize, strides, padding, data_format="NHWC", name=None)。對ksize和strides
#定義的理解要基於data_format進行。默認NHWC,表示4維數據,[batch,height,width,channels]. 下面函數中的ksize,
#strides中,每次處理都是一張圖片,對應的處理數據是一個通道(例如,只是黑白圖片)。長寬都是2,代表是2*2的 #池化區域,也反應出下採樣的速度。
def max_pool_2x2(x):
  return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

#定義第一層卷積核。shape在這裏,對應卷積核filter。 #其中filter的結構爲:[filter_height, filter_width, in_channels, out_channels]。這裏,卷積核的高和寬都是5, #輸入通道1,輸出通道數爲32,也就是說,有32個卷積核參與卷積。
W_conv1 = weight_variable([5, 5, 1, 32])
#偏置量定義,偏置的維度是32.
b_conv1 = bias_variable([32])

#將輸入tensor進行形狀調整,調整成爲一個28*28的圖片,由於輸入的時候x是一個[None,784],有與reshape的輸入項shape
#是[-1,28,28,1],後續三個維度數據28,28,1相乘後獲得784,因此,-1值在reshape函數中的特殊含義就能夠映射程None。即 #輸入圖片的數量batch。
x_image = tf.reshape(x, [-1,28,28,1])

#將2維卷積的值加上一個偏置後的tensor,進行relu操做,一種激活函數,關於激活函數,有不少內容須要研究,在此不表。
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
#對激活函數返回結果進行下采樣池化操做。
h_pool1 = max_pool_2x2(h_conv1)

#第二層卷積,卷積核大小5*5,輸入通道有32個,輸出通道有64個,從輸出通道數看,第二層的卷積單元有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])
#進行矩陣乘,加偏置後進行relu激活
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

keep_prob = tf.placeholder("float")
#對第二層卷積通過relu後的結果,基於tensor值keep_prob進行保留或者丟棄相關維度上的數據。這個是爲了防止過擬合,快速收斂。
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

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

#最後,添加一個softmax層,就像前面的單層softmax regression同樣。softmax是一個多選擇分類函數,其做用和sigmoid這個2值 #分類做用地位同樣,在咱們這個例子裏面,softmax輸出是10個。
y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

#實際值y_與預測值y_conv的天然對數求乘積,在對應的維度上上求和,該值做爲梯度降低法的輸入
cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv))

#下面基於步長1e-4來求梯度,梯度降低方法爲AdamOptimizer。
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

#首先分別在訓練值y_conv以及實際標籤值y_的第一個軸向取最大值,比較是否相等
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))

#對correct_prediction值進行浮點化轉換,而後求均值,獲得精度。
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

#先經過tf執行全局變量的初始化,而後啓用session運行圖。
sess.run(tf.global_variables_initializer())
for i in range(20000):
  #從mnist的train數據集中取出50批數據,返回的batch實際上是一個列表,元素0表示圖像數據,元素1表示標籤值
  batch = mnist.train.next_batch(50)
  if i%100 == 0:
    #計算精度,經過所取的batch中的圖像數據以及標籤值還有dropout參數,帶入到accuracy定義時所涉及到的相關變量中,進行 #session的運算,獲得一個輸出,也就是經過已知的訓練圖片數據和標籤值進行似然估計,而後基於梯度降低,進行權值訓練。
    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)
  #此步主要是用來訓練W和bias用的。基於似然估計函數進行梯度降低,收斂後,就等於W和bias都訓練好了。
  train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})

#對測試圖片和測試標籤值以及給定的keep_prob進行feed操做,進行計算求出識別率。就至關於前面訓練好的W和bias做爲已知參數。  
print "test accuracy %g"%accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0})

 

代碼是tensorflow的案例,這裏,重點是對代碼的理解,重點是對卷積的邏輯的理解。涉及到幾個概念,什麼是卷積,什麼是池化。

卷積,數學表達式以下:

卷積,是一個比較抽象的概念,表示一個輸入信號,通過一個系統做用後,獲得輸出結果。經過一個比喻來讓你們對卷積有個感性的認識(這回比喻,來自網絡):

好比說你的老闆命令你幹活,你卻到樓下打檯球去了,後來被老闆發現,他 很是氣憤,扇了你一巴掌(注意,這就是輸入信號,脈衝),因而你的臉上會漸漸地鼓起來一個包,你的臉就是一個系統,而鼓起來的包就是你的臉對巴掌的響應,好,這樣就和信號系統創建起來意義對應的聯繫。下面還須要一些假設來保證論證的嚴謹:假定你的臉是線性時不變系統,也就是說,不管何時老闆打你一巴掌,打在你臉的同一位置,你的臉上老是會在相同的時間間隔內鼓起來一個相同高度的包,而且假定以鼓起來的包的大小做爲系統輸出。好了,下面能夠進入核心內容——卷積了!

 

若是你天天都到地下去打檯球,那麼老闆天天都要扇你一巴掌,不過當老闆打你一巴掌後,你5分鐘就消腫了,因此時間長了,你甚至就適應這種生活了……若是有一天,老闆忍無可忍,以0.5秒的間隔開始不間斷的扇你的過程,這樣問題就來了,第一次扇你鼓起來的包還沒消腫,第二個巴掌就來了,你臉上的包就可能鼓起來兩倍高,老闆不斷扇你,脈衝不斷做用在你臉上,效果不斷疊加了,這樣這些效果就能夠求和了,結果就是你臉上的包的高度隨時間變化的一個函數了(注意理解);若是老闆再狠一點,頻率愈來愈高,以致於你都辨別不清時間間隔了,那麼,求和就變成積分了。能夠這樣理解,在這個過程當中的某一固定的時刻,你的臉上的包的鼓起程度和什麼有關呢?和以前每次打你都有關!可是各次的貢獻是不同的,越早打的巴掌,貢獻越小,因此這就是說,某一時刻的輸出是以前不少次輸入乘以各自的衰減係數以後的疊加而造成某一點的輸出,而後再把不一樣時刻的輸出點放在一塊兒,造成一個函數,這就是卷積,卷積以後的函數就是你臉上的包的大小隨時間變化的函數。原本你的包幾分鐘就能夠消腫,但是若是連續打,幾個小時也消不了腫了,這難道不是一種平滑過程麼?反映到劍橋大學的公式上,f(a)就是第a個巴掌,g(x-a)就是第a個巴掌在x時刻的做用程度,乘起來再疊加就ok了,你們說是否是這個道理呢?我想這個例子已經很是形象了,你對卷積有了更加具體深入的瞭解了嗎?

可是,對於CNN領域的卷積,不是連續信號的時域積分思路。而是輸入圖像X與卷積核W進行內積。卷積的過程,實際上是一種特徵提取的過程!

卷積過程,將原始輸入圖像進行了維度更新,一般會下降維度,如上述圖示,輸入5*5的圖片,在3*3的卷積核做用下,輸出爲一個3*3的矩陣。

上述這種padding爲SAME的狀況下進行的卷積,如何計算輸出結果的中有多少個特徵值呢? 假如輸入1張維度r*c的圖片,通過K個卷積核的維度是a*b。那麼,卷積後獲得的特徵值總數以下:

 

池化,是一種數據採樣操做,有均值池化,最大值池化等分類。池化能夠有效的下降特徵值的數量,減小計算量。池化,是將一個區域的特徵用一個特徵來表示。以下圖:

  

原始特徵矩陣是12*12,池化單元是4*4,圖中灰色區域表示4個池化區。左邊的圖和右邊的圖,利用MAX_POOLing,在池化後取值,好比左上角,將都會是1.也就是說,圖像目標有少許位置移動,對於目標識別是不受影響的。池化能夠抗干擾

 

 

 

說完了卷積核池化這兩個重要的CNN中的概念,下面這個多層卷積的圖片,是否是能夠理解每一層之間的數據傳遞關係了?

簡單解釋以下

1.input layer:輸入層是一個31*39的灰度圖片,通道數是1,也能夠理解輸入圖片的depth是1,不少論文或者技術文檔中,輸入通道數(in_channels)或者圖片厚度(depth)都有用到。

2.convolutional layer 1: 第一層卷積層,卷積核大小爲4*4,輸出結果厚度depth爲20,表示有20個4*4的卷積核與輸入圖片進行特徵提取。卷基層神經元的大小爲36*28,這個神經元大小是這麼算出來的:(39 - 4 + 1)*(31 - 4 +1).

3.Max-pooling layer 1:第一層池化層,池化單元大小爲2*2,池化不會改變輸入數據的厚度,因此輸出仍是20,池化將神經元的大小降爲一半,18*14.

4.convolutional layer 2:第二層卷積層,卷積核大小爲3*3,輸出結果厚度depth爲40,表示有40個3*3的卷積核與前一級的輸出進行了特徵提取。卷積層神經元的大小爲16*12,這個神經元大小是這麼算出來的:(18 - 3 + 1)*(14 - 3 +1)。

5.Max-pooling layer 2:第二層池化層,池化單元大小爲2*2,池化不會改變輸入數據的厚度,因此輸出仍是40,池化將神經元的大小降爲一半,8*6.

6.convolutional layer 3:第三層卷積層,卷積核大小爲3*3,輸出結果厚度depth爲60,表示有60個3*3的卷積核與前一級的輸出進行了特徵提取。卷積層神經元的大小爲6*4,這個神經元大小是這麼算出來的:(8 - 3 + 1)*(6 - 3 +1)。

7.Max-pooling layer 3:第三層池化層,池化單元大小爲2*2,池化不會改變輸入數據的厚度,因此輸出仍是60,池化將神經元的大小降爲一半,3*2.

8.convolutional layer 4:第四層卷積層,卷積核大小爲2*2,輸出結果厚度depth爲80,表示有80個2*2的卷積核與前一級的輸出進行了特徵提取。卷積層神經元的大小爲2*1,這個神經元大小是這麼算出來的:(3 - 2 + 1)*(2 - 2 +1)。

9.接下來,對第四層輸出結果,通過一次展平操做,80*(2*1)即獲得160個輸出特徵。

10.通過softmax分類,便可獲得最終輸出預測結果。

 

以上,是我對MNIST研究過程當中對tensorflow的CNN過程的理解,不對之處,還往牛人指出!

相關文章
相關標籤/搜索