使用tensorflow操作MNIST資料

戰爭熱誠發表於2019-07-24

  本節開始學習使用tensorflow教程,當然從最簡單的MNIST開始。這怎麼說呢,就好比程式設計入門有Hello World,機器學習入門有MNIST。在此節,我將訓練一個機器學習模型用於預測圖片裡面的數字。

  MNIST 是一個非常有名的手寫體數字識別資料集,在很多資料中,這個資料集都會被用做深度學習的入門樣例。而Tensorflow的封裝讓MNIST資料集變得更加方便。MNIST是NIST資料集的一個子集,它包含了60000張圖片作為訓練資料,10000張圖片作為測試資料。在MNIST資料集中的每一張圖片都代表了0~9中的一個數字。圖片的大小都為28*28,且數字都會出現在圖片的正中間,如下圖:

  在上圖中右側顯示了一張數字1的圖片,而右側顯示了這個圖片所對應的畫素矩陣,MNIST資料集提供了四個下載檔案,在tensorflow中可以將這四個檔案直接下載放到一個目錄中並載入,如下程式碼input_data.read_data_sets所示,如果指定目錄中沒有資料,那麼tensorflow會自動去網路上進行下載。下面程式碼介紹瞭如何使用tensorflow操作MNIST資料集。

  MNIST資料集的官網是Yann LeCun's website。在這裡,我們提供了一份python原始碼用於自動下載和安裝這個資料集。你可以下載這份程式碼,然後用下面的程式碼匯入到你的專案裡面,也可以直接複製貼上到你的程式碼檔案裡面。

import tensorflow.examples.tutorials.mnist.input_data as input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

  這裡,我直接下載了資料集,並放在了我的程式碼裡面。測試如下:

# _*_coding:utf-8_*_

from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
print("OK")
# 列印“Training data size: 55000”
print("Training data size: ", mnist.train.num_examples)
# 列印“Validating data size: 5000”
print("Validating data size: ", mnist.validation.num_examples)
# 列印“Testing data size: 10000”
print("Testing data size: ", mnist.test.num_examples)
# 列印“Example training data: [0. 0. 0. ... 0.380 0.376 ... 0.]”
print("Example training data: ", mnist.train.images[0])
# 列印“Example training data label: [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]”
print("Example training data label: ", mnist.train.labels[0])

batch_size = 100
# 從train的集合中選取batch_size個訓練資料
xs, ys = mnist.train.next_batch(batch_size)
# 輸出“X shape:(100,784)”
print("X shape: ", xs.shape)
# 輸出"Y shape:(100,10)"
print("Y shape: ", ys.shape)

  結果如下:

Extracting MNIST_data\train-images-idx3-ubyte.gz
Extracting MNIST_data\train-labels-idx1-ubyte.gz
Extracting MNIST_data\t10k-images-idx3-ubyte.gz
Extracting MNIST_data\t10k-labels-idx1-ubyte.gz
OK
Training data size:  55000
Validating data size:  5000
Testing data size:  10000
X shape:  (100, 784)
Y shape:  (100, 10)

  從上面的程式碼可以看出,通過input_data.read_data_sets函式生成的類會自動將MNIST資料集劃分成train,validation和test三個資料集,其中train這個集合內含有55000張圖片,validation集合內含有5000張圖片,這兩個集合組成了MNIST本身提供的訓練資料集。test集合內有10000張圖片,這些圖片都來自與MNIST提供的測試資料集。處理後的每一張圖片是一個長度為784的一維陣列,這個陣列中的元素對應了圖片畫素矩陣中的每一個數字(28*28=784)。因為神經網路的輸入是一個特徵向量,所以在此把一張二維影象的畫素矩陣放到一個一維陣列中可以方便tensorflow將圖片的畫素矩陣提供給神經網路的輸入層。畫素矩陣中元素的取值範圍為[0, 1],它代表了顏色的深淺。其中0表示白色背景,1表示黑色前景。

Softmax迴歸的再複習

  我們知道MNIST的每一張圖片都表示一個數字,從0到9。我們希望得到給定圖片代表每個數字的概率。比如說,我們的模型可能推測一張包含9的圖片代表數字9 的概率為80%但是判斷它是8 的概率是5%(因為8和9都有上半部分的小圓),然後給予它代表其他數字的概率更小的值。

  這是一個使用softmax regression模型的經典案例。softmax模型可以用來給不同的物件分配概率。即使在之後,我們訓練更加精細的模型的時候,最後一步也是需要softmax來分配概率。

softmax迴歸第一步

  為了得到一張給定圖片屬於某個特定數字類的證據(evidence),我們對圖片畫素值進行加權求和。如果這個畫素具有很強的證據說明這種圖片不屬於該類,那麼相應的權值為負數,相反如果這個畫素擁有很強的證據說明這張圖片不屬於該類,那麼相應的權值為負數,相反如果這個畫素擁有有力的證據支援這張圖片屬於這個類,那麼權值是正數。

  下面的圖片顯示了一個模型學習到的圖片上每個畫素對於特定數字類的權值,紅色代表負數權值,藍色代表正數權值。

  我們也需要加入一個額外的偏置量(bias),因為輸入往往會帶有一些無關的干擾量。因為對於給定的輸入圖片 x  它代表的是數字 i 的證據可以表示為:

  其中,Wi代表權重,bi代表數字 i 類的偏置量, j 代表給定圖片 x 的畫素索引用於畫素求和。然後用 softmax 函式可以把這些證據轉換成概率 y:

   這裡的softmax可以看成是一個激勵(activation)函式或者連結(link)函式,把我們定義的線性函式的輸出轉換成我們想要的格式,也就是關於10個數字類的概率分佈。因此,給定一張圖片,它對於每一個數字的吻合度可以被softmax函式轉換成一個概率值。softmax函式可以定義為:

  展開等式右邊的子式,可以得到:

  但是更多的時候把softmax模型函式定義為前一種形式:把輸入值當做冪指函式求值,再正則化這些結果值。這個冪運算表示,更大的證據對應更大的假設模型(hypothesis)裡面的乘數權重值。反之,擁有更少的證據意味著在假設模型裡面擁有更小的乘數係數。假設模型裡面的權值不可以是0值或者負值。Softmax然後會正則化這些權重值,使他們的總和等於1,以此構造一個有效的概率分佈。

   對於softmax迴歸模型可以用下面的圖解釋,對於輸入的 xs 加權求和,再分別加上一個偏置量,最後再輸入到 softmax 函式中:

  如果把它寫成一個等式,我們可以得到:

  我們也可以用向量表示這個計算過程:用矩陣乘法和向量相加。這有助於提高計算效率:

  更進一步,可以寫成更加緊湊的方式:

訓練模型的步驟

  為了訓練模型,我們首先需要定義一個指標來評估這個模型是好的。其實,在機器學習中,我們通常定義指標來表示一個模型是壞的,這個指標稱為成本(cost)或者損失(loss),然後近鄰最小化這個指標。但是這兩種方式是相同的。

  一個非常常見的的成本函式是“交叉熵(cross-entropy)”。交叉熵產生於資訊理論裡面的資訊壓縮編碼技術,但是後來他演變為從博弈論到機器學習等其他領域裡的重要技術手段,它的定義如下:

  Y是我們預測的概率分佈, y' 是實際的分佈(我們輸入的 one-hot vector)。比較粗糙的理解是,交叉熵是用來衡量我們的預測用於描述真相的低效性。

   為了計算交叉熵,我們首先需要新增一個新的佔位符用於輸入正確值:

y_ = tf.placeholder("float", [None,10])

  然後我們可以用交叉熵的公式計算交叉熵:

cross_entropy = -tf.reduce_sum(y_*tf.log(y))

  首先,用 tf.log 計算 y 的每個元素的對數。接下來,我們把 y_ 的每一個元素 和 tf.log(y) 的對應元素相乘。最後用 tf.reduce_sum 計算張量的所有元素總和。(注意:這裡的交叉熵不僅僅用來衡量單一的一對預測和真實值,而是所有的圖片的交叉熵的總和。對於所有的資料點的預測表現比單一資料點的表現能更好的描述我們的模型的效能)。

  當我們知道我們需要我們的模型做什麼的時候,用TensorFlow來訓練它是非常容易的。因為TensorFlow擁有一張描述我們各個計算單元的圖,它可以自動的使用反向傳播演算法(backpropagation  algorithm)來有效的確定變數是如何影響想要最小化的那個成本值。然後TensorFlow會用選擇的優化演算法來不斷地修改變數以降低成本。

train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

  在這裡,我們要求TensorFlow用梯度下降演算法(gradient descent alogrithm)以0.01的學習速率最小化交叉熵。梯度下降演算法(gradient descent algorithm)是一個簡單的學習過程,TensorFlow只需將每個變數一點點地往使成本不斷降低的方向移動。當然TensorFlow也提供了其他許多優化演算法:只要簡單地調整一行程式碼就可以使用其他的演算法。

  TensorFlow在這裡實際上所做的是,它會在後臺給描述你的計算的那張圖裡面增加一系列新的計算操作單元用於實現反向傳播演算法和梯度下降演算法。然後,它返回給你的只是一個單一的操作,當執行這個操作時,它用梯度下降演算法訓練你的模型,微調你的變數,不斷減少成本。

  現在,我們已經設定好了我們的模型,在執行計算之前,我們需要新增一個操作來初始化我們建立的變數並初始化,但是我們可以在一個Session裡面啟動我們的模型:

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

  然後開始訓練模型,這裡我們讓模型訓練訓練1000次:

for i in range(1000):
  batch_xs, batch_ys = mnist.train.next_batch(100)
  sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

  該訓練的每個步驟中,我們都會隨機抓取訓練資料中的100個批處理資料點,然後我們用這些資料點作為引數替換之前的佔位符來執行 train_step。而且我們可以通過 feed_dict 將 x 和 y_ 張量 佔位符用訓練資料替代。(注意:在計算圖中,我們可以用 feed_dict 來替代任何張量,並不僅限於替換佔位符)

  使用一小部分的隨機資料來進行訓練被稱為隨機訓練(stochastic training)- 在這裡更確切的說是隨機梯度下降訓練。在理想情況下,我們希望用我們所有的資料來進行每一步的訓練,因為這能給我們更好的訓練結果,但顯然這需要很大的計算開銷。所以,每一次訓練我們可以使用不同的資料子集,這樣做既可以減少計算開銷,又可以最大化地學習到資料集的總體特性。

儲存檢查點(checkpoint)的步驟

  為了得到可以用來後續恢復模型以進一步訓練或評估的檢查點檔案(checkpoint file),我們例項化一個 tf.train.Saver。

saver = tf.train.Saver()

  在訓練迴圈中,將定期呼叫 saver.save() 方法,向訓練資料夾中寫入包含了當前所有可訓練變數值的檢查點檔案。

saver.save(sess, FLAGS.train_dir, global_step=step)

  這樣,我們以後就可以使用 saver.restore() 方法,過載模型的引數,繼續訓練。

saver.restore(sess, FLAGS.train_dir)

  

評估模型的步驟

  那麼我們的模型效能如何呢?

  首先讓我們找出那些預測正確的標籤。tf.argmax 是一個非常有用的函式,它能給出某個 tensor 物件在某一維上的其資料最大值所在的索引值。由於標籤向量是由0, 1 組成,所以最大值1所在的索引位置就是類別標籤,比如 tf.argmax(y, 1) 返回的是模型對於任一輸入 x 預測到的標籤值,而 tf.argmax(y_, 1) 代表正確的標籤,我們可以用 tf.equal 來檢測我們的預測是否真實標籤匹配(索引位置一樣表示匹配)。

correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))

  這裡返回一個布林陣列,為了計算我們分類的準確率,我們將布林值轉換為浮點數來代表對,錯,然後取平均值。例如: [True, False, True, True] 變為 [1, 0, 1, 1],計算出平均值為 0.75。

accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

  最後,我們可以計算出在測試資料上的準確率,大概是91%。

print(accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels}))

  

傳統神經網路訓練MNIST資料

1,訓練步驟

  • 輸入層:拉伸的手寫文字影象,維度為 [-1, 28*28]
  • 全連線層:500個節點,維度為[-1, 500]
  • 輸出層:分類輸出,維度為 [-1, 10]

2,訓練程式碼及其結果

  傳統的神經網路訓練程式碼:

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

BATCH_SIZE = 100  # 訓練批次
INPUT_NODE = 784  # 784=28*28 輸入節點
OUTPUT_NODE = 10  # 輸出節點
LAYER1_NODE = 784  # 層級節點
TRAIN_STEP = 10000  # 訓練步數
LEARNING_RATE = 0.01
L2NORM_RATE = 0.001


def train(mnist):
    # define input placeholder
    # None表示此張量的第一個維度可以是任何長度的
    input_x = tf.placeholder(tf.float32, [None, INPUT_NODE], name='input_x')
    input_y = tf.placeholder(tf.float32, [None, OUTPUT_NODE], name='input_y')

    # define weights and biases
    w1 = tf.Variable(tf.truncated_normal(shape=[INPUT_NODE, LAYER1_NODE], stddev=0.1))
    b1 = tf.Variable(tf.constant(0.1, shape=[LAYER1_NODE]))

    w2 = tf.Variable(tf.truncated_normal(shape=[LAYER1_NODE, OUTPUT_NODE], stddev=0.1))
    b2 = tf.Variable(tf.constant(0.1, shape=[OUTPUT_NODE]))

    layer1 = tf.nn.relu(tf.nn.xw_plus_b(input_x, w1, b1))
    y_hat = tf.nn.xw_plus_b(layer1, w2, b2)
    print("1 step ok!")

    # define loss
    cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=y_hat, labels=input_y)
    cross_entropy_mean = tf.reduce_mean(cross_entropy)
    regularization = tf.nn.l2_loss(w1) + tf.nn.l2_loss(w2) + tf.nn.l2_loss(b1) + tf.nn.l2_loss(b2)
    loss = cross_entropy_mean + L2NORM_RATE * regularization
    print("2 step ok")

    # define accuracy
    correct_predictions = tf.equal(tf.argmax(y_hat, 1), tf.argmax(input_y, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_predictions, tf.float32))
    print("3 step ok")

    # train operation
    global_step = tf.Variable(0, trainable=False)
    train_op = tf.train.AdamOptimizer(LEARNING_RATE).minimize(loss, global_step=global_step)
    print("4 step ok ")

    with tf.Session() as sess:
        tf.global_variables_initializer().run()  # 初始化建立的變數
        print("5 step OK")

        # 開始訓練模型
        for i in range(TRAIN_STEP):
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            feed_dict = {
                input_x: xs,
                input_y: ys,
            }
            _, step, train_loss, train_acc = sess.run([train_op, global_step, loss, accuracy], feed_dict=feed_dict)
            if (i % 100 == 0):
                print("After %d steps, in train data, loss is %g, accuracy is %g." % (step, train_loss, train_acc))

        test_feed = {input_x: mnist.test.images, input_y: mnist.test.labels}
        test_acc = sess.run(accuracy, feed_dict=test_feed)
        print("After %d steps, in test data, accuracy is %g." % (TRAIN_STEP, test_acc))


if __name__ == '__main__':
    mnist = input_data.read_data_sets("data", one_hot=True)
    print("0 step ok!")
    train(mnist)

  結果如下:

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
0 step ok!
1 step ok!
2 step ok
3 step ok
4 step ok 
5 step OK
After 1 steps, in train data, loss is 5.44352, accuracy is 0.03.
After 101 steps, in train data, loss is 0.665012, accuracy is 0.95.

...  ...

After 9901 steps, in train data, loss is 0.304703, accuracy is 0.96.
After 10000 steps, in test data, accuracy is 0.9612.

  

3,部分程式碼解析:

佔位符

  我們通過為輸入影象和目標輸出類別建立節點,來開始構建計算圖:

x = tf.placeholder("float", [None, 784])

  x 不是一個特定的值,而是一個佔位符 placeholder,我們在TensorFlow執行計算時輸入這個值我們希望能夠輸入任意數量的MNIST影象,每張圖展平為784維(28*28)的向量。我們用二維的浮點數張量來表示這些圖,這個張量的形狀是 [None, 784],這裡的None表示此張量的第一個維度可以是任意長度的。

  雖然palceholder 的shape引數是可選的,但是有了它,TensorFlow能夠自動捕捉因資料維度不一致導致的錯誤。

變數

  一個變數代表著TensorFlow計算圖中的一個值,能夠在計算過程中使用,甚至進行修改。在機器學習的應用過程中,模型引數一般用Variable來表示。

w1 = tf.Variable(tf.truncated_normal(shape=[INPUT_NODE, LAYER1_NODE], stddev=0.1))
b1 = tf.Variable(tf.constant(0.1, shape=[LAYER1_NODE]))

  我們的模型也需要權重值和偏置量,當然我們可以將其當做是另外的輸入(使用佔位符),這裡使用Variable。一個Variable代表一個可修改的張量,它可以用於計算輸入值,也可以在計算中被修改。對於各種機器學習英語,一般都會有模型引數,可以用Variable來表示。

  變數需要通過session初始化後,才能在session中使用,這一初始化的步驟為,為初始化指定具體值,並將其分配給每個變數,可以一次性為所有變數完成此操作。

sess.run(tf.initialize_all_variables())

  

卷積神經網路-CNN訓練MNIST資料

  在MNIST上只有96%的正確率是在是糟糕,所以這裡我們用一個稍微複雜的模型:卷積神經網路來改善效果,這裡大概會達到100%的準確率。

1,訓練步驟

  • 輸入層:手寫文字影象,維度為[-1, 28, 28, 1]
  • 卷積層1:filter的shape為5*5*32,strides為1,padding為“SAME”。卷積後維度為 [-1, 28, 28, 32]
  • 池化層2:max-pooling,ksize為2*2, 步長為2,池化後維度為 [-1, 14, 14, 32]
  • 卷積層3:filter的shape為5*5*64,strides為1,padding為“SAME”。卷積後維度為 [-1, 14, 14, 64]
  • 池化層4:max-pooling,ksize為2*2, 步長為2。池化後維度為 [-1, 7, 7, 64]
  • 池化後結果展開:將池化層4 feature map展開, [-1, 7, 7, 64] ==> [-1, 3136]
  • 全連線層5:輸入維度 [-1, 3136],輸出維度 [-1, 512]
  • 全連線層6:輸入維度 [-1, 512],輸出維度[-1, 10]。經過softmax後,得到分類結果。

2,訓練程式碼及其結果展示

  卷積神經網路的訓練程式碼如下:

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

BATCH_SIZE = 100  # 訓練批次
INPUT_NODE = 784  # 784=28*28 輸入節點
OUTPUT_NODE = 10  # 輸出節點
LAYER1_NODE = 784  # 層級節點
TRAIN_STEP = 10000  # 訓練步數
LEARNING_RATE = 0.01
L2NORM_RATE = 0.001


def train(mnist):
    # define input placeholder
    # None表示此張量的第一個維度可以是任何長度的
    input_x = tf.placeholder(tf.float32, [None, INPUT_NODE], name='input_x')
    input_y = tf.placeholder(tf.float32, [None, OUTPUT_NODE], name='input_y')

    # define weights and biases
    w1 = tf.Variable(tf.truncated_normal(shape=[INPUT_NODE, LAYER1_NODE], stddev=0.1))
    b1 = tf.Variable(tf.constant(0.1, shape=[LAYER1_NODE]))

    w2 = tf.Variable(tf.truncated_normal(shape=[LAYER1_NODE, OUTPUT_NODE], stddev=0.1))
    b2 = tf.Variable(tf.constant(0.1, shape=[OUTPUT_NODE]))

    layer1 = tf.nn.relu(tf.nn.xw_plus_b(input_x, w1, b1))
    y_hat = tf.nn.xw_plus_b(layer1, w2, b2)
    print("1 step ok!")

    # define loss
    cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=y_hat, labels=input_y)
    cross_entropy_mean = tf.reduce_mean(cross_entropy)
    regularization = tf.nn.l2_loss(w1) + tf.nn.l2_loss(w2) + tf.nn.l2_loss(b1) + tf.nn.l2_loss(b2)
    loss = cross_entropy_mean + L2NORM_RATE * regularization
    print("2 step ok")

    # define accuracy
    correct_predictions = tf.equal(tf.argmax(y_hat, 1), tf.argmax(input_y, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_predictions, tf.float32))
    print("3 step ok")

    # train operation
    global_step = tf.Variable(0, trainable=False)
    train_op = tf.train.AdamOptimizer(LEARNING_RATE).minimize(loss, global_step=global_step)
    print("4 step ok ")

    with tf.Session() as sess:
        tf.global_variables_initializer().run()  # 初始化建立的變數
        print("5 step OK")

        # 開始訓練模型
        for i in range(TRAIN_STEP):
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            feed_dict = {
                input_x: xs,
                input_y: ys,
            }
            _, step, train_loss, train_acc = sess.run([train_op, global_step, loss, accuracy], feed_dict=feed_dict)
            if (i % 100 == 0):
                print("After %d steps, in train data, loss is %g, accuracy is %g." % (step, train_loss, train_acc))

        test_feed = {input_x: mnist.test.images, input_y: mnist.test.labels}
        test_acc = sess.run(accuracy, feed_dict=test_feed)
        print("After %d steps, in test data, accuracy is %g." % (TRAIN_STEP, test_acc))


if __name__ == '__main__':
    mnist = input_data.read_data_sets("data", one_hot=True)
    print("0 step ok!")
    train(mnist)

  訓練結果如下:

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
0 step ok!
1 step ok!
2 step OK!
3 step OK!
4 step OK!
5 step ok!
6 step OK
7 step ok!
8 step OK!
9, step OK!
step:1 ,train loss:10.3222, train_acc:0.17
step:101 ,train loss:0.727434, train_acc:0.93
step:201 ,train loss:0.829298, train_acc:0.84
step:301 ,train loss:0.630073, train_acc:0.95
step:401 ,train loss:0.53797, train_acc:0.95
step:501 ,train loss:0.624309, train_acc:0.92
step:601 ,train loss:0.412459, train_acc:1
step:701 ,train loss:0.392073, train_acc:1
... ...
step:2601 ,train loss:0.173203, train_acc:1
step:2701 ,train loss:0.226434, train_acc:0.99
step:2801 ,train loss:0.172851, train_acc:0.99
step:2901 ,train loss:0.227201, train_acc:0.96
After 3000 training steps, in test dataset, loss is 0.165731, acc is 0.989

  

3,部分程式碼解析:

初始化權重

  為了不在建立模型的時候反覆做初始化的操作,我們定義兩個函式用於初始化。

# 定義W初始化函式
def get_weights(shape):
    form = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(form)


# 定義b初始化函式
def get_biases(shape):
    form = tf.constant(0.1, shape=shape)
    return tf.Variable(form)

  為了建立此模型,我們需要建立大量的權重和偏置項。這個模型中的權重在初始化時應該加入少量的噪聲來打破對稱性以及避免0梯度。由於我們使用的是ReLU神經元,因此比較好的做法是用一個較小的正數來初始化偏置項,以避免神經元節點輸出恆為0 的問題(dead neurons)。為了不在建立模型的時候反覆做初始化操作,我們定義兩個函式用於初始化。

 卷積和池化

  TensorFlow在卷積和池化上有很強的靈活性,那麼如何處理邊界?步長應該設定多大?這裡我們卷積使用的是1步長(stride size),0邊距(padding size)。寶座輸出和輸出的大小相同,我們的池化是用簡單的2*2大小的模板做 max pooling 。

第一層卷積

  現在我們開始實現第一層卷積,它是由一個卷積接一個 max pooling 完成。卷積在每個5*5的patch中算出 32個特徵。卷積的權重張量形狀是 [5, 5, 1, 32],前兩個維度是patch的大小,接著是輸出通道的數目,最後是輸出的通道數目。為了使用這一層卷積,我們將x 變成一個4d向量,其第二,第三對應圖片的寬,高。最後一維代表輸出的通道數目。

  最後我們將x 和權重向量進行卷積,加上偏置項,然後應用ReLU啟用函式,程式碼如下:

# 第一層:卷積層conv1
'''
input: [-1, 28, 28, 1]
filter: [5, 5, 32]
output: [-1, 28, 28, 32]
'''
with tf.name_scope("conv1"):
    w = get_weights([FILTER1_SIZE, FILTER1_SIZE, 1, FILTER1_NUM])
    b = get_biases([FILTER1_NUM])
    conv1_op = tf.nn.conv2d(
        input=input_x,
        filter=w,
        strides=[1, 1, 1, 1],
        padding="SAME",
        name='conv1_op'
    )

    conv1 = tf.nn.relu(tf.nn.bias_add(conv1_op, b), name='relu')

  

第二層池化

   前面也說過,我們的池化用簡單傳統的2*2大小模板做 max pooling .

# 第二層:池化層pooling2
'''
input: [-1, 28, 28, 32]
output: [-1. 14, 14, 32]
'''
with tf.name_scope('pooling2'):
    pooling2 = tf.nn.max_pool(
        value=conv1,
        ksize=[1, 2, 2, 1],
        strides=[1, 2, 2, 1],
        padding='SAME',
        name='pooling1'
    )

  

第三層卷積

  為了構建一個更深的網路,我們會把幾個類似的層堆疊起來,第二層中,每個5*5的patch 會得到64個特徵,程式碼如下:

# 第三次:卷積層conv3
'''
input" [-1, 14, 14, 32]
filter: [5, 5, 64]
output: [-1, 14, 14, 64]
'''
with tf.name_scope('conv3'):
    w = get_weights([FILTER3_SIZE, FILTER3_SIZE, FILTER1_NUM, FILTER3_NUM])
    b = get_biases([FILTER3_NUM])
    conv3_op = tf.nn.conv2d(
        input=pooling2,
        filter=w,
        strides=[1, 1, 1, 1],
        padding="SAME",
        name='conv3_op'
    )
    conv3 = tf.nn.relu(tf.nn.bias_add(conv3_op, b), name='relu')

  

第四層池化

   然後進行池化,隨機減少特徵:

# 第四層:池化層pooling4
    '''
    input: [-1, 14, 14, 64]
    output: [-1, 7, 7, 64]
    '''
    with tf.name_scope("pooling4"):
        pooling4 = tf.nn.max_pool(
            value=conv3,
            ksize=[1, 2, 2, 1],
            strides=[1, 2, 2, 1],
            padding="SAME",
            name="pooling4"
        )

  

全連線層

  現在圖片的尺寸減少到7*7,我們加入一個3136個神經元的全連線層,用於處理整個圖片。我們先把池化層輸出的張量reshape成一些向量,也就是展開它。然後輸入其權重公式,偏置項公式,然後求其L2_Loss函式,最後程式碼如下:

# 池化結果展開
'''
input: [-1, 7,7,64]
output: [-1, 3136]
'''
pooling4_flat = tf.reshape(pooling4, [-1, FLAT_SIZE])
print("5 step ok!")

# 第五層:全連線層 FC5
'''
input: [-1, 3136]  (56*56=3136)
output: [-1, 512]
'''
with tf.name_scope('fc5'):
    w = get_weights([FLAT_SIZE, FC5_SIZE])
    b = get_biases([FC5_SIZE])
    fc5 = tf.nn.relu(tf.nn.xw_plus_b(pooling4_flat, w, b, name='fc5'), name='relu')
    fc5_drop = tf.nn.dropout(fc5, droput_keep_prob)
    l2_loss += tf.nn.l2_loss(w) + tf.nn.l2_loss(b)
print("6 step OK")

# 第六層:全連線層(輸出)
'''
input: [-1, 512]
output: [-1, 10]
'''
with tf.name_scope('fc6'):
    w = get_weights([FC5_SIZE, OUTPUT_SIZE])
    b = get_biases([OUTPUT_SIZE])
    y_hat = tf.nn.xw_plus_b(fc5_drop, w, b, name='y_hat')
    l2_loss += tf.nn.l2_loss(w) + tf.nn.l2_loss(b)
print("7 step ok!")

cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=y_hat, labels=input_y)
cross_entropy_mean = tf.reduce_mean(cross_entropy)
loss = cross_entropy_mean + L2NORM_RATE * l2_loss

  

簡易前饋神經網路訓練MNIST資料

  下面利用TensorFlow使用MNIST資料集訓練並評估一個用於識別手寫數字的簡易前饋神經網路(feed-forward neural network)。

1,程式碼如下:

  其中說明一下下面兩個檔案的目的:

  • 1,full_connected_mnist.py    構建一個完全連線(fully  connected)的MNIST模型所需的程式碼
  • 2,full_connected_feed.py     利用下載的資料集訓練構建好的MNIST模型的主要程式碼

  full_connected_mnist.py程式碼如下:

# _*_coding:utf-8_*_
"""Builds the MNIST network.
# 為模型構建實現推理 損失 訓練的模型
Implements the inference/loss/training pattern for model building.
# 推理  根據執行網路的需要 構建模型向前做預測
1. inference() - Builds the model as far as required for running the network forward to make predictions.
#  將生成loss所需的層新增到推理模型中
2. loss() - Adds to the inference model the layers required to generate loss.
# 訓練  將生成和所需的ops新增到損失模型中並應用梯度
3. training() - Adds to the loss model the Ops required to generate and apply gradients.
This file is used by the various "fully_connected_*.py" files and not meant to
be run.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import math

import tensorflow as tf

# The MNIST dataset has 10 classes representing the digits 0 through 9
NUM_CLASSES = 10

# The MNIST images are always 28*28 pixels
IMAGES_SIZE = 28
IMAGES_PIXELS = IMAGES_SIZE * IMAGES_SIZE


def inference(images, hidden1_units, hidden2_units):
    """Build the MNIST model up to where it may be used for inference.
  Args:
    images: Images placeholder, from inputs().
    hidden1_units: Size of the first hidden layer.
    hidden2_units: Size of the second hidden layer.
  Returns:
    softmax_linear: Output tensor with the computed logits.
  """
    # hidden1
    with tf.name_scope('hidden1'):
        weights = tf.Variable(
            tf.truncated_normal([IMAGES_PIXELS, hidden1_units],
                                stddev=1.0 / math.sqrt(float(IMAGES_PIXELS))),
            name='weights'
        )
        biases = tf.nn.relu(tf.matmul([hidden1_units]),
                            name='weights')
        hidden1 = tf.nn.relu(tf.matmul(images, weights) + biases)
    # hidden2
    with tf.name_scope('hidden2'):
        weights = tf.Variable(
            tf.truncated_normal([hidden1_units, hidden2_units],
                                stddev=1.0 / math.sqrt(float(hidden1_units))),
            name='weights'
        )
        biases = tf.Variable(tf.zeros([hidden2_units]),
                             name='biases')
        hidden2 = tf.nn.relu(tf.matmul(hidden1, weights) + biases)
    # Linear
    with tf.name_scope('softmax_linear'):
        weights = tf.Variable(
            tf.truncated_normal([hidden2_units, NUM_CLASSES],
                                stddev=1.0 / math.sqrt(float(hidden2_units))),
            name='weights'
        )
        biases = tf.nn.relu(tf.zeros([NUM_CLASSES]),
                            name='biases')
        logits = tf.matmul(hidden2, weights) + biases
    return logits


def loss(logits, labels):
    """Calculates the loss from the logits and the labels.
  Args:
    logits: Logits tensor, float - [batch_size, NUM_CLASSES].
    labels: Labels tensor, int32 - [batch_size].
  Returns:
    loss: Loss tensor of type float.
  """
    labels = tf.to_int64(labels)
    return tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)


def training(loss, learning_rate):
    """Sets up the training Ops.
  Creates a summarizer to track the loss over time in TensorBoard.
  Creates an optimizer and applies the gradients to all trainable variables.
  The Op returned by this function is what must be passed to the
  `sess.run()` call to cause the model to train.
  Args:
    loss: Loss tensor, from loss().
    learning_rate: The learning rate to use for gradient descent.
  Returns:
    train_op: The Op for training.
  """
    # add a scalar summary for the snapshot loss
    tf.summary.scalar('loss', loss)
    # create the gradient descent optimizer with the given learning rate
    optimizer = tf.train.GradientDescentOptimizer(learning_rate)
    # create a variable to track the global step
    global_step = tf.Variable(0, name='global_step', trainable=False)
    # Use the optimizer to apply the gradients that minimize the loss
    # (and also increment the global step counter) as a single training step.
    train_op = optimizer.minimize(loss, global_step=global_step)
    return train_op


def evaluation(logits, labels):
    """Evaluate the quality of the logits at predicting the label.
    Args:
      logits: Logits tensor, float - [batch_size, NUM_CLASSES].
      labels: Labels tensor, int32 - [batch_size], with values in the
        range [0, NUM_CLASSES).
    Returns:
      A scalar int32 tensor with the number of examples (out of batch_size)
      that were predicted correctly.
    """
    # For a classifier model, we can use the in_top_k Op.
    # It returns a bool tensor with shape [batch_size] that is true for
    # the examples where the label is in the top k (here k=1)
    # of all logits for that example.
    correct = tf.nn.in_top_k(logits, labels, 1)
    # Return the number of true entries.
    return tf.reduce_sum(tf.cast(correct, tf.int32))

  full_connected_feed.py程式碼如下:

#_*_coding:utf-8_*_
'''Trains and Evaluates the MNIST  network using a feed dictionary'''
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

# pulint:disable=missing-docstring
import argparse
import os
import sys
import time

from six.moves import xrange   # pylint: disable=redefined-builtin
import tensorflow as tf

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

# Basic model parameters as external flags
FLAGS = None

def placeholder_inputs(batch__size):
    '''
        生成一個佔位符變數來表述輸入張量
        這些佔位符被模型構建的其餘部分用作輸入程式碼,並將從下面 .run() 迴圈中下載資料提供
    :param batch__size:
    :return: 返回一個 影象的佔位符,一個標籤佔位符
    '''
    # Note that the shapes of the placeholders match the shapes of the full
    # image and label tensors, except the first dimension is now batch_size
    # rather than the full size of the train or test data sets.
    images_placeholder = tf.placeholder(tf.float32, shape=(batch__size, mnist.IMAGE_PIXELS))
    labels_placeholder = tf.placeholder(tf.int32, shape=(batch__size))
    return images_placeholder, labels_placeholder

def fill_feed_dict(data_set, images_pl, labels_pl):
    """Fills the feed_dict for training the given step.
      A feed_dict takes the form of:
      feed_dict = {
          <placeholder>: <tensor of values to be passed for placeholder>,
          ....
      }
      Args:
        data_set: The set of images and labels, from input_data.read_data_sets()
        images_pl: The images placeholder, from placeholder_inputs().
        labels_pl: The labels placeholder, from placeholder_inputs().
      Returns:
        feed_dict: The feed dictionary mapping from placeholders to values.
      """
    # Create the feed_dict for the placeholders filled with the next
    # `batch size` examples.
    images_feed, labels_feed = data_set.next_batch(FLAGS.batch_size, FLAGS.fake_data)
    feed_dict = {
        images_pl: images_feed,
        labels_pl: labels_feed,
    }
    return feed_dict

def do_evaluation(sess, eval_correct, images_placeholder, labels_placeholder, data_set):
    '''
        對陣個資料進行評估
    :param sess: The session in which the model has been trained.
    :param eval_correct: The Tensor that returns the number of correct predictions.
    :param images_placeholder: The images placeholder.
    :param labels_placeholder: The labels placeholder.
    :param data_set: The set of images and labels to evaluate, from
      input_data.read_data_sets().
    :return:
    '''
    # and run one epoch of eval
    true_count = 0  # counts the number of correct predictions
    steps_per_epoch = data_set.num_examples // FLAGS.batch_size
    num_examples = steps_per_epoch * FLAGS.batch_size
    for step in range(steps_per_epoch):
        feed_dict = fill_feed_dict(data_set, images_placeholder, labels_placeholder)
        true_count += sess.run(eval_correct, feed_dict=feed_dict)
    precision = float(true_count) / num_examples
    print('Num examples: %d  Num correct: %d  Precision @ 1: %0.04f' %
          (num_examples, true_count, precision))

def run_training():
    '''Train MNIST for  a number of steps'''
    # get the sets of images and labels for training  validation and test on MNIST
    data_sets = input_data.read_data_sets(FLAGS.input_data_dir, FLAGS.fake_data)

    # Tell Tensorflow that the model will be built into the default graph
    with tf.Graph().as_default():
        #Generate placeholders for the images and labels
        images_placeholder, labels_placeholder = placeholder_inputs(FLAGS.batch_size)

    # build a graph that computes predictions from the inference model
    logits = mnist.inference(images_placeholder, FLAGS.hidden1, FLAGS.hidden2)

    # add to graph the ops for loss calculation
    loss = mnist.loss(logits, labels_placeholder)

    # add to the graph the ops that calculate and apply gradients
    train_op = mnist.training(loss, FLAGS.learning_rate)

    # add the Op to compare the logits to the labels during evaluation
    eval_correct = mnist.evaluation(logits, labels_placeholder)

    # bulid the summary Tensor based on the TF collection of Summaries
    summary = tf.summary.merge_all()

    # add the variable initializer Op
    init = tf.global_variables_initializer()

    # create a saver fro writing training checkpoint
    saver = tf.train.Saver()

    # create a session for running Ops on the Graph
    sess = tf.compat.v1.Session()

    # Instantiate a SummaryWriter to output summaries and the graph
    summary_writer = tf.summary.FileWriter(FLAGS.log_dir, sess.graph)

    # and then after everything is built

    # run the Op to initialize the variables
    sess.run(init)

    # start the training loop
    for step in range(FLAGS.max_steps):
        start_time = time.time()

        # Fill a feed dictionary with the actual set of images and labels
        # for this particular training step.
        feed_dict = fill_feed_dict(data_sets.train,
                                   images_placeholder,
                                   labels_placeholder)

        # Run one step of the model.  The return values are the activations
        # from the `train_op` (which is discarded) and the `loss` Op.  To
        # inspect the values of your Ops or variables, you may include them
        # in the list passed to sess.run() and the value tensors will be
        # returned in the tuple from the call.
        _, loss_value = sess.run([train_op, loss],
                                 feed_dict=feed_dict)

        duration = time.time() - start_time

        # write the summaries and print an overview fairly often
        if step % 100 == 0:
            # Print status to stdout.
            print('Step %d: loss = %.2f (%.3f sec)' % (step, loss_value, duration))
            # Update the events file.
            summary_str = sess.run(summary, feed_dict=feed_dict)
            summary_writer.add_summary(summary_str, step)
            summary_writer.flush()

        # Save a checkpoint and evaluate the model periodically.
        if (step + 1) % 1000 == 0 or (step + 1) == FLAGS.max_steps:
            checkpoint_file = os.path.join(FLAGS.log_dir, 'model.ckpt')
            saver.save(sess, checkpoint_file, global_step=step)
            # Evaluate against the training set.
            print('Training Data Eval:')
            do_evaluation(sess,
                    eval_correct,
                    images_placeholder,
                    labels_placeholder,
                    data_sets.train)
            # Evaluate against the validation set.
            print('Validation Data Eval:')
            do_evaluation(sess,
                    eval_correct,
                    images_placeholder,
                    labels_placeholder,
                    data_sets.validation)
            # Evaluate against the test set.
            print('Test Data Eval:')
            do_evaluation(sess,
                    eval_correct,
                    images_placeholder,
                    labels_placeholder,
                    data_sets.test)


def main():
    if tf.gfile.Exists(FLAGS.log_dir):
        tf.gfile.DeleteRecursively(FLAGS.log_dir)
    tf.gfile.MakeDirs(FLAGS.log_dir)
    run_training()


def main(_):
  if tf.gfile.Exists(FLAGS.log_dir):
    tf.gfile.DeleteRecursively(FLAGS.log_dir)
  tf.gfile.MakeDirs(FLAGS.log_dir)
  run_training()


if __name__ == '__main__':
  parser = argparse.ArgumentParser()
  parser.add_argument(
      '--learning_rate',
      type=float,
      default=0.01,
      help='Initial learning rate.'
  )
  parser.add_argument(
      '--max_steps',
      type=int,
      default=2000,
      help='Number of steps to run trainer.'
  )
  parser.add_argument(
      '--hidden1',
      type=int,
      default=128,
      help='Number of units in hidden layer 1.'
  )
  parser.add_argument(
      '--hidden2',
      type=int,
      default=32,
      help='Number of units in hidden layer 2.'
  )
  parser.add_argument(
      '--batch_size',
      type=int,
      default=100,
      help='Batch size.  Must divide evenly into the dataset sizes.'
  )
  parser.add_argument(
      '--input_data_dir',
      type=str,
      default=os.path.join(os.getenv('TEST_TMPDIR', ''),
                           'input_data'),
      help='Directory to put the input data.'
  )
  parser.add_argument(
      '--log_dir',
      type=str,
      default=os.path.join(os.getenv('TEST_TMPDIR', ''),
                           'fully_connected_feed'),
      help='Directory to put the log data.'
  )
  parser.add_argument(
      '--fake_data',
      default=False,
      help='If true, uses fake data for unit testing.',
      action='store_true'
  )

  FLAGS, unparsed = parser.parse_known_args()
  tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)

  注意事項:

此處引數注意修改!!

  結果展示:

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
Step 0: loss = 2.30 (0.114 sec)
Step 100: loss = 2.11 (0.002 sec)
Step 200: loss = 1.92 (0.002 sec)
Step 300: loss = 1.49 (0.002 sec)
Step 400: loss = 1.22 (0.002 sec)
Step 500: loss = 0.95 (0.003 sec)
Step 600: loss = 0.73 (0.003 sec)
Step 700: loss = 0.58 (0.004 sec)
Step 800: loss = 0.58 (0.002 sec)
Step 900: loss = 0.50 (0.002 sec)
Training Data Eval:
Num examples: 55000  Num correct: 47310  Precision @ 1: 0.8602
Validation Data Eval:
Num examples: 5000  Num correct: 4365  Precision @ 1: 0.8730
Test Data Eval:
Num examples: 10000  Num correct: 8628  Precision @ 1: 0.8628
Step 1000: loss = 0.51 (0.017 sec)
Step 1100: loss = 0.46 (0.104 sec)
Step 1200: loss = 0.50 (0.002 sec)
Step 1300: loss = 0.40 (0.003 sec)
Step 1400: loss = 0.57 (0.003 sec)
Step 1500: loss = 0.40 (0.003 sec)
Step 1600: loss = 0.42 (0.003 sec)
Step 1700: loss = 0.44 (0.003 sec)
Step 1800: loss = 0.41 (0.002 sec)
Step 1900: loss = 0.37 (0.000 sec)
Training Data Eval:
Num examples: 55000  Num correct: 49375  Precision @ 1: 0.8977
Validation Data Eval:
Num examples: 5000  Num correct: 4529  Precision @ 1: 0.9058
Test Data Eval:
Num examples: 10000  Num correct: 8985  Precision @ 1: 0.8985

  準確率比起卷積神經網路還是差點,但是效果還是不錯的。這裡我們主要是學習這個過程,瞭解深度學習訓練模型的過程,所以還好。

 

 2,部分程式碼解析

下載

  在run_training() 方法的一開始, input_data.read_data_sets() 函式會確保我們的本地訓練資料夾中,是否已經下載了正確的資料,然後將這些資料解壓並返回一個含有DataSet例項的字典。

data_sets = input_data.read_data_sets(FLAGS.train_dir, FLAGS.fake_data)

  注意:fake_data標記是用於單元測試的。

  資料展示如下:

輸入與佔位符(inputs and placeholders)

  placeholder_inputs() 函式將生成兩個 tf.placeholder 操作,定義傳入圖表中的 shape 引數, shape引數中包括 batch_size 值,後續還會將實際的訓練用例傳入圖表。

images_placeholder = tf.placeholder(tf.float32, shape=(batch_size,
                                                       IMAGE_PIXELS))

labels_placeholder = tf.placeholder(tf.int32, shape=(batch_size))

  在訓練迴圈(training loop)的後續步驟中,傳入的整個影象和標籤資料集會被切片,以符合每一個操作所設定的batch_size值,佔位符操作將會填補以符合這個batch_size值。然後使用feed_dict引數,將資料傳入sess.run()函式。

構建圖表(Build the Graph)

  在為資料建立佔位符之後,就可以執行full_connected_mnist.py檔案,經過三階段的模式函式操作: inference()   loss()   training()   圖表就構建完成了。

  • inference():儘可能的構建好圖表,滿足促使神經網路向前反饋並作出預測的要求。
  • loss():往inference 圖表中新增生成損失(loss)所需要的操作(ops)
  • training() :往損失函式中新增計算並應用梯度(gradients)所需要的操作

 推理(Inference)

  inference()函式會盡可能的構建圖表,做到返回包含了預測結果(output prediction)的Tensor。

  它接受影象佔位符為輸入,在此基礎上藉助ReLU(Rectified Linear Units)啟用函式,構建一對完全連線層(layers),以及一個有著十個節點(node),指明瞭輸出 logits 模型的線性層。

  每一層都建立於一個唯一的 tf.name_scope之下,建立於該作用域之下的所有元素都將帶有其字首。

with tf.name_scope('hidden1') as scope:

  在定義的作用域中,每一層所使用的權重和偏差都在 tf.Variable例項中生成,並且包含了各自期望的shape。

weights = tf.Variable(
    tf.truncated_normal([IMAGE_PIXELS, hidden1_units],
                        stddev=1.0 / math.sqrt(float(IMAGE_PIXELS))),
    name='weights')

biases = tf.Variable(tf.zeros([hidden1_units]),
                     name='biases')

  例如,當這些層是在hidden1 作用域下生成時,賦予權重變數的獨特名稱將會是“hidden1/weights”。

  每個變數在構建時,都會獲得初始化操作(initializer ops)。

  在這種最常見的情況下,通過tf.truncated_normal函式初始化權重變數,給賦予的shape則是一個二維tensor,其中第一個維度代表該層中權重變數所連線(connect from)的單元數量,第二個維度代表該層中權重變數所連線到的(connect to)單元數量。對於名叫hidden1的第一層,相應的維度則是[IMAGE_PIXELS, hidden1_units],因為權重變數將影象輸入連線到了hidden1層。tf.truncated_normal初始函式將根據所得到的均值和標準差,生成一個隨機分佈。

  然後,通過tf.zeros函式初始化偏差變數(biases),確保所有偏差的起始值都是0,而它們的shape則是其在該層中所接到的(connect to)單元數量。

  圖表的三個主要操作,分別是兩個tf.nn.relu操作,它們中嵌入了隱藏層所需的tf.matmul;以及logits模型所需的另外一個tf.matmul。三者依次生成,各自的tf.Variable例項則與輸入佔位符或下一層的輸出tensor所連線。

hidden1 = tf.nn.relu(tf.matmul(images, weights) + biases)

hidden2 = tf.nn.relu(tf.matmul(hidden1, weights) + biases)

logits = tf.matmul(hidden2, weights) + biases

  最後,程式會返回包含了輸出結果的logits Tensor。

 損失(loss)

  loss()函式通過新增所需要的損失操作,進一步構建圖表。

   首先,labels_placeholder中的值,將被編碼為一個含有1-hot values 的Tensor。例如,如果類識別符號為“3”,那麼該值就會被轉換為:[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]

batch_size = tf.size(labels)
labels = tf.expand_dims(labels, 1)
indices = tf.expand_dims(tf.range(0, batch_size, 1), 1)
concated = tf.concat(1, [indices, labels])
onehot_labels = tf.sparse_to_dense(
    concated, tf.pack([batch_size, NUM_CLASSES]), 1.0, 0.0)

  之後,又新增一個 tf.nn.softmax_cross_entropy_with_logits操作,用來比較 inference() 函式與 1-hot 標籤所輸出的 logits Tensor。

cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits,
                                                        onehot_labels,
                                                        name='xentropy')

  然後,使用tf.reduce_mean() 函式,計算 batch 維度(第一維度)下交叉熵(cross entropy)的平均值,將該值作為總損失。

loss = tf.reduce_mean(cross_entropy, name='xentropy_mean')

  最後程式會返回包含了損失值的Tensor。

訓練training()

  training() 函式新增了通過梯度下降(gradient descent)將損失最小化所需要的操作。

  首先,該函式從loss() 函式中獲得損失Tensor,將其交給 tf.scalar_summary,後者在於SummaryWriter配合使用時,可以向事件檔案(events file)中生成彙總值(summary values)。每次寫入彙總值時,它都會釋放損失Tensor的當前值(snapshot value)。

tf.scalar_summary(loss.op.name, loss)

  接下來,我們例項化一個tf.train.GradientDescentOptimizer,負責按照所要求的學習效率(learning rate)應用梯度下降法(gradients)。

optimizer = tf.train.GradientDescentOptimizer(FLAGS.learning_rate)

  之後,我們生成一個變數用於儲存全域性訓練步驟(global training step)的數值,並使用minimize()函式更新系統中的三角權重(triangle weights)、增加全域性步驟的操作。根據慣例,這個操作被稱為 train_op,是TensorFlow會話(session)誘發一個完整訓練步驟所必須執行的操作(見下文)。

global_step = tf.Variable(0, name='global_step', trainable=False)
train_op = optimizer.minimize(loss, global_step=global_step)

  最後,程式返回包含了訓練操作(training op)輸出結果的Tensor。

圖表

  在 run_training() 這個函式的一開始,是一個python語言的with命令,這個命令表明所有已經構建的操作都要與預設的 tf.Graph 全域性例項關聯起來。

with tf.Graph().as_default():

  tf.Graph例項是一系列可以作為整體執行的操作。TensorFlow的大部分場景只需要依賴預設圖表一個例項即可。

會話

  完成全部的構建準備,生成全部所需的操作之後,我們就可以建立一個tf.Session,用於執行圖表。

sess = tf.Session()

  當然,我們也可以利用with 程式碼塊生成Session,限制作用域:

with tf.Session() as sess:

  Session函式中沒有傳入引數,表明該程式碼將會依附於(如果還沒有建立會話,則會建立新的會話)預設的本地會話。

  生成會話之後,所有tf.Variable例項都會立即通過呼叫各自初始化操作中的sess.run()函式進行初始化。

init = tf.initialize_all_variables()
sess.run(init)

  sess.run()方法將會執行圖表中與作為引數傳入的操作相對應的完整子集。在初次呼叫時,init操作只包含了變數初始化程式tf.group。圖表的其他部分不會在這裡,而是在下面的訓練迴圈執行。

訓練迴圈

  完成會話中變數的初始化之後,就可以開始訓練了。

  訓練的每一步都是通過使用者程式碼控制,而能實現有效訓練的最簡單迴圈就是:

for step in range(max_steps):
    sess.run(train_op)

  但是我們這裡需要更復雜一點,因為我們必須把輸入的資料根據每一步的情況進行切分,以匹配之前生成的佔位符。

向圖表提供反饋

  執行每一步時,我們的程式碼會生成一個反饋字典(feed dictionary),其中包含對應步驟中訓練所要使用的例子,這些例子的雜湊鍵就是其所代表的佔位符操作。

  fill_feed_dict函式會查詢給定的DataSet,索要下一批次batch_size的影象和標籤,與佔位符相匹配的Tensor則會包含下一批次的影象和標籤。

images_feed, labels_feed = data_set.next_batch(FLAGS.batch_size)

  然後,以佔位符為雜湊鍵,建立一個python字典物件,鍵值則是其代表的反饋Tensor。

feed_dict = {
    images_placeholder: images_feed,
    labels_placeholder: labels_feed,
}

  這個字典隨後作為feed_dict引數,傳入sess.run()函式中,為這一步的訓練提供輸入樣例。

檢查狀態

  在執行sess.run函式時,要在程式碼中明確其需要獲取的兩個值:[train_op, loss]

for step in range(FLAGS.max_steps):
    feed_dict = fill_feed_dict(data_sets.train,
                               images_placeholder,
                               labels_placeholder)
    _, loss_value = sess.run([train_op, loss],
                             feed_dict=feed_dict)

  因為要獲取這兩個值,sess.run()會返回一個有兩個元素的元組。其中每一個Tensor物件,對應了返回的元組中的numpy陣列,而這些陣列中包含了當前這步訓練中對應Tensor的值。由於train_op並不會產生輸出,其在返回的元祖中的對應元素就是None,所以會被拋棄。但是,如果模型在訓練中出現偏差,loss Tensor的值可能會變成NaN,所以我們要獲取它的值,並記錄下來。

  假設訓練一切正常,沒有出現NaN,訓練迴圈會每隔100個訓練步驟,就列印一行簡單的狀態文字,告知使用者當前的訓練狀態。

if step % 100 == 0:
    print 'Step %d: loss = %.2f (%.3f sec)' % (step, loss_value, duration)

  

狀態視覺化

  為了釋放TensorBoard所使用的事件檔案(events file),所有的即時資料(在這裡只有一個)都要在圖表構建階段合併至一個操作(op)中。

summary_op = tf.merge_all_summaries()

  在建立好會話(session)之後,可以例項化一個tf.train.SummaryWriter,用於寫入包含了圖表本身和即時資料具體值的事件檔案。

summary_writer = tf.train.SummaryWriter(FLAGS.train_dir,
                                        graph_def=sess.graph_def)

  最後,每次執行summary_op時,都會往事件檔案中寫入最新的即時資料,函式的輸出會傳入事件檔案讀寫器(writer)的add_summary()函式。

summary_str = sess.run(summary_op, feed_dict=feed_dict)
summary_writer.add_summary(summary_str, step)

  

執行sess會話的下面程式碼報錯:

    sess = tf.compat.v1.Session()

AttributeError: module 'tensorflow.python.util.compat' has no attribute 'v1'

  解決方法:

    sess = tf.Session()

  

 

參考文獻:https://blog.csdn.net/GeForce_GTX1080Ti/article/details/80119194

TensorFlow官網(http://www.tensorfly.cn/tfdoc/tutorials/mnist_beginners.html

http://wiki.jikexueyuan.com/project/tensorflow-zh/tutorials/mnist_tf.html

https://github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/tutorials/mnist/fully_connected_feed.py

此處是做以筆記,記錄學習過程,若有侵權,請聯絡我

相關文章