tensorflow學習筆記——使用TensorFlow操做MNIST數據(2)

               tensorflow學習筆記——使用TensorFlow操做MNIST數據(1)html

一:神經網絡知識點整理

1.1,多層:使用多層權重,例如多層全鏈接方式

  如下定義了三個隱藏層的全鏈接方式的神經網絡樣例代碼:java

import tensorflow as tf

l1 = tf.matmul(x, w1)
l2 = tf.matmul(l1, w2)
y = tf.matmul(l2,w3)

  

1.2,激活層:引入激活函數,讓每一層去線性化

  激活函數有多種,例如經常使用的 tf.nn.relu  tf.nn.tanh  tf.nn.sigoid  tf.nn.elu,下面顯示了幾種經常使用的非線性激活函數的函數圖像:python

  樣例代碼:git

import tensorflow as tf

a = tf.nn.relu(tf.matmul(x, w1) + biase1)
y = tf.nn.relu(tf.matmul(a, w2) + biase2)

  

1.3,損失函數

  經典損失函數,交叉熵(cross entropy)用於計算預測結果矩陣 Y 和實際結果矩陣 Y_ 之間的距離樣例代碼:正則表達式

import tensorflow as tf

cross_entropy = - tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0)))

  

import tensorflow as tf

v = tf.constant([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
tf.reduce_mean(tf.clip_by_value(v, 0.0, 10.0))

  對於分類問題,一般把交叉熵與softmax迴歸一塊兒使用算法

import tensorflow as tf

cross_entropy = tf.nn.softmax_cross_entropy_with_logits(y, y_)

  對於迴歸問題,一般使用mse(均方偏差函數)計算損失函數編程

import tensorflow as tf

mse_loss = tf.reduce_mean(tf.square(y_ - y))

# 與如下函數計算結果徹底一致
dataset_size = 1000
mse_loss = tf.reduce_sum(tf.pow(y_ - y, 2)) / dataset_size

  自定義條件化的損失函數數組

import tensorflow as tf

loss_less = 10
loss_more = 1
loss = tf.reduce_sum(tf.where(tf.greater(y, y_), (y - y_) * loss_more, (y_ - y) * loss_less))
train_step = tf.train.AdamOptimizer(0.001).minimize(loss)

  

1.4,神經網絡優化算法,訓練優化器

  通常優化器的目標是優化權重W和誤差biases,最小化損失函數的結果,如下優化器會不斷優化W和 biases。網絡

  Adam優化算法是一個全局最優勢的優化算法,引入了二次方梯度校訂,相比於基礎SGD算法,不容易陷入局部優勢,並且速度更快。數據結構

  本質上是帶有動量項的RMSprop,它利用梯度的一階矩估計和二階矩估計動態調整每一個參數的學習率。Adam的優勢主要在於通過偏置校訂後,每一次迭代學習率都有個肯定範圍,使得參數比較平穩。

 

import tensorflow as tf

LEARNING_RATE = 0.001
mse_loss = tf.reduce_mean(tf.square(y_ - y))
train_op = tf.train.AdamOptimizer(LEARNING_RATE).minimize(mse_loss)

  

1.5,優化學習率LEARNING_RATE

  在訓練神經網絡時,須要設置學習率(learning rate)控制參數更新的速度,學習率決定了參數每次更新的幅度,學習率設置過大可能致使沒法收斂,在極優值的兩側來回移動。學習率設置太小雖然能保證收斂性,但可能致使收斂過慢。

  爲了解決學習率的問題,Tensorflow 提供了一種更加靈活的學習率設置方法——指數衰減法。tf.train.exponential_decay 函數實現了指數衰減學習率。經過這個函數,能夠先使用較大的學習率來快速獲得一個比較優的解,而後隨着迭代的繼續逐步減小學習率,使得模型在訓練後期更加穩定。exponential_decay 函數會指數級地減小學習率,它實現了一下代碼的功能:

decayed_learning_rate = learning_rate * decay_rate ^(global_step / decay_steps)

  其中 decayed_learning_rate 爲每一輪優化時使用的學習率,learning_rate 爲事先設定的初始學習率,decay_rate 爲衰減係數,decay_steps 爲衰減速度。tf.train.exponential_decay 函數能夠經過設置參數 staircase 選擇不一樣的衰減方式。

import tensorflow as tf

global_step = tf.Variable(0)
learning_rate = tf.train.exponential_decay(
    learning_rate=0.1, 
    global_step=global_step, 
    decay_steps=100, 
    decay_rate=0.96, 
    staircase=True, 
    name=None
)
train_op = tf.train.AdamOptimizer(learning_rate).minimize(loss, global_step=global_step)

  

1.6,過擬合問題(正則化)

  避免訓練出來的模型過度複雜,即模型記住了全部數據(包括噪聲引發的偏差),所以須要引入正則化函數疊加的方式,避免模型出現過擬合

import tensorflow as tf

v_lambda = 0.001
w = tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1))
y = tf.matmul(x, w)
mse_loss = tf.reduce_mean(tf.square(y_ - y) + tf.contrib.layers.l2_regularizer(v_lambda)(w))

  

1.7,滑動平均模型

  用於控制模型的變化速度,能夠控制權重W以及誤差 biases 例如 :avg_class.average(w)  avg_avergae(biases)

import tensorflow as tf

v1 = tf.Variable(0, dtype=tf.float32)
step = tf.Variable(0, trainable=False)
ema = tf.train.ExponentialMovingAverage(decay=0.99, num_updates=step)
# 每一次操做的時候,列表變量[v1]都會被更新
maintain_averages_op = ema.apply([v1]) 

with tf.Session() as sess:
    
    # 初始化
    init_op = tf.global_variables_initializer()
    sess.run(init_op)
    print(sess.run([v1, ema.average(v1)]))
    
    # 更新step和v1的取值
    sess.run(tf.assign(step, 10000))  
    sess.run(tf.assign(v1, 10))
    sess.run(maintain_averages_op)
    print(sess.run([v1, ema.average(v1)]))

  

  

二:gfile文件操做詳解

2.1,gfile模塊是什麼?

  gfile模塊定義在tensorflow/python/platform/gfile.py,但其源代碼實現主要位於tensorflow/tensorflow/python/lib/io/file_io.py,那麼gfile模塊主要功能是什麼呢?

  google上的定義爲:

 

翻譯過來爲:

    沒有線程鎖的文件I / O操做包裝器

tf.gfile模塊的主要角色是:

  • 1.提供一個接近Python文件對象的API
  • 2.提供基於TensorFlow C ++ FileSystem API的實現。 

  C ++ FileSystem API支持多種文件系統實現,包括本地文件,谷歌雲存儲(以gs://開頭)和HDFS(以hdfs:/開頭)。 TensorFlow將它們導出爲tf.gfile,以便咱們可使用這些實現來保存和加載檢查點,編寫TensorBoard log以及訪問訓練數據(以及其餘用途)。可是,若是全部文件都是本地文件,則可使用常規的Python文件API而不會形成任何問題。

  以上爲google對tf.gfile的說明。

2.二、gfile API介紹

  下面將分別介紹每個gfile API!

2-1)tf.gfile.Copy(oldpath, newpath, overwrite=False)

  拷貝源文件並建立目標文件,無返回,其形參說明以下:

  • oldpath:帶路徑名字的拷貝源文件;
  • newpath:帶路徑名字的拷貝目標文件;
  • overwrite:目標文件已經存在時是否要覆蓋,默認爲false,若是目標文件已經存在則會報錯

2-2)tf.gfile.MkDir(dirname)

  建立一個目錄,dirname爲目錄名字,無返回。

2-3)tf.gfile.Remove(filename)

  刪除文件,filename即文件名,無返回。

2-4)tf.gfile.DeleteRecursively(dirname)

  遞歸刪除全部目錄及其文件,dirname即目錄名,無返回。

2-5)tf.gfile.Exists(filename)

  判斷目錄或文件是否存在,filename可爲目錄路徑或帶文件名的路徑,有該目錄則返回True,不然False。

2-6)tf.gfile.Glob(filename)

  查找匹配pattern的文件並以列表的形式返回,filename能夠是一個具體的文件名,也能夠是包含通配符的正則表達式。

2-7)tf.gfile.IsDirectory(dirname)

  判斷所給目錄是否存在,若是存在則返回True,不然返回False,dirname是目錄名。

2-8)tf.gfile.ListDirectory(dirname)

  羅列dirname目錄下的全部文件並以列表形式返回,dirname必須是目錄名。

2-9)tf.gfile.MakeDirs(dirname)

  以遞歸方式創建父目錄及其子目錄,若是目錄已存在且是可覆蓋則會建立成功,不然報錯,無返回。

2-10)tf.gfile.Rename(oldname, newname, overwrite=False)

  重命名或移動一個文件或目錄,無返回,其形參說明以下:

  • oldname:舊目錄或舊文件;
  • newname:新目錄或新文件;
  • overwrite:默認爲false,若是新目錄或新文件已經存在則會報錯,不然重命名或移動成功。

2-11)tf.gfile.Stat(filename)

  返回目錄的統計數據,該函數會返回FileStatistics數據結構,以dir(tf.gfile.Stat(filename))獲取返回數據的屬性以下:

2-12)tf.gfile.Walk(top, in_order=True)

  遞歸獲取目錄信息生成器,top是目錄名,in_order默認爲True指示順序遍歷目錄,不然將無序遍歷,每次生成返回以下格式信息(dirname, [subdirname, subdirname, ...], [filename, filename, ...])。

2-13)tf.gfile.GFile(filename, mode)

  獲取文本操做句柄,相似於python提供的文本操做open()函數,filename是要打開的文件名,mode是以何種方式去讀寫,將會返回一個文本操做句柄。

tf.gfile.Open()是該接口的同名,可任意使用其中一個!

2-14)tf.gfile.FastGFile(filename, mode)

  該函數與tf.gfile.GFile的差異僅僅在於「無阻塞」,即該函數會無阻賽以較快的方式獲取文本操做句柄

 

三,將MNIST數據集轉換爲TFRecord文件

  TFRecord 文件中的數據都是經過 tf.train.Example Protocol Buffer 的格式存儲的。如下爲 tf.train.Example 的數據結構:

message Example {
	Features features = 1;
};
message Features{
	map<string, Feature> feature = 1;
};
message Feature {
	oneof kind {
		BytesList bytes_list = 1;
		FloatList float_list = 1;
		Int64List int64_list = 3;
	}
};

  tf.train.Example 中包含了一個從屬性名稱到取值的字典。其中屬性名稱爲一個字符串,屬性的取值能夠爲字符串(BytesList),實數列表(FloatList)或者整數列表(Int64List)。

3.1,將MNIST數據集中的全部文件存儲到一個TFRecord文件

# _*_coding:utf-8_*_
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import os
import numpy as np

# 生成整數的屬性
def _int64_feature(value):
    return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))


# 生成字符串型的屬性
def _bytes_feature(value):
    return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))


def create_records():
    '''
    實現將 MNIST 數據集 轉化爲records
    注意:讀取的圖像數據默認是uint8,而後轉化爲tf 的字符串型BytesList 保存,
    :return:
    '''
    # 導入MNIST數據集
    mnist = input_data.read_data_sets('data/', dtype=tf.uint8, one_hot=True)
    images = mnist.train.images
    labels = mnist.train.labels
    # 訓練圖像的分辨率,做爲example的屬性
    pixels = images.shape[1]
    num_examples = mnist.train.num_examples


    # 存儲TFRecord文件的地址
    filename = 'record/output_mnist.tfrecords'

    # 建立一個writer來寫TFRecord 文件
    writer = tf.python_io.TFRecordWriter(filename)

    # 將每張圖片都轉化爲一個Example
    for i in range(num_examples):
        # 將圖像轉爲字符串
        image_raw = images[i].tostring()

        # 將一個樣例轉化爲Example  Protocol Buffer 並將全部信息寫入這個數據結構
        example = tf.train.Example(features=tf.train.Features(
            feature={
                'pixels': _int64_feature(pixels),
                'label': _int64_feature(np.argmax(labels[i])),
                'image_raw': _bytes_feature(image_raw)
            }
        ))

        # 將Example寫入TFRecord 文件
        writer.write(example.SerializeToString())

    print("data processing success")
    writer.close()

if __name__ == '__main__':
    dir_name = 'record'
    if not os.path.exists(dir_name):
        os.mkdir(dir_name)
    create_records()

  上面程式能夠將MNIST數據集中全部的訓練數據存儲到一個TFRecord 文件中。當數據量較大時,也能夠將數據寫入多個 TFRecord 文件。TensorFlow對從文件列表中讀取數據提供了很好的支持。

3.2,讀取封裝好的MNIST數據的TFRecord文件

#_*_coding:utf-8_*_
import tensorflow as tf

def read_tfrecord():
    """
    讀取tfrecord文件
    :return:
    """
    filename = 'record/output.tfrecords'
    # 建立一個隊列來維護輸入文件列表
    filename_queue = tf.train.string_input_producer([filename])

    # 建立一個reader來讀取TFRecord文件中Example
    reader = tf.TFRecordReader()

    # 從文件中讀取一個Example
    _, serialized_example = reader.read(filename_queue)

    # 用FixedLenFeature 將讀入的Example解析成 tensor
    features = tf.parse_single_example(
        serialized_example,
        features={
            'image_raw': tf.FixedLenFeature([], tf.string),
            'pixels': tf.FixedLenFeature([], tf.int64),
            'label': tf.FixedLenFeature([], tf.int64)
        }
    )

    # tf.decode_raw 將字符串解析成圖像對應的像素數組
    images = tf.decode_raw(features['image_raw'], tf.uint8)
    labels = tf.cast(features['label'], tf.int32)
    pixels = tf.cast(features['pixels'], tf.int32)

    init_op = tf.global_variables_initializer()

    with tf.Session() as sess :
        sess.run(init_op)
        # 啓動多線程處理輸入數據
        coord = tf.train.Coordinator()
        threads = tf.train.start_queue_runners(sess=sess, coord=coord)

        # 每次運行讀出一個Example,當全部樣例讀取完以後,在此樣例中程序會重頭讀取
        for i in range(10):
            # 在會話中取出image 和 label
            image, label = sess.run([images, labels])
            print(label)
        coord.request_stop()
        coord.join(threads)

if __name__ == '__main__':
    read_tfrecord()

  

四,TensorFlow訓練神經網絡

  在神經網絡的結構上,深度學習一方面須要使用激活函數實現神經網絡模型的去線性化,另外一方面須要一個或多個隱藏層使得神經網絡的結構更深,以解決複雜問題,在訓練神經網絡時,咱們學習了使用帶指數衰減的學習率設置,使用正則化來避免過分擬合,以及使用滑動平均模型來使得最終模型更加健壯。如下代碼給出了一個在MNIST數據集上實現這些功能的完整的TensorFlow程序。

#_*_coding:utf-8_*_
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

# MNIST 數據集相關的常數
# 輸入層的節點數,對於MNIST數據集,這個就等於圖片的像素
INPUT_NODE = 784
# 輸出層的節點數,這個等於類別的數目,由於在MNIST數據集中須要區分的
# 是0-9這個10個數字,因此這裏輸出層的節點數爲10
OUTPUT_NODE = 10

# 配置神經網絡的參數
# 隱藏層節點數,這裏使用只有一個隱藏層的網絡結構做爲樣例
# 這個隱藏層有500個節點
LAYER1_NODE = 500
# 一個訓練batch中的訓練數據個數,數字越小時,訓練過程越接近隨機梯度降低
# 數字越大時,訓練越接近梯度降低
BATCH_SIZE = 100

# 基礎的學習率
LEARNING_RATE_BASE = 0.8
# 學習率的衰減率
LEARNING_RATE_DECAT = 0.99
# 描述模型複雜度的正則化項在損失函數中的係數
REGULARIZATION_RATE = 0.0001
# 訓練輪數
TRAINING_STEPS = 30000
# 滑動平均衰減率
MOVING_AVERAGE_DECAY = 0.99

# 一個輔助函數,給定神經網絡的輸入和全部參數,計算神經網絡的前向傳播結果
# 在這裏定義了一個使用ReLU激活函數的三層全鏈接神經網絡
# 經過加入隱藏層實現了多層網絡結構,經過ReLU激活函數實現了去線性化
# 在這個函數中也支持傳入用於計算參數平均值的類,這樣方便在測試時使用滑動平均模型
def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):
    # 當沒有提供滑動平均類時,直接使用參數當前的取值
    if avg_class == None:
        # 計算隱藏層的前向傳播結果,這裏使用了ReLU激活函數
        layer1 = tf.nn.relu(tf.matmul(input_tensor, weights1) + biases1)

        # 計算輸出層的前向傳播結果,由於在計算損失函數時會一併計算softmax函數
        # 因此這裏不須要加入激活函數,並且不加入softmax不會影響預測結果
        # 由於預測時使用的是不一樣類型對應節點輸出值的相對大小,
        # 有沒有softmax層對最後分類的結果沒有影響
        # 因而在計算整個神經網絡的前向傳播時能夠不加入最後的softmax層
        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)
    # 在使用TensorFlow訓練神經網絡時,通常會將表明訓練輪數的變量指定爲不可訓練的參數
    global_step = tf.Variable(0, trainable=False)

    # 給定滑動平均衰減率和訓練輪數的變量,初始化滑動平均類。
    # 當給定訓練輪數的變量能夠加快訓練早期變量的更新速度
    varibale_averages = tf.train.ExponentialMovingAverage(
        MOVING_AVERAGE_DECAY, global_step
    )

    # 在全部表明神經網絡參數的變量上使用滑動平均。
    # 其餘輔助變量(好比global_step)就不須要了
    # tf.trainable_variables 返回的就是圖上集合GraphKeys.TRAINABLE_VARIABLES中的元素
    # 這個集合的元素就是全部沒有指定trainable=False 的參數
    varibale_averages_op = varibale_averages.apply(tf.trainable_variables())

    # 計算使用了滑動平均以後的前向傳播結果。由於滑動平均不會改變變量自己的額取值
    # 而是會委會一個影子變量來記錄其滑動平均值,因此當須要使用這個滑動平均值時,須要明確調用average函數
    average_y = inference(x, varibale_averages, weights1, biases1, weights2, biases2)

    # 計算交叉熵做爲刻畫預測值和真實值之間差距的損失函數,這裏使用了TensorFlow中提供的
    # sparse_softmax_cross_entropy_with_logits函數來計算交叉熵
    # 當分類問題中只有一個正確答案時,可使用這個函數來加速交叉熵的計算
    # MNIST問題的圖片中只包含了一個0-9中的一個數字,因此可使用這個函數來計算交叉熵損失
    # 這個函數的第一個參數是神經網絡不包含softmax層的前向傳播結果,第二個是訓練數據的正確答案
    #由於標準答案是一個長度爲10的一位數組,而該函數須要提供了一個正確的答案數字
    #因此須要使用 tf.argmax函數來獲得正確答案對應的類別編號
    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_DECAT,   # 學習率衰減速度
        staircase=True
    )
    # 使用tf.train.GradientDescentOptimizer優化算法來優化損失函數
    # 注意這裏損失函數包含了交叉熵損失和L2正則化損失
    train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)

    # 在訓練神經網絡模型時,沒過一遍數據即須要經過反向傳播來更新神經網絡中的參數
    # 又要更新每個參數的滑動平均值,爲了一次完成多個操做,西面兩行程序和下面代碼是等價的
    # train_op = tf.group(train_step, varibale_averages_op)
    with tf.control_dependencies([train_step, varibale_averages_op]):
        train_op = tf.no_op(name='train')

    # 檢驗使用了滑動平均模型的神經網絡前向傳播結果是否正確。tf.argmax(average_y, 1)
    # 計算每個樣例的預測答案,其中average_y 是一個 batch_size*10的二維數組,
    # 每一行表示一個樣例的前向傳播結果。tf.argmax的第二個參數「1」表示選取最大值的的操做
    # 僅在第一個維度中進行,也便是說,只有每一行選取最大值對應的下標
    # 因而獲得的結果是一個長度爲batch的一維數組,這個一維數組中的值就表示了
    # 每個樣例對應的數字識別結果
    # tf.rqual 判斷兩個張量的每一維是否相等,若是相等返回True,不然返回False。
    correct_prediction = tf.equal(tf.argmax(average_y, 1), tf.argmax(y_, 1))
    # 這個運算首選將一個布爾型的數值轉換爲實數型,而後計算平均值,這個平均值
    # 就是模型在這一組數據上的正確率
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

    # 初始化會話並開始訓練過程
    with tf.Session() as sess:
        tf.global_variables_initializer().run()
        # 準備驗證數據,通常在神經網絡的訓練過程當中會經過驗證數據來判斷
        # 大體判斷中止的條件和評判訓練的效果
        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):
            # 每1000輪輸出一次在驗證數據集上的測試結果
            if i % 1000 == 0:
                # 計算滑動平均模型在驗證數據上的結果,由於MNIST數據集比較小,
                # 因此一次能夠處理全部的驗證數據
                # 當神經網絡模型比較複雜或者驗證數據比較大,太大的batch會致使計算時間過長
                # 甚至發生內存溢出的錯誤
                validate_acc = sess.run(accuracy, feed_dict=validate_feed)
                print('After %d training step(s), validation accuracy using average model is %g'%(i, validate_acc))
            # 產生這一輪使用的一個batch的訓練數據,並運行訓練過程
            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(s), test accuracy using average model is %g'%(TRAINING_STEPS, test_acc))

# 主程序入口
def main(argv=None):
    # 聲明處理MNIST數據集的類,這個類在初始化的時候會自動下載數據
    mnist = input_data.read_data_sets('data', one_hot=True)
    train(mnist)

# Tensorflow 提供的一個主程序入口,tf.app.run會調用上面定義的main函數
if __name__ == '__main__':
    # tf.app.run(main=None)
    main()

  運行上面的程序,獲得的結果以下:

Extracting data\train-images-idx3-ubyte.gz
Extracting data\train-labels-idx1-ubyte.gz
Extracting data\t10k-images-idx3-ubyte.gz
Extracting data\t10k-labels-idx1-ubyte.gz
After 0 training step(s), validation accuracy using average model is 0.143
After 1000 training step(s), validation accuracy using average model is 0.9776
After 2000 training step(s), validation accuracy using average model is 0.9816
After 3000 training step(s), validation accuracy using average model is 0.9832
After 4000 training step(s), validation accuracy using average model is 0.9838
After 5000 training step(s), validation accuracy using average model is 0.9836
... ...
After 27000 training step(s), validation accuracy using average model is 0.9846
After 28000 training step(s), validation accuracy using average model is 0.9844
After 29000 training step(s), validation accuracy using average model is 0.9844
After 30000 training step(s), test accuracy using average model is 0.9842

  從上面的結果能夠看出,在訓練初期,隨着訓練的進行,模型在驗證數據集上的表現愈來愈好。從第4000輪開始,模型在驗證數據集上的表現就開始波動,這說明模型已經接近極小值了。因此迭代也就結束了。

代碼中一些錯誤的改正

  這些代碼均來自與《TensorFlow實戰:Google深度學習框架》這本書,緣由是TensorFlow的版本相對書上的版本比較高,因此一些API接口都變了。因此有些函數在書中的程序是錯誤的,致使程序在運行的時候就會報錯。

錯誤1以下:

ValueError: Only call `sparse_softmax_cross_entropy_with_logits` with named 
arguments (labels=..., logits=..., ...)

  解決:這個緣由是函數的API發生了變化,咱們須要添加labels 和 logits。

  修改代碼以下:

原代碼:
# 計算交叉熵及其平均值
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(y,tf.argmax(y_, 1))

修改後的代碼:
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y,logits=tf.argmax(y_, 1))


***********出現新的錯誤*************
ValueError: Rank mismatch: Rank of labels (received 2) should equal rank of
 logits minus 1 (received 1).

咱們將代碼修改以下:
labels=tf.argmax(y_, 1),logits=y

  後面的緣由是由於計算交叉熵的時候,比較的兩個機率分佈放反了。由於交叉熵是衡量一個機率分佈區表達另一個機率分佈的難度,值越低越好,因此是用預測的結果來表達正確的標籤。

   當運行完例子,會報以下錯誤:

Traceback (most recent call last):
  File "E:\PyCharm 2019.1.3\helpers\pydev\pydevconsole.py", line 221, in do_exit
    import java.lang.System
  File "E:\PyCharm 2019.1.3\helpers\pydev\_pydev_bundle\pydev_import_hook.py", line 21, in do_import
    module = self._system_import(name, *args, **kwargs)
ModuleNotFoundError: No module named 'java'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "E:\PyCharm 2019.1.3\helpers\pydev\_pydev_bundle\pydev_umd.py", line 197, in runfile
    pydev_imports.execfile(filename, global_vars, local_vars)  # execute the script
  File "E:\PyCharm 2019.1.3\helpers\pydev\_pydev_imps\_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "E:/backup/pycode.py", line 180, in <module>
    tf.app.run(main=None)
  File "E:\Anaconda3\envs\python36\lib\site-packages\tensorflow\python\platform\app.py", line 48, in run
    _sys.exit(main(_sys.argv[:1] + flags_passthrough))
  File "E:\PyCharm 2019.1.3\helpers\pydev\pydevconsole.py", line 226, in do_exit
    os._exit(args[0])
TypeError: an integer is required (got type NoneType)

  小小分析一波,咱們發現已經執行完代碼了。執行完後會報錯誤提示:

TypeError: an integer is required (got type NoneType)

  經查看,是TensorFlow版本問題,在r0.11以前的版本里,tf.app.run的代碼以下:

def run(main=None):
   f = flags.FLAGS
   flags_passthrough = f._parse_flags()
   main = main or sys.modules['__main__'].main
   sys.exit(main(sys.argv[:1] + flags_passthrough))

  沒有argv 參數,argv參數是在 r0.12後加入的。因此須要升級版本。其實不升級版本也能夠,咱們這樣執行便可。

if __name__ == '__main__':
    # 咱們這裏不使用TensorFlow提供的主程序入口 tf.app.run函數
    # tf.app.run(main=None)
    main()

  

五,使用驗證數據集判斷模型效果

  在上面程序中咱們使用了神經網絡解決MNIST問題,在程序的開始設置了初始學習率,學習率衰減率,隱藏層節點數量,迭代輪數等七種不一樣的參數。那麼如何設置這些參數的取值呢?在大部分狀況下,配置神經網絡的這些參數都是須要經過實驗來調整的。雖然一個神經網絡模型的效果最終是經過測試數據來判斷的,可是咱們不能直接經過模型在測試數據上的效果來選擇參數,使用測試數據來選取參數可能會致使神經網絡模型過分擬合測試數據,從而失去對未知數據的預判能力。由於一個神經網絡模型的最終目標是對未知數據提供判斷,因此爲了評估模型在未知數據上的效果,須要保證測試數據在訓練過程當中是不可見的。只有這樣才能保證經過測試數據評估出來的效果和真實應用場景下模型對未知數據預判的效果是接近的。因而,爲了評測神經網絡模型在不一樣參數取值下的效果,通常會從訓練數據中抽取一部分做爲驗證數據。使用驗證數據就能夠評判不一樣參數取值下模型的表現。除了使用驗證數據,還能夠採用交叉驗證(cross validation)的方式來驗證模型效果。但由於神經網絡訓練時間自己就比較長,採用 cross validation 會花費大量的時間,因此在海量數據的狀況下,通常會更多的採用驗證數據集的形式來評測模型的效果。

  爲了說明驗證數據在必定程度上能夠做爲模型效果的評判標準,咱們將對比在不一樣迭代輪數的狀況下,模型在驗證數據和測試數據上的正確率。爲了同時獲得同一個模型在驗證數據和測試數據上的正確率,能夠在每1000輪的輸出中加入在測試數據集上的正確率。當咱們再上門代碼中加入下面代碼,就能夠獲得每1000輪迭代後,使用了滑動平均的模型在驗證數據和測試數據上的正確率。

if i % 1000 == 0:
    validate_acc = sess.run(accuracy, feed_dict=validate_feed)
    # 計算滑動平均模型在測試數據和驗證數據上的正確率
    test_acc = sess.run(accuracy, feed_dict=test_feed)
    print('After %d training step(s), validation accuracy using average model is %g,'
          'test accuracy using average model is %g'%(i, validate_acc, test_acc))

  下圖給出了上面代碼獲得的每1000輪滑動平均模型在不一樣數據集上的正確率曲線,其中灰色曲線表示隨着迭代輪數的增長,模型在驗證數據上的正確率,黑色曲線表示在測試數據上的正確率。從圖中能夠看出雖然兩條曲線不會徹底重合,可是這兩條曲線的趨勢基本同樣,並且他們的相關關係(correlation coeddicient)大於0.9999。這意味着在MNIST問題上,徹底能夠經過模型在驗證數據上的表現來判斷一個模型的優劣。

  固然,以上結論是針對MNIST這個數據集的額,對其它問題,還須要具體問題具體分析。不一樣問題的數據分佈式不同的。若是驗證數據分佈不能很好地表明測試數據分佈。那麼模型在這兩個數據集上的表現就有可能會不同。因此,驗證數據的選取方法是很是重要的,通常來講選取的驗證數據分佈越接近測試數據分佈,模型在驗證數據上的表現越能夠體現模型在測試數據上的表現。經過上面的實驗,至少能夠說明經過神經網絡在驗證數據上的效果來選取模型的參數是一個可行的方案。

六,不一樣模型效果比較

  下面經過MNIST數據集來比較值錢提到的不一樣優化方法對神經網絡模型正確率的影響。下面將使用神經網絡模型在MNIST測試數據集上的正確率做爲評價不一樣優化方法的標準。而且一個模型在MNIST測試數據集上行的正確率簡稱爲「正確率」。以前提到了設計神經網絡時的五種優化方法。在神經網絡結構的設計上,須要使用激活函數和多層隱藏層。在神經網絡優化時,可使用指數衰減的學習率,加入正則化的損失函數以及滑動平均模型,在下圖中給出了在相同神經網絡參數下,使用不一樣優化方法啊,通過3000輪訓練迭代後,獲得的最終模型的正確率。下圖的結果包含了使用全部優化方法訓練獲得的模型和不用其中某一項優化方法訓練獲得的模型。經過這種方式,能夠有效驗證每一項優化方法的效果。

  從圖中能夠很明顯的看出,調整神經網絡的結構對最終的正確率有很是大的影響。沒有隱藏層或者激活函數時,模型的正確率只有大約92.6%,這個數字要遠遠小於使用了隱藏層和激活函數時能夠達到的大約98.4%的正確率。這說明神經網絡的結構對最終模型的效果有本質性的影響。

   從上圖的結果能夠發現使用滑動平均模型,指數衰減的學習率和使用正則化帶來的正確率的提高並非特別明顯。其中使用了全部優化算法的模型和不使用滑動平均的模型以及不適用指數衰減的學習率的模型均可以達到大約98.4%的正確率。這是由於滑動平均模型和指數衰減的學習率在必定程度上都是限制神經網絡中參數更新的速度,然而在MNIST數據上,由於模型收斂的速度很快,因此這兩種優化對最終模型的影響不大。從以前的結果能夠看出,當模型迭代達到4000輪的時候正確率就已經接近最終的正確率了。而在迭代的早期,是否使用滑動平均模型或者指數衰減的學習率對訓練的結果影響相對較小,下圖顯示了不一樣迭代輪數時,使用了全部優化方法的模型的正確率與平均絕對梯度的變化趨勢。

  下圖顯示了不一樣迭代輪數時,正確率與衰減以後的學習率的變化趨勢:

  從圖中能夠看出前4000輪迭代對模型的改變是最大的。在4000輪以後,由於梯度自己比較小,因此參數的改變也就是比較緩慢了。因而滑動平均模型或者指數衰減的學習率的做用也就沒有那麼突出了。從上圖能夠看到,學習率曲線呈現出階梯狀衰減,在前4000輪時,衰減以後的學習率和最初的學習率差距並不大。那麼,這是否能說明這些優化方法做用不大呢?答案是否認的,當問題更加複雜時,迭代不會這麼快接近收斂,這時滑動平均模型和指數衰減的學習率能夠發揮更大的做用。好比在Cifar-10 圖像分類數據集上,使用滑動平均模型能夠將錯誤率下降11%,而使用指數衰減的學習率能夠將錯誤率下降7%。

  相對滑動平均模型和直屬衰減學習率,使用加入正則化的損失函數給模型效果帶來的提高要相對顯著。使用了正則化損失函數的神經網絡模型能夠下降大約6%的錯誤率(從1.69%下降到1.59%)。下圖給出了正則化給模型優化過程帶來的影響。而且下面兩個圖給出了不一樣損失函數的神經網絡模型。一個模型只最小化交叉熵損失,另外一個模型優化的是交叉熵和L2正則化的損失的和。下面先給出這個模型優化函數的聲明語句:

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)

#*********************************************************
# 只最小化交叉熵損失
train_step = tf.train.GradientDescentOptimizer(learning_rate
                                               ).minimize(cross_entropy_mean,global_step=global_step)

#*********************************************************
# 優化交叉熵和L2正則化損失的和
loss = cross_entropy_mean + regluaraztion
train_step = tf.train.GradientDescentOptimizer(learning_rate
                                               ).minimize(loss, global_step=global_step)

  下圖灰色和黑色的實線給出了兩個模型正確率的變化趨勢,虛線給出了在當前訓練batch上的交叉熵損失:

  從圖中能夠看出,只優化交叉熵的模型在訓練數據上的交叉熵損失(灰色虛線)要比優化總損失的模型更小(黑色虛線)。然而在測試數據上,優化總損失的模型(褐色實線)卻要比只優化交叉熵的模型(灰色實線)。這其中緣故,就是過擬合問題,只優化交叉熵的模型能夠更好地擬合訓練數據(交叉熵損失更小),可是卻不能很好的挖掘數據中潛在的規律來判斷未知的測試數據,因此在測試數據上的正確率低。

   下圖顯示了不一樣模型的損失函數的變化趨勢,左側顯示了只優化交叉熵的模型損失函數的變化規律,能夠看到隨着迭代的進行,正則化損失是在不斷加大的。由於MNIST問題相對比較簡單,迭代後期的梯度很小,因此正則化損失的增加也不快。若是問題更加複雜,迭代後期的梯度更大,就會發現總損失(交叉熵損失加上正則化損失)會呈現出一個U字形,在圖的右側,顯示了優化總損失的模型損失函數的變化規律,從圖中能夠看出,這個模型的正則化損失部分也能夠隨着迭代的進行愈來愈小,從而使得總體的損失呈現一個逐步遞減的趨勢。

 

  總的來講,經過MNIST數據集有效地驗證了激活函數,隱藏層能夠給模型的效果帶來質的飛躍。因爲MNIST問題自己相對簡單,滑動平均模型,指數衰減的學習率和正則化損失對最終正確率的提高效果不明顯。可是經過進一步分析實驗的結果,能夠得出這些優化方法確實能夠解決神經網絡優化過程當中的問題。當須要解決的問題和使用到的神經網絡模型更加複雜時,這些優化方法將更有可能對訓練效果產生更大的影響。

七,變量管理

   上面咱們將計算神經網絡前向傳播結果的過程抽象成了一個函數。經過這種方式在訓練和測試的過程當中能夠統一調用同一個函數來得模型的前向傳播結果。

  在上面代碼中,這個函數定義以下:

def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):

  在定義中能夠看到,這個函數的參數中包含了神經網絡中的全部參數。然而,當神經網絡的結構更加複雜,參數更多時,就須要一個更好的方式來傳遞和管理神經網絡中的參數了。TensorFlow提供了經過變量名稱來建立或者獲取一個變量的機制。經過這個機制,在不一樣的函數中能夠直接經過變量的名字來使用變量,而不須要將變量經過 tf.get_variable 和 tf.get.variable_scope 函數實現的。下面將分別介紹如何使用這兩個函數。

  前面學習了經過tf.Variable 函數來建立一個變量。除了tf.Variable函數,TensorFlow還提供了tf.get_variable函數來建立或者獲取變量。當 tf.get_variable用於建立變量時,它和tf.Variable的功能是基本等價的。如下代碼給出了經過兩個函數建立同一個變量的樣例:

# 下面兩個定義是等價的
v = tf.get_variable('v', shape=[1],
                    initializer=tf.constant_initializer(1.0))

v = tf.Variable(tf.constant(1.0, shape=[1]), name='v')

  從上面的代碼能夠看出,經過tf.get_variable 和 tf.get.variable_scope 函數建立變量的過程基本是一致的。tf.get_variable 函數調用時提供的維度(shape)信息以及初始化方法(initializer)的參數和tf.Variable 函數調用時提供的初始化過程當中的參數也相似。TensorFlow中提供的initializer 函數和以前提到的隨機數以及常量生成函數大部分是一一對應的。好比,在上面的樣例程序中使用到的常數初始化函數 tf.constant_initializer 和常數生成函數 tf.constant 功能上就是一直的。TensorFlow 提供了七種不一樣的初始化函數,下表總結了他們的功能和主要參數。

  tf.get_varibale 函數與 tf.Variable 函數最大的區別在於指定變量名稱的參數。對於 tf.Variable 函數,變量名稱是一個可選的參數,經過 name='v' 的形式給出。可是對於 tf.get_variable 函數,變量名稱是一個必填的參數。tf.get_variable會根據這個名字去建立或者獲取變量。在上面的樣例程序中,tf.get_variable首先會視圖去建立一個名字爲 v 的參數,若是建立失敗(好比已經有同名的參數),那麼這個程序就會報錯。這是爲了不無心識的變量複用形成的錯誤。好比在定義神經網絡參數時,第一層網絡的權重已經叫 weights 了,那麼在建立第二層神經網絡時,若是參數名仍然叫 weights,就會觸發變量重用的錯誤。不然兩層神經網絡共用一個權重會出現一些比較難以發現的錯誤。若是須要經過 tf.get_variable 獲取一個已經建立的變量,須要經過 tf.variable_scope 函數來生成一個上下文管理器,並明確指定在這個上下文管理器中, tf.get_variable將直接獲取已經生成的變量。下面給出一段代碼來講明如何經過tf.variable_scope 函數來控制 tf.get_variable 函數獲取已經建立過的變量。

# 在名字爲foo的命名空間內建立名字爲 v 的變量
with tf.variable_scope('foo'):
    v = tf.get_variable(
        'v', [1], initializer=tf.constant_initializer(1.0)
    )

# 由於在命名空間foo中已經存在名字爲 v 的變量,因此下面的代碼將會報錯
with tf.variable_scope('foo'):
    v = tf.get_variable('v', [1])

'''
報錯以下:
ValueError: Variable foo/v already exists, disallowed. Did you mean 
to set reuse=True in VarScope?
'''

# 在生成上下文管理器時,將參數reuse設置爲True,這樣tf.get_variable函數將直接獲取已經聲明的函數
with tf.variable_scope('foo', reuse=True):
    v1 = tf.get_variable('v', [1])
    print(v == v1)
    # 輸出爲True,表明v,v1表明的是相同的TensorFlow中變量

# 將參數reuse設置爲True時,tf.variable_scope將只能獲取已經建立過的變量
# 由於在命名空間bar中尚未建立變量 v 因此下面的代碼將會報錯
with tf.variable_scope('bar', reuse=True):
    v = tf.get_variable('v', [1])
'''
報錯以下:
ValueError: Variable bar/v does not exist, or was not created with tf.get_variable().
 Did you mean to set reuse=None in VarScope?'''

  上面的樣例簡單地說明了經過 tf.variable_scope函數能夠控制 tf.get_variable函數的語義。當 tf.variable_scope函數使用參數 reuse=True生成上下文管理器時,這個上下文管理器內全部的tf.get_variable函數會直接獲取已經建立的變量。若是變量不存在,則 tf.get_variable函數將會報錯;相反,若是 tf.variable_scope函數使用參數 reuse=None或者 reuse=False建立上下文管理器,tf.get_variable操做將建立新的變量。若是同名的變量已經存在,則 tf.get_variable函數將報錯,TensorFlow中 tf.variable_scope 函數是能夠嵌套的,下面程序說明了當 tf.varibale_scope 函數嵌套時,reuse參數的取值是如何肯定的:

with tf.variable_scope('root'):
    # 能夠經過td.get_varable_scope().reuse函數來獲取當前上下文管理器中reuse參數的取值
    print(tf.get_variable_scope().reuse)
    # 輸出爲False 即最外層reuse是False

    # 新建一個嵌套的上下文管理器,並制定reuse爲True
    with tf.variable_scope('foo', reuse=True):
        print(tf.get_variable_scope().reuse)
        # 輸出爲True,由於咱們設置了其reuse爲True

        # 新建一個嵌套的上下文管理器但不指定reuse,這時候reuse的取值會和上一層層保持一致
        with tf.variable_scope('bar'):
            print(tf.get_variable_scope().reuse)
            # 輸出爲True,沒有設置的話,將和上一層保持一致
    print(tf.get_variable_scope().reuse)
    # 這裏輸出爲False。退出reuse設置爲True的上下文以後,reuse的值又回到了False

  tf.variable_scope函數生成的上下文管理器也會建立一個TensorFlow中的命名空間,在命名空間內建立的變量名稱都會帶上這個命名空間名做爲前綴。因此 tf.variable_scope函數除了能夠控制tf.get_variable 執行的功能以外,這個函數也提供了一個管理變量命名空間的方式,如下代碼顯示瞭如何經過tf.variable_scope來管理變量的名稱:

v1 = tf.get_variable('v', [1])
print(v1.name)
# 輸出 v:0
# 其中 v 爲變量的名稱  0表示這個變量是生成變量這個運算的第一個結果

with tf.variable_scope('foo'):
    v2 = tf.get_variable('v', [1])
    print(v2.name)
    # 輸出 foo/v:0
    # 在tf.variable_scope中建立的變量,名稱前面會加入命名空間的名稱
    # 並經過/來分隔名稱空間的名稱和變量的名稱

with tf.variable_scope('foo'):
    with tf.variable_scope("bar"):
        v3 = tf.get_variable('v', [1])
        print(v3.name)
        # 輸出 foo/bar/v:0
        # 命名空間能夠嵌套,同時遍歷的名稱也會加入全部命名空間的名稱做爲前綴
    v4 = tf.get_variable('v1', [1])
    print(v4.name)
    # 輸出 foo/v1:0
    # 當命名空間退出以後,變量名稱也就不會再被加入其前綴了

# 建立一個名稱爲空的命名空間,並設置reuse=True
with tf.variable_scope("", reuse=True):
    v5 = tf.get_variable('foo/bar/v', [1])
    # 能夠直接經過帶命名空間名稱的變量名來獲取其餘命名空間下的變量
    # 好比這裏指定名稱 foo/bar/v 來獲取在命名空間 foo/bar/中建立的變量
    print(v5==v3)
    # 輸出結果爲 : True

    v6 = tf.get_variable('foo/v1', [1])
    print(v6==v4)
    # 輸出結果爲 : True

  經過tf.variable_scope 和 tf.get_variable 函數,下面代碼對計算前向傳播結果的函數 作了一些改進:

def inference(input_tensor, reuse=False):
    # 定義第一層神經網絡的變量和前向傳播過程
    with tf.variable_scope('layer1', reuse=reuse):
        # 根據傳進來的reuse來判斷是建立新變量仍是使用已經建立好的
        # 在第一次構造網絡時須要建立新的變量,之後每次調用這個函數直接使用reuse=True
        # 在以後,就不須要每次將變量傳進來了。
        weights = tf.get_variable('weights', [INPUT_NODE, LAYER1_NODE],
                                  initializer=tf.truncated_normal_initializer(stddev=0.1))
        biases = tf.get_variable('biases', [OUTPUT_NODE],
                                 initializer=tf.constant_initializer(0.0))
        layer1 = tf.nn.relu(tf.matmul(input_tensor, weights) + biases)

    # 相似的定義第二層神經網絡的變量和前向傳播過程
    with tf.variable_scope('layer2', reuse=reuse):
        weights = tf.get_variable('weights', [LAYER1_NODE, OUTPUT_NODE],
                                  initializer=tf.truncated_normal_initializer(stddev=0.1))
        biases = tf.get_variable("biases", [OUTPUT_NODE],
                                 initializer=tf.constant_initializer(0.0))
        layer2 = tf.matmul(layer1, weights) + biases
    # 返回最後的前向傳播結果
    return layer2

x = tf.placeholder(tf.float32, [None, INPUT_NODE], name='x-input')
y = inference(x)

# 在程序中須要使用訓練好的神經網絡進行推導時,能夠直接調用 inference(new_x, True)
# 若是須要使用滑動平均模型能夠參考以前的代碼,把計算滑動平均的類傳到inference函數中
# 獲取或者建立變量的部分不須要改變
new_x = ...
new_y = inference(new_x, True)

  使用上面的代碼,就不須要再將全部的變量都做爲參數傳遞到不一樣的函數中了。當神經網絡結構更加複雜,參數更多時,使用這種變量管理的方式將大大提升程序的可讀性。

八,TensorFlow最佳實踐樣例程序

  在上面已經給出了一個完整的TensorFlow程序來解決MNIST問題,然而這個程序的可擴展性並很差,由於計算前向傳播的函數須要將全部變量都傳入,當神經網絡結構變得更加複雜,參數更多時,程序可讀性會變得很是差。並且這種方式會致使程序中有大量的冗餘代碼,下降編程的效率。而且上面程序並無持久化訓練好的模型。當程序退出時,訓練好的模型也就沒法被使用了,這致使獲得的模型沒法被重用。更嚴重的是,通常神經網絡模型訓練的時候都比較長,少則幾個小時,多則幾天甚至幾周。若是在訓練過程當中程序死機了,那麼沒有保存訓練的中間結果會浪費大量的時間和資源。因此,在訓練的過程當中須要每隔一段時間保存一次模型訓練的中間結果。

  結合變量管理機制和TensorFlow模型持久化機制,咱們將學習一個TensorFlow訓練設計網絡的最佳實踐。將訓練和測試分紅兩個獨立的程序,這可使得每個組件更加靈活。好比訓練神經網絡的程序能夠持續輸出訓練好的模型,而測試程序能夠每隔一段時間檢測最新模型的正確率,若是模型效果更好,則將這個模型提供給產品使用。除了將不一樣功能模型分開,咱們還將前向傳播的過程抽象成一個單獨的庫函數,由於神經網絡的前向傳播過程在訓練和測試的過程當中都會用到,因此經過庫函數的方式使用起來便可更加方便,又能夠保證訓練和測試過程當中使用的前向傳播方法必定是一致的。

  下面將重構以前的程序來解決MNIST問題,重構以後的代碼將會被拆成3個程序,第一個是 mnist_inference.py,它定義了前向傳播的過程以及神經網絡中的參數,第二個是 mnist_train.py 它定義了神經網絡的訓練過程,第三個是 mnist_eval.py ,它定義了測試過程。

  下面給出 mnist_inference.py中的的內容:

#_*_coding:utf-8_*_
import tensorflow as tf

# 定義神經網絡結構相關參數
INPUT_NODE = 784
OUTPUT_NODE = 10
LAYER1_NODE = 500

# 經過get_variable函數來獲取變量。在訓練神經網絡時會建立這些變量
# 在測試時會經過保存的模型加載這些變量的取值
# 由於能夠在變量加載時將滑動平均變量重命名,因此能夠經過一樣的名字在訓練時使用變量自己
# 並且在測試時使用變量的滑動平均值,在這個函數中也會將變量的正則化損失加入損失集合
def get_weight_variable(shape, regularizer):
    weights = tf.get_variable(
        'weights', shape,
        initializer=tf.truncated_normal_initializer(stddev=0.1)
    )
    # 當給出正則化生成函數時,將當前變量的正則化損失計入名字爲losses的集合
    #這裏使用add_to_collection函數將一個張量加入一個集合,而這個集合的的名稱爲losses
    # 這是自定義的集合,再也不TensorFlow自主管理的集合列表中
    if regularizer != None:
        tf.add_to_collection('losses', regularizer(weights))
    return weights

# 定義神經網絡的前向傳播過程
def inference(input_tensor, regularizer):
    # 聲明第一層神經網絡的變量並完成前向傳播過程
    with tf.variable_scope('layer1'):
        # 這裏經過 tf.get_variable或tf.Variable沒有本質區別
        # 由於在訓練或者是測試中沒有在同一個程序中屢次調用這個函數
        # 若是在同一個程序中屢次調用,在第一次調用以後就須要將reuse參數設置爲True
        weights = get_weight_variable(
            [INPUT_NODE, LAYER1_NODE], regularizer
        )
        biases = tf.get_variable(
            'biases', [LAYER1_NODE],
            initializer=tf.constant_initializer(0.0)
        )
        layer1 = tf.nn.relu(tf.matmul(input_tensor, weights) + biases)

    # 相似的聲明第二層神經網絡的變量並完成前向傳播過程
    with tf.variable_scope('layer2'):
        weights = get_weight_variable(
            [LAYER1_NODE, OUTPUT_NODE], regularizer
        )
        biases = tf.get_variable(
            'biases', [OUTPUT_NODE],
            initializer=tf.constant_initializer(0.0)
        )
        layer2 = tf.matmul(layer1, weights) + biases

    # 返回最後前向傳播的結果
    return layer2

  在這段代碼中定義了神經網絡的前向傳播算法,不管是訓練時仍是測試時,均可以直接調用 Inference 這個函數,而不用關心具體的神經網絡結構,使用定義好的前向傳播過程,下面代碼給出了神經網絡的訓練程式 mnist_train.py的代碼:

#_*_coding:utf-8_*_
import os
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

# 加載 mnist_inference.py 中定義的常量和前向傳播的函數
import mnist_inference

# 配置神經網絡的參數
BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.8
LEARNING_RATE_DECAY = 0.99
REGULARAZTION_RATE = 0.0001
TRAINING_STEPS = 30000
MOVING_AVERAGE_DECAY = 0.99
# 模型保存的路徑和文件名
MODEL_SAVE_PATH = 'model1/'
MODEL_NAME = 'model.ckpt'

def train(mnist):
    # 定義輸入輸出 placeholder
    x = tf.placeholder(
        tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input'
    )
    y_ = tf.placeholder(
        tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input'
    )

    regularizer = tf.contrib.layers.l2_regularizer(REGULARAZTION_RATE)
    # 直接使用bookmnost_inference.py中定義的前向傳播過程
    y = bookmnist_inference.inference(x, 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)
            _, loss_value, step = sess.run([train_op, loss, global_step],
                                           feed_dict={x: xs, y_: ys})
            # 每1000輪保存一次模型
            if i % 1000 == 0:
                # 輸出當前的訓練狀況,這裏只輸出了模型在當前訓練batch上的損失函數大小
                # 經過損失函數的大小能夠大概瞭解訓練的狀況,在驗證集上正確率信息會有一個單獨的程序來生成
                print("Afer %d training step(s), loss on training batch is %g"%(step, loss_value))

                # 保存當前模型,注意這裏給出的global_step參數,這樣可讓每一個被保存模型的文件名末尾加上訓練點額輪數
                saver.save(
                    sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME),
                    global_step=global_step
                )

def main(argv=None):
    mnist = input_data.read_data_sets('data', one_hot=True)
    train(mnist)

if __name__ == '__main__':
    main()

  運行上面的程式,能夠獲得相似下面的代碼:

Extracting data\train-images-idx3-ubyte.gz
Extracting data\train-labels-idx1-ubyte.gz
Extracting data\t10k-images-idx3-ubyte.gz
Extracting data\t10k-labels-idx1-ubyte.gz
Afer 1 training step(s), loss on training batch is 2.97567
Afer 1001 training step(s), loss on training batch is 0.228694
Afer 2001 training step(s), loss on training batch is 0.161067
Afer 3001 training step(s), loss on training batch is 0.148354
Afer 4001 training step(s), loss on training batch is 0.121623
Afer 5001 training step(s), loss on training batch is 0.106996
Afer 6001 training step(s), loss on training batch is 0.104219
Afer 7001 training step(s), loss on training batch is 0.112429
Afer 8001 training step(s), loss on training batch is 0.077253
... ...
Afer 29001 training step(s), loss on training batch is 0.03582

  在新的訓練代碼中,再也不將訓練和測試跑一塊兒,訓練過程當中,每1000輪輸出一次在當前訓練batch上損失函數的大小來大體估計訓練的效果。在上面的程式中,每1000輪保存一次訓練好的模型,這樣能夠經過一個單獨的測試程序,更加方便的在滑動平均模型上作測試,下面給出測試程序 mnist_eval.py

 

#_*_coding:utf-8_*_
import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

# 加載 mnist_inference.py 和 mnist_train.py中定義的常量和函數
import mnist_inference
import mnist_train

# 每10秒加載一次最新的模型,並在測試數據上測試最新模型的正確率
EVAL_INTERVAL_SECS = 10

def evalute(mnist):
    with tf.Graph().as_default() as g:
        # 定義輸入輸出格式
        x = tf.placeholder(
            tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input'
        )
        y_ = tf.placeholder(
            tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input'
        )
        validate_feed = {x: mnist.validation.images,
                         y_: mnist.validation.labels}

        # 直接經過調用封裝好的函數來計算前向傳播的額結果
        #由於測試時不關注正則化損失的值,因此這裏用於計算正則化損失函數被設置爲None
        y = mnist_inference.inference(x, None)

        # 使用前向傳播的結果計算正確率,若是須要對未知的樣本進行分類,
        # 那麼使用 tf.argmax(y, 1)就能夠獲得輸入樣例的預測類別了
        correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

        # 經過變量重命名的方式來加載模型,這樣在前向傳播的過程當中就不須要調用求滑動平均的函數獲取平均值
        # 這樣就能夠徹底共享以前mnist_inference.py中定義的前向傳播過程
        variable_averages = tf.train.ExponentialMovingAverage(
            mnist_train.MOVING_AVERAGE_DECAY
        )
        variables_to_restore = variable_averages.variables_to_restore()
        saver = tf.train.Saver(variables_to_restore)

        # 每隔EVAL_INTERVAL_SECS 秒調用一次計算正確率的過程以檢測訓練過程當中正確率的變化
        while True:
            with tf.Session() as sess:
                # tf.train.get_checkpoint_state函數會經過checkpoint文件
                # 自動找到目錄中最新模型的文件名
                ckpt = tf.train.get_checkpoint_state(
                    mnist_train.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]
                    accuracy_score = sess.run(accuracy, feed_dict=validate_feed)
                    print("After %s training step(s) , validation accuracy ='%g"%(global_step, accuracy_score))
                else:
                    print("No checkpoint file found")
                    return
                time.sleep(EVAL_INTERVAL_SECS)

def main(argv=None):
    mnist = input_data.read_data_sets('data', one_hot=True)
    evalute(mnist)


if __name__ == '__main__':
    main()

  上面的代碼會每隔10秒運行一次,每次運行都是讀取最新保存的模型,並在MNIST驗證數據上計算模型的正確率。若是須要離線預測未知數據的類別(好比這個樣例程序能夠判斷手寫數字圖片中所包含的數字),只須要將計算正確率的部分改成答案輸出便可。運行上面代碼能夠獲得相似下面的結果,注意由於這個程序每10秒會自動運行一次,而訓練程序不必定每10秒輸出一個新模型,因此下面的結果中會發現有些模型被測試了屢次,通常在解決真實問題時,不會這麼頻繁的運行評測程序。

Extracting data\train-images-idx3-ubyte.gz
Extracting data\train-labels-idx1-ubyte.gz
Extracting data\t10k-images-idx3-ubyte.gz
Extracting data\t10k-labels-idx1-ubyte.gz
After 29001 training step(s) , validation accuracy ='0.9856
After 29001 training step(s) , validation accuracy ='0.9856
After 29001 training step(s) , validation accuracy ='0.9856
After 29001 training step(s) , validation accuracy ='0.9856
After 29001 training step(s) , validation accuracy ='0.9856
After 29001 training step(s) , validation accuracy ='0.9856
After 29001 training step(s) , validation accuracy ='0.9856
... ...
相關文章
相關標籤/搜索