TensorFlow 2.x - MNIST 圖像分類(2)

在上一節(jié) TensorFlow 2.x - MNIST 圖像分類(1) 中,我們搭建了一個全連接的神經(jīng)網(wǎng)絡(luò)用于手寫數(shù)字識別。本節(jié)將在上一節(jié)的基礎(chǔ)上,將使用卷積神經(jīng)網(wǎng)絡(luò) (Convolutional Neural Network, CNN) 來搭建網(wǎng)絡(luò)識別數(shù)字。

CNN

一、為什么要用 CNN

在全連接神經(jīng)網(wǎng)絡(luò)中,每相鄰兩層之間的每個神經(jīng)元之間都是相連的。當(dāng)輸入層的特征維度變得很高時,這時全連接網(wǎng)絡(luò)需要訓(xùn)練的參數(shù)就會增大很多,計算速度就會變得很慢。

例如一張黑白的 28×28 的手寫數(shù)字圖片,輸入層的神經(jīng)元就有 784 個,若在中間只使用一層 15 個神經(jīng)元的隱藏層,那么神經(jīng)元的權(quán)重參數(shù) w 就有 784 × 15 = 11760 個;若輸入的是 28×28 帶有顏色的 RGB 顏色的手寫數(shù)字圖片,那么參數(shù) w 的個數(shù)還需要再乘以 3,而這僅僅是 28×28 像素的圖片。

28×28 像素的數(shù)字

如果需要識別一張高清圖片呢?很容易看出使用全連接神經(jīng)網(wǎng)絡(luò)處理圖像中的需要訓(xùn)練參數(shù)過多的問題。這就需要使用 CNN 來減少需要訓(xùn)練的參數(shù) w。如果你還不了解 CNN,建議先看一下 《卷積神經(jīng)網(wǎng)絡(luò)—CNN》,沒錯,也是我的文章。

二、加載數(shù)據(jù)

加載數(shù)據(jù)與上一節(jié)基本相同,也是使用 MNIST 數(shù)據(jù)集。

import tensorflow as tf
from tensorflow.keras import datasets, layers, models

class DataSource(object):
    def __init__(self):
        # mnist數(shù)據(jù)集存儲的位置
        (train_images, train_labels), (test_images, test_labels) = datasets.mnist.load_data()

        # 6萬張訓(xùn)練圖片,1萬張測試圖片
        train_images = train_images.reshape((60000, 28, 28, 1))
        test_images = test_images.reshape((10000, 28, 28, 1))
        # 像素值映射到 0 - 1 之間
        train_images, test_images = train_images / 255.0, test_images / 255.0

        self.train_images, self.train_labels = train_images, train_labels
        self.test_images, self.test_labels = test_images, test_labels

三、搭建網(wǎng)絡(luò)

模型定義的前半部分主要使用 Keras.layers 提供的 Conv2D (卷積) 與MaxPooling2D (池化) 函數(shù)。

CNN 的輸入層的 input_shape 為 (image_height, image_width, color_channels) 的張量。mnist 數(shù)據(jù)集是黑白的,因此只有一個 color_channel,即 color_channel = 1;彩色圖片有 3 個顏色通道 (R,G,B),所以 color_channel = 3。

對于 mnist 數(shù)據(jù)集,輸入的張量維度就是 (28, 28, 1),通過參數(shù) input_shape 傳給網(wǎng)絡(luò)的第一層。

class CNN(object):
    def __init__(self):
        model = models.Sequential()
        # 輸入層,用32個3*3的卷積核進行卷積操作,28*28為待訓(xùn)練圖片的大小
        model.add(layers.Conv2D(filters=32, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1)))
        # 添加池化層1,2*2采樣
        model.add(layers.MaxPooling2D((2, 2)))
        # 第2層卷積,用64個3*3的卷積核進行卷積操作
        model.add(layers.Conv2D(filters=64, kernel_size=(3, 3), activation='relu'))
        # 添加池化層2,2*2采樣
        model.add(layers.MaxPooling2D((2, 2)))
        # dropout
        model.add(layers.Dropout(.2))
        # Flatten層,連接卷積層與全連接層
        model.add(layers.Flatten())
        # 全連接層,特征進一步提取,64為輸出空間的維數(shù),activation參數(shù)將激活函數(shù)設(shè)置為ReLu函數(shù)
        model.add(layers.Dense(64, activation='relu'))
        # 輸出層,輸出預(yù)期結(jié)果,10為輸出空間的維數(shù)
        model.add(layers.Dense(10, activation='softmax'))
        # 打印模型的結(jié)構(gòu)
        model.summary()

        self.model = model

每一個 Conv2D 和 MaxPooling2D 層的輸出都是一個三維的張量 (height, width, channels)。height 和 width 會逐漸地變小。輸出的 channel 的個數(shù),是由 filters 參數(shù)控制,隨著 height 和 width 的變小,channel 可以變大。

整個卷積過程就是把圖片的寬高減小,圖片的“厚度”增加。

  1. 原始圖片大小為 (28, 28, 1);
  2. 經(jīng)過輸入層卷積后變?yōu)?(26, 26, 32),池化后變?yōu)?(13, 13, 32);
  3. 經(jīng)過第二層卷積后變?yōu)?(11, 11, 64),池化后變?yōu)? (5, 5, 64)。

模型的后半部分,是定義全連接層。layers.Flatten 會將三維的張量轉(zhuǎn)為一維的向量。再使用 layers.Dense 層,構(gòu)造了 2 層全連接層,逐步地將一維向量再變?yōu)?10。最后一層的激活函數(shù)是 softmax,10 位恰好可以表達 0-9 十個數(shù)字。

四、開始訓(xùn)練并保存訓(xùn)練結(jié)果

class Train:
    def __init__(self):
        self.cnn = CNN()
        self.data = DataSource()

    def train(self):
        check_path = './ckpt/cp-{epoch:04d}.ckpt'
        # period 每隔5epoch保存一次
        save_model_cb = tf.keras.callbacks.ModelCheckpoint(check_path, save_weights_only=True, verbose=1, period=5)

        self.cnn.model.compile(
            # 設(shè)置優(yōu)化器為Adam優(yōu)化器,Adam是一種常用的自適應(yīng)學(xué)習(xí)率優(yōu)化算法,它能夠自動調(diào)整學(xué)習(xí)率,適應(yīng)不同參數(shù)的變化情況,從而更有效地訓(xùn)練模型。
            optimizer = 'adam',
            # 設(shè)置損失函數(shù)為交叉熵?fù)p失函數(shù)(tf.keras.losses.SparseCategoricalCrossentropy())
            # from_logits為True時,會將y_pred轉(zhuǎn)化為概率(用softmax),否則不進行轉(zhuǎn)換,通常情況下用True結(jié)果更穩(wěn)定
            loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits = True),
            # 設(shè)置性能指標(biāo)列表,將在模型訓(xùn)練時監(jiān)控列表中的指標(biāo)
            metrics = ['accuracy'])

        self.cnn.model.fit(self.data.train_images, self.data.train_labels, epochs=5, callbacks=[save_model_cb])

        test_loss, test_acc = self.cnn.model.evaluate(self.data.test_images, self.data.test_labels)
        print("準(zhǔn)確率: %.4f,共測試了%d張圖片 " % (test_acc, len(self.data.test_labels)))


if __name__ == "__main__":
    app = Train()
    app.train()

訓(xùn)練 5 次,準(zhǔn)確率就能達到 99% 以上了。

在第五輪時,模型參數(shù)成功保存在了./ckpt/cp-0005.ckpt。接下來我們就可以加載保存的模型參數(shù),恢復(fù)整個卷積神經(jīng)網(wǎng)絡(luò),進行真實圖片的預(yù)測了。

五、圖片預(yù)測

這里我們從本地選擇一張 28 * 28 的數(shù)字圖片,然后輸入給程序??纯茨懿荒軠?zhǔn)確預(yù)測。
安裝 PIL:pip3 install pillow。

import tensorflow as tf
from PIL import Image
import numpy as np

class Predict(object):
    def __init__(self):
        latest = tf.train.latest_checkpoint('./ckpt')
        self.cnn = CNN()
        # 恢復(fù)網(wǎng)絡(luò)權(quán)重
        self.cnn.model.load_weights(latest)

    def predict(self, image_path):
        # 以黑白方式讀取圖片
        img = Image.open(image_path).convert('L')
        flatten_img = np.reshape(img, (28, 28, 1)) / 255
        x = np.array([1 - flatten_img])

        # 預(yù)測
        y = self.cnn.model.predict(x)

        # np.argmax()取得最大值的下標(biāo),即代表的數(shù)字
        print(image_path)
        print(y[0])
        print('-> Predict digit', np.argmax(y[0]))

if __name__ == "__main__":
    app = Predict()
    app.predict('/Users/Desktop/4.png')

最后,可以在 GitHub 上看到完整的代碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容