在PaddlePaddle上實現MNIST手寫體數字識別

飛槳PaddlePaddle發表於2018-03-29

不久之前,機器之心聯合百度推出 PaddlePaddle 專欄,為想要學習這一平臺的技術人員推薦相關教程與資源。在框架解析安裝教程的介紹之後,本次專欄將教你如何在 PaddlePaddle 上實現 MNIST 手寫數字識別。

目錄

  • 目錄
  • 資料集的介紹
  • 定義神經網路
  • 開始訓練模型
    • 匯入依賴包
    • 初始化 Paddle
    • 獲取訓練器
    • 開始訓練
  • 使用引數預測
    • 初始化 PaddlePaddle
    • 獲取訓練好的引數
    • 讀取圖片
    • 開始預測
  • 所有程式碼
  • 專案程式碼
  • 參考閱讀

資料集的介紹

如題目所示, 本次訓練使用到的是 MNIST 資料庫的手寫數字, 這個資料集包含 60,000 個示例的訓練集以及 10,000 個示例的測試集. 圖片是 28x28 的畫素矩陣,標籤則對應著 0~9 的 10 個數字。每張圖片都經過了大小歸一化和居中處理. 該資料集的圖片是一個黑白的單通道圖片, 其中圖片如下:
在PaddlePaddle上實現MNIST手寫體數字識別該資料集非常小, 很適合影像識別的入門使用, 該資料集一共有 4 個檔案, 分別是訓練資料和其對應的標籤, 測試資料和其對應的標籤. 檔案如表所示:

檔名稱大小說明
train-images-idx3-ubyte9.9M訓練資料圖片,60,000 條資料
train-labels-idx1-ubyte28.9K訓練資料標籤,60,000 條資料
t10k-images-idx3-ubyte1.6M測試資料圖片,10,000 條資料
t10k-labels-idx1-ubyte4.5K測試資料標籤,10,000 條資料

這個資料集針對 170 多 M 的 CIFAR 資料集來說, 實在是小太多了. 這使得我們訓練起來非常快, 這能一下子激發開發者的興趣.
在訓練時, 開發者不需要單獨去下載該資料集, PaddlePaddle 已經幫我們封裝好了, 在我們呼叫paddle.dataset.mnist的時候, 會自動在下載到快取目錄/home/username/.cache/paddle/dataset/mnist下, 當以後再使用的時候, 可以直接在快取中獲取, 就不會去下載了。

定義神經網路

我們這次使用的是卷積神經網路 LeNet-5,官方一共提供了 3 個分類器,分別是 Softmax 迴歸,多層感知器,卷積神經網路 LeNet-5,在影像識別問題上,一直是使用卷積神經網路較多。我們建立一個cnn.py的 Python 檔案來定義一個 LeNet-5 神經網路,程式碼如下:

# coding=utf-8
import paddle.v2 as paddle

# 卷積神經網路LeNet-5,獲取分類器
def convolutional_neural_network():
    # 定義資料模型,資料大小是28*28,即784
    img = paddle.layer.data(name="pixel",
                            type=paddle.data_type.dense_vector(784))
    # 第一個卷積--池化層
    conv_pool_1 = paddle.networks.simple_img_conv_pool(input=img,
                                                       filter_size=5,
                                                       num_filters=20,
                                                       num_channel=1,
                                                       pool_size=2,
                                                       pool_stride=2,
                                                       act=paddle.activation.Relu())
    # 第二個卷積--池化層
    conv_pool_2 = paddle.networks.simple_img_conv_pool(input=conv_pool_1,
                                                       filter_size=5,
                                                       num_filters=50,
                                                       num_channel=20,
                                                       pool_size=2,
                                                       pool_stride=2,
                                                       act=paddle.activation.Relu())
    # 以softmax為啟用函式的全連線輸出層,輸出層的大小必須為數字的個數10
    predict = paddle.layer.fc(input=conv_pool_2,
                              size=10,
                              act=paddle.activation.Softmax())
    return predict

開始訓練模型

我們建立一個train.py的 Python 檔案來做訓練模型。

匯入依賴包

首先要先匯入依賴包, 其中就包含了最重要的 PaddlePaddle 的 V2 包

# encoding:utf-8
import os
import sys
import paddle.v2 as paddle
from cnn import convolutional_neural_network

初始化 Paddle

然後我們建立一個類, 再在類中建立一個初始化函式, 在初始化函式中來初始化我們的 PaddlePaddle,在初始化 PaddlePaddle 的時候,就要指定是否使用 GPU 來訓練我們的模型,同時使用多少個執行緒來訓練。

class TestMNIST:
    def __init__(self):
        # 該模型執行在CUP上,CUP的數量為2
        paddle.init(use_gpu=False, trainer_count=2)

獲取訓練器

透過上面一步獲取的分類器和圖片的標籤來生成一個損失函式, 透過損失函式就可以建立訓練引數了。
之後也要建立一個最佳化方法,這個最佳化方法是定義學習率等等在訓練中的處理。
最後透過訓練引數,最佳化方法,損失函式這 3 個引數建立訓練器

# *****************獲取訓練器********************************
def get_trainer(self):

    # 獲取分類器
    out = convolutional_neural_network()

    # 定義標籤
    label = paddle.layer.data(name="label",
                              type=paddle.data_type.integer_value(10))

    # 獲取損失函式
    cost = paddle.layer.classification_cost(input=out, label=label)

    # 獲取引數
    parameters = paddle.parameters.create(layers=cost)

    """
    定義最佳化方法
    learning_rate 迭代的速度
    momentum 跟前面動量最佳化的比例
    regularzation 正則化,防止過擬合
    :leng re
    """
    optimizer = paddle.optimizer.Momentum(learning_rate=0.1 / 128.0,
                                          momentum=0.9,
                                          regularization=paddle.optimizer.L2Regularization(rate=0.0005 * 128))
    '''
    建立訓練器
    cost 損失函式
    parameters 訓練引數,可以透過建立,也可以使用之前訓練好的引數
    update_equation 最佳化方法
    '''
    trainer = paddle.trainer.SGD(cost=cost,
                                 parameters=parameters,
                                 update_equation=optimizer)
    return trainer

開始訓練

最後就可以的開始訓練了, 透過上一步得到的訓練器開始訓練, 訓練的時候要用到 3 個引數.
第一個是訓練資料, 這個訓練資料就是我們的 MNIST 資料集.
第二個是訓練的輪數, 表示我們要訓練多少輪, 次數越多準確率越高, 最終會穩定在一個固定的準確率上.
第三個是訓練過程中的一些事件處理, 比如會在每個 batch 列印一次日誌, 在每個 pass 之後儲存一下引數和測試一下測試資料集的預測準確率.

# *****************開始訓練********************************
def start_trainer(self):
    # 獲取訓練器
    trainer = self.get_trainer()

    # 定義訓練事件
    def event_handler(event):
        lists = []
        if isinstance(event, paddle.event.EndIteration):
            if event.batch_id % 100 == 0:
                print "\nPass %d, Batch %d, Cost %f, %s" % (
                    event.pass_id, event.batch_id, event.cost, event.metrics)
            else:
                sys.stdout.write('.')
                sys.stdout.flush()
        if isinstance(event, paddle.event.EndPass):
            # 儲存訓練好的引數
            model_path = '../model'
            if not os.path.exists(model_path):
                os.makedirs(model_path)
            with open(model_path + "/model.tar", 'w') as f:
                trainer.save_parameter_to_tar(f=f)

            result = trainer.test(reader=paddle.batch(paddle.dataset.mnist.test(), batch_size=128))
            print "\nTest with Pass %d, Cost %f, %s\n" % (event.pass_id, result.cost, result.metrics)
            lists.append((event.pass_id, result.cost, result.metrics['classification_error_evaluator']))

    # 獲取資料
    reader = paddle.batch(paddle.reader.shuffle(paddle.dataset.mnist.train(), buf_size=20000),
                          batch_size=128)
    '''
    開始訓練
    reader 訓練資料
    num_passes 訓練的輪數
    event_handler 訓練的事件,比如在訓練的時候要做一些什麼事情
    '''
    trainer.train(reader=reader,
                  num_passes=100,
                  event_handler=event_handler)

然後在main入口中呼叫我們的訓練函式, 就可以訓練了

if __name__ == "__main__":
    testMNIST = TestMNIST()
    # 開始訓練
    testMNIST.start_trainer()

在訓練過程中會輸出這樣的日誌:

Pass 0, Batch 0, Cost 2.991905, {'classification_error_evaluator': 0.859375}
...................................................................................................
Pass 0, Batch 100, Cost 0.891881, {'classification_error_evaluator': 0.3046875}
...................................................................................................
Pass 0, Batch 200, Cost 0.309183, {'classification_error_evaluator': 0.0859375}
...................................................................................................
Pass 0, Batch 300, Cost 0.289464, {'classification_error_evaluator': 0.078125}
...................................................................................................
Pass 0, Batch 400, Cost 0.131645, {'classification_error_evaluator': 0.03125}
....................................................................
Test with Pass 0, Cost 0.117626, {'classification_error_evaluator': 0.03790000081062317}

使用引數預測

我們建立一個infer.py的 Python 檔案,用來做模型預測的。

初始化 PaddlePaddle

在預測的時候也是要初始化 PaddlePaddle 的

class TestMNIST:
    def __init__(self):
        # 該模型執行在CUP上,CUP的數量為2
        paddle.init(use_gpu=False, trainer_count=2)

獲取訓練好的引數

在訓練的時候, 我們在 pass 訓練結束後都會儲存他的引數, 儲存這些引數我們現在就可以使用它來預測了

# *****************獲取引數********************************
def get_parameters(self):
    with open("../model/model.tar", 'r') as f:
        parameters = paddle.parameters.Parameters.from_tar(f)
    return parameters

讀取圖片

在使用圖片進行預測時,我們要對圖片進行處理,,處理成跟訓練的圖片一樣,28*28 的灰度圖,最後影像會轉化成一個浮點陣列。

# *****************獲取你要預測的引數********************************
def get_TestData(self):
    def load_images(file):
        # 對圖進行灰度化處理
        im = Image.open(file).convert('L')
        # 縮小到跟訓練資料一樣大小
        im = im.resize((28, 28), Image.ANTIALIAS)
        im = np.array(im).astype(np.float32).flatten()
        im = im / 255.0
        return im

    test_data = []
    test_data.append((load_images('../images/infer_3.png'),))
    return

開始預測

透過傳入分類器,訓練好的引數,預測資料這個 3 個引數就可以進行預測了。這個分類器就是我們之前定義的。

# *****************使用訓練好的引數進行預測********************************
def to_prediction(self, out, parameters, test_data):

    # 開始預測
    probs = paddle.infer(output_layer=out,
                         parameters=parameters,
                         input=test_data)
    # 處理預測結果並列印
    lab = np.argsort(-probs)
    print "預測結果為: %d" % lab[0][0]

main入口中呼叫預測函式

if __name__ == "__main__":
    testMNIST = TestMNIST()
    out = convolutional_neural_network()
    parameters = testMNIST.get_parameters()
    test_data = testMNIST.get_TestData()
    # 開始預測
    testMNIST.to_prediction(out=out, parameters=parameters, test_data=test_data)

輸出的預測結果是:

預測結果為: 3

所有程式碼

infer.py程式碼:

# coding=utf-8
import paddle.v2 as paddle

# 卷積神經網路LeNet-5,獲取分類器
def convolutional_neural_network():
    # 定義資料模型,資料大小是28*28,即784
    img = paddle.layer.data(name="pixel",
                            type=paddle.data_type.dense_vector(784))
    # 第一個卷積--池化層
    conv_pool_1 = paddle.networks.simple_img_conv_pool(input=img,
                                                       filter_size=5,
                                                       num_filters=20,
                                                       num_channel=1,
                                                       pool_size=2,
                                                       pool_stride=2,
                                                       act=paddle.activation.Relu())
    # 第二個卷積--池化層
    conv_pool_2 = paddle.networks.simple_img_conv_pool(input=conv_pool_1,
                                                       filter_size=5,
                                                       num_filters=50,
                                                       num_channel=20,
                                                       pool_size=2,
                                                       pool_stride=2,
                                                       act=paddle.activation.Relu())
    # 以softmax為啟用函式的全連線輸出層,輸出層的大小必須為數字的個數10
    predict = paddle.layer.fc(input=conv_pool_2,
                              size=10,
                              act=paddle.activation.Softmax())
    return predict

train.py程式碼:

# encoding:utf-8
import os
import sys
import paddle.v2 as paddle
from cnn import convolutional_neural_network


class TestMNIST:
    def __init__(self):
        # 該模型執行在CUP上,CUP的數量為2
        paddle.init(use_gpu=False, trainer_count=2)

    # *****************獲取訓練器********************************
    def get_trainer(self):

        # 獲取分類器
        out = convolutional_neural_network()

        # 定義標籤
        label = paddle.layer.data(name="label",
                                  type=paddle.data_type.integer_value(10))

        # 獲取損失函式
        cost = paddle.layer.classification_cost(input=out, label=label)

        # 獲取引數
        parameters = paddle.parameters.create(layers=cost)

        """
        定義最佳化方法
        learning_rate 迭代的速度
        momentum 跟前面動量最佳化的比例
        regularzation 正則化,防止過擬合
        :leng re
        """
        optimizer = paddle.optimizer.Momentum(learning_rate=0.1 / 128.0,
                                              momentum=0.9,
                                              regularization=paddle.optimizer.L2Regularization(rate=0.0005 * 128))
        '''
        建立訓練器
        cost 分類器
        parameters 訓練引數,可以透過建立,也可以使用之前訓練好的引數
        update_equation 最佳化方法
        '''
        trainer = paddle.trainer.SGD(cost=cost,
                                     parameters=parameters,
                                     update_equation=optimizer)
        return trainer

    # *****************開始訓練********************************
    def start_trainer(self):
        # 獲取訓練器
        trainer = self.get_trainer()

        # 定義訓練事件
        def event_handler(event):
            lists = []
            if isinstance(event, paddle.event.EndIteration):
                if event.batch_id % 100 == 0:
                    print "\nPass %d, Batch %d, Cost %f, %s" % (
                        event.pass_id, event.batch_id, event.cost, event.metrics)
                else:
                    sys.stdout.write('.')
                    sys.stdout.flush()
            if isinstance(event, paddle.event.EndPass):
                # 儲存訓練好的引數
                model_path = '../model'
                if not os.path.exists(model_path):
                    os.makedirs(model_path)
                with open(model_path + "/model.tar", 'w') as f:
                    trainer.save_parameter_to_tar(f=f)
                # 使用測試進行測試
                result = trainer.test(reader=paddle.batch(paddle.dataset.mnist.test(), batch_size=128))
                print "\nTest with Pass %d, Cost %f, %s\n" % (event.pass_id, result.cost, result.metrics)
                lists.append((event.pass_id, result.cost, result.metrics['classification_error_evaluator']))

        # 獲取資料
        reader = paddle.batch(paddle.reader.shuffle(paddle.dataset.mnist.train(), buf_size=20000),
                              batch_size=128)
        '''
        開始訓練
        reader 訓練資料
        num_passes 訓練的輪數
        event_handler 訓練的事件,比如在訓練的時候要做一些什麼事情
        '''
        trainer.train(reader=reader,
                      num_passes=100,
                      event_handler=event_handler)


if __name__ == "__main__":
    testMNIST = TestMNIST()
    # 開始訓練
    testMNIST.start_trainer()

infer.py程式碼:

# encoding:utf-8
import numpy as np
import paddle.v2 as paddle
from PIL import Image
from cnn import convolutional_neural_network


class TestMNIST:
    def __init__(self):
        # 該模型執行在CUP上,CUP的數量為2
        paddle.init(use_gpu=False, trainer_count=2)


    # *****************獲取引數********************************
    def get_parameters(self):
        with open("../model/model.tar", 'r') as f:
            parameters = paddle.parameters.Parameters.from_tar(f)
        return parameters


    # *****************獲取你要預測的引數********************************
    def get_TestData(self ,path):
        def load_images(file):
            # 對圖進行灰度化處理
            im = Image.open(file).convert('L')
            # 縮小到跟訓練資料一樣大小
            im = im.resize((28, 28), Image.ANTIALIAS)
            im = np.array(im).astype(np.float32).flatten()
            im = im / 255.0
            return im

        test_data = []
        test_data.append((load_images(path),))
        return test_data

    # *****************使用訓練好的引數進行預測********************************
    def to_prediction(self, out, parameters, test_data):

        # 開始預測
        probs = paddle.infer(output_layer=out,
                             parameters=parameters,
                             input=test_data)
        # 處理預測結果並列印
        lab = np.argsort(-probs)
        print "預測結果為: %d" % lab[0][0]


if __name__ == "__main__":
    testMNIST = TestMNIST()
    # 開始預測
    out = convolutional_neural_network()
    parameters = testMNIST.get_parameters()
    test_data = testMNIST.get_TestData('../images/infer_3.png')
    testMNIST.to_prediction(out=out, parameters=parameters, test_data=test_data)

專案程式碼

GitHub 地址:https://github.com/yeyupiaoling/LearnPaddle

參考閱讀

專欄 | 百度深度學習平臺PaddlePaddle框架解析

專欄 | 新手入門?一步一步教你如何安裝PaddlePaddle

相關文章