目錄
一、背景介紹
1.1 卷積神經網絡
1.2 深度學習框架
1.3 MNIST 數據集
二、方法和原理
2.1 部署網絡模型
(1)權重初始化
(2)卷積和池化
(3)搭建卷積層1
(4)搭建卷積層2
(5)搭建全連接層3
(6)搭建輸出層
2.2 訓練和評估模型
三、結果
3.1 訓練過程
3.2 測試過程
四、討論與結論
一、背景介紹
1.1 卷積神經網絡
近年來,深度學習的概念非常火熱。深度學習的概念最早由Hinton等人在2006年提出。基於深度置信網絡(DBN),提出非監督貪心逐層訓練算法,為解決深層結構相關的優化難題帶來希望,隨后提出多層自動編碼器深層結構。此外Lecun等人提出的卷積神經網絡(Convolutional Neural Networks / CNNs / ConvNets)是第一個真正多層結構學習算法,它利用空間相對關系減少參數數目以提高訓練性能。Alex在2012年提出的AlexNet網絡結構模型引爆了神經網絡的應用熱潮,並贏得了2012屆圖像識別大賽的冠軍,使得CNN成為在圖像分類上的核心算法模型。
1.2 深度學習框架
隨着深度學習研究的熱潮持續高漲,各種開源深度學習框架也層出不窮,其中包括TensorFlow、Caffe2、Keras、CNTK、Pytorch、MXNet、Leaf、Theano、DeepLearning4等等。Google、Microsoft、Facebook等巨頭都參與了這場深度學習框架大戰。目前,由谷歌推出的TensorFlow和Facebook推出的Pytorch、Caffe2較受歡迎。下面將簡單介紹這三種框架的特點。
Caffe全稱為Convolutional Architecture for Fast Feature Embedding,是一個被廣泛使用的開源深度學習框架(在TensorFlow出現之前一直是深度學習領域GitHub star最多的項目)。Caffe的主要優勢包括如下幾點:
-
容易上手,網絡結構都是以配置文件形式定義,不需要用代碼設計網絡。
-
訓練速度快,能夠訓練state-of-the-art的模型與大規模的數據。
-
組件模塊化,可以方便地拓展到新的模型和學習任務上。
Caffe的核心概念是Layer,每一個神經網絡的模塊都是一個Layer。Layer接收輸入數據,同時經過內部計算產生輸出數據。設計網絡結構時,只需要把各個Layer拼接在一起構成完整的網絡。Caffe2是caffe的升級版,在各方面均有一定提升。
2017 年初,Facebook 在機器學習和科學計算工具 Torch 的基礎上,針對 Python 語言發布了一個全新的機器學習工具包 PyTorch。一經發布,這款開源工具包就受到了業界的廣泛關注和討論,經過幾個月的發展,目前 PyTorch 已經成為從業者最重要的研發工具之一。其最大的特點是支持動態圖的創建。其他主流框架都是采用靜態圖創建,靜態圖定義的缺陷是在處理數據前必須定義好完整的一套模型,能夠處理所有的邊際情況。比如在聲明模型前必須知道整個數據中句子的最大長度。相反動態圖模型能夠非常自由的定義模型。同時,PyTorch繼承了Torch,支持Python,支持更加便捷的Debug,所以非常受歡迎。
TensorFlow是相對高階的機器學習庫,用戶可以方便地用它設計神經網絡結構,而不必為了追求高效率的實現親自寫C++或CUDA代碼。它和Theano一樣都支持自動求導,用戶不需要再通過反向傳播求解梯度。其核心代碼和Caffe一樣是用C++編寫的,使用C++簡化了線上部署的復雜度,並讓手機這種內存和CPU資源都緊張的設備可以運行復雜模型(Python則會比較消耗資源,並且執行效率不高)。除了核心代碼的C++接口,TensorFlow還有官方的Python、Go和Java接口,是通過SWIG(Simplified Wrapper and Interface Generator)實現的,這樣用戶就可以在一個硬件配置較好的機器中用Python進行實驗,並在資源比較緊張的嵌入式環境或需要低延遲的環境中用C++部署模型。
TensorFlow的另外一個重要特點是它靈活的移植性,可以將同一份代碼幾乎不經過修改就輕松地部署到有任意數量CPU或GPU的PC、服務器或者移動設備上。用戶能夠將訓練好的模型方便地部署到多種硬件、操作系統平台上,支持Intel和AMD的CPU,通過CUDA支持NVIDIA的GPU,支持Linux和Mac、Windows,也能夠基於ARM架構編譯和優化,在移動設備(Android和iOS)上表現得很好。TensorFlow還有功能強大的可視化組件TensorBoard,能可視化網絡結構和訓練過程,對於觀察復雜的網絡結構和監控長時間、大規模的訓練很有幫助。TensorFlow針對生產環境高度優化,它產品級的高質量代碼和設計都可以保證在生產環境中穩定運行,同時TensorFlow廣泛地被工業界使用,產生了良性循環,成為了深度學習領域的事實標准。
1.3 MNIST 數據集
除了神經網絡理論體系的發展和深度學習框架的推進,數據集的完善同樣促進了人工智能領域的發展。MNIST(Mixed National Institute of Standards and Technology database)是一個計算機視覺數據集,它包含70000張手寫數字的灰度圖片,其中每一張圖片包含 28 X 28 個像素點。可以用一個數字數組來表示這張圖片。如圖1所示。
圖1 MNIST數據格式
每一張圖片都有對應的標簽,也就是圖片對應的數字,例如上面這張圖片的標簽就是 1。數據集被分成兩部分:60000 行的訓練數據集(mnist.train)和10000行的測試數據集(mnist.test)。
其中:60000 行的訓練集分拆為 55000 行的訓練集和 5000 行的驗證集。60000行的訓練數據集是一個形狀為 [60000, 784] 的張量,第一個維度數字用來索引圖片,第二個維度數字用來索引每張圖片中的像素點。在此張量里的每一個元素,都表示某張圖片里的某個像素的強度值,值介於 0 和 1 之間。
60000 行的訓練數據集標簽是介於 0 到 9 的數字,用來描述給定圖片里表示的數字。稱為 "one-hot vectors"。一個 one-hot 向量除了某一位的數字是 1 以外其余各維度數字都是 0。所以在此教程中,數字 n 將表示成一個只有在第 n 維度(從 0 開始)數字為 1 的 10 維向量。比如,標簽 0 將表示成 ( [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] )。因此,其標簽是一個 [60000, 10] 的數字矩陣。如圖2(a)、(b)所示。
(a)
(b)
圖2 MNIST訓練集格式
下面將借助於TensorFlow框架,在Mnist數據集的基礎上,通過卷積神經網絡,進行手寫數字識別的仿真測試。
二、方法和原理
2.1 部署網絡模型
本次仿真采取一個四層的網絡,來實現手寫數字0~9的識別。網絡結構如下:
Convolutional Layer1 + Max Pooling |
Convolutional Layer2 + Max Pooling |
Fully Connected Layer1 + Dropout |
Fully Connected Layer2 To Prediction |
(1)權重初始化
為了創建這個模型,我們需要創建大量的權重和偏置項。這個模型中的權重在初始化時應該加入少量的噪聲來打破對稱性以及避免0梯度。由於我們使用的是ReLU神經元,因此比較好的做法是用一個較小的正數來初始化偏置項,以避免神經元節點輸出恆為0的問題(dead neurons)。為了不在建立模型的時候反復做初始化操作,我們定義兩個函數用於初始化。
(2)卷積和池化
TensorFlow在卷積和池化上有很強的靈活性。卷積使用1步長(stride size),0邊距(padding size)的模板,保證輸出和輸入是同一個大小。池化用簡單傳統的2x2大小的模板做max pooling。為了代碼更簡潔,把這部分抽象成一個函數。
(3)搭建卷積層1
第一層由一個卷積接一個max pooling完成。卷積在每個5x5的patch中算出32個特征。卷積的權重張量形狀是[5, 5, 1, 32],前兩個維度是patch的大小,接着是輸入的通道數目,最后是輸出的通道數目。而對於每一個輸出通道都有一個對應的偏置量。
接下來將卷積核與輸入的x_image進行卷積,並通過relu激活函數,再最大池化處理。
(4)搭建卷積層2
第二層構建一個更深的網絡,每個5x5的patch會得到64個特征。卷積核大小5x5x32,數量為64個,構造過程類似上一層。
(5)搭建全連接層3
原始圖片尺寸是28x28,經過兩次的2x2的池化后,長寬尺寸降低到7x7。現在加入一個有1024個神經元的全連接層,將上一層輸出的結果Vector化,變成一個向量,將其與權重W_fc1相乘,加上偏置b_fc1,對其使用ReLU。
為了減少過擬合,我們在輸出層之前加入dropout。我們用一個placeholder來代表一個神經元的輸出在dropout中保持不變的概率。這樣我們可以在訓練過程中啟用dropout,在測試過程中關閉dropout。 TensorFlow的tf.nn.dropout操作除了可以屏蔽神經元的輸出外,還會自動處理神經元輸出值的scale。所以用dropout的時候可以不用考慮scale。
(6)搭建輸出層
最后一層使用全連接層,並通過softmax回歸來輸出預測結果。softmax模型可以用來給不同的對象分配概率。可以用下面的圖解釋,對於輸入的x_i加權求和,再分別加上一個偏置量,最后再輸入到softmax函數中。
其計算公式為
2.2 訓練和評估模型
為了訓練我們的模型,我們首先需要定義損失函數(loss function),然后盡量最小化這個指標。這里使用的損失函數是"交叉熵"(cross-entropy)。交叉熵產生於信息論里面的信息壓縮編碼技術,但是它后來演變成為從博弈論到機器學習等其他領域里的重要技術手段。它的定義如下:
計算交叉熵后,就可以使用梯度下降來優化參數。由於前面已經部署好網絡結構,所以TensorFlow可以使用反向傳播算法計算梯度,自動地優化參數,直到交叉熵最小。TensorFlow提供了多種優化器,這里選擇更加復雜的Adam優化器來做梯度最速下降,學習率0.0001。每次訓練隨機選擇50個樣本,加快訓練速度,每輪訓練結束后,計算預測准確度。
三、結果
3.1 訓練過程
在CNN網絡訓練過程中,通過Tensorflow中的可視化工具Tensorboard,可以跟蹤網絡的整個訓練過程中的信息,比如每次循環過程中的參數變化、損失變化等。
圖4 主計算圖模型
Tensorboard可以記錄與展示以下數據形式:
-
標量Scalars
-
圖片Images
-
音頻Audio
-
計算圖Graph
-
數據分布Distribution
-
直方圖Histograms
-
嵌入向量Embeddings
在本次實驗的訓練過程中,我們將在相關代碼處放置節點,統計訓練過程中的損失和准確率,同時觀察部分訓練樣本,Tensorboard會把模型訓練過程中的各種數據匯總起來,存在自定義的路徑與日志文件中,然后在指定的web端可視化地展現這些信息。
圖5 訓練過程中的精確度
圖6 訓練過程中的損失
如圖4所示,是整個網絡的計算圖模型。清晰地展示了四層網絡之間的關系。
如圖5所示,是訓練過程中的精確度,隨着訓練步數的增加,精確度逐漸提升。在迭代將近2000次時,訓練准確度穩定在百分之95以上。
如圖6所示,是訓練過程中的損失,損失是通過交叉熵來衡量。隨着訓練步數的增加,損失逐漸降低。
結合圖5和圖6,可以觀察到,在訓練至100次時,損失達到一個比較小的水平,精確度提高到一個比較高的水平。
3.2 測試過程
訓練過程結束后,將驗證測試集的預測精確度,結果通過控制台打印輸出。
可以觀察到,最終的預測准確度是97.45%,總迭代次數2000次,是一個比較理想的結果。根據經驗,訓練次數超過20000次后,精度將維持在99%以上。
四、討論與結論
通過2層的CNN網絡加上2層的全連接層,能夠達到一個不錯的預測效果。本次仿真中,使用的激活函數是Relu函數,一定程度上避免了梯度消失和梯度飽和的問題。同時在全連接層加入了dropout,以一定概率舍棄網絡中的神經元,這樣可以避免一定的偶然性,最后訓練得到的網絡將會更加健壯,泛化性能更強。
源代碼:

1 import tensorflow as tf 2 import input_data # download and extract the data set automatically 3 4 5 def weight_variable(shape): 6 initial = tf.truncated_normal(shape, stddev=0.1) 7 return tf.Variable(initial) 8 9 10 def bias_variable(shape): 11 initial = tf.constant(0.1, shape=shape) 12 return tf.Variable(initial) 13 14 15 def conv2d(x, W): 16 return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME') 17 18 19 def max_pool_2x2(x): 20 return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') 21 22 23 # get the data source 24 mnist = input_data.read_data_sets("MNIST_data/", one_hot=True) 25 26 # input image:pixel 28*28 = 784 27 with tf.name_scope('input'): 28 x = tf.placeholder(tf.float32, [None, 784]) 29 y_ = tf.placeholder('float', [None, 10]) # y_ is realistic result 30 31 with tf.name_scope('image'): 32 x_image = tf.reshape(x, [-1, 28, 28, 1]) # any dim, width, height, channel(depth) 33 tf.summary.image('input_image', x_image, 8) 34 35 # the first convolution layer 36 with tf.name_scope('conv_layer1'): 37 W_conv1 = weight_variable([5, 5, 1, 32]) # convolution kernel: 5*5*1, number of kernel: 32 38 b_conv1 = bias_variable([32]) 39 40 h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) # make convolution, output: 28*28*32 41 42 with tf.name_scope('pooling_layer'): 43 h_pool1 = max_pool_2x2(h_conv1) # make pooling, output: 14*14*32 44 45 # the second convolution layer 46 with tf.name_scope('conv_layer2'): 47 W_conv2 = weight_variable([5, 5, 32, 64]) # convolution kernel: 5*5, depth: 32, number of kernel: 64 48 b_conv2 = bias_variable([64]) 49 h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2) # output: 14*14*64 50 51 with tf.name_scope('pooling_layer'): 52 h_pool2 = max_pool_2x2(h_conv2) # output: 7*7*64 53 54 55 # the first fully connected layer 56 with tf.name_scope('fc_layer3'): 57 W_fc1 = weight_variable([7 * 7 * 64, 1024]) 58 b_fc1 = bias_variable([1024]) # size: 1*1024 59 h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64]) 60 h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1) # output: 1*1024 61 62 # dropout 63 with tf.name_scope('dropout'): 64 keep_prob = tf.placeholder(tf.float32) 65 h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) 66 67 68 # the second fully connected layer 69 # train the model: y = softmax(x * w + b) 70 with tf.name_scope('output_fc_layer4'): 71 W_fc2 = weight_variable([1024, 10]) 72 b_fc2 = bias_variable([10]) # size: 1*10 73 74 with tf.name_scope('softmax'): 75 y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2) # output: 1*10 76 77 with tf.name_scope('lost'): 78 cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv)) 79 tf.summary.scalar('lost', cross_entropy) 80 81 with tf.name_scope('train'): 82 train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy) 83 84 with tf.name_scope('accuracy'): 85 correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1)) 86 accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) 87 tf.summary.scalar('accuracy', accuracy) 88 89 merged = tf.summary.merge_all() 90 train_summary = tf.summary.FileWriter(r'C:\Users\Administrator\tf\log', tf.get_default_graph()) 91 92 # init all variables 93 init = tf.global_variables_initializer() 94 95 # run session 96 with tf.Session() as sess: 97 sess.run(init) 98 # train data: get w and b 99 for i in range(2000): # train 2000 times 100 batch = mnist.train.next_batch(50) 101 102 result, _ = sess.run([merged, train_step], feed_dict={x: batch[0], y_: batch[1], keep_prob: 1.0}) 103 # train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5}) 104 105 if i % 100 == 0: 106 # train_accuracy = sess.run(accuracy, feed_dict) 107 train_accuracy = accuracy.eval(feed_dict={x: batch[0], y_: batch[1], keep_prob: 1.0}) # no dropout 108 print('step %d, training accuracy %g' % (i, train_accuracy)) 109 110 # result = sess.run(merged, feed_dict={x: batch[0], y_: batch[1]}) 111 train_summary.add_summary(result, i) 112 113 train_summary.close() 114 115 print('test accuracy %g' % accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0})) 116 117 118 # open tensor_board in windows-cmd 119 # tensorboard --logdir=C:\Users\Administrator\tf