Tensorflow入門教程(3)
上次文章:
點線面:Tensorflow入門教程(2)本系列文章相關代碼在這裡:
Github-LearningTensorflow本次文章的代碼:
CNN_mnist同步連載於個人公眾號(名稱 SaoYan):
http://weixin.qq.com/r/aUTn-z-ElGLrrXuc9xF7 (二維碼自動識別)
上次文章中,我們構建了一個線性回歸模型。模型本身不重要,重要的是體會到用Tensorflow寫程序的模式:定義Tensor對象,定義模型,定義loss function,選擇優化器,運行Session。這次我們把線性回歸模型升級為卷積神經網路,並且依然按照這個模式進行。
1.輔助函數
為了後面代碼的簡潔直觀,我們首先定義以下幾個函數
- weight()
這個函數返回一個從截斷正態分布(truncated normal distribution)中初始化的權重參數。後面調用這個函數獲得每一次卷積的「核」。注意返回類型是tf.Variable,因為卷積核是可更新參數。
- bias()
這個函數返回一個常量0.1構成的偏置項參數。注意返回類型也是tf.Variable。
- conv2d()
這個函數構造一個卷積層,給定輸入、卷積核,調用Tensorflow函數tf.nn.conv2d計算卷積輸出。
關於tf.nn.conv2d的輸入參數:
這個是Tensorflow內置的二維卷積操作函數。
參數1:輸入Tensor,尺寸必須為(batch, in_height, in_width, in_channels),例如送入一批320x320x3的圖像,數量為64個,那麼輸入參數就是一個尺寸為64x320x320x3的Tensor。
參數2:卷積核,尺寸必須滿足(filter_height, filter_width, in_channels, out_channels),例如處理上面那個64x320x320x3的輸入,採用5x5的卷積核,輸出特徵為32個(32個核),那麼卷積核應該是一個5x5x3x32的Tensor。
參數3:卷積步長。在二維卷積中,只有height和width這兩個維度上能調整步長的參數,因此這個參數必須等於[1,s,s,1],其中s是某正整數。對應輸入Tensor尺寸的四個維度batch, height, width, channels,也就是在batch和channels兩個維度上面不設步長(也就是步長為1),在height和width兩個維度上面設置卷積步長s。
參數4:填充類型。如果是"SAME", 那捲積之前填充0以保證輸入和輸出的height、width一致;如果是"VALID",那麼不進行填充,輸出的height、width小於輸入。
- max_pool_2x2()
這個函數定義了一個2x2的池化層。調用了Tensorflow內置函數tf.nn.max_pool。
關於tf.nn.max_pool的輸入參數:
這個是Tensorflow內置的二維池化操作函數。
參數1:輸入Tensor,尺寸要求與tf.nn.conv2d相同。
參數2:每個池化單元的尺寸,與tf.nn.conv2d中strides參數同理,必須等於[1,k,k,1]。也就是只能在height、width兩個維度上面進行池化。
參數3:步長,同上。
2.輸入和輸出數據
我們使用經典的MNIST手寫字元數據集。Tensorflow已經內置了數據讀取和預處理的函數,直接調用即可。輸入數據是單通道圖像,輸出數據是0~9的標籤值,標籤採用one-hot編碼(例如標籤0編碼為1000000000,標籤1編碼為0100000000,以此類推)。
然後我們定義placeholder對象
3.定義模型
- 兩層卷積
這裡調用了前面定義的四個函數,還調用了Tensorflow內置的ReLU激活函數tf.nn.relu。
- 全連接層+dropout
注意這裡兩個代碼實現上的技巧:
第一,用卷積來實現全連接層。假設前面所有卷積以後得到了WxHxC的一個特徵(feature map),如果我們希望通過全連接層得到1xK的特徵,那麼等效的卷積操作就是用一個WxHxCxK的卷積核,並且不進行填充(這點不要忘記!)
第二,tf.nn.dropout的第二個輸入參數採用placeholder類型。為什麼要這樣呢?因為這個參數在Session運行的過程中需要改變。在進行梯度下降的時候,要實施dropout防止過擬合;但是在測試結果的時候,不進行dropout,也就是「p_keep=1」。
- 輸出層
最後再進行一次全連接,獲得輸出。
注意:這裡依然使用了卷積操作來等效全連接層,但是由於使用的卷積操作,那麼輸出的尺寸就必定符合卷積的要求,也就是說,輸出的尺寸是Nx1x1x10,其中N是batch的大小。然而我們希望的輸出是Nx10(對batch里的每一個樣本,輸出一個類別向量),所以我們調用了tf.squeeze函數,這個函數的作用是去掉尺寸為1的維度。也就是Nx1x1x10降維成Nx10。
4.定義loss function和優化器
- 交叉商損失函數(cross entropy)
注意tf.nn.softmax_cross_entropy_with_logits這個函數的輸入參數:
logits這個參數是輸出端未進行log運算的原始結果
這樣設計的目的是避免用戶直接使用log導致數值計算不穩定,所以將取log和計算loss全部封裝在了一起,並且在內部用一些方法避免了數值計算的不穩定性。
- Adam優化器
- 另外,我們還想衡量一下準確率,也就是預測正確的樣本/總樣本。
這裡的train_step,accuracy等都是定義了一個操作符,後面需要用Session運行它們或得結果。
5.訓練CNN
首先是一些參數
然後開始運行Session
這裡每一句代碼都作了注釋,思路也很清晰,有前兩次文章所講的基礎,理解起來很容易,不做贅述。
一些總結
1.在實際編程過程中,有很多需要注意的問題,最常見的一個就是Tensor的尺寸。不同的函數,輸入、輸出的尺寸都有嚴格的規定。實踐中,特別注意一下幾個問題
- WHC不等於CHW。一個圖像數據,既可以是320x320x3的矩陣,也可以通過轉置操作變換成3x320x320的矩陣,但是Tensorflow各種函數的輸入參數格式是固定的。因此,如果你使用的數據集不符合格式,注意進行合理的數據預處理。
- N不等於Nx1。前面提到,輸出層之後調用了tf.squeeze函數,去除了冗餘的維度。這一點也很重要。請時刻檢查你的網路中各處數據的維度,確保它們是符合函數輸入參數要求的。
2. 這次代碼中涉及的很多Tensorflow的內置函數沒有一一講解,一是因為太多太雜,二是因為它們都很簡單很直觀,而且和numpy中的函數很類似,通過查閱官方文檔完全可以理解。(例如tf.argmax,tf.reduce_mean,tf.cast)
推薦閱讀:
TAG:TensorFlow | 深度學習DeepLearning | 機器學習 |