1. 程式人生 > >卷積神經網路(二):應用簡單卷積網路實現MNIST數字識別

卷積神經網路(二):應用簡單卷積網路實現MNIST數字識別

卷積神經網路簡單實現MNIST數字識別

本篇的主要內容:

  • 一個兩層卷積層的簡單卷積網路的TensorFlow的實現

網路的結構

在這裡插入圖片描述

在這張圖裡,我把每一層的輸入以及輸出的結構都標註了,結合閱讀程式碼食用效果更佳。

具體程式碼

具體的內容,都寫在相應位置的註釋中了,可能一下子全部程式碼都貼出來不太好看,所以我非常貼心的分成了好幾個部分:

匯入包

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

mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

資料準備:

# 準備資料
trainimg = mnist.train.images
trainlabel = mnist.train.labels
testimg = mnist.test.images
testlabel = mnist.test.labels
print('MNIST ready')

定義不同層的引數與偏置:

# 定義不同層的權重 與 偏置
# 使用 字典的形式方便使用
n_input = 784
n_output = 10
weights = {
    'wc1': tf.Variable(tf.random_normal([3, 3, 1, 64],  stddev=0.1)),  # 第一層卷積層
    'wc2': tf.Variable(tf.random_normal([3, 3, 64, 128], stddev=0.1)), # 第二層卷積層
    'wd1': tf.Variable(tf.random_normal([7*7*128, 1024], stddev=0.1)),  #第一層全連線層
    'wd2': tf.Variable(tf.random_normal([1024, n_output], stddev=0.1))  #第二層全連線層
}
biases = {
    'bc1': tf.Variable(tf.random_normal([64], stddev=0.1)),         # 分別是上面四層的偏置
    'bc2': tf.Variable(tf.random_normal([128], stddev=0.1)),
    'bd1': tf.Variable(tf.random_normal([1024], stddev=0.1)),
    'bd2': tf.Variable(tf.random_normal([n_output], stddev=0.1))
}

定義整個網路的結構:

# 定義神經網路結構
def conv_basic(_input, _w, _b, _keepratio):
    # 首先對輸入資料進行預處理
    # 一開始 影象是以 784 長度的向量儲存
    # tf 中要求的輸入格式是 四維 向量的形式 所以首先進行資料的預處理
    # [a, b, c, d] 中 四個維度 分別是指 batch_size、 height、 width、deepth(channels)
    # 關於reshape函式,稍後解釋
    _input_r = tf.reshape(_input, shape=[-1, 28, 28, 1])

    # 建立第一層 是卷積層 conv1
    # 這個建立的函式 後面詳細介紹
    _conv1 = tf.nn.conv2d(_input_r, _w['wc1'], strides=[1,1,1,1], padding='SAME')
    # 這裡 也可以合在一起寫 因為一般卷積操作之後會跟一個啟用函式 進行去線性化 這裡使用的 ReLU()函式
    _conv1 = tf.nn.relu(tf.nn.bias_add(_conv1, _b['bc1']))

    # 建立第二層 是池化層 pool1
    _pool1 = tf.nn.max_pool(_conv1, ksize=[1, 2, 2, 1] ,strides=[1, 2, 2, 1], padding='SAME')
    # 這個函式稍後詳細介紹
    _pool_dr1 = tf.nn.dropout(_pool1, _keepratio)

    # 建立第三層 是卷積層  conv2  (接下來與前面的建立過程是一樣的)
    _conv2 = tf.nn.conv2d(_pool_dr1, _w['wc2'], strides=[1,1,1,1], padding='SAME')
    _conv2 = tf.nn.relu(tf.nn.bias_add(_conv2, _b['bc2']))
    # 建立第四層 是池化層 pool2
    _pool2 = tf.nn.max_pool(_conv2, ksize=[1,2,2,1], strides=[1,2,2,1], padding='SAME')
    _pool_dr2 = tf.nn.dropout(_pool2, _keepratio)

    # 向量化處理 因為接下來要建立 全連線層 就像之前一樣 全連線層要進行的處理時向量形式的
    _dense1 = tf.reshape(_pool_dr2, [-1, _w['wd1'].get_shape().as_list()[0]])

    # 建立第五層 是全連線層 fc1
    _fc1 = tf.nn.relu(tf.add(tf.matmul(_dense1, _w['wd1']), _b['bd1']))
    _fc_dr1 = tf.nn.dropout(_fc1, _keepratio)

    # 建立第六層 是全連線層 fc2
    _out = tf.add(tf.matmul(_fc_dr1, _w['wd2']), _b['bd2'])

    # 對上面所有的計算 建立字典形式來返回多個內容
    out = {
        'input_r': _input_r, 'conv1':_conv1, 'pool1': _pool1, 'pool1_dr1':_pool_dr1,
        'conv2': _conv2, 'pool2': _pool2, 'pool_dr2': _pool_dr2, 'dense1': _dense1,
        'fc1' : _fc1, 'fc1_dr1': _fc_dr1, 'out': _out
    }
    return out
print("CNN READY")

定義優化器以及損失函式、正確率等計算方法:

# 建立幾個佔位符 用來輸入資料
x = tf.placeholder(tf.float32, [None, n_input])
y = tf.placeholder(tf.float32, [None, n_output])
keepratio = tf.placeholder(tf.float32)


# 定義損失函式 使用的是交叉熵函式
# 定義優化器 優化方式是 隨機梯度下降

_pred = conv_basic(x, weights, biases, keepratio)['out']
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=_pred, labels=y))
optm = tf.train.AdamOptimizer(learning_rate=0.001).minimize(cost)

# 定義正確率計算函式
_corr = tf.equal(tf.argmax(_pred, 1), tf.argmax(y, 1))
accr = tf.reduce_mean(tf.cast(_corr, tf.float32))


init_op = tf.global_variables_initializer()

print("GRAPH READY")

執行部分:

sess = tf.Session()
sess.run(init_op)

training_epoches = 15      # 訓練輪數
batch_size = 16            # 每一批次數量
display_step = 1           # 用於一定步數之後 輸出中間結果

for epoch in range(training_epoches):
    avg_cost = 0
    # total_batch = int(mnist.train.num_examples/batch_size)   本來應該是這樣定義 total_batch 的
    total_batch = 100           # 個人硬體條件有限。。 所以直接指定 total_batch
    batch_xs, batch_ys = None, None
    for i in range(total_batch):
        batch_xs, batch_ys = mnist.train.next_batch(batch_size)
        sess.run(optm, feed_dict={x: batch_xs, y:batch_ys, keepratio:0.7})
        avg_cost += sess.run(cost, feed_dict={x: batch_xs, y:batch_ys, keepratio:1.})/total_batch

    if epoch % display_step == 0:
        print('Epoch: %03d/%03d cost: %.9f'%(epoch, training_epoches, avg_cost))
        #train_acc = sess.run(accr, feed_dict={x:batch_xs, y:batch_ys, keepratio:1.})   # 這裡可以輸出訓練集的正確率
        test_acc = sess.run(accr, feed_dict={x:testimg, y:testlabel, keepratio:1.})     # 測試集的正確率
        #print("Training accuracy: %.03f" % train_acc)
        print("Test accuracy: %.03f" % test_acc)
sess.close()
print("OPTIMIZATION FINISHED")

受限於機器。。。我設定的訓練輪數只有 15 次(也很吃力),最後在 Test 資料集上的識別率大概是在 97.5% 以上 ,在中介結果來看,應該隨著訓練的增加,識別率將會繼續增加。

MNIST ready
CNN READY
GRAPH READY
Epoch: 000/015 cost: 2.028865358
Test accuracy: 0.805
Epoch: 001/015 cost: 0.471636516
Test accuracy: 0.907
Epoch: 002/015 cost: 0.289570311
Test accuracy: 0.936
Epoch: 003/015 cost: 0.197701737
Test accuracy: 0.950
Epoch: 004/015 cost: 0.200011803
Test accuracy: 0.955
Epoch: 005/015 cost: 0.173358922
Test accuracy: 0.961
Epoch: 006/015 cost: 0.129958615
Test accuracy: 0.967
Epoch: 007/015 cost: 0.119177620
Test accuracy: 0.969
Epoch: 008/015 cost: 0.099255772
Test accuracy: 0.970
Epoch: 009/015 cost: 0.102970457
Test accuracy: 0.973
Epoch: 010/015 cost: 0.093222790
Test accuracy: 0.970
Epoch: 011/015 cost: 0.100271105
Test accuracy: 0.975
Epoch: 012/015 cost: 0.081711438
Test accuracy: 0.978
Epoch: 013/015 cost: 0.074602938
Test accuracy: 0.979
Epoch: 014/015 cost: 0.090728084
Test accuracy: 0.978
OPTIMIZATION FINISHED

幾個說明點

  • 輸入資料格式調整

在TensorFlow中,卷積的輸入要求是四維陣列的形式,所以我們直接獲得的mnist資料集不能直接使用,使用reshape可以得到符合規定的輸入。
標準的輸入格式:

[batch_size, height, width, deepth]

batch_size就是每一批要進行處理的資料數量
height 是輸入圖片的高度
width 是輸入圖片的寬度
deepth 也可以說是 channel,也就是通道數,例如輸入的影象是RGB格式的話,通道數就是3

  • help函式

如果對 TensorFlow 的某些函式不瞭解,在IPython 或者Jupyter notebook中可以使用:

print(help(tf.nn.conv2d))

獲得函式的說明。
當然,如果使用Pycharm的話,可以直接點選函式,在右側的 Documentation 內就會出現函式的介紹。

  • reshape()函式
  • conv2d()函式
conv = tf.nn.conv2d()  # 建立卷積層

# 定義
def conv2d(input: Any,    # 輸入資料
           filter: Any,   # 卷積核權重矩陣
           strides: Any,  # 一般使用四維的輸入形式 
           padding: Any,  # 取值有 SAME 和 VALID
           use_cudnn_on_gpu: bool = True,
           data_format: str = "NHWC",
           dilations: List[int] = [1, 1, 1, 1],
           name: Any = None) -> Any
           
# 定義例子

_conv1 = tf.nn.conv2d(_input_r, _w['wc1'], strides=[1,1,1,1], padding='SAME')
  • dropout()函式

這個函式的作用就是在訓練過程中隨機 “殺死” 一些神經元,也就是說為模型的收斂做了一點貢獻,還可以防止過擬合,在TensorFlow中一般是會使用在全連線層上,上面的程式碼中,放在了卷積層附近,當然,對於這樣小型的網路,其實並不會有什麼區別。具體的解釋可以看這篇部落格:
談談TensorFlow的dropout

以上~