【Tensorflow_DL_Note8】MNIST數字識別問題以及神經網路結構設計和引數優化(1)

馬衛飛發表於2018-04-20

一 MNIST資料處理

    “Hello World”是任何一門程式語言的入門程式,而深度學習中的“Hello World”就是MNIST手寫體數字識別問題。

      MNIST資料集是一個大型的手寫體數字資料庫,廣泛的用於機器學習領域的訓練和測試,由紐約大學的Yann LeCun教授整理,下載連結如下所示:http://yann.lecun.com/exdb/mnist/。MNIST資料集是NIST資料集的一個子集,它包含60000個【訓練集和】10000個【測試集】,每張影象都已經進行了歸一化、數字居中處理、固定尺寸為28畫素x28畫素。

             

       如上圖所示,MNIST資料集提供了四個下載檔案,分別為訓練集合,訓練集合的標籤,測試集,測試集的標籤。雖然,這個集合僅僅提供了【訓練集】和【測試集】,但是在實際的應用過程,我們會將【測試集】中的一部分資料分割出來作為最終訓練模型的【驗證集】,以便測試【最終訓練出來的模型】在【未知資料集合】上的【預測能力】。在Tensorflow中,Tensorflow對於MNIST資料集合的下載和使用進行了封裝,Tensotflow的封裝,使得MNIST資料集合的使用更加的方便,具體的封裝如下所示:

#========================================================================================================
#檔案說明:
#       Tensorflow對MNIST資料集合的封裝
#開發環境:
#       Win10+Tensorflow+OpenCv3.3+Python3.5+PyCharm5.0.3
#時間地點:
#       陝西師範大學 文集樓 2018.4.20
#========================================================================================================
from tensorflow.examples.tutorials.mnist import input_data
#【1】將MNIST資料集合讀入記憶體,如果指定的路徑下沒有下載好的MNIST資料集合,那麼,Tensorflow會自動從網上下載資料。
mnist = input_data.read_data_sets('F:/MnistSet/', one_hot=True)
#【2】列印Training data size 55000
print("【INFO】Training data size = ",mnist.train.num_examples)
#【3】列印【驗證集】Validating data size: 5000
print("【INFO】Validating data size:",mnist.validation.num_examples)
#【4】列印Testing data size:10000
print("【INFO】Testing data size:",mnist.test.num_examples)
#【5】列印Example training data:
print("【INFO】Example training data:",mnist.train.images[0])
#【6】列印Example training data label
print("【INFO】Example training data label:",mnist.train.labels[0])
#========================================================================================================
#檔案說明:
#       【1】從上面的程式碼可以看出,通過input_data.read_data_sets函式生成的類會自動的將MNIST資料集劃分為【train】、
#           【Validation】、【Test】三個資料集合,其中train這個集合中有55000張圖片,validation這個集合中有5000張圖
#            片,這兩個集合組成了MNIST資料集本身提供的訓練資料集。Test集合中有10000張圖片,這些圖片來自MNIST資料集提
#            供的的【測試集】。處理後的每一張圖片是一個長度為784的一維陣列,這個陣列中的元素對應了圖片畫素矩陣中的每一
#            個數字28x28=784.
#       【2】因為神經網路的輸入是一個【特徵向量】,所以,在此把一張二維影象的畫素矩陣放到一個一維陣列中可以方便Tensor-
#           flow將【圖片】的【畫素矩陣】提供為【神經網路】的【輸入層】。
#       【3】為了方便使用【SGD:隨機梯度下降演算法】,input_data.read_data_sets函式生成的類還提供了mnist.train.next_batch
#           函式,它可以從所有的訓練資料中讀取一小部分作為一個訓練batch。下面的這份程式碼顯示了這個功能。
#========================================================================================================
from tensorflow.examples.tutorials.mnist import input_data
#【1】將MNIST資料集合讀入記憶體,如果指定的路徑下沒有下載好的MNIST資料集合,那麼,Tensorflow會自動從網上下載資料。
mnist = input_data.read_data_sets('F:/MnistSet/', one_hot=True)
#【2】列印Training data size 55000
print("【INFO】Training data size = ",mnist.train.num_examples)
#【3】列印【驗證集】Validating data size: 5000
print("【INFO】Validating data size:",mnist.validation.num_examples)
#【4】列印Testing data size:10000
print("【INFO】Testing data size:",mnist.test.num_examples)
#【5】列印Example training data:
#print("【INFO】Example training data:",mnist.train.images[0])
#【6】列印Example training data label
print("【INFO】Example training data label:",mnist.train.labels[0])
#========================================================================================================
#檔案說明:
#       【1】從上面的程式碼可以看出,通過input_data.read_data_sets函式生成的類會自動的將MNIST資料集劃分為【train】、
#           【Validation】、【Test】三個資料集合,其中train這個集合中有55000張圖片,validation這個集合中有5000張圖
#            片,這兩個集合組成了MNIST資料集本身提供的訓練資料集。Test集合中有10000張圖片,這些圖片來自MNIST資料集提
#            供的的【測試集】。處理後的每一張圖片是一個長度為784的一維陣列,這個陣列中的元素對應了圖片畫素矩陣中的每一
#            個數字28x28=784.
#       【2】因為神經網路的輸入是一個【特徵向量】,所以,在此把一張二維影象的畫素矩陣放到一個一維陣列中可以方便Tensor-
#           flow將【圖片】的【畫素矩陣】提供為【神經網路】的【輸入層】。
#       【3】為了方便使用【SGD:隨機梯度下降演算法】,input_data.read_data_sets函式生成的類還提供了mnist.train.next_batch
#           函式,它可以從所有的訓練資料中讀取一小部分作為一個訓練batch。下面的這份程式碼顯示了這個功能。
#========================================================================================================
batch_size = 100
xs,ys      = mnist.train.next_batch(batch_size)
#【7】從train的集合中選取batch_size個訓練資料
print("【INFO】X Shape:",xs.shape)
print("【INFO】Y Shape:",ys.shape)

二 神經網路模型的訓練及不同模型結果的對比

2.1 Tensorflow訓練神經網路

        此塊,我們將給出一個完整的Tensorflow程式來解決MNIST手寫體數字識別問題。在我們具體設計神經網路的模型之前,我們先回顧以下神經網路設計中的要點,在神經網路結構的設計中,深度學習一方面需要使用使用【啟用函式】實現【神經網路  模型】的【去線性化】;另一方面,需要使用一層或者多層的【隱藏層】使得【神經網路的深度】更深,這樣便有利於解決更加複雜問題。

       在具體【訓練神經網路】的時候,我們需要設定合理的【帶指數衰減的學習率】、使用【正則化】來避免【過擬合】,以及使用【滑動平均模型】使得最終的模型更加的健壯。下面給出的程式碼實現了完整功能的在MNIST資料集上的神經網路的訓練。

#coding=UTF-8
#========================================================================================================
#檔案說明:
#       完整的MNIST資料集合神經網路的訓練和設計
#開發環境:
#       Win10+Tensorflow+OpenCv3.3+Python3.5+PyCharm5.0.3
#時間地點:
#       陝西師範大學 文集樓 2018.4.20
#========================================================================================================
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

#【1】【MNIST資料集】相關的【常數】
INPUT_NODE = 784                        #[1]輸入層的節點數。對於MNIST資料集來說,這個就等於圖片的畫素點數
OUTPUT_NODE= 10                         #[2]輸出層的節點數。這個就等於類別的數目,因為在MNIST資料集中,需要區分的是
                                        #   0~9這10個數字,所以,這裡輸出層的節點數為10.
#【2】【配置神經網路的引數】
LAYER1_NODE= 500                        #[1]隱藏層的節點數。這裡使用只有一個隱藏層的網路結構作為樣例,這個隱藏層的節
                                        #   點數為500
BATCH_SIZE = 100                        #[2]一個訓練batch中訓練資料的個數。數字越小時,訓練過程越接近【隨機梯度下降】;
                                        #   數字越大時,訓練越接近【梯度下降演算法】。
LEARNING_RATE_BASE  = 0.8               #[1]基礎的學習率
LEARNING_RATE_DECAY = 0.99              #[2]學習率的衰減
REGULARIZATION_RATE = 0.0001            #[3]描述【模型複雜度】的【正則化】在【損失函式】中的【係數】
TRAINING_STEPS      = 30000             #[4]訓練輪數
MOVING_AVERAGE_DECAY= 0.99              #[5]滑動平均模型的衰減率
#========================================================================================================
#函式說明:
#       【1】一個輔助函式,給定【神經網路】的【輸入】和【所有引數】,計算【神經網路】的【前向傳播結果】。
#       【2】在這裡,定義了一個使用【ReLU啟用函式】的三層全連線神經網路。
#       【3】通過加入【隱藏層】實現【多層網路結構】,通過【RELU啟用函式】實現【去線性化】
#       【4】在這個函式中,也支援傳入用於計算引數平均值得類,這樣方便在測試時使用【滑動平均模型】
#========================================================================================================
def inference(input_tensor,avg_class,weights1,biases1,weights2,biases2):
    #【1】當沒有提供【滑動平均類】時,直接使用【引數】的【當前取值】
    if avg_class == None:
        #【2】計算【隱藏層】的【前向傳播結果】,這裡使用了【ReLU】啟用函式
        layer1 = tf.nn.relu(tf.matmul(input_tensor,weights1)+biases1)
        #【3】計算【輸出層】的【前向傳播結果】。因為在計算【損失函式】時,會一併計算softmax()函式,所以,這裡不需要加入
        #啟用函式。而且,不加入softmax不會影響預測結果。因為,預測時,使用的不是同類別對應節點輸出值得相對大小,有沒有
        #softmax層對於最後分類結果的計算沒有影響。於是,在計算整個神經網路的前向傳播網路時可以不加入最後的softmax層
        return tf.matmul(layer1,weights2)+biases2
    else:
        #【1】首先使用avg_class.average函式來計算得出變數的【滑動平均值】,然後,再計算相應的神經網路前向傳播結果。
        layer1 = tf.nn.relu(tf.matmul(input_tensor,avg_class.average(weights1))+avg_class.average(biases1))
        return tf.matmul(layer1,avg_class.average(weights2)+avg_class.average(biases2))
#========================================================================================================
#函式說明:
#       訓練模型的過程
#========================================================================================================
def train(mnist) -> object:
    x = tf.placeholder(tf.float32,[None,INPUT_NODE],name='x-input')              #【1】輸入的資料
    y_= tf.placeholder(tf.float32,[None,OUTPUT_NODE],name='y-input')             #【2】真實的標籤值
    #【1】生成【隱藏層】的【引數】
    #【2】truncated_normal(),生成一個符合【正太分佈】的【隨機數】,但是,如果生成的隨機數偏離平均值超過2個標準差,那
    #麼這個隨機數將會被重新隨機
    weights1 = tf.Variable(tf.truncated_normal([INPUT_NODE,LAYER1_NODE],mean=0,stddev=0.1))
    biases1  = tf.Variable(tf.constant(0.1,shape=[LAYER1_NODE]))
    #【3】生成【輸出層】的【引數】
    weights2 = tf.Variable(tf.truncated_normal([LAYER1_NODE,OUTPUT_NODE],mean=0,stddev=0.1))
    biases2  = tf.Variable(tf.constant(0.1,shape=[OUTPUT_NODE]))
    #【4】計算在【當前引數】下,【神經網路前向傳播】的【結果】。這裡給出的用於計算【滑動平均的類】為None,所以,函式不會
    #不會使用【引數】的【滑動平均】
    y = inference(x,None,weights1,biases1,weights2,biases2)                       #【3】前向傳播的【預測結果】
    #【5】定義儲存訓練輪數的變數。這個變數不需要計算滑動平均值,所以,這裡指定這個變數為【不可訓練的變數】。在使用Tensorflow
    #訓練神經網路時,一般會將代表【輪數的變數】指定為【不可訓練的引數】
    global_step = tf.Variable(0,trainable=False)
    #【6】給定【滑動平均衰減率】和【訓練輪數的變數】,初始化【滑動平均類】。給定【訓練輪數的變數】可以加快【訓練早期變數】的
    #更新速度。
    variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY,global_step)
    #【7】在所有代表【神經網路引數的變數】上使用【滑動平均】。其他輔助變數,比如global_step就不需要了。tf.trainable_variables
    #返回的就是【圖上集合】GraphKeys.TRAINABLE_VARIABLES中的元素。這個集合中的元素就是所有沒有指定trainable=False的可訓練
    #引數
    variable_averages_op = variable_averages.apply(tf.trainable_variables())
    #【8】計算使用了【滑動平均之後】的【前向傳播結果】。經過【滑動平均】不會改變【變數】本身的取值,而是會維護一個【影子變數】
    #來記錄其【滑動平均值】。所以,當需要使用這個【滑動平均】時,需要明確的呼叫average函式。
    average_y = inference(x,variable_averages,weights1,biases1,weights2,biases2)
    #【9】計算【交叉熵】作為刻畫【預測值】和【真實值】之間差距的【損失函式】。這裡使用了Tensorflow中提供的sparse_softmax_cross
    #_entropy_with_logits函式來計算【交叉熵】。當【分類問題】只有一個正確答案時,可以使用這個函式來加速【交叉熵】的計算。MNIST
    #問題的圖片中只包含了0~9中的一個數字,所以,可以使用這個函式來計算【交叉熵損失】。
    #      【1】這個函式的第一個引數是神經網路不包括softmax層的【前向傳播結果】
    #      【2】第二個是訓練資料的正確答案。因為標準答案是一個長度為10的一維陣列,而該函式需要提供的是一個正確答案的數字,所以,
    #          使用tf.argmax函式來得到正確答案對應的編號。
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(tf.argmax(1, y_), y)
    #【10】計算當前batch中所有樣例的交叉熵平均值
    cross_entropy_mean = tf.reduce_mean(cross_entropy)
    #【11】計算【L2正則化】【損失函式】
    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
    #【12】計算【模型】的【正則化損失】,一般只計算【神經網路】邊上權重的【正則化損失】,而不使用【偏置項】。
    regularization=regularizer(weights1)+regularizer(weights2)
    #【13】【總損失】等於【交叉熵損失】和【正則化損失的和】
    loss = cross_entropy_mean + regularization
    #【14】設定【指數衰減的學習率】
    learning_rate = tf.train.exponential_decay(
        LEARNING_RATE_BASE,                    #[1]基礎的學習率,隨著迭代的進行,更新變數時使用的學習率在這個基礎上遞減
        global_step,                           #[2]當前迭代的輪數
        mnist.train.num_examples/BATCH_SIZE,   #[3]過完所有的訓練資料需要的【總迭代次數】
        LEARNING_RATE_DECAY)                   #[4]學習率衰減速度

    #【1】使用tf.train.GradientDescentOptimizer優化演算法來優化損失函式。注意,這裡的【損失函式】包含了【交叉熵損失】和【L2正則化損失】
    train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss,global_step=global_step)
    #【2】在訓練【神經網路模型】時,每過一遍資料即需要通過【反向傳播】來更新【神經網路】中的【引數】,又要更新每一個【引數】的【滑動平均值】
    #為了一次完成多個操作,Tensorflow提供了tf.control_dependencies和tf.group兩種機制。下面兩行程式和
    #train_op = tf.group(train_step,variables_averages_op)是等價的
    with tf.control_dependencies([train_step,variables_averages_opl]):
        train_op = tf.no_op(name = 'train')
    #【3】檢驗使用了【滑動平均模型】的【神經網路前向傳播結果】是否正確。
    #    [1]tf.argmax(average_y,1)計算每一個樣例的【預測答案】。其中average_y是一個batch_size*10的二維陣列,每一行表示一個【樣例】的
    #       【前向傳播結果】;第一個引數1表示選取最大值的操作在第一個維度中進行,也就是說,只在每一行選取最大值對應的下標。於是,得到的結果
    #        是一個長度為batch_size的一維陣列,這個一維陣列中的值就表示了每一個樣例對應的【數字識別結果】。
    #    [2]tf.equal判斷兩個張量的每一維是否相等,如果相等返回True,否則返回False
    correct_prediction = tf.equal(tf.argmax(average_y,1),tf.argmax(y_,1))
    #【4】這個運算,首先講一個布林型的數值轉換為實數型,然後,計算平均值。這個平均值就是模型在這一組資料上的正確率
    accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))

    #【1】初始會話並開始訓練過程
    with tf.Session() as sess:
        tf.global_variables_initializer().run()
        #【2】準備驗證條件。一般在神經網路的訓練過程中會通過驗證資料來大致判斷停止的條件和評判訓練的效果
        validate_feed = {x:mnist.validation.images,y_:mnist.validation.labels}
        #【3】準備測試資料。在真實的應用中,這部分資料在訓練時是不可見的,這個資料只是作為模型優劣的最後評價標準
        test_feed     = {x:mnist.test.images,y_:mnist.test.labels}
        #【4】迭代的訓練神經網路
        for i in range(TRAINING_STEPS):
            #【5】每1000輪輸出一次在【驗證資料集】上的測試結果
            if i%1000 == 0:
                #【6】計算滑動平均模型在驗證資料上的結果。因為MNIST資料集比較小,所以一次可以處理所有的驗證資料。為了計算方便,本樣例程式
                #沒有將【驗證資料劃分】為更小的batch。當神經網路模型比較複雜或者驗證資料集合比較大時,太大的batch會導致計算時間過長甚至發
                #生記憶體溢位的錯誤
                validate_acc = sess.run(accuracy,feed_dict=validate_feed)
                print("【INFO】After %d training steps,validate accuracy""using average model is %g"%(i,validate_acc))
            #【7】產生這一輪使用的一個batch的訓練資料,並執行訓練過程
            xs,ys = mnist.train.next_batch(BATCH_SIZE)
            sess.run(train_op,feed_dict={x:xs,y_:ys})
        #【8】在訓練資料結束之後,在測試資料上檢測神經網路模型的最終正確率
        test_acc = sess.run(accuracy,feed_dict=test_feed)
        print("【INFO】After %d training steps,test accuracy using average""model is %g"%(TRAINING_STEPS,test_acc))

#主程式的入口
def main(argv=None):
    #宣告處理MNIST資料集的類,這個類在初始化時會自動下載資料
    mnist = input_data.read_data_sets("F:/MnistSet/",one_hot = True)
    train(mnist)
#Tensorflow提供的一個主程式入口,tf.app.run會呼叫上面定義的main函式
if __name__ == '__main__':
    tf.app.run()

















相關文章