機器學習筆記(十三):TensorFlow實戰五(經典卷積神經網路: LeNet -5 )
1 - 引言
之前我們介紹了一下卷積神經網路的基本結構——卷積層和池化層。通過這兩個結構我們可以任意的構建各種各樣的卷積神經網路模型,不同結構的網路模型也有不同的效果。但是怎樣的神經網路模型具有比較好的效果呢?
下圖展示了CNN的發展歷程。
經過人們不斷的嘗試,誕生了許多有有著里程碑式意義的CNN模型。因此我們接下來會學習這些非常經典的卷積神經網路
- LeNet -5
- AlexNet
- VGG
- Inception
- ResNet
2 - LeNet-5模型
LeNet-5模型是Yann LeCun教授於1998年在論文Gradient-Based Learning Applied to Document Recognition中提出的,它是第一個成功應用與數字識別問題的卷積神經網路。在MNIST資料集上,LeNet-5模型可以達到大約99.2%的正確率,LeNet-5模型如下圖所示:
下面我們來詳細介紹一個LeNet-5模型每一層的結構
2.1 第一層:卷積層
資料維數詳細說明:
這一層的輸入就是原始的影象畫素,LeNet-5模型輸入層為32X32X1的影象(只能識別灰度影象而不能識別彩色影象)。第一個卷積層的過濾器尺寸為5X5,深度為6(深度既是通道值),不使用padding,步長為1,因為沒有使用padding,這一層的輸出尺寸為32-5+1=28,深度為6。這一個卷積層總共有5x5x1x6+6=156個引數,其中6個為偏置項引數。本層卷積層總共有28x28x6x(5x5+1)=122304個連線
總結:
輸入圖片:
卷積核大小:
卷積核種類:6
輸出featuremap大小:
神經元數量:
可訓練引數:
連線數:
2.2 第二層:池化層
這一層的輸入為第一層的輸出,是一個28x28x6的節點矩陣。本層採用的過濾器大小為2x2,步長為2,所以輸出矩陣為14x14x6。
輸入:
取樣區域:
取樣方式:4個輸入相加,乘以一個可訓練引數,再加上一個可訓練偏置。結果通過sigmoid
取樣種類:6
輸出featureMap大小:
神經元數量:
連線數:
S2中每個特徵圖的大小是C1中特徵圖大小的1/4。
詳細說明:第一次卷積之後緊接著就是池化運算,使用$ 2*2$核 進行池化,於是得到了S2,6個 的 特徵圖(28/2=14)。S2這個pooling層是對C1中的 區域內的畫素求和乘以一個權值係數再加上一個偏置,然後將這個結果再做一次對映。同時有5x14x14x6=5880個連線。
2.3 第三層:卷積層
本層的輸入矩陣大小為14x14x6,使用的過濾器大小為5x5,深度為16。本層不使用padding,步長為1,所以輸出節點為10x10x16。所以有5x5x6x16+16 = 2416個引數。10x10x16x(25+1)=41600個連線。
輸入:14x14x6
卷積核大小:
卷積核種類:16
輸出featureMap大小:
2.4 第四層:池化層
本層輸入矩陣大小為10x10x16,採用的過濾器大小為2x2,步長為2,本層輸出矩陣大小為5x5x16
2.5 第五層:卷積層
輸入:5x5x16
卷積核大小:5*5
卷積核種類:120
輸出featureMap大小:1*1(5-5+1)
可訓練引數/連線:120*(1655+1)=48120
雖然LeNet-5模型的論文中將這一層成為卷積層,但是因為過濾器的大小就是5x5,所以和全連線層沒有區別。
2.6 F6層-全連線層
本層的輸入節點個數為120個,輸出節點個數為84個,總共引數為120x84+84 = 10164個。
2.7 Output層-全連線層
本層輸入節點為84個,輸出節點個數為10個,總共引數為84x10+10 = 850個。
3 - TensorFlow實現LeNet-5模型
** mnist_train_LeNet_5.py檔案:**
import os
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import numpy as np
#載入mnist_inference.py中定義的常量和前向傳播的函式。
import mnist_inference_LeNet_5
BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.8 # 最開始的學習率
LEARNING_RATE_DECAY = 0.99 # 在指數衰減學習率的過程中用到
REGULARIZATION_RATE = 0.0001 # 描述模型複雜度的正則化項在損失函式中的係數
TRAINING_STEPS = 30000 # 訓練輪數,注意,訓練一個Batch就是一個step
MOVING_AVERAGE_DECAY = 0.99 # 滑動平均模型的衰減率,最後我會講解滑動平均模型
#模型儲存的路徑和中文名
MODEL_SAVE_PATH = "/path/to/model/"
MODEL_NAME = "model.ckpt"
def train(mnist):
# 定義輸入輸出placeholder。
x = tf.placeholder(tf.float32,[
BATCH_SIZE, #第一維表示一個batch中樣例的個數
mnist_inference_LeNet_5.IMAGE_SIZE, #第二維和第三維表示圖片的尺寸
mnist_inference_LeNet_5.IMAGE_SIZE,
mnist_inference_LeNet_5.NUM_CHANNELS], #第四維表示圖片的深度,對於RBG格式的圖片,深度為5
name='x-input')
y_ = tf.placeholder(tf.float32, [None, mnist_inference_LeNet_5.OUTPUT_NODE], name='y-input')
regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
# 直接使用mnist_inference.py中定義的前向傳播過程
y = mnist_inference_LeNet_5.inference(x,train, regularizer)
global_step = tf.Variable(0, trainable=False)
# 定義損失函式、學習率、滑動平均操作以及訓練過程
variable_averages = tf.train.ExponentialMovingAverage(
MOVING_AVERAGE_DECAY, global_step
)
variable_averages_op = variable_averages.apply(
tf.trainable_variables()
)
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
logits=y, labels=tf.argmax(y_, 1)
)
cross_entropy_mean = tf.reduce_mean(cross_entropy)
loss = cross_entropy_mean + 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
)
train_step = tf.train.GradientDescentOptimizer(learning_rate)\
.minimize(loss, global_step=global_step)
with tf.control_dependencies([train_step, variable_averages_op]):
train_op = tf.no_op(name='train')
# 初始化TensorFlow持久化類
saver = tf.train.Saver()
with tf.Session() as sess:
tf.global_variables_initializer().run()
# 在訓練過程中不再測試模型在驗證資料上的表現,驗證和測試的過程將會有一個獨
# 立的程式來完成。
for i in range(TRAINING_STEPS):
xs, ys = mnist.train.next_batch(BATCH_SIZE)
xs = np.reshape(xs,(
BATCH_SIZE,
mnist_inference_LeNet_5.IMAGE_SIZE,
mnist_inference_LeNet_5.IMAGE_SIZE,
mnist_inference_LeNet_5.NUM_CHANNELS))
_, loss_value, step = sess.run([train_op, loss, global_step],
feed_dict={x: xs, y_: ys})
# 每1000輪儲存一次模型
if i % 1000 == 0:
# 輸出當前的訓練情況。這裡只輸出了模型在當前訓練batch上的損失
# 函式大小。通過損失函式的大小可以大概瞭解訓練的情況。在驗證數
# 據集上正確率的資訊會有一個單獨的程式來生成
print("After %d training step(s), loss on training "
"batch is %g." % (step, loss_value))
# 儲存當前的模型。注意這裡給出了global_step引數,這樣可以讓每個
# 被儲存的模型的檔名末尾加上訓練的輪數,比如“model.ckpt-1000”,
# 表示訓練1000輪之後得到的模型。
saver.save(
sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME),
global_step=global_step
)
# 主程式入口
def main(argv=None):
# 宣告處理MNIST資料集的類,這個類在初始化時會自動下載資料。
mnist = input_data.read_data_sets("/path/to/MNIST_data", one_hot=True)
train(mnist)
# TensorFlow提供的一個主程式入口,tf.app.run會呼叫上面定義的main函式
if __name__ == "__main__":
tf.app.run()
mnist_inference_LeNet_5.py檔案:
# -*- coding: utf-8 -*-
import tensorflow as tf
# 定義神經網路結構相關的引數
INPUT_NODE = 784
OUTPUT_NODE = 10
IMAGE_SIZE = 20
NUM_CHANNELS = 1
NUM_LABELS = 10
#第一層卷積層的尺寸和深度
CONV1_DEEP = 32
CONV1_SIZE = 5
#第二層卷積層的尺寸和深度
CONV2_DEEP = 64
CONV2_SIZE = 5
#全連線層的節點個數
FC_SIZE = 512
#定義卷積神經網路的前向傳播過程。這裡添加了一個新的引數train,用於區分訓練過程和測試
#過程,在這個過程中將用到dropout方法(只在訓練時使用)
def inference(input_tensor,train,regularizer):
#通過不同的名稱空間隔離不同層的變數,這可以讓每一層中的變數名只需要
#考慮在當前層的作用。而不需要擔心重名的問題。
with tf.variable_scope('layer1-conv1'):
conv1_weights = tf.get_variable(
"weight",[CONV1_SIZE,CONV1_SIZE,NUM_CHANNELS,CONV1_DEEP],
initializer=tf.truncated_normal_initializer(stddev=0.1))
conv1_biases = tf.get_variable(
"bias",[CONV1_DEEP],initializer=tf.constant_initializer(0.0))
#使用邊長為5,深度為32的過濾器,過濾器移動的步長為1,且使用全零填充
conv1 = tf.nn.conv2d(
input_tensor,conv1_weights,strides=[1,1,1,1],padding='SAME')
relu1 = tf.nn.relu(tf.nn.bias_add(conv1,conv1_biases))
#實現第二層池化層的前向傳播過程,這裡選用最大池化層,池化層邊長為2,
#使用全0填充且移動的步長為2,這一層的輸入時上一層的輸出,也就是28x28x32
#的矩陣,輸出為14x14x32的矩陣
with tf.name_scope('layer2-pool1'):
pool1 = tf.nn.max_pool(
relu1,ksize=[1,2,2,1],strides=[1,2,1,1],padding='SAME')
#宣告第三層卷積層的變數並實現前向傳播過程,這一層的輸入為14x14x32的矩陣
#輸出為14x14x64的矩陣
with tf.variable_scope('layer3-conv2'):
conv2_weights = tf.get_variable(
"weight",[CONV2_SIZE,CONV2_SIZE,CONV1_DEEP,CONV2_DEEP],
initializer=tf.truncated_normal_initializer(stddev=0.1))
conv2_biases = tf.get_variable(
"bias",[CONV2_DEEP],initializer=tf.constant_initializer(0.0))
#使用邊長為5,深度為64的過濾器,過濾器移動的步長為1,且使用全零填充
conv2 = tf.nn.conv2d(
pool1,conv2_weights,strides=[1,1,1,1],padding='SAME')
relu2 = tf.nn.relu(tf.nn.bias_add(conv2,conv2_biases))
#實現第四層池化層的前向傳播過程,這一層和第二層的結構是一樣的,這一層的輸入為
#14x14x64的矩陣,輸出為7x7x64的矩陣。
with tf.name_scope('layer4-pool2'):
pool2 = tf.nn.max_pool(
relu2,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
#將第四層池化層的輸出轉化為第五層全連線的輸入格式。第四層的輸入為7x7x64的矩陣,
#然而第五層全連線層需要的輸入格式為向量,所以在這裡需要將這個7x7x64的矩陣拉直成
#一個向量,pool2.get_shape函式可以得到第四層輸出矩陣的維度而不需要手工計算。
#注意因為每一層神經網路的輸入輸出都為一個batch的矩陣,所以這裡得到的維度也包含一個
#batch中資料的個數
pool_shape = pool2.get_shape().as_list()
#計算將矩陣拉成向量之後的長度,這個長度就是矩陣長寬及深度的成績,注意這裡
#pool_shape[0]為一個batch鍾資料的個數。
nodes = pool_shape[1]*pool_shape[2]*pool_shape[3]
#通過tf.reshape函式將第四層的輸出變成一個batch的向量。
reshaped = tf.reshape(pool2,[pool_shape[0],nodes])
#宣告第五層全連線的變數並實現前向傳播的過程,這一層的輸入時拉直之後的一組向量
#向量長度為3136,輸出是一組長度為512的向量。這裡我們引入了dropout的概念。
#dropout會隨機的將部分節點輸出改為0,避免過擬合的問題,這種優化方法一般只在
#全連線層而不是卷積層使用
with tf.variable_scope('layer5-fc1'):
fc1_weights = tf.get_variable(
"weight",[nodes,FC_SIZE],initializer=tf.truncated_normal_initializer(stddev=0.1))
#只有全連線層的權重需要加入正則化
if regularizer !=None:
tf.add_to_collection('losses',regularizer(fc1_weights))
fc1_biases = tf.get_variable(
"bias",[FC_SIZE],initializer=tf.constant_initializer(0.1))
fc1 = tf.nn.relu(tf.matmul(reshaped,fc1_weights)+fc1_biases)
if train: fc1 = tf.nn.dropout(fc1,0.5)
#宣告第六層全連線層的變數並實現前向傳播過程,這一層為長度512的向量
#輸出一組長度為10的向量,並且輸出通過sotfmax之後就得到了最後的分類結果
with tf.variable_scope('layer6-fc2'):
fc2_weights = tf.get_variable(
"weight",[FC_SIZE,NUM_LABELS],initializer=tf.truncated_normal_initializer(stddev=0.1))
if regularizer !=None:
tf.add_to_collection('losses',regularizer(fc2_weights))
fc2_biases = tf.get_variable(
"bias",[NUM_LABELS],initializer=tf.constant_initializer(0.1))
logit = tf.matmul(fc1,fc2_weights)+fc2_biases
return logit
After 1 training step(s), loss on training batch is 3.2112.
After 1001 training step(s), loss on training batch is 0.231712.
After 2001 training step(s), loss on training batch is 0.182711.
·
·
·
After 27001 training step(s), loss on training batch is 0.0336458.
After 28001 training step(s), loss on training batch is 0.036755.
After 29001 training step(s), loss on training batch is 0.0390648.