【Get】用深度學習識別手寫數字

前置參考讀物: python

《機器學習,看完就明白了》傳送門git

獲取數據源

訓練數據直接使用開源的手寫數據集MNIST。github

MNIST數據集是一個開源的手寫數據庫。它提供了大量的數據樣本做爲訓練集和驗證集。這個數據集擁有 60000 個訓練樣本,和 10000 個測試樣本。 web

MNIST 官網(一個很 low 的網站)傳送門:http://yann.lecun.com/exdb/mnist/算法

image_mnist_web

就是上面那幾個紅色的、帶下劃線的!!!數據庫

若是你下載的 tensorflow 包括了 mnist 的例子的話,很幸運,你能夠直接引用到數據加載的工具: 數組

from tensorflow.contrib.learn.python.learn.datasets.mnist import read_data_sets
複製代碼

不然的話,你須要本身寫一個工具,用於訓練數據的下載和讀取。但你能夠在這個地址中找到這段代碼。 bash

【mnist.py 傳送門】網絡

咱們實際在外面,須要調用的就是 read_data_sets 這個函數。 app

關於數據下載,你能夠直接到 CoorChice 給出的 MNIST 官網上直接下好數據,而後使用 read_data_sets函數從儲存路徑讀取就行。

開始構建網絡

定義幾個輔助函數

首先,先抽象出幾個函數,用來建立 權重、偏置量、卷積核和池化層。它們是這樣的。

# 定義一個用於建立 權重 變量的函數
def weight_variable(shape):
    initial = tf.truncated_normal(shape, stddev=0.1)
    var = tf.Variable(initial)
    # 記錄每個權重,由於後面要使用正則化
    # 至於緣由,後面具體再說
    tf.add_to_collection(tf.GraphKeys.WEIGHTS, var)
    return var


# 定義一個用於建立 偏置量 變量的函數
def bias_variable(shape):
    # 初始化值爲0.1
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)


# 構建卷積函數
# 該卷積核每次在長、寬上移動一個步長,padding採用"SAME"策略
def conv2d(x, W):
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')


# 該池化核大小爲2x2,長、寬上步長爲2,padding採用"SAME"策略
def max_pool_2x2(x):
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
複製代碼

在上面這段代碼中,CoorChice 有必要解釋解釋權重 weight 的生成。

tf.truncated_normal(shape, mean, stddev)
複製代碼

這個函數會從一個正太分佈(該正太分佈的均值爲 mean,默認爲0)中產生隨機數,什麼意思呢?

image_truncated

看上圖,stddev 表示標準差,就是圖中橫座標上的取值,也就是說,這個函數會從 [mean ± 2stddev] 的範圍內產生隨機數,若是產生的隨機數不在這個範圍內,就會再次隨機產生,直到隨機數落在這個範圍內。

至於爲何要用這種方法來初始化 weight 呢?這是根據各位大牛們的經驗所得,使用這種方式產生的 weight 不容易出現梯度消失或者爆炸的問題。

反正這也是一門玄學。

構建網絡結構

先看一下完整代碼,咱們在逐一解釋。

import tensorflow as tf

class CnnModel_MNIST:
    def __init__(self):
        # 建立佔位tensor,用於裝載數據
        self.x_data = tf.placeholder(tf.float32, [None, 784])
        self.y_data = tf.placeholder(tf.float32, [None, 10])

        # -----------------------構建第一層卷積-----------------------
        with tf.name_scope('hidden1'):
            W_conv1 = weight_variable([5, 5, 1, 32])
            b_conv1 = bias_variable([32])
            x_image = tf.reshape(self.x_data, [-1, 28, 28, 1])
            h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
            h_pool1 = max_pool_2x2(h_conv1)

        # -----------------------構建第二層卷積------------------------
        with tf.name_scope('hidden2'):
            W_conv2 = weight_variable([5, 5, 32, 64])
            b_conv2 = bias_variable([64])
            h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
            h_pool2 = max_pool_2x2(h_conv2)

        # -----------------------構建密集(全)連接層------------------------
        with tf.name_scope('FC1'):
            W_fc1 = weight_variable([7 * 7 * 64, 1024])
            b_fc1 = bias_variable([1024])
            h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
            h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

        # -----------------------加入Dropout------------------------
        with tf.name_scope('dropout'):
            self.keep_prob = tf.placeholder(tf.float32)
            h_fc1_drop = tf.nn.dropout(h_fc1, self.keep_prob)

        # -----------------------構建輸出層------------------------
        with tf.name_scope('output'):
            W_fc2 = weight_variable([1024, 10])
            b_fc2 = bias_variable([10])

            self.y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

複製代碼

建立數據輸入的佔位符

x_data = tf.placeholder(tf.float32, [None, 784])
y_data = tf.placeholder(tf.float32, [None, 10])
複製代碼

咱們須要先建立兩個佔位變量來容納待會兒輸入的數據,而後把它們帶着到網絡中進行運算。

  • x_data 是用來容納訓練數據的,它的 shape 形狀在這裏被莫名其妙的定義爲 [None, 784]。其實這裏是有學問的,且聽 CoorChice 慢慢道來。

    第一維定義爲 None 表示不肯定,後面會被實際的數值替代。這樣作是由於咱們一開始並不知道會有多少張圖片數據會被輸入。或者當咱們採起 mini-batch 的梯度降低策略時,能夠自由的設置 batch 的大小。

    第二個維度定義爲 784,這徹底是由於咱們數據集中的圖片大小被統一爲了 28*28 。

  • y_data 是用來容納訓練數據的標籤的,它的 shape 之因此被定義爲 [None, 10] ,是由於它的第一維爲 None 與 x_data 具備相同理由,而第二維爲 10 是由於咱們總共有 0~9 共 10 種類別的數字。

構建第一層網絡

W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
x_image = tf.reshape(self.x_data, [-1, 28, 28, 1])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
複製代碼

權值 w 的構建,

W_conv1 = weight_variable([5, 5, 1, 32])
複製代碼

傳入的數組表示 w 的形狀,其實就定義了該層卷積核的大小和數量:

  • 每一個卷積核的大小爲 5x5

  • 輸入通道數爲 1,由於咱們用的圖片是灰度圖。若是是用不帶透明通道的 rgb 彩色圖該值就設爲 3,若是在帶了透明通道的 rgba 彩色圖該值就設爲 4

  • 該層輸出通道數爲 32,即該層有 32 個卷積核。對於隱藏層中每一個卷積層中的卷積核大小如何的肯定,再次強調,這是一個玄學,憑感受設置。最靠譜的方法是用一些公開的網絡模型,照着巨人們的設置,畢竟是通過反覆嘗試論證出來的。

偏置量 b 的構建

b_conv1 = bias_variable([32])
複製代碼

b 的大小和卷積核個數相對應就好了。什麼意思呢?就是每一個卷積核和輸入卷積後,再加上一個偏置量就好。回顧一下卷積核的結構。

wx + b
複製代碼

改變輸入數據的形狀

x_image = tf.reshape(self.x_data, [-1, 28, 28, 1])
複製代碼

這行代碼做用是改變咱們輸入數據的形狀,讓它能夠和咱們的卷積核進行卷積。由於輸入的數據 x_data 是一個 Nx784 的張量,因此須要先變爲 M x 28 x 28 x 1 的形狀才能進行運算。

第一個維度的 -1 表示大小待定,優先知足後面 3 個維度,最後再計算第一個維度的大小。也就是這樣的:N x 784 / (28 x 28 x 1)

第2、第三維度實際上表示每張輸入圖片的大小需改成 28 x 28。

第四維度表示通道數,灰度爲 1,rgb 爲 3,rgba 爲 4 。

構建卷積並加上激活函數

h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
複製代碼

這行代碼包含了 3 個操做:

  • conv2d(x_image, W_conv1),將輸入與該層的全部卷積核進行卷積運算

這是一個卷積運算的動態示意圖。

每次從輸入中取出一個和卷積核大小相同的張量進行卷積運算。

image_conv_compute

其意義就是,將卷積核旋轉 180 度,而後再輸入進行計算。

image_conv_compute2

因爲涉及到旋轉操做,因此卷積核的大小一般會取奇數,這樣能讓卷積核有一個明顯的旋轉中心。

  • conv2d(x_image, W_conv1) + b_conv1,每次卷積後加上一個偏置量

  • tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1),最後加上一個激活函數,增長非線性的變換

此處使用的激活函數是比較流行的 ReLu 函數,它的的函數式很簡單:

y = max(x, 0)
複製代碼

圖像也比較直觀:

image

ReLu激活函數的好處在於,因爲它在第一象限就是 x,因此可以大量的減小計算,從而加速收斂。同時它天生就能減少梯度消失發生的可能性,不過梯度爆炸仍是可能會發生。

池化

h_pool1 = max_pool_2x2(h_conv1)
複製代碼

最後,再加上一個池化層,可以壓縮權值數量,減少最後模型的大小,提升模型的泛化性。

這裏使用了一個 2x2 的 max_pooling,且步長取 1。能夠回到上面定義的 max_pool_2x2(x) 函數回顧一下。

max_pooling 實際就是取一個 2x2 張量中的最大值,這樣可以過濾掉一些並非很重要的噪聲。


構建第二層網絡

W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
複製代碼

第二層和第一層構建的套路基本是同樣的,其實咱們再多加幾層也都是按照這個模式走的。

須要注意的就是第二層中,w 的輸入通道數爲上一層最後的輸出通道數,也就是 hpool1 的輸出,這裏直接算出了是 32,由於上面已經定義好了啊。

若是不肯定的時候,能夠經過這種方式來肯定輸入通道數:

in = h_pool1.get_shape()[-1].value
複製代碼

[-1] 表示無論 h_pool1 的形狀如何,都取它最後一維的大小。

構建全連接層

W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
複製代碼

全鏈接層則起到將學到的「分佈式特徵表示」映射到樣本標記空間的做用

這句話看起來很具體,然而並不知道它在說些什麼。

那麼,咱們試着理解一下,先看看全連接層的結構。

定義 w 和 b

W_fc1 = weight_variable([7 * 7 * 64, 1024])
複製代碼

上面這行代碼,將fc1 的 w 形狀設定爲 [7 * 7 * 64, 1024]。第一個維度大小爲 7 * 7 * 64,由於通過前面一層的卷積層的池化層後,輸出的就是一個 7 * 7 * 64 的張量,因此這裏的輸入就是上一層的輸出。嗯,這一點在整個神經網絡裏都是這樣。

第二個維度大小爲 1024。這個就有點詭異了!爲何是這個數值?

實際上,這是咱們能夠本身隨便設定的,它表示該全連接層的神經元個數,數量越多計算耗時越長,可是數量過少,對前面提取出來的特徵的分類效果又不夠好。

所以,咱們的全連接層擁有的特徵數量就是 7 x 7 x 64 x 1024。數量仍是比較驚人的。

b_fc1 = bias_variable([1024])
複製代碼

一樣,b 的數量須要對應於 w 的最後一個維度,也就是一個神經元對應一個偏置量b。

變形輸入

h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
複製代碼

爲了能和上面定義的全連接層特徵張量相乘,須要把輸入的張量變形。其實對於每個 7 x 7 x64 的輸入張量而言,就是將它們展平成一個一維的向量。

第一個維度取 -1 同上面提到過的意思同樣,最後肯定這個維度。實際上就是最後一個池化層輸出的數量。

構建線性函數,加上 ReLu 函數

h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
複製代碼

上面的代碼仍是按照 wx + b 的線性公式構建函數,而後加上 ReLu 函數增長非線性變化。

這一波計算,清晰的表達出了,當一個來自最後一層池化層的 [7 x 7 x 64] 的輸出通過全連接層後,就被平鋪成了一個 [1 x 1024] 的向量。至關於把前面分散的特徵所有連接在了一塊兒,這也就是爲何說前面的卷積核是從局部觀察,而全連接層是從全局的視野去觀察的,由於它這一層整合全部前面的特徵。整合了全部的特徵,咱們就能夠進行後續的分類操做了。

至此,相信你對全連接層有了一個大概的瞭解。從中能夠看出一些貓膩來。

  • 全連接層增長模型的複雜度,由於增長了不少神經元來擴充特徵集。也所以,它有助於提高模型的準確率。

  • 但隨着特徵數量的爆炸式增長,訓練速度必然會變慢。並且若是全連接層設置的神經元數量過多,會出現過擬合的現象。因此,須要適當的設置,不能一味的貪多。

加入 Dropout

self.keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, self.keep_prob)
複製代碼

在全連接層以後,每每會跟着 Dropout 操做。因爲在神經網絡中,神經元的個數很是爆炸,每每會產生過擬合的問題,特別是引入了全連接層這種操做以後。因此咱們須要作些什麼來讓過擬合發生的機率減少一些。

Dropout 就是一種很流行的方案。

h_fc1_drop = tf.nn.dropout(h_fc1, self.keep_prob)
複製代碼

這行代碼的第二個參數,咱們能夠動態的傳入一個數值,表示每一個神經元有多大的機率失效,其實就是不參與計算。

形象點描述就是這樣一個過程。每一個神經元進行運算前,都按照設置的 keep_prob 機率決定它要不要參與計算。好比 keep_prob=0.5 的話,表示每一個神經元有 50% 的機率失效。

不難看出,Dropout 操做可以必定程度上加快訓練速度,同時下降過擬合的可能性。

構建輸出層

W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])

self.y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)
複製代碼

最後一層輸出層,咱們就能夠獲得一個結果了。

輸出層的函數結構仍然是 wx+b 的線性結構。

這裏有必要解釋一下輸出的 w 的形狀。第一個維度不用說,就是上一層輸出的結果,這裏上一層是從全連接層輸出出來 1024 個神經元。第二個維度是咱們分類的總類目數,因爲識別的是 0-9 的手寫數字,因此總共有 10 個類別。

構建好線性函數後,在加入一個非線性的激活函數。在分類場景中, Softmax 是一個在輸出層被普遍使用的激活函數。

這是 Softmax 的公式,很容易看出,它的值域爲 [0, 1]。這就比較厲害,直接就轉成一個個的機率了。就是說,每一個輸出對應類別的機率是多少。

再這個看看形象的示意圖理解一下。

這,就是網絡!

image_graph

最後,這個網絡就成型了。這實際上是一個很簡單的網絡,總共 4 層,包含兩個卷積層,一個全連接層和一個輸出層。

從圖中能夠清晰的看到數據的流向。

就要開始訓練了

# coding=utf-8

import time
from input_data import *
from cnn_utils import *
from cnn_model import CnnMnistNetwork

train_times = 35000
base_path = "../mnist/"
save_path = base_path + str(train_times) + "/"

# 讀取數據
mnist = read_data_sets("MNIST_data/", one_hot=True)

# 建立網絡
network = CnnMnistNetwork()
x_data = network.x_data
y_data = network.y_data
y_conv = network.y_conv
keep_prob = network.keep_prob
# ------------------------構建損失函數---------------------
with tf.name_scope("cross_entropy"):
    # 建立正則化對象,此處使用的是 L2 範數
    regularization = tf.contrib.layers.l2_regularizer(scale=(5.0 / 50000))
    # 應用正則化到參數集上
    reg_term = tf.contrib.layers.apply_regularization(regularization)
    # 在損失函數中加入正則化項
    cross_entropy = (-tf.reduce_sum(y_data * tf.log(y_conv)) + reg_term)
tf.scalar_summary('loss', cross_entropy)
with tf.name_scope("train_step"):
    # 使用 Adam 進行損失函數的梯度降低求解
    train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

# ------------------------構建模型評估函數---------------------
with tf.name_scope("accuracy"):
    with tf.name_scope("correct_prediction"):
        # 對比預測結果和標籤
        correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_data, 1))
    with tf.name_scope("accuracy"):
        # 計算準確率
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
tf.scalar_summary('accuracy', accuracy)

# 建立會話
sess = tf.InteractiveSession()

# 合併 summary
summary_merged = tf.merge_all_summaries()
train_writer = tf.train.SummaryWriter(save_path + "graph/train", sess.graph)
test_writer = tf.train.SummaryWriter(save_path + "graph/test")

start_time = int(round(time.time() * 1000))

# 初始化參數
sess.run(tf.initialize_all_variables())

for i in range(train_times):
    # 從訓練集中取出 50 個樣本進行一波訓練
    batch = mnist.train.next_batch(50)
    if i % 100 == 0:
        summary, train_accuracy = sess.run([summary_merged, accuracy],
                                           feed_dict={x_data: batch[0], y_data: batch[1], keep_prob: 1.0})
        test_writer.add_summary(summary, i)
        consume_time = int(round(time.time() * 1000)) - start_time
        print("當前共訓練 " + str(i) + "次, 累計耗時:" + str(consume_time) + "ms,實時準確率爲:%g" % (train_accuracy))
    # 記錄訓練時數據,每訓練1000次保存一次訓練信息
    if i % 1000 == 0:
        run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
        run_metadata = tf.RunMetadata()
        # 訓練一次,dropout 的參數設置爲 0.5
        summary, _ = sess.run([summary_merged, train_step],
                              feed_dict={x_data: batch[0], y_data: batch[1], keep_prob: 0.5}, options=run_options,
                              run_metadata=run_metadata)
        train_writer.add_run_metadata(run_metadata, str(i))
        train_writer.add_summary(summary, i)
    else:
        summary, _ = sess.run([summary_merged, train_step],
                              feed_dict={x_data: batch[0], y_data: batch[1], keep_prob: 0.5})
        train_writer.add_summary(summary, i)
    # 每訓練 2000 次保存一次模型
    if i != 0 and i % 2000 == 0:
        test_accuracy = int(
            accuracy.eval(feed_dict={x_data: mnist.test.images, y_data: mnist.test.labels, keep_prob: 1.0}) * 100)
        save_model(base_path + str(i) + "_" + str(test_accuracy) + "%/", sess, i)

# 在測試集計算準確率
summary, test_accuracy = sess.run([summary_merged, accuracy],
                                  feed_dict={x_data: mnist.test.images, y_data: mnist.test.labels, keep_prob: 1.0})
train_writer.add_summary(summary)
print("測試集準確率:%g" % (test_accuracy))

print("訓練完成!")
train_writer.close()
test_writer.close()
# 保存模型
save_model(save_path, sess, train_times)

複製代碼

先放一波完整代碼,後面再挑着重點說一說。

其中涉及到一些深度學中的基本概念,本篇篇幅已經夠長了,CoorChice 就不在這裏過多解釋了。若是還不清楚,能夠先跳到如下這篇文章,花個幾分鐘瞭解了基本概念後再繼續往下。

《機器學習,看完就明白了》傳送門

構建損失函數

# 建立正則化對象,此處使用的是 L2 範數
regularization = tf.contrib.layers.l2_regularizer(scale=(5.0 / 50000))
# 應用正則化到參數集上
reg_term = tf.contrib.layers.apply_regularization(regularization)
# 在損失函數中加入正則化項
cross_entropy = (-tf.reduce_sum(y_data * tf.log(y_conv)) + reg_term)
tf.scalar_summary('loss', cross_entropy)
with tf.name_scope("train_step"):
# 使用 Adam 進行損失函數的梯度降低求解
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
複製代碼

實際上,構建損失函數的關鍵代碼就兩行:

cross_entropy = (-tf.reduce_sum(y_data * tf.log(y_conv)) + reg_term)
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
複製代碼

第一行構建出一個 交叉熵 損失函數,第二行對損失函數作梯度降低得到 tensor。

這裏使用了 Adam優化算法 可以爲不一樣的參數動態的計算不一樣的自適應學習率,這與 SGD 恆定不變的學習率有區別。這種優化算法使得各個參數的變化比較平穩,計算消耗的內存更小,收斂也會更快一點。

但,網上看到有說它的訓練效果不如 SGD 的 ???

原本,損失函數的構建到此就結束了,反手就能夠開始訓練了。可是 CoorChice 在訓練過程當中發現,每次當訓練進行到 2w 屢次的時候就會出現梯度爆炸的現象。忽然的 loss 就都變爲 NaN,accuracy 原本好好的 0.99 呢,驟降的趨近於 0 !

因而 CoorChice 二話不說,打開 Google 就是一通搜索,網上說的各類各樣的緣由都有。

這種問題也不太好肯定具體是由於哪個緣由致使的,因而就加個正則化試試。結果就行了!

來看看正則化是怎麼加。

前面在構建 w 函數里加了一行代碼:

tf.add_to_collection(tf.GraphKeys.WEIGHTS, var)
複製代碼

目的就是爲了把每一個 w 放到集合中,以便此時進行正則化使用。

此處,CoorChice 選擇使用高端一點的 L2範式,加入正則化後的 Loss 公式以下:

c0就是本來的損失函數部分,這裏就是 交叉熵,這部分又被稱做是 經驗風險。後面的一部分就是咱們的 L2正則化式了,它實際就是把每一個權重平方後求和,而後除以 w 的數量,在乘以個重要度係數。正則化的部分又叫做 結構風險,由於它是基於 w 計算出的一個數值,加在 交叉熵 上,從而每次增大交叉熵的值,也就是增大梯度,達到懲罰loss的效果。它必定程度上削弱了網絡中特徵值的做用,從而使模型的泛化性提升,也就能進一步的避免過擬合發生的可能。

再回過頭看看上面的代碼,就理解正則化是如何加入到網絡中的。

構建評估模型

# 對比預測結果和標籤
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_data, 1))
# 計算準確率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
複製代碼

這兩行代碼,構建了一個用於評估模型準確率的 accuracy tensor。第一行實際就是比較了一下預測值和真實值,結果是一個 bool 向量,在第二行中轉化爲浮點數,求個平均就是準確率了。

開始訓練啦!

for i in range(train_times):
    # 從訓練集中取出 50 個樣本進行一波訓練
    batch = mnist.train.next_batch(50)
    summary, _ = sess.run([summary_merged, train_step],
                              feed_dict={x_data: batch[0], y_data: batch[1], keep_prob: 0.5})
    # 每訓練 2000 次保存一次模型
    if i != 0 and i % 2000 == 0:
        test_accuracy = int(
            accuracy.eval(feed_dict={x_data: mnist.test.images, y_data: mnist.test.labels, keep_prob: 1.0}) * 100)
        save_model(base_path + str(i) + "_" + str(test_accuracy) + "%/", sess, i)
複製代碼

首先從訓練集中隨機的取出 50 個樣本做爲一次訓練的輸入。爲何要這麼幹呢?由於訓練集有赤裸裸的 60000 個樣本啊!訓練一次太耗時了,特別是在平時開發用的筆記本上。

這麼作理論上準確率沒有全集訓練高,可是也能達到 99% 的準確率,卻能節省巨量的時間,這點理論上的準確率仍是能夠適當的捨棄的。

接着就調用 sess.run(train_step) 開始一次訓練了,注意此處因爲加了 dropout,因此每次 feed_dict 中須要設定它的值。

if i != 0 and i % 2000 == 0:
        test_accuracy = int(
            accuracy.eval(feed_dict={x_data: mnist.test.images, y_data: mnist.test.labels, keep_prob: 1.0}) * 100)
        save_model(base_path + str(i) + "_" + str(test_accuracy) + "%/", sess, i)
複製代碼

CoorChice 每訓練兩千次,在測試集上測試一下,而後保存一下模型。這是良好的習慣。由於一旦訓練起來,不少不可控的因素,多保存些模型,後面還能夠挑最合的。

上面這個過程 CoorChice 循環了整整 35000 次!電腦全速運轉了一夜才訓練完成。

上圖就是一堆存檔的模型文件夾,能夠看到,一開始其實準確率其實也不低,97%。隨着訓練次數的增長,準確率就穩定在了 99% 了。

使用模型進行識別

如今,模型已經訓練好了,接下來就可使用這個模型識別咱們本身手寫的數字了。

# coding=utf-8

import numpy as np
from PIL import Image
import os
from cnn_model import CnnMnistNetwork
import tensorflow.python as tf

train_times = 20000
num = 5
image_path = "num_images_test/num"
CKPT_DIR = "../mnist/" + str(train_times) + "_99%"
# 將數字圖片縮放爲標準的 28*28,接着進行灰度處理
img = Image.open(image_path + str(num) +".png").resize((28, 28), Image.ANTIALIAS).convert("L")
# os.system("open " + image_path + str(num) + ".png")
flatten_img = np.reshape(img, 784)
arr = np.array([1 - flatten_img])
print(arr)

# 建立模型對應的網絡
network = CnnMnistNetwork()
x_data = network.x_data
y = network.y_conv
keep_prob = network.keep_prob
# 建立會話
sess = tf.InteractiveSession()
# 初始化參數
sess.run(tf.initialize_all_variables())
saver = tf.train.Saver()
ckpt = tf.train.get_checkpoint_state(CKPT_DIR)
if ckpt and ckpt.model_checkpoint_path:
    # 讀取恢復模型
    saver.restore(sess, ckpt.model_checkpoint_path)
    # 載入數據,進行識別
    y = sess.run(y, feed_dict={x_data: arr, keep_prob:1.0})
    # 取最大可能
    result = str(np.argmax(y, 1))
    print("\n指望結果" + str(num) + ", 預測結果:" + result)
    os.system("open num_images_test/num" + result[1] + ".png")
else:
    print("沒有模型")
複製代碼

使用模型比較簡單,就是讀取一張圖片,而後建立出模型所對應的網絡結構來,接着讀取模型,輸入圖片,就能獲得識別結果了。

閒扯兩句

MNIST 數據的訓練至關因而機器學習的 HelloWorld 程序,咱們構建了一個 4 層的簡單的網絡進行訓練識別,最後獲得的模型準確率也是不錯的。

完整的體驗瞭如何從 0 開始構建一個神經網絡,而後保存模型,再讀取模型進行識別。總的來講,這個過程思路仍是比較簡單的,關鍵就在於一些參數設置,還有出現問題如何去解決。好比,CoorChice 在訓練過程當中就碰到了 NaN 的問題。機器學習仍是比較依靠經驗的一門技術,須要在不斷的實戰中總結出一套本身的分析、解決問題的套路來。

  • 抽出空餘時間寫文章分享須要信仰,還請各位看官動動小手點個贊,快給 CoorChice 充值信仰吧 😄
  • CoorChice 會不按期的分享乾貨,想要上車只需進到 CoorChice的【我的主頁】 點個關注就行了哦。
相關文章
相關標籤/搜索