1. 程式人生 > >人工智慧實踐:TensorFlow筆記學習(七)—— 卷積神經網路基礎

人工智慧實踐:TensorFlow筆記學習(七)—— 卷積神經網路基礎

大綱

7.1 卷積神經網路

7.2  lenet5程式碼講解

目標

掌握卷積神經網路的搭建方法

7.1 卷積神經網路

全連線 NN:每個神經元與前後相鄰層的每一個神經元都有連線關係,輸入是特徵,輸出為預測的結果。

引數個數:(前層X後層+後層)

 

 一張解析度僅僅是28x28的黑白影象,就有近40萬個待優化的引數。現實生活中高解析度的彩色影象,畫素點更多,且為紅綠藍三通道資訊。待優化的引數過多,容易導致模型過擬合。為避免這種現象,實際應用中一般不會將原始圖片直接喂入全連線網路。在實際應用中,會先對原始影象進行特徵提取,把提取到的特徵餵給全連線網路,再讓全連線網路計算出分類評估值。 


例:先將此圖進行多次特徵提取,再把提取後的計算機可讀特徵餵給全連線網路。

卷積Convolutional  

卷積是一種有效提取圖片特徵的方法。一般用一個正方形卷積核,遍歷圖片上的每一個畫素點。圖片與卷積核重合區域內相對應的每一個畫素值乘卷積核內相對應點的權重,然後求和,再加上偏置後,最後得到輸出圖片中的一個畫素點。


例:上面是 5x5x1 的灰度圖片,1 表示單通道,5x5 表示解析度,共有 5 行 5 列個灰度值。若用一個 3x3x1 的卷積核對此 5x5x1 的灰度圖片進行卷積,偏置項 b=1,則求卷積的計算是:

(-1)x1+0x0+1x2+(-1)x5+0x4+1x2+(-1)x3+0x4+1x5+1=1(注意不要忘記加偏置 1)

輸出圖片邊長=(輸入圖片邊長–卷積核長+1)/步長,此圖為:(5 – 3 + 1)/ 1= 3,輸出圖片是 3x3 的解析度,用了 1 個卷積核,輸出深度是 1,最後輸出的是3x3x1 的圖片。

全零填充Padding 

有時會在輸入圖片周圍進行全零填充,這樣可以保證輸出圖片的尺寸和輸入圖片一致。


例:在前面5x5x1 的圖片周圍進行全零填充,可使輸出圖片仍保持 5x5x1 的維度。這個全零填充的過程叫做 padding。

輸出資料體的尺寸=(W−F+2P)/S+1

W:輸入資料體尺寸,F:卷積層中神經元感知域,S:步長,P:零填充的數量。 

例:輸入是 7×7,濾波器是 3×3,步長為 1,填充為 0,那麼就能得到一個5 × 5 的輸出。如果步長為 2,輸出就是 3×3。如果輸入量是32x32x3,核是 5x5x3,不用全零填充,輸出是(32-5+1)/1=28,如果要讓輸出量保持在 32x32x3,可以對該層加一個大小為 2 的零填充。可以根據需求計算出需要填充幾層零。32=(32-5+2P)/1 +1,計算出 P=2,即需填充 2 層零。

使用 padding 和不使用 padding 的輸出維度

上一行公式是使用padding的輸出圖片邊長,下一行公式是不使用padding的輸出圖片邊長。公式如果不能整除,需要向上取整數。如果用全零填充,也就是 padding=SAME。如果不用全零填充,也就是 padding=VALID。

Tensorflow 給出的計算卷積的函式

函式中要給出四個資訊:對輸入圖片的描述、對卷積核的描述、對卷積核滑動步長的描述以及是否使用padding

1)對輸入圖片的描述:用 batch 給出一次喂入多少張圖片,每張圖片的解析度大小,比如 5 行 5 列,以及這些圖片包含幾個通道的資訊,如果是灰度圖則為單通道,引數寫 1,如果是彩色圖則為紅綠藍三通道,引數寫 3。    

2)對卷積核的描述:要給出卷積核的行解析度和列解析度、通道數以及用了幾個卷積核。比如上圖描述,表示卷積核行列解析度分別為3 行和 3 列,且是 1 通道的,一共有 16 個這樣的卷積核,卷積核的通道數是由輸入圖片的通道數決定的,卷積核的通道數等於輸入圖片的通道數,所以卷積核的通道數也是1。一共有 16 個這樣的卷積核,說明卷積操作後輸出圖片的深度是 16,也就是輸出為 16 通道。

3)對卷積核滑動步長的描述:上圖第二個引數表示橫向滑動步長,第三個引數表示縱向滑動步長。第一個1 和最後一個 1 這裡固定的。這句表示橫向縱向 都以 1 為步長。

4)是否使用padding:用的是VALID。注意這裡是以字串的形式給出VALID。

對多通道的圖片求卷積

多數情況下,輸入的圖片是RGB 三個顏色組成的彩色圖,輸入的圖片包含了紅、綠、藍三層資料,卷積核的深度應該等於輸入圖片的通道數,所以使用3x3x3 的卷積核,最後一個 3 表示匹配輸入影象的 3 個通道,這樣這個卷積核有三層,每層會隨機生成 9 個待優化的引數,一共有27 個待優化引數 w 和一個偏置 b。

 對於彩色圖,按層分解開,可以直觀表示為上面這張圖,三個顏色分量:紅色分量、綠色分量和藍色分量。   卷積計算方法和單層卷積核相似,卷積核為了匹配紅綠藍三個顏色,把三層的卷積核套在三層的彩色圖片上,重合的27 個畫素進行對應點的乘加運算,最後的結果再加上偏置項 b,求得輸出圖片中的一個值。  這個5x5x3 的輸入圖片加了全零填充,使用 3x3x3 的卷積核,所有 27 個點與對應的待優化引數相乘,乘積求和再加上偏置 b 得到輸出圖片中的一個值6。

針對上面這幅彩色圖片,用conv2d 函式實現可以表示為:  一次輸入batch 張圖片,輸入圖片的解析度是 5x5,是 3 通道的,卷積核是 3x3x3,一共有 16 個卷積核,這樣輸出的深度就是 16,核滑動橫向步長是 1,縱向步長也是 1,padding 選擇 same,保證輸出是5x5 解析度。由於一共用了 16 個卷積核,所以輸出圖片是 5x5x16。

池化 Pooling

 Tensorflow 給出了計算池化的函式。最大池化用 tf.nn.max_pool 函式,平均池化用 tf.nn.avg_pool 函式。

 函式中要給出四個資訊,對輸入的描述、對池化核的描述、對池化核滑動步長的描述和是否使用 padding

 1)對輸入的描述:給出一次輸入batch 張圖片、行列解析度、輸入通道的個數。

 2)對池化核的描述:只描述行解析度和列解析度,第一個和最後一個引數固定是 1。

 3)對池化核滑動步長的描述:只描述橫向滑動步長和縱向滑動步長,第一個和最後一個引數固定是1。

 4)是否使用padding:padding 可以是使用零填充SAME 或者不使用零填充VALID。

捨棄 Dropout 

在神經網路訓練過程中,為了減少過多引數常使用 dropout 的方法,將一部分神經元按照一定概率從神經網路中捨棄。這種捨棄是臨時性的,僅在訓練時捨棄一些神經元;在使用神經網路時,會把所有的神經元恢復到神經網路中。比如上面這張圖,在訓練時一些神經元不參加神經網路計算了。Dropout 可以有效減少過擬合。

Tensorflow提供的 dropout 的函式:用 tf.nn.dropout 函式。

第一個引數連結上一層的輸出,第二個引數給出神經元捨棄的概率。  在實際應用中,常常在前向傳播構建神經網路時使用 dropout 來減小過擬合加快模型的訓練速度。

dropout 一般會放到全連線網路中。如果在訓練引數的過程中,輸出=tf.nn.dropout(上層輸出,暫時捨棄神經元的概率),這樣就有指定概率的神經元被隨機置零,置零的神經元不參加當前輪的引數優化。

卷積 NN:藉助卷積核(kernel)提取特徵後,送入全連線網路。      

卷積神經網路可以認為由兩部分組成,一部分是對輸入圖片進行特徵提取,另一部分就是全連線網路,只不過喂入全連線網路的不再是原始圖片,而是經過若干次卷積、啟用和池化後的特徵資訊。      

卷積神經網路從誕生到現在,已經出現了許多經典網路結構,比如 Lenet-5、Alenet、VGGNet、GoogleNet 和ResNet 等。每一種網路結構都是以卷積、啟用、池化全連線這四種操作為基礎進行擴充套件。

CNN模型的發展歷史Lenet-5,AlexNet ,VGGNet,GoogleNet,ResNet....

Lenet-5是最早出現的卷積神經網路,由 Lecun 團隊首先提出,Lenet-5 有效解決了手寫數字的識別問題。

7.2  lenet5程式碼講解

Lenet神經網路是YannLeCun等人在1998年提出的,該神經網路充分考慮影象的相關性。

Lenet神經網路結構為:

①輸入為32*32*1的圖片大小,為單通道的輸入;

②進行卷積,卷積核大小為5*5*1,個數為6,步長為1,非全零填充模式; 

③將卷積結果通過非線性啟用函式;

④進行池化,池化大小為2*2,步長為1,全零填充模式;

⑤進行卷積,卷積核大小為5*5*6,個數為16,步長為1,非全零填充模式;

⑥將卷積結果通過非線性啟用函式;

⑦進行池化,池化大小為2*2,步長為1,全零填充模式;

⑧全連線層進行10分類。

Lenet神經網路的結構圖及特徵提取過程如下所示:

Lenet神經網路的輸入是32*32*1,經過5*5*1的卷積核,卷積核個數為6個,採用非全零填充方式,步長為 1,根據非全零填充計算公式:輸出尺寸=(輸入尺寸-卷積核尺寸+1)/步長=(32-5+1)/1=28.故經過卷積後輸出為 28*28*6。

經過第一層池化層,池化大小為2*2,全零填充,步長為2,由全零填充計算公式:輸出尺寸=輸入尺寸/步長=28/2=14,池化層不改變深度,深度仍為6。用同樣計算方法,得到第二層池化後的輸出為 5*5*16。將第二池化層後的輸出拉直送入全連線層。

根據Lenet神經網路的結構可得,Lenet神經網路具有如下特點:

卷積(Conv)、池化(ave-pooling)、非線性啟用函式(sigmoid)相互交替;

②層與層之間稀疏連線,減少計算複雜度。

對Lenet神經網路進行微調,使其適應Mnist資料集: 由於Mnist資料集中圖片大小為28*28*1的灰度圖片,而Lenet神經網路的輸入為32*32*1,故需要對Lenet神經網路進行微調。

①輸入為28*28*1的圖片大小,為單通道的輸入;

②進行卷積,卷積核大小為5*5*1,個數為32,步長為1,全零填充模式; 

③將卷積結果通過非線性啟用函式;

④進行池化,池化大小為2*2,步長為2,全零填充模式;

⑤進行卷積,卷積核大小為5*5*32,個數為64,步長為1,全零填充模式;

⑥將卷積結果通過非線性啟用函式;

⑦進行池化,池化大小為2*2,步長為2,全零填充模式;

⑧全連線層,進行10分類。

Lenet進行微調後的結構如下所示: 


Lenet神經網路在Mnist資料集上的實現,主要分為三個部分:前向傳播過程(mnist_lenet5_forward.py)、反向傳播過程(mnist_lenet5_backword.py)、測試過程(mnist_lenet5_test.py)。

第一,前向傳播過程(mnist_lenet5_forward.py)實現對網路中引數和偏置的初始化、定義卷積結構和池化結構、定義前向傳播過程。具體程式碼如下所示:

#coding:utf-8
import tensorflow as tf
#每張圖片解析度為28*28
IMAGE_SIZE = 28
#Mnist資料集為灰度圖,故輸入圖片通道數NUM_CHANNELS取值為1
NUM_CHANNELS = 1
#第一層卷積核大小為5
CONV1_SIZE = 5
#卷積核個數為32
CONV1_KERNEL_NUM = 32
#第二層卷積核大小為5
CONV2_SIZE = 5
#卷積核個數為64
CONV2_KERNEL_NUM = 64
#全連線層第一層為 512 個神經元
FC_SIZE = 512
#全連線層第二層為 10 個神經元
OUTPUT_NODE = 10

#權重w計算
def get_weight(shape, regularizer):
	w = tf.Variable(tf.truncated_normal(shape,stddev=0.1))
	if regularizer != None: tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(regularizer)(w)) 
	return w

#偏置b計算
def get_bias(shape): 
	b = tf.Variable(tf.zeros(shape))  
	return b

#卷積層計算
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 forward(x, train, regularizer):
	#實現第一層卷積
    conv1_w = get_weight([CONV1_SIZE, CONV1_SIZE, NUM_CHANNELS, CONV1_KERNEL_NUM], regularizer) 
    conv1_b = get_bias([CONV1_KERNEL_NUM]) 
    conv1 = conv2d(x, conv1_w) 
	#非線性啟用
    relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_b)) 
	#最大池化
    pool1 = max_pool_2x2(relu1) 

    #實現第二層卷積
    conv2_w = get_weight([CONV2_SIZE, CONV2_SIZE, CONV1_KERNEL_NUM, CONV2_KERNEL_NUM],regularizer) 
    conv2_b = get_bias([CONV2_KERNEL_NUM])
    conv2 = conv2d(pool1, conv2_w) 
    relu2 = tf.nn.relu(tf.nn.bias_add(conv2, conv2_b))
    pool2 = max_pool_2x2(relu2)
     
	#獲取一個張量的維度
    pool_shape = pool2.get_shape().as_list() 
	#pool_shape[1] 為長 pool_shape[2] 為寬 pool_shape[3]為高
    nodes = pool_shape[1] * pool_shape[2] * pool_shape[3] 
	#得到矩陣被拉長後的長度,pool_shape[0]為batch值
    reshaped = tf.reshape(pool2, [pool_shape[0], nodes]) 

    #實現第三層全連線層
    fc1_w = get_weight([nodes, FC_SIZE], regularizer) 
    fc1_b = get_bias([FC_SIZE]) 
    fc1 = tf.nn.relu(tf.matmul(reshaped, fc1_w) + fc1_b) 
	#如果是訓練階段,則對該層輸出使用dropout
    if train: fc1 = tf.nn.dropout(fc1, 0.5)

    #實現第四層全連線層
    fc2_w = get_weight([FC_SIZE, OUTPUT_NODE], regularizer)
    fc2_b = get_bias([OUTPUT_NODE])
    y = tf.matmul(fc1, fc2_w) + fc2_b
    return y 

1)定義前向傳播過程中常用到的引數

圖片大小即每張圖片解析度為28*28,故IMAGE_SIZE取值為28;Mnist資料集為灰度圖,故輸入圖片通道數NUM_CHANNELS取值為1;第一層卷積核大小為5,卷積核個數為32,故CONV1_SIZE取值為5,CONV1_KERNEL_NUM取值為32;第二層卷積核大小為5,卷積核個數為64,故CONV2_SIZE取值為5,CONV2_KERNEL_NUM為 64;全連線層第一層為 512 個神經元,全連線層第二層為 10 個神經元,故FC_SIZE取值為512,OUTPUT_NODE取值為10,實現10分類輸出。

2)把前向傳播過程中,常用到的方法定義為函式,方便呼叫

在mnist_lenet5_forward.py檔案中,定義四個常用函式:權重w生成函式、偏置b生成函式、卷積層計算函式、最大池化層計算函式,其中,權重w生成函式和偏置b生成函式與之前的定義相同。

卷積層計算函式描述如下:

tf.nn.conv2d(輸入描述[batch,行解析度,列解析度,通道數],卷積核描述[行解析度,列解析度,通道數,卷積核個數], 核滑動步長[1,行步長,列步長,1],填充模式padding)例如:

tf.nn.conv2d(x=[100,28,28,1], w=[5,5,1,6],strides=[1,1,1,1],padding='SAME')

本例表示卷積輸入x為28*28*1,一個batch_size為100,卷積核大小為5*5,卷積核個數為6,垂直方向步長為1,水平方向步長為1,填充方式為全零填充。

最大池化層計算函式描述如下:

tf.nn.max_pool(輸入描述[batch,行解析度,列解析度,通道數],池化核描述[1,行解析度,列解析度,1], 池化核滑動步長[1,行步長,列步長,1],填充模式padding)例如:

tf.nn.max_pool(x=[100,28,28,1],ksize=[1, 2,2, 1], strides=[1, 2, 2, 1],padding='SAME')

本例表示卷積輸入x為28*28*1,一個batch_size為100,池化核大小用ksize,第一維和第四維都為 1,池化核大小為 2*2,垂直方向步長為 1,水平方向步長為1,填充方式為全零填充。 

3)定義前向傳播過程

實現第一層卷積

conv1_w=get_weight([CONV1_SIZE,CONV1_SIZE,NUM_CHANNELS,CONV1_KERNEL_NUM],regularizer)  

conv1_b = get_bias([CONV1_KERNEL_NUM]) 

根據先前定義的引數大小,初始化第一層卷積核和偏置項。

conv1 = conv2d(x, conv1_w) 

實現卷積運算,輸入引數為x和第一層卷積核引數。

relu1 = tf.nn.relu(tf.nn.bias_add(conv1,conv1_b)) 

第一層卷積的輸出值作為非線性啟用函式的輸入值,首先通過tf.nn.bias_add()對卷積後的輸出新增偏置,並過tf.nn.relu()完成非線性啟用。

pool1 = max_pool_2x2(relu1) 

根據先前定義的池化函式,將第一層啟用後的輸出值進行最大池化。

tf.nn.relu()用來實現非線性啟用,相比sigmoid和tanh函式,relu函式可以實現快速的收斂。

實現第二層卷積

conv2_w=get_weight([CONV2_SIZE,CONV2_SIZE,CONV1_KERNEL_NUM,CONV2_KERNEL_NUM],regularizer) 

conv2_b = get_bias([CONV2_KERNEL_NUM])

初始化第二層卷積層的變數和偏置項,該層每個卷積核的通道數要與上一層 卷積核的個數一致。

conv2 = conv2d(pool1, conv2_w)

實現卷積運算,輸入引數為上一層的輸出pool1和第二層卷積核引數。

relu2 = tf.nn.relu(tf.nn.bias_add(conv2,conv2_b))

實現第二層非線性啟用函式。

pool2 = max_pool_2x2(relu2)

根據先前定義的池化函式,將第二層啟用後的輸出值進行最大池化。

將第二層池化層的輸出pool2矩陣轉化為全連線層的輸入格式即向量形式

pool_shape =pool2.get_shape().as_list() 

根據.get_shape()函式得到pool2 輸出矩陣的維度,並存入 list 中。其中,pool_shape[0]為一個batch值。

nodes = pool_shape[1] * pool_shape[2] *pool_shape[3]

從list中依次取出矩陣的長寬及深度,並求三者的乘積,得到矩陣被拉長後的長度。

reshaped = tf.reshape(pool2,[pool_shape[0], nodes]) 

將pool2轉換為一個batch的向量再傳入後續的全連線。

get_shape函式用於獲取一個張量的維度,並且輸出張量每個維度上面的值。例如:

A =tf.random_normal(shape=[3,4]) print A.get_shape()輸出結果為:(3,4)

實現第三層全連線層

fc1_w = get_weight([nodes, FC_SIZE],regularizer) 

初始化全連線層的權重,並加入正則化。

fc1_b = get_bias([FC_SIZE]) 

初始化全連線層的偏置項。

fc1 = tf.nn.relu(tf.matmul(reshaped, fc1_w)+ fc1_b) 

將轉換後的reshaped向量與權重fc1_w做矩陣乘法運算,然後再加上偏置,最後再使用relu進行啟用。

if train: fc1 = tf.nn.dropout(fc1, 0.5)

如果是訓練階段,則對該層輸出使用dropout,也就是隨機的將該層輸出中的一半神經元置為無效,是為了避免過擬合而設定的,一般只在全連線層中使用。   

實現第四層全連線層的前向傳播過程

fc2_w =get_weight([FC_SIZE, OUTPUT_NODE], regularizer) fc2_b = get_bias([OUTPUT_NODE])

初始化全連線層對應的變數。

y = tf.matmul(fc1, fc2_w) + fc2_b

將轉換後的reshaped向量與權重fc2_w做矩陣乘法運算,然後再加上偏置。

return y

返回輸出值有,完成整個前向傳播過程,從而實現對Mnist資料集的10分類。

第二,反向傳播過程(mnist_lenet5_backward.py),完成訓練神經網路的引數。具體程式碼如下所示:

#coding:utf-8
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import mnist_lenet5_forward
import os
import numpy as np

#batch的數量
BATCH_SIZE = 100
#初始學習率
LEARNING_RATE_BASE =  0.005 
#學習率衰減率
LEARNING_RATE_DECAY = 0.99 
#正則化
REGULARIZER = 0.0001
#最大迭代次數
STEPS = 50000 
#滑動平均衰減率
MOVING_AVERAGE_DECAY = 0.99 
#模型儲存路徑
MODEL_SAVE_PATH="./model/"
#模型名稱
MODEL_NAME="mnist_model" 

def backward(mnist):
	#卷積層輸入為四階張量
	#第一階表示每輪喂入的圖片數量,第二階和第三階分別表示圖片的行解析度和列解析度,第四階表示通道數
    x = tf.placeholder(tf.float32,[
	BATCH_SIZE,
	mnist_lenet5_forward.IMAGE_SIZE,
	mnist_lenet5_forward.IMAGE_SIZE,
	mnist_lenet5_forward.NUM_CHANNELS]) 
    y_ = tf.placeholder(tf.float32, [None, mnist_lenet5_forward.OUTPUT_NODE])
	#前向傳播過程
    y = mnist_lenet5_forward.forward(x,True, REGULARIZER) 
	#宣告一個全域性計數器
    global_step = tf.Variable(0, trainable=False) 
    #對網路最後一層的輸出y做softmax,求取輸出屬於某一類的概率
    ce = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
	#向量求均值
    cem = tf.reduce_mean(ce) 
    #正則化的損失值
    loss = cem + tf.add_n(tf.get_collection('losses')) 
    #指數衰減學習率 
    learning_rate = tf.train.exponential_decay( 
        LEARNING_RATE_BASE,
        global_step,
        mnist.train.num_examples / BATCH_SIZE, 
		LEARNING_RATE_DECAY,
        staircase=True) 
    #梯度下降演算法的優化器
    #train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)
    train_step = tf.train.MomentumOptimizer(learning_rate,0.9).minimize(loss, global_step=global_step)
    #採用滑動平均的方法更新引數
	ema = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
    ema_op = ema.apply(tf.trainable_variables())
	#將train_step和ema_op兩個訓練操作繫結到train_op上
    with tf.control_dependencies([train_step, ema_op]): 
        train_op = tf.no_op(name='train')

    #例項化一個儲存和恢復變數的saver
    saver = tf.train.Saver() 
    #建立一個會話 
    with tf.Session() as sess: 
        init_op = tf.global_variables_initializer() 
        sess.run(init_op) 
        #通過 checkpoint 檔案定位到最新儲存的模型,若檔案存在,則載入最新的模型
        ckpt = tf.train.get_checkpoint_state(MODEL_SAVE_PATH) 
        if ckpt and ckpt.model_checkpoint_path:
        	saver.restore(sess, ckpt.model_checkpoint_path) 
       
        for i in range(STEPS):
			#讀取一個batch資料,將輸入資料xs轉成與網路輸入相同形狀的矩陣
            xs, ys = mnist.train.next_batch(BATCH_SIZE) 
            reshaped_xs = np.reshape(xs,(  
		    BATCH_SIZE,
        	mnist_lenet5_forward.IMAGE_SIZE,
        	mnist_lenet5_forward.IMAGE_SIZE,
        	mnist_lenet5_forward.NUM_CHANNELS))
			#讀取一個batch資料,將輸入資料xs轉成與網路輸入相同形狀的矩陣
            _, loss_value, step = sess.run([train_op, loss, global_step], feed_dict={x: reshaped_xs, y_: ys}) 
            if i % 100 == 0: 
                print("After %d training step(s), loss on training batch is %g." % (step, loss_value))
                saver.save(sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME), global_step=global_step)

def main():
    mnist = input_data.read_data_sets("./data/", one_hot=True) 
    backward(mnist)

if __name__ == '__main__':
    main()

1)定義訓練過程中的超引數

規定一個batch的數量為 100,故 BATCH_SIZE 取值為100;設定初始學習率為 0.005.學習率衰減率為0.99;最大迭代次數為50000,故STEPS取值為50000;滑動平均衰減率設定為0.99,並規定模型儲存路徑以及儲存的模型名稱。

2)完成反向傳播過程

給x, y_是佔位

x = tf.placeholder(tf.float32,[BATCH_SIZE,  mnist_lenet5_forward.IMAGE_SIZE,  mnist_lenet5_forward.IMAGE_SIZE,  mnist_lenet5_forward.NUM_CHANNELS]) 

y_ = tf.placeholder(tf.float32,[None,mnist_lenet5_forward.OUTPUT_NODE])

x, y_是定義的佔位符,指定引數為浮點型。由於卷積層輸入為四階張量,故 x 的佔位符表示為上述形式,第一階表示每輪喂入的圖片數量,第二階和第三階分別表示圖片的行解析度和列解析度,第四階表示通道數。

x = tf.placeholder(dtype,shape,name=None)

tf.placeholder()函式有三個引數,dtype 表示資料型別,常用的型別為

tf,float32,tf.float64等數值型別,shape表示資料形狀,namen表示名稱。

呼叫前向傳播過程

y = mnist_lenet5_forward.forward(x,True,REGULARIZER) 

呼叫前向傳播網路得到維度為10的tensor。

求含有正則化的損失值

global_step = tf.Variable(0,trainable=False) 

宣告一個全域性計數器,並輸出化為0

ce =tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y,labels=tf.argmax(y_, 1))

對網路最後一層的輸出y做softmax,求取輸出屬於某一類的概率,結果為一個 num_classes大小的向量,再將此向量和實際標籤值做交叉熵,返回一個向量值。

cem = tf.reduce_mean(ce)

通過tf.reduce_mean()函式對得到的向量求均值,得到loss。

loss = cem +tf.add_n(tf.get_collection('losses')) 

新增正則化中的losses值到loss中。

sparse_softmax_cross_entropy_with_logits(_sentinel=None,

labels=None, logits=None, name=None)

此函式的引數logits為神經網路最後一層的輸出,它的大小為[batch_size,num_classes],引數labels表示實際標籤值,大小為[batch_size,num_classes]。

第一步是先對網路最後一層的輸出做一個softmax,輸出為屬於某一屬性的概率向量;再將概率向量與實際標籤向量做交叉熵,返回向量。

tf.reduce_mean( input_tensor, 

reduction_indices=None,

keep_dims=False,

 name=None)

此函式表示對得到的向量求取均值。引數input_tensor表示要減少的張量;引數reduction_indices表示求取均值的維度;引數keep_dims含義為:如果為true,則保留長度為1的縮小尺寸。name表示操作的名稱。 例如:

x = tf.constant([[1., 1.], [2., 2.]])

tf.reduce_mean(x)      #表示對向量整體求均值1.5  

tf.reduce_mean(x, 0)   #表示對向量在列上求均值[1.5,1.5] 

tf.reduce_mean(x, 1)   #表示對向量在行上求均值[1., 2.]

實現指數衰減學習率

learning_rate =tf.train.exponential_decay(

LEARNING_RATE_BASE,

                  global_step,

                  mnist.train.num_examples /BATCH_SIZE,              LEARNING_RATE_DECAY,

                  staircase=True) 

tf.train.exponential_decay函式中引數LEARNING_RATE_BASE表示初始學習率,引數LEARNING_RATE_DECAY表示學習率衰減速率。實現指數級的減小學習率,可以讓模型在訓練的前期快速接近較優解,又可以保證模型在訓練後期不會有太大波動。其中,當staircase=True時,為階梯形衰減,(global_step/ decay_steps)則被轉化為整數;當staircase=False時,為曲線形衰減,以此根據staircase來選擇不同的衰減方式。

計算公式為:

decayed_learning_rate=learining_rate*decay_rate^(global_step/decay_steps) train_step=tf.train.GradientDescentOptimizer(learning_rate).minimize(loss,global_step=global_step)

此函式的引數learning_rate為傳入的學習率,構造一個實現梯度下降演算法的優化器,再通過使用minimize更新儲存要訓練的變數的列表來減小loss。 

實現滑動平均模型

ema =tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY,global_step)

ema_op =ema.apply(tf.trainable_variables())

tf.train.ExponentialMovingAverage函式採用滑動平均的方法更新引數。此函式的引數MOVING_AVERAGE_DECAY表示衰減速率,用於控制模型的更新速度;此函式維護一個影子變數,影子變數初始值為變數初始值。影子變數值的更新方式如下:shadow_variable= decay * shadow_variable + (1-decay) * variable。

其中,shadow_variable是影子變數,variable表示待更新的變數,decay為衰減速率。decay一般設為接近於1的數(0.99,0.999),decay越大模型越穩定。

將train_step和ema_op兩個訓練操作繫結到train_op上

with tf.control_dependencies([train_step, ema_op]): 

train_op =tf.no_op(name='train')

例項化一個儲存和恢復變數的saver,並建立一個會話

saver = tf.train.Saver() 

with tf.Session() as sess: 

init_op =tf.global_variables_initializer()    sess.run(init_op) 

建立一個會話,並通過python中的上下文管理器來管理這個會話,初始化計算圖中的變數,並用sess.run實現初始化。

ckpt =tf.train.get_checkpoint_state(MODEL_SAVE_PATH)      if ckpt and ckpt.model_checkpoint_path:

saver.restore(sess, ckpt.model_checkpoint_path) 

通過 checkpoint 檔案定位到最新儲存的模型,若檔案存在,則載入最新的模型。

    fori in range(STEPS):

        xs, ys =mnist.train.next_batch(BATCH_SIZE)        

         reshaped_xs = np.reshape(xs,(  BATCH_SIZE,                 

        mnist_lenet5_forward.IMAGE_SIZE,                  

        mnist_lenet5_forward.IMAGE_SIZE,  mnist_lenet5_forward.NUM_CHANNELS)) 

讀取一個batch資料,將輸入資料xs轉成與網路輸入相同形狀的矩陣。

_, loss_value, step = sess.run([train_op,loss, global_step], feed_dict={x: reshaped_xs, y_: ys})

喂入訓練影象和標籤,開始訓練。

if i % 100 == 0: 

print("After %d training step(s), loss on training batch is%g." %

(step,loss_value)) 

每迭代100次列印loss資訊,並儲存最新的模型。 訓練Lenet網路後,輸出結果如下: 


第三,測試過程(mnist_lenet5_test.py),對Mnist資料集中的測試資料進行預測,測試模型準確率。具體程式碼如下所示: 

#coding:utf-8
import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import mnist_lenet5_forward
import mnist_lenet5_backward
import numpy as np

TEST_INTERVAL_SECS = 5

#建立一個預設圖,在該圖中執行以下操作
def test(mnist):
    with tf.Graph().as_default() as g: 
        x = tf.placeholder(tf.float32,[
            mnist.test.num_examples,
            mnist_lenet5_forward.IMAGE_SIZE,
            mnist_lenet5_forward.IMAGE_SIZE,
            mnist_lenet5_forward.NUM_CHANNELS]) 
        y_ = tf.placeholder(tf.float32, [None, mnist_lenet5_forward.OUTPUT_NODE])
		#訓練好的網路,故不使用 dropout
        y = mnist_lenet5_forward.forward(x,False,None)

        ema = tf.train.ExponentialMovingAverage(mnist_lenet5_backward.MOVING_AVERAGE_DECAY)
        ema_restore = ema.variables_to_restore()
        saver = tf.train.Saver(ema_restore)

		#判斷預測值和實際值是否相同 
        correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1)) 
		## 求平均得到準確率
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) 

        while True:
            with tf.Session() as sess:
                ckpt = tf.train.get_checkpoint_state(mnist_lenet5_backward.MODEL_SAVE_PATH)
                if ckpt and ckpt.model_checkpoint_path:
                    saver.restore(sess, ckpt.model_checkpoint_path)	
					# 根據讀入的模型名字切分出該模型是屬於迭代了多少次儲存的 
                    global_step = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1] 
                    reshaped_x = np.reshape(mnist.test.images,(
                    mnist.test.num_examples,
        	        mnist_lenet5_forward.IMAGE_SIZE,
        	        mnist_lenet5_forward.IMAGE_SIZE,
        	        mnist_lenet5_forward.NUM_CHANNELS))
					#利用多執行緒提高圖片和標籤的批獲取效率
                    coord = tf.train.Coordinator()#3
                    threads = tf.train.start_queue_runners(sess=sess, coord=coord)#4
                    accuracy_score = sess.run(accuracy, feed_dict={x:reshaped_x,y_:mnist.test.labels}) 
                    print("After %s training step(s), test accuracy = %g" % (global_step, accuracy_score))
					#關閉執行緒協調器
                    coord.request_stop()#6
                    coord.join(threads)#7
                else:
                    print('No checkpoint file found')
                    return
            time.sleep(TEST_INTERVAL_SECS) 

def main():
    mnist = input_data.read_data_sets("./data/", one_hot=True)
    test(mnist)

if __name__ == '__main__':
    main()

1)在測試程式中使用的是訓練好的網路,故不使用 dropout,而是讓所有神經元都參與運算,從而輸出識別準確率。 

2)correct_prediction= tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1)) 

tf.equaf(x,y)此函式用於判斷函式的兩個引數x與y是否相等,一般x表示預測值,y表示實際值。

3)accuracy =tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

求平均得到預測準確率。 

在測試集上,輸出結果如下: 


由輸出結果表明,在測試集上的準確率可以達到99%左右,Lenet效能良好。

致謝

感謝曹老師的辛勤付出,來源曹健,人工智慧實踐:TensorFlow筆記,北京大學