1. 程式人生 > >Tensorflow案例5:CNN演算法-Mnist手寫數字識別

Tensorflow案例5:CNN演算法-Mnist手寫數字識別

學習目標

  • 目標
    • 應用tf.nn.conv2d實現卷積計算
    • 應用tf.nn.relu實現啟用函式計算
    • 應用tf.nn.max_pool實現池化層的計算
    • 應用卷積神經網路實現影象分類識別
  • 應用
    • CNN-Mnist手寫數字識別

1、網路設計

我們自己定義一個卷積神經網路去做識別,這裡定義的結構有些是通常大家都會採用的數量以及熟練整個網路計算流程。但是至於怎麼定義結構是沒辦法確定的,也就是神經網路的黑盒子特性,如果想自己設計網路通常還是比較困難的,可以使用一些現有的網路結構如之前的GoogleNet、VGG等等

1.1 網路結構

1.2 具體引數

  • 第一層
    • 卷積:32個filter、大小5*5、strides=1、padding=“SAME”
    • 啟用:Relu
    • 池化:大小2x2、strides2
  • 第一層
    • 卷積:64個filter、大小5*5、strides=1、padding=“SAME”
    • 啟用:Relu
    • 池化:大小2x2、strides2
  • 全連線層

經過每一層圖片資料大小的變化需要確定,Mnist輸入的每批次若干圖片資料大小為[None, 784],如果要進過卷積計算,需要變成[None, 28, 28, 1]

  • 第一層
    • 卷積:[None, 28, 28, 1]———>[None, 28, 28, 32]
      • 權重數量:[5, 5, 1 ,32]
      • 偏置數量:[32]
    • 啟用:[None, 28, 28, 32]———>[None, 28, 28, 32]
    • 池化:[None, 28, 28, 32]———>[None, 14, 14, 32]
  • 第二層
    • 卷積:[None, 14, 14, 32]———>[None, 14, 14, 64]
      • 權重數量:[5, 5, 32 ,64]
      • 偏置數量:[64]
    • 啟用:[None, 14, 14, 64]———>[None, 14, 14, 64]
    • 池化:[None, 14, 14, 64]———>[None, 7, 7, 64]
  • 全連線層
    • [None, 7, 7, 64]———>[None, 7 * 7 * 64]
    • 權重數量:[7 * 7 * 64, 10],由分類別數而定
    • 偏置數量:[10],由分類別數而定

2、案例:CNN識別Mnist手寫數字

2.1 流程

1、準備資料

2、卷積、啟用、池化(兩層)

3、全連線層

4、計算損失、優化

5、計算準確率

2.2 程式碼

  • 網路結構實現

    def conv_model():
    “”"
    自定義的卷積網路結構
    :return: x, y_true, y_predict
    “”"
    # 1、準備資料佔位符
    # x [None, 784] y_true [None, 10]
    with tf.variable_scope(“data”):

          x = tf.placeholder(tf.float32, [None, 784])
    
          y_true = tf.placeholder(tf.int32, [None, 10])
    
      # 2、卷積層一 32個filter, 大小5*5,strides=1, padding=“SAME”
    
      with tf.variable_scope("conv1"):
          # 隨機初始化這一層卷積權重 [5, 5, 1, 32], 偏置[32]
          w_conv1 = weight_variables([5, 5, 1, 32])
    
          b_conv1 = bias_variables([32])
    
          # 首先進行卷積計算
          # x [None, 784]--->[None, 28, 28, 1]  x_conv1 -->[None, 28, 28, 32]
          x_conv1_reshape = tf.reshape(x, [-1, 28, 28, 1])
          # input-->4D
          x_conv1 = tf.nn.conv2d(x_conv1_reshape, w_conv1, strides=[1, 1, 1, 1], padding="SAME") + b_conv1
    
          # 進行啟用函式計算
          #  x_relu1 -->[None, 28, 28, 32]
          x_relu1 = tf.nn.relu(x_conv1)
    
          # 進行池化層計算
          # 2*2, strides 2
          #  [None, 28, 28, 32]------>[None, 14, 14, 32]
          x_pool1 = tf.nn.max_pool(x_relu1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")
    
      # 3、卷積層二 64個filter, 大小5*5,strides=1,padding=“SAME”
      # 輸入:[None, 14, 14, 32]
      with tf.variable_scope("conv2"):
          # 每個filter帶32張5*5的觀察權重,一共有64個filter去觀察
          # 隨機初始化這一層卷積權重 [5, 5, 32, 64], 偏置[64]
          w_conv2 = weight_variables([5, 5, 32, 64])
    
          b_conv2 = bias_variables([64])
    
          # 首先進行卷積計算
          # x [None, 14, 14, 32]  x_conv2 -->[None, 14, 14, 64]
          # input-->4D
          x_conv2 = tf.nn.conv2d(x_pool1, w_conv2, strides=[1, 1, 1, 1], padding="SAME") + b_conv2
    
          # 進行啟用函式計算
          #  x_relu1 -->[None, 28, 28, 32]
          x_relu2 = tf.nn.relu(x_conv2)
    
          # 進行池化層計算
          # 2*2, strides 2
          #  [None, 14, 14, 64]------>x_pool2[None, 7, 7, 64]
          x_pool2 = tf.nn.max_pool(x_relu2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")
    
      # 4、全連線層輸出
      # 每個樣本輸出類別的個數10個結果
      # 輸入:x_poll2 = [None, 7, 7, 64]
      # 矩陣運算: [None, 7 * 7 * 64] * [7 * 7 * 64, 10] +[10] = [None, 10]
      with tf.variable_scope("fc"):
          # 確定全連線層權重和偏置
          w_fc = weight_variables([7 * 7 * 64, 10])
    
          b_fc = bias_variables([10])
    
          # 對上一層的輸出結果的形狀進行處理成2維形狀
          x_fc = tf.reshape(x_pool2, [-1, 7 * 7 * 64])
    
          # 進行全連線層運算
          y_predict = tf.matmul(x_fc, w_fc) + b_fc
    
      return x, y_true, y_predict
    
  • 損失計算優化、準確率計算

      # 1、準備資料API
      mnist = input_data.read_data_sets("./data/mnist/input_data/", one_hot=True)
    
      # 2、定義模型,兩個卷積層、一個全連線層
      x, y_true, y_predict = conv_model()
    
      # 3、softmax計算和損失計算
      with tf.variable_scope("softmax_loss"):
    
          # labels:真實值 [None, 10]  one_hot
          # logits:全臉層的輸出[None,10]
          # 返回每個樣本的損失組成的列表
          loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_true,
                                                                        logits=y_predict))
      # 4、梯度下降損失優化
      with tf.variable_scope("optimizer"):
          # 學習率
          train_op = tf.train.GradientDescentOptimizer(0.1).minimize(loss)
          # train_op = tf.train.AdamOptimizer(0.1).minimize(loss)
    
      # 5、準確率計算
      with tf.variable_scope("accuracy"):
    
          equal_list = tf.equal(tf.argmax(y_true, 1), tf.argmax(y_predict, 1))
    
          accuracy = tf.reduce_mean(tf.cast(equal_list, tf.float32))
    
      # 初始化變數op
      init_op = tf.global_variables_initializer()
    
  • 會話執行

 # 會話執行
        with tf.Session() as sess:
    
            # 初始化變數
            sess.run(init_op)
    
            # 迴圈去訓練模型
            for i in range(2000):
    
                # 獲取資料,實時提供
                # 每步提供50個樣本訓練
                mnist_x, mnist_y = mnist.train.next_batch(50)
    
                sess.run(train_op, feed_dict={x:mnist_x, y_true: mnist_y})
    
                # 列印準確率大小
                print("第%d步訓練的準確率為:--%f" % (i,
                                            sess.run(accuracy, feed_dict={x:mnist_x, y_true: mnist_y})
                                            ))

### 2.3 學習率過大問題

發現當我們設定0.1的學習率之後,準確率一直上不去,並且列印引數發現已經變為NaN,這個地方是不是與之前在做線性迴歸的時候似曾相識。對於卷積網路來說,更容易發生梯度爆炸現象,只能通過調節學習來避免。

完整程式碼

# -*- coding=utf-8 -*-
import os
# os.environ["TF_CPP_MIN_LOG_LEVEL"]='1' # 這是預設的顯示等級,顯示所有資訊  
# os.environ["TF_CPP_MIN_LOG_LEVEL"]='2' # 只顯示 warning 和 Error   
# os.environ["TF_CPP_MIN_LOG_LEVEL"]='3' # 只顯示 Error

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
from tensorflow.contrib.slim.python.slim.nets.inception_v3 import inception_v3_base  # TensorFlow網路模型API
# (補充)卷積網路其它用途
# 影象目標檢測
# Yolo:GoogleNet+ bounding boxes
# SSD:VGG + region proposals


# 定義一個是否訓練/預測的標誌
tf.app.flags.DEFINE_integer("is_train", 1, "訓練or預測")
# 訓練步數
tf.app.flags.DEFINE_integer("train_step", 0, "訓練模型的步數")
# 定義模型的路徑
tf.app.flags.DEFINE_string("model_dir", " ", "模型儲存的路徑+模型名字")
FLAGS = tf.app.flags.FLAGS

# 此模型終端的執行方法
# python3 day03_cnn_Mnist.py --is_train=1 --train_step=2000  # 訓練
# python3 day03_cnn_Mnist.py --is_train=0  # 預測

# tensorboard 終端檢視
# tensorboard --logdir="./temp/summary/"

# 定義兩個專門初始化權重和偏置的函式
def weight_variables(shape):
    w = tf.Variable(tf.random_normal(shape=shape, mean=0.0, stddev=0.01))
    return w


def bias_variables(shape):
    b = tf.Variable(tf.random_normal(shape=shape, mean=0.0, stddev=0.01))
    return b


def cnn_model():
    """
    自定義CNN 卷積模型
    1) tf.nn.conv2d(input, filter, strides=, padding=, name=None)
        計算給定4-D input和filter張量的2維卷積
        input:給定的輸入張量,具有[batch,heigth,width,channel],型別為float32,64
        filter:指定過濾器的權重數量,[filter_height, filter_width, in_channels, out_channels]
        strides:strides = [1, stride, stride, 1],步長
        padding:“SAME”, “VALID”,使用的填充演算法的型別,使用“SAME”。其中”VALID”表示滑動超出部分捨棄,“SAME”表示填充,使得變化後height,width一樣大

    2) tf.nn.max_pool(value, ksize=, strides=, padding=,name=None)
        輸入上執行最大池數
        value:4-D Tensor形狀[batch, height, width, channels]
        channel:並不是原始圖片的通道數,而是多少filter觀察
        ksize:池化視窗大小,[1, ksize, ksize, 1]
        strides:步長大小,[1,strides,strides,1]
        padding:“SAME”, “VALID”,使用的填充演算法的型別,預設使用“SAME”

    第一層
    卷積:32個filter、大小 5*5、strides=1、padding="SAME"
    啟用:RELU
    池化:大小 2*2 、strides=2
    第二層
    卷積:64個filter、大小 5*5、strides=1、padding="SAME"
    啟用:RELU
    池化:大小 2*2 、strides=2
    全連線層:[7*7*64, 10] [10]
    :return:
    """
    # 1、準備資料的佔位符,便於後面卷積計算
    # x:[None, 784], y_true=[None, 10]
    with tf.variable_scope("x_data"):
        x = tf.placeholder(dtype=tf.float32, shape=[None, 784], name="x")
        y_true = tf.placeholder(dtype=tf.float32, shape=[None, 10], name="y_true")

    # 2、第一層
    # 卷積:32個filter、大小 5*5、strides=1、padding="SAME"
    # 啟用:RELU
    # 池化:大小 2 * 2 、strides = 2
    with tf.variable_scope("conv_1"):
        # 準備權重和偏置
        # 權重數量:[5, 5, 1, 32]
        # 偏置:[32]
        w_conv1 = weight_variables(shape=[5, 5, 1, 32])
        b_conv1 = bias_variables(shape=[32])

        # 特徵狀況變成4維,用於卷積運算
        x_reshape = tf.reshape(x, [-1, 28, 28, 1])

        # 進行卷積、啟用函式運算
        # [None, 28, 28, 1] --> [None, 28, 28, 32]
        # [None, 28, 28, 32] --> [None, 28, 28, 32]
        conv1 = tf.nn.conv2d(x_reshape,
                             w_conv1,
                             strides=[1, 1, 1, 1],
                             padding="SAME",
                             name="conv1") + b_conv1
        x_relu1 = tf.nn.relu(conv1, name="relu1")

        # 進行池化層
        # [None, 28, 28, 32] --> [None, 14, 14, 32]
        x_pool1 = tf.nn.max_pool(x_relu1,
                                 ksize=[1, 2, 2, 1],
                                 strides=[1, 2, 2, 1],
                                 padding="SAME",
                                 name="pool1")

    # 3、第二層
    # 卷積:64個filter、大小 5*5、strides=1、padding="SAME"
    # 啟用:RELU
    # 池化:大小 2*2 、strides=2
    with tf.variable_scope("conv_2"):
        # 準備權重和偏置
        # 權重數量:[5, 5, 32, 64]
        # 偏置:[64]
        w_conv2 = weight_variables(shape=[5, 5, 32, 64])
        b_conv2 = bias_variables(shape=[64])

        # 進行卷積、啟用運算
        # 卷積:[None, 14, 14, 32] --> [None, 14, 14, 64]
        # 啟用:[None, 14, 14, 64] --> [None, 14, 14, 64]
        conv2 = tf.nn.conv2d(x_pool1,
                             w_conv2,
                             strides=[1, 1, 1, 1],
                             padding="SAME",
                             name="conv2") + b_conv2
        x_relu2 = tf.nn.relu(conv2, name="relu2")

        # 進行池化運算
        # 池化:[None, 14, 14, 64]  --> [None, 7, 7, 64]
        x_pool2 = tf.nn.max_pool(x_relu2,
                                 ksize=[1, 2, 2, 1],
                                 strides=[1, 2, 2, 1],
                                 padding="SAME",
                                 name="pool2")

    # 4、全連線層
    # 全連線層:[7*7*64, 10] [10]
    with tf.variable_scope("fc"):
        # 初始化權重,偏置
        w_fc = weight_variables(shape=[7 * 7 * 64, 10])
        b_fc = bias_variables(shape=[10])

        # 矩陣運算轉換為二維
        x_fc_reshape = tf.reshape(x_pool2, shape=[-1, 7 * 7 * 64])

        # 全連線層矩陣運算
        y_predict = tf.matmul(x_fc_reshape, w_fc) + b_fc

    return x, y_true, y_predict, w_conv1, w_conv2, b_conv1, b_conv2


def train():
    """
    卷積網路識別訓練
    :return:
    """
    # 1、準備資料輸入
    mnist = input_data.read_data_sets("../data/mnist/input_data/", one_hot=True)

    # 2、建立卷積網路模型
    # y_true: [None, 10]
    # y_predict: [None, 10]
    x, y_true, y_predict, w_conv1, w_conv2, b_conv1, b_conv2 = cnn_model()

    # 3、根據輸出結果與真實結果建立softmax、交叉熵損失計算
    with tf.variable_scope("soft_cross"):
        # 先進行網路輸出的值的概率計算softmax,再進行交叉熵損失計算
        all_loss = tf.nn.softmax_cross_entropy_with_logits(labels=y_true, logits=y_predict, name="compute_loss")

        # 求出平均損失
        loss = tf.reduce_mean(all_loss)

    # 4、梯度下降優化
    with tf.variable_scope("GD"):
        train_op = tf.train.GradientDescentOptimizer(learning_rate=0.1).minimize(loss=loss)
        # train_op = tf.train.AdamOptimizer(learning_rate=0.1).minimize(loss=loss)

    # 5、準確率計算
    with tf.variable_scope("accuracy"):
        equal_list = tf.equal(tf.argmax(y_true, 1), tf.argmax(y_predict, 1))
        accuracy = tf.reduce_mean(tf.cast(equal_list, dtype=tf.float32))

    # 6、tensorflowboard展示的資料
    # 1)收集要在tensorflowboard觀察的張量值
    # 數值型  --> scalar 準確率, 損失值
    tf.summary.scalar("loss", loss)
    tf.summary.scalar("accuracy", accuracy)

    # 維度高的張量值
    tf.summary.histogram("w1", w_conv1)
    tf.summary.histogram("b1", b_conv1)
    tf.summary.histogram("w2", w_conv2)
    tf.summary.histogram("b2", b_conv2)

    # 2)合併變數
    merged = tf.summary.merge_all()

    # 7、建立儲存模型的OP
    saver = tf.train.Saver()

    # 開啟會話進行訓練
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())

        # 建立tensorboard的events檔案
        filte_writer = tf.summary.FileWriter("./temp/summary/", graph=sess.graph)

        # 9、載入本地模型繼續訓練或者拿來進行預測測試集
        checkoutpoint = tf.train.latest_checkpoint("./temp/cnn_model/")
        # 判斷模型是否存在
        if checkoutpoint:
            saver.restore(sess, checkoutpoint)

        # 判斷是訓練還是預測
        if FLAGS.is_train == 1:
        # 迴圈步數去訓練
            for i in range(2000):
                # 每次給50個樣本
                mnist_x, mnist_y = mnist.train.next_batch(50)

                _, loss_run, accuracy_run,summary = sess.run([train_op, loss, accuracy, merged],
                                                     feed_dict={x: mnist_x, y_true: mnist_y})

                # 列印每步訓練的效果
                print("第{}步的50個樣本損失為:{:.6f}, 準確率為:{:.2%}".format(i, loss_run, accuracy_run))

                # 3) 寫入執行的結果到檔案當中
                filte_writer.add_summary(summary, i)

                # 每隔100步儲存一次模型的引數
                if i % 100 == 0:
                    # saver.save(sess, FLAGS.model_dir)
                    saver.save(sess, save_path="./temp/cnn_model/cnn_model")
        else:
            # 進行預測
            # 匯入模型
            # 載入模型,從模型當中找出與當前訓練的模型程式碼當中(名字一樣的OP操作),覆蓋原來的值
            checkoutpoint = tf.train.latest_checkpoint("./temp/cnn_model/")
            # 判斷模型是否存在
            if checkoutpoint:
                saver.restore(sess, checkoutpoint)

                # 預測100個樣本
                N= 200
                a = 0
                for i in range(N):
                    image, label = mnist.test.next_batch(1)
                    # 直接執行網路的輸出預測結果
                    print("第{0}樣本,真實的圖片數字為:{1}, 神經網路預測的數字為:{2}".format(
                    # print("sample:{0},image_true_num:{1}, NN_predict_num:{2}".format(
                        i,
                        tf.argmax(label, 1).eval(),
                        tf.argmax(sess.run(y_predict, feed_dict={x: image, y_true: label}), 1).eval())
                    )
                    if tf.argmax(label, 1).eval() == tf.argmax(sess.run(y_predict, feed_dict={x: image, y_true: label}), 1).eval():
                        a += 1
                test_accuracy = a/N

                print("測試正確率:{:.2%}".format(test_accuracy))
            else:
                print("模型不存在,checkoutpoint 請輸出入正確的模型路徑")

    return None


if __name__ == '__main__':
    train()

3、拓展-Tensorflow高階API實現結構

高階API可以更快的構建模型,但是對神經網路的執行流程瞭解清晰還是需要去使用底層API去構建模型,更好理解網路的原理

https://www.tensorflow.org/tutorials/layers

def cnn_model_fn(features, labels, mode):
  """Model function for CNN."""
  # Input Layer
  input_layer = tf.reshape(features["x"], [-1, 28, 28, 1])

  # Convolutional Layer #1
  conv1 = tf.layers.conv2d(
      inputs=input_layer,
      filters=32,
      kernel_size=[5, 5],
      padding="same",
      activation=tf.nn.relu)

  # Pooling Layer #1
  pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)

  # Convolutional Layer #2 and Pooling Layer #2
  conv2 = tf.layers.conv2d(
      inputs=pool1,
      filters=64,
      kernel_size=[5, 5],
      padding="same",
      activation=tf.nn.relu)
  pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)

  # 全連線層計算+dropout
  pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])
  dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu)
  dropout = tf.layers.dropout(
      inputs=dense, rate=0.4, training=mode == tf.estimator.ModeKeys.TRAIN)

  # 得出預測結果
  logits = tf.layers.dense(inputs=dropout, units=10)

  predictions = {
      # Generate predictions (for PREDICT and EVAL mode)
      "classes": tf.argmax(input=logits, axis=1),
      # Add `softmax_tensor` to the graph. It is used for PREDICT and by the
      # `logging_hook`.
      "probabilities": tf.nn.softmax(logits, name="softmax_tensor")
  }

  if mode == tf.estimator.ModeKeys.PREDICT:
    return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions)

  # 計算損失
  loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)

  # 配置train_op
  if mode == tf.estimator.ModeKeys.TRAIN:
    optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001)
    train_op = optimizer.minimize(
        loss=loss,
        global_step=tf.train.get_global_step())
    return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op)

  # 評估模型
  eval_metric_ops = {
      "accuracy": tf.metrics.accuracy(
          labels=labels, predictions=predictions["classes"])}
  return tf.estimator.EstimatorSpec(
      mode=mode, loss=loss, eval_metric_ops=eval_metric_ops)