1. 程式人生 > >MNIST手寫數字識別(三)應用優化

MNIST手寫數字識別(三)應用優化

本篇的主要內容

  • 應用三種優化方式,對之前的模型進行優化
  • 介紹一些在程式中用到的函式

學習於《TensorFlow實戰Google深度學習框架》一書

程式

相比於第一次的簡單邏輯迴歸模型,這一次的調整了網路結構,添加了一個500個節點的隱藏層,在結構中,設定了動態學習率,添加了正則化項,並使用了滑動平均模型穩定整個模型。
整體的結構流程如下:

輸入資料輸入層隱藏層輸出層輸出資料

這次的程式碼比較長,所以劃分了幾個函式,這樣結構比較清楚一些。

程式碼:
第一部分:
必要的包以及要使用到的一些常量 直接定義在外邊

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

# MNIST 資料集相關的常數
# 輸入層的幾點疏對於MNIST來說就是是圖片展平之後的畫素數量
INPUT_NODE = 784
# 輸出層的節點數量 等分類類別的數量 需要進行區分的是10個數字 所以輸出節點數目是10
OUTPUT_NODE = 10

# 配置神經網路的引數
# 隱藏層的結點數 建立的模型只有一個隱藏層結構
LAYER1_NODE = 500  # 這個隱藏層有500個節點
# 一個訓練的batch中訓練資料個數 數字越小 訓練過程越接近隨機梯度下降(SGD)
# 數字越大,訓練越接近梯度下降
BATCH_SIZE = 100

# 基礎的學習率
LEARNING_RATE_BASE = 0.8
# 學習率的衰減率
LEARNING_RATE_DECAY = 0.99
# 正則化項在損失函式中的係數
REGULARIZATION_RATE = 0.0001
# 訓練的輪數
TRAINING_STEPS = 30000
# 滑動平均衰減率
MOVING_AVERAGE_DECAY = 0.99

第一個函式,定義了網路的結構,傳入所需的權重以及偏置,計算前向傳播的結果,在前面的過程中,這就是主體部分了,這個函式輸出的結果就是對於每張圖,輸出的未進行softmax迴歸的1*10的向量,這裡有兩個返回,後一個是有滑動平均輸入時候的結果。

# 使用的啟用函式是 ReLU 啟用函式

def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):
    # 如果沒有提供滑動平均類 直接使用當前的引數取值
    if avg_class is None:
        # 計算隱藏層的前向傳播結果 使用ReLU哈數進行啟用
        layer1 = tf.nn.relu(tf.matmul(input_tensor, weights1) + biases1)
        return tf.matmul(layer1, weights2) + biases2
    else:
        # 首先使用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):
    x = tf.placeholder(tf.float32, [None, INPUT_NODE], name='x-input')
    y_ = tf.placeholder(tf.float32, [None, OUTPUT_NODE], name='y-input')

    # 生成 隱藏層的引數
    weights1 = tf.Variable(
        tf.truncated_normal([INPUT_NODE, LAYER1_NODE], stddev=0.1)
    )
    biases1 = tf.Variable(tf.constant(0.1, shape=[LAYER1_NODE]))
    # 生成 輸出層引數
    weights2 = tf.Variable(
        tf.truncated_normal([LAYER1_NODE, OUTPUT_NODE], stddev=0.1)
    )
    biases2 = tf.Variable(tf.constant(0.1, shape=[OUTPUT_NODE]))


    # 計算當前引數下神經網路前向傳播的結果  指定計算滑動平均的類為 None
    # 也就是函式不會使用引數的滑動平均值
    y = inference(x, None, weights1, biases1, weights2, biases2)
    # 定義儲存訓練額輪數的變數 此處的這個變數不需要計算滑動平均值
    # 所以設定為  不可訓練  (trainable = False)
    global_step = tf.Variable(0, trainable=False)

    # 初始化 滑動平均類
    variable_averages = tf.train.ExponentialMovingAverage(
        MOVING_AVERAGE_DECAY, global_step
    )
    # 計算所有可訓練的變數的滑動平均
    # tf.trainable_variables() 是所有的 可以訓練的變數(引數)
    variable_averages_op = variable_averages.apply(tf.trainable_variables())

    # 計算使用了滑動平均之後的前向傳播結果 指定了 variable_average
    average_y = inference(
        x, variable_averages, weights1, biases1, weights2, biases2
    )

    # 使用的損失函式是交叉熵 這裡的交叉熵與之前的呼叫不太一樣
    # 簡單來說 就是將softmax迴歸 與 交叉熵 合併在一起  目的是為了加速計算 後面會具體介紹
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
        logits=y, labels=tf.argmax(y_, 1)
    )
    # 計算所有batch中所有樣例的交叉熵平均值
    cross_entropy_mean = tf.reduce_mean(cross_entropy)

    # 為損失函式新增 l2 正則化項
    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
    # 注意一般只會計算權重的正則化損失 不加入偏置
    regularization = regularizer(weights1) + regularizer(weights2)
    # 完整的損失函式
    loss = cross_entropy_mean + regularization

    # 指定指數衰減的學習率
    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)

    # 在訓練模型的過程中 需要反向傳播來更新引數 也需要更新引數的滑動平均值
    # 使用這種方式可以同時執行這兩個操作
    train_op = tf.group(train_step, variable_averages_op)

    # 檢驗使用了滑動平均模型的神經網路前向傳播結果是否正確 #
    # average_y是模型的輸出結果  y_ 是輸入正確的標籤集合
    corrext_prediction = tf.equal(tf.argmax(average_y, 1), tf.argmax(y_, 1))

    # 這個與之前的程式一樣 都是計算一組資料上的準確率
    accuracy = tf.reduce_mean(tf.cast(corrext_prediction, tf.float32))

    with tf.Session() as sess:
        init_op = tf.global_variables_initializer()
        sess.run(init_op)

        # 準備訓練資料集 和 測試資料集
        validate_feed = {x: mnist.validation.images,
                         y_: mnist.validation.labels}

        test_feed = {x: mnist.test.images,
                     y_: mnist.test.labels}

        for i in range(TRAINING_STEPS):
            if i % 1000 == 0:        # 1000輪輸出一次結果
                validate_acc = sess.run(accuracy, feed_dict=validate_feed)
                print("After %d training steps, validation accuracy using average model is %g " %(i, validate_acc))

            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            sess.run(train_op, feed_dict={x: xs, y_: ys})

        test_acc = sess.run(accuracy, feed_dict=test_feed)
        print("After %d training step, test accuracy using average model is %g" % (TRAINING_STEPS, test_acc))

接下來就是簡單呼叫上面的函式實現執行就可以了:

# 主程式入口
def main(argv=None):
    mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
    train(mnist)

# TensorFlow 定義的主程式入口 tf.app.run() 方法會呼叫main函式
if __name__ == '__main__':
    tf.app.run()

結果這樣的優化,準確率相比之前提高到了98%左右,輸出結果(部分),可以看到,模型收斂的速度比較快,在2000次左右的時候就已經趨於穩定了。

After 0 training steps, validation accuracy using average model is 0.0674 
After 1000 training steps, validation accuracy using average model is 0.9738 
After 2000 training steps, validation accuracy using average model is 0.98 
After 3000 training steps, validation accuracy using average model is 0.9818 
After 4000 training steps, validation accuracy using average model is 0.9858 
After 5000 training steps, validation accuracy using average model is 0.9844 
After 6000 training steps, validation accuracy using average model is 0.9842 
After 7000 training steps, validation accuracy using average model is 0.9852 
After 8000 training steps, validation accuracy using average model is 0.9852 
After 9000 training steps, validation accuracy using average model is 0.9854 
After 10000 training steps, validation accuracy using average model is 0.9848 

如果我們改動上面的程式碼,上面的程式碼應用了三種優化方式,其實對於準確率的提高,最重要的是正則化處理,如果去掉滑動平均模型部分,可以看到準確率的變化不大,也就是說明我們模型的引數變化不大,如果去掉動態學習率部分,可以明顯看出來在準確率上,達到同樣的準確率,確實花費的訓練時間要多,:

After 0 training steps, validation accuracy using average model is 0.106 
After 1000 training steps, validation accuracy using average model is 0.9666 
After 2000 training steps, validation accuracy using average model is 0.9728 
After 3000 training steps, validation accuracy using average model is 0.977 
After 4000 training steps, validation accuracy using average model is 0.9794 
After 5000 training steps, validation accuracy using average model is 0.9806 
After 6000 training steps, validation accuracy using average model is 0.981 
After 7000 training steps, validation accuracy using average model is 0.9806 
After 8000 training steps, validation accuracy using average model is 0.9808 
After 9000 training steps, validation accuracy using average model is 0.9816 
After 10000 training steps, validation accuracy using average model is 0.9816 

在去掉正則化處理之後,可以看到正確率是在不太到98%的地方浮動的,相比上面是有變化的,相比之前的模型,準確率上升當然還是得益於模型結構的調整:

After 0 training steps, validation accuracy using average model is 0.0968 
After 1000 training steps, validation accuracy using average model is 0.9636 
After 2000 training steps, validation accuracy using average model is 0.9706 
After 3000 training steps, validation accuracy using average model is 0.9758 
After 4000 training steps, validation accuracy using average model is 0.9774 
After 5000 training steps, validation accuracy using average model is 0.979 
After 6000 training steps, validation accuracy using average model is 0.98 
After 7000 training steps, validation accuracy using average model is 0.9788 
After 8000 training steps, validation accuracy using average model is 0.98 
After 9000 training steps, validation accuracy using average model is 0.9794 
After 10000 training steps, validation accuracy using average model is 0.9806 

正則化對整體模型的準確度確實起到了很大的作用。

新的交叉熵函式

第一個就是交叉熵函式的變化,之前的模型中,我們首先拿到模型的輸出結果,進行softmax迴歸處理,然後使用計算與正確的函式之間的交叉熵,最後優化這個函式,得到最後的模型,在這裡,使用了一個新的函式,其實本質上,這個函式就是將 softmax迴歸和交叉熵結合在一起了,加速整個訓練過程,具體的結構如下:

def sparse_softmax_cross_entropy_with_logits(_sentinel: Any = None,
                                             labels: Any = None,
                                             logits: Any = None,
                                             name: Any = None) -> Union[Tensor, None, IndexedSlices, SparseTensor]

我們在使用的時候只需注意 labels 和 logits即可,labels 傳入的是正確的類別,在上面的程式中,也就是已知的y部分,但是要注意的與之前不同的是,這裡的輸入要求是非稀疏的(就拿這個例子來說,對於一張圖片的label,在資料中給的是 [1, 0, 0, 0, …]這樣的形式,這就是稀疏的,如果明確指出這個數字是 1,2…,就是非稀疏的),也就是輸入的shape會是 batchsize×1batch size \times 1的形式,所以在上面的程式中,我們使用了 argmax()函式將原本的label轉化為列為1的矩陣輸入, logits就是我們模型中輸出的結果,這與之前是一樣的 shape是 batchsize×10batch size \times 10。此外,這裡會一塊進行softmax迴歸,所以我們在函式 inference()部分沒有計算softmax。
在TensorFlow中,有一個與這個函式很相似的函式:tf.nn.softmax_cross_entropy_with_entropy,這個函式唯一的區別就是label部分是稀疏的,一般影象處理的結果都是非稀疏的,所以這個函式沒有之前那個一個應用多。

同時執行多個操作

在TensorFlow中提供了同時執行多個操作的函式,有兩種方式:

  • tf.control_dependencies([a, b, c…], train_op = tf.no_op(name=‘XXXX’))
  • train_op = tf.group(a, b, c…)

在上面,採用的是第二種方式。

通過調整結構和新增優化,模型的準確率達到了98.5%左右,接下來繼續學習CNN,之後用新的網路結構急需解決這個問題。

以上~