mnist手寫數字識別——深度學習入門專案(tensorflow+keras+Sequential模型)

Charzueus發表於2020-08-03

前言

今天記錄一下深度學習的另外一個入門專案——《mnist資料集手寫數字識別》,這是一個入門必備的學習案例,主要使用了tensorflow下的keras網路結構的Sequential模型,常用層的Dense全連線層、Activation啟用層和Reshape層。還有其他方法訓練手寫數字識別模型,可以基於pytorch實現的,《Pytorch實現基於卷積神經網路的面部表情識別(詳細步驟)》 這篇就是基於pytorch實現,pytorch裡也封裝了mnist的資料集,實現方法應該類似,正在學習中……
這一篇記錄則是基於keras的Sequential模型實現的。

 

1、mnist手寫數字真面目

我們使用離線下載的資料集進行匯入,一定程度上解決了從遠端載入資料緩慢的問題,這裡有兩種資料集提供給大家,分別是:

  1. mnist.npz資料集
    它是把手寫數字的影像資料和對應標籤整合在一起,而且訓練集與測試集也在裡面,使用的時候無需拆分檔案,只需要簡單程式碼劃分資料,可直接下載本地 mnist手寫數字識別資料集npz檔案.zip
  2. mnist.zip資料集
    它包含了兩個壓縮包,分別是訓練集和測試集(檔名:mnist_traint_data.zip和mnist_test_data.zip),每個資料集解壓后里面分別是資料和對應的標籤,所以最後由4個檔案,可直接下載本地 mnist訓練資料+測試資料(手寫數字識別).zip

  1.1、mnist.npz(整合)資料集

下載好mnist手寫數字識別資料集npz檔案.zip之後,解壓得到mnist.npz之後,我們這裡開始寫程式碼看看手寫數字影像的真面目。
顯示影像程式碼:

import numpy as np
import matplotlib.pyplot as plt


def load_mnist(): # 自定義載入資料
    path = r'D:\mnist_data\mnist.npz'  # 放置mnist.npz的目錄。注意斜槓
    f = np.load(path)
    x_train, y_train = f['x_train'], f['y_train']  # 程式碼實現分離資料集裡面的訓練集和測試集以及對應標籤
    x_test, y_test = f['x_test'], f['y_test']  # x_train為訓練資料,y_train為對應標籤

    f.close() # 關閉檔案
    return (x_train, y_train), (x_test, y_test)


def main():
    (X_train, y_train_label), (test_image, test_label) = load_mnist() #後續可以顯示訓練資料的數字或者測試資料的

    fig, ax = plt.subplots(nrows=5, ncols=5, sharex=True, sharey=True)  # 顯示影像
    ax = ax.flatten()
    for i in range(25):
        img = X_train[i].reshape(28, 28)
        # img = X_train[y_train_label == 8][i].reshape(28, 28)  # 顯示標籤為8的數字影像
        ax[i].set_title(y_train_label[i])
        ax[i].imshow(img, cmap='Greys', interpolation='nearest')
    ax[0].set_xticks([])
    ax[0].set_yticks([])
    plt.tight_layout()
    plt.show()

if __name__ == '__main__':
    main()

效果如下:
圖1
也可以花樣輸出:
程式碼:

import numpy as np
import matplotlib.pyplot as plt


def load_mnist():
    path = r'D:\mnist_data\mnist.npz'  # 放置mnist.npz的目錄。注意斜槓
    f = np.load(path)
    x_train, y_train = f['x_train'], f['y_train']  # 程式碼實現分離資料集裡面的訓練集和測試集以及對應標籤
    x_test, y_test = f['x_test'], f['y_test']  # x_train為訓練資料,y_train為對應標籤

    f.close() # 關閉檔案
    return (x_train, y_train), (x_test, y_test)


def main():
    (X_train, y_train_label), (test_image, test_label) = load_mnist()
    plt.subplot(221)#顯示影像
    plt.imshow(X_train[0], cmap=plt.get_cmap('Accent'))
    plt.subplot(222)
    plt.imshow(X_train[1], cmap=plt.get_cmap('gray'))
    plt.subplot(223)
    plt.imshow(X_train[2], cmap=plt.get_cmap('Blues'))
    plt.subplot(224)
    plt.imshow(X_train[3], cmap=plt.get_cmap('Oranges'))
    plt.show()


if __name__ == '__main__':
    main()

影像顯示:
圖2

  1.2、mnist資料集(訓練測試資料與標籤分離)

這裡介紹第二中方法,也就是資料集是分離的,下載好mnist訓練資料+測試資料(手寫數字識別).zip之後,解壓得到檔案如圖:
圖3
進去解壓得到:
檔案
可以看到分別是訓練集和測試集,包括資料和標籤。
這種方法比較麻煩,沒想到吧!^_ ^ ,大家可以選擇第一種步驟簡單
最後得到:
在這裡插入圖片描述
匯入時候需要用到的是這些.gz檔案。
顯示影像程式碼:

import gzip
import os
import numpy as np
import matplotlib.pyplot as plt

local_file = 'D:\mnist_data'
files = ['train-images-idx3-ubyte.gz', 'train-labels-idx1-ubyte.gz',
         't10k-images-idx3-ubyte.gz', 't10k-labels-idx1-ubyte.gz']


def load_local_mnist(filename):# 載入檔案
    paths = []
    file_read = []
    for file in files:
        paths.append(os.path.join(filename, file))
    for path in paths:
        file_read.append(gzip.open(path, 'rb'))
    # print(file_read)

    train_labels = np.frombuffer(file_read[1].read(), np.uint8, offset=8)#檔案讀取以及格式轉換
    train_images = np.frombuffer(file_read[0].read(), np.uint8, offset=16) \
        .reshape(len(train_labels), 28, 28)
    test_labels = np.frombuffer(file_read[3].read(), np.uint8, offset=8)
    test_images = np.frombuffer(file_read[2].read(), np.uint8, offset=16) \
        .reshape(len(test_labels), 28, 28)
    return (train_images, train_labels), (test_images, test_labels)


def main():
    (x_train, y_train), (x_test, y_test) = load_local_mnist(local_file)

    fig, ax = plt.subplots(nrows=6, ncols=6, sharex=True, sharey=True)#顯示影像
    ax = ax.flatten()
    for i in range(36):
        img=x_test[i].reshape(28,28)
        # img = x_train[y_train == 8][i].reshape(28, 28)  # 顯示標籤為8的數字影像
        ax[i].set_title(y_train[i])
        ax[i].imshow(img, cmap='Greys', interpolation='nearest')
    ax[0].set_xticks([])
    ax[0].set_yticks([])
    plt.tight_layout()
    plt.show()


if __name__ == '__main__':
    main()

輸出結果:
結果1

2、Sequential模型訓練

這裡實現主要使用了tensorflow下的keras網路結構的Sequential模型,常用層的Dense全連線層、Activation啟用層和Reshape層。tensorflow安裝有問題可參考初入機器學習,安裝tensorflow包等問題總結
模型比較簡單,網路搭建以及模型選擇的損失函式、優化器可見程式碼。

import numpy as np
import os
import gzip
from tensorflow import keras
from tensorflow.keras.optimizers import SGD
from tensorflow_core.python.keras.utils import np_utils
from tensorflow.keras.layers import Dense, Dropout, Activation

local_file = 'D:\mnist_data'
files = ['train-images-idx3-ubyte.gz', 'train-labels-idx1-ubyte.gz',
         't10k-images-idx3-ubyte.gz', 't10k-labels-idx1-ubyte.gz']

def load_local_mnist(filename):  # 載入檔案
    paths = []
    file_read = []
    for file in files:
        paths.append(os.path.join(filename, file))
    for path in paths:
        file_read.append(gzip.open(path, 'rb'))
    # print(file_read)

    train_labels = np.frombuffer(file_read[1].read(), np.uint8, offset=8)  # 檔案讀取以及格式轉換
    train_images = np.frombuffer(file_read[0].read(), np.uint8, offset=16) \
        .reshape(len(train_labels), 28, 28)
    test_labels = np.frombuffer(file_read[3].read(), np.uint8, offset=8)
    test_images = np.frombuffer(file_read[2].read(), np.uint8, offset=16) \
        .reshape(len(test_labels), 28, 28)
    return (train_images, train_labels), (test_images, test_labels)


def load_data():# 載入模型需要的資料
    (x_train, y_train), (x_test, y_test) = load_local_mnist(local_file)
    number = 10000
    x_train = x_train[0:number]
    y_train = y_train[0:number]
    x_train = x_train.reshape(number, 28 * 28)
    x_test = x_test.reshape(x_test.shape[0], 28 * 28)
    x_train = x_train.astype('float32')
    x_test = x_test.astype('float32')

    y_train = np_utils.to_categorical(y_train, 10)
    y_test = np_utils.to_categorical(y_test, 10)
    x_train = x_train
    x_test = x_test

    x_train = x_train / 255
    x_test = x_test / 255
    return (x_train, y_train), (x_test, y_test)


(X_train, Y_train), (X_test, Y_test) = load_data()
model = keras.Sequential()# 模型選擇
model.add(Dense(input_dim=28 * 28, units=690,
                activation='relu'))  # tanh  activation:Sigmoid、tanh、ReLU、LeakyReLU、pReLU、ELU、maxout
model.add(Dense(units=690, activation='relu'))
model.add(Dense(units=690, activation='relu'))  # tanh
model.add(Dense(units=10, activation='relu'))
model.compile(loss='mse', optimizer=SGD(lr=0.1),
              metrics=['accuracy'])  # loss:mse,categorical_crossentropy,optimizer: rmsprop 或 adagrad、SGD(此處推薦)
model.fit(X_train, Y_train, batch_size=100, epochs=20)
result = model.evaluate(X_test, Y_test)
print('TEST ACC:', result[1])

經過稍微調優,發現輸入層啟用函式使用relu和tanh效果好,其他網路層使用relu。另外,損失函式使用了MSE(均方誤差),優化器使用 SGD(隨即梯度下降),學習率learning rate調到0.1,度量常用正確率。
引數batch_size=100, epochs=20,增加引數更新以及訓練速度。
以上引數以及選擇訓練效果如下:
結果1
使用優化器為adagrad效果:
結果2
大家也可以自行各種嘗試,優化器和損失函式選擇,引數調優等,進一步提高正確率。

這裡提供另一種寫法,模型構建類似。

import tensorflow as tf
from tensorflow.keras import datasets, layers, optimizers, models, metrics
from tensorflow.keras.optimizers import SGD

import os

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'  # 忽略tensorflow版本警告
(xs, ys), _ = datasets.mnist.load_data()
print('datasets:', xs.shape, ys.shape, xs.min(), xs.max())

# tf.compat.v1.enable_eager_execution()
tf.enable_eager_execution()
xs = tf.convert_to_tensor(xs, dtype=tf.float32) / 255.
db = tf.data.Dataset.from_tensor_slices((xs, ys))
db = db.batch(100).repeat(20)

network = models.Sequential([layers.Dense(256, activation='relu'),
                             layers.Dense(256, activation='relu'),
                             layers.Dense(256, activation='relu'),
                             layers.Dense(10)])
network.build(input_shape=(None, 28 * 28))
network.summary()

optimizer = optimizers.SGD(lr=0.01)
acc_meter = metrics.Accuracy()# 度量正確率

for step, (x, y) in enumerate(db):

    with tf.GradientTape() as tape:
        # [b, 28, 28] => [b, 784] 784維=24*24
        x = tf.reshape(x, (-1, 28 * 28))#-1的含義,陣列新的shape屬性應該要與原來的配套,根據剩下的維度計算出陣列的另外一個shape屬性值。
        # [b, 784] => [b, 10]
        out = network(x)
        # [b] => [b, 10]
        y_onehot = tf.one_hot(y, depth=10)  # 獨熱編碼,y = 0 對應的輸出是[1,0,0,0,0,0,0,0,0,0],範圍0-9,depth深度10層表示10個數字
        # [b, 10]
        loss = tf.square(out - y_onehot)# 計算模型預測與實際的損失
        # [b]
        loss = tf.reduce_sum(loss) / 32

    acc_meter.update_state(tf.argmax(out, axis=1), y)
    grads = tape.gradient(loss, network.trainable_variables)# 計算梯度
    optimizer.apply_gradients(zip(grads, network.trainable_variables))

    if step % 200 == 0:
        print(step, 'loss:', float(loss), 'acc:', acc_meter.result().numpy())
        acc_meter.reset_states()

最後正確率比上面好一點,如圖:
圖3

寫在後面

經過這次學習,感覺收穫了許多,之前只是在理論知識上的理解,現在配合程式碼實踐,模型訓練,理解更加深刻,還存在不足,歡迎大家指正交流,這個過程的詳細步驟,希望能幫助跟我一樣入門需要的夥伴,記錄學習過程,感覺總結一下很好,繼續加油!

我的CSDN部落格:mnist手寫數字識別深度學習入門專案(tensorflow+keras+Sequential模型)
我的部落格園:mnist手寫數字識別——深度學習入門專案(tensorflow+keras+Sequential模型)

版權宣告:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連結和本宣告。
本文連結:https://blog.csdn.net/Charzous/article/details/107748508

相關文章