天天看點

深度學習經典入門項目—手寫數字識别

目錄

  • ​​手寫數字識别任務​​
  • ​​建構手寫數字識别的神經網絡模型​​
  • ​​資料處理​​
  • ​​手寫數字的資料處理​​
  • ​​手寫數字的資料下載下傳​​
  • ​​模型設計​​
  • ​​房價預測的網絡結構​​
  • ​​手寫數字識别的網絡結構​​
  • ​​定義模型結構​​
  • ​​配置​​
  • ​​訓練​​
  • ​​測試​​
  • ​​資料處理​​
  • ​​預測​​

手寫數字識别任務

手寫數字識别解決了郵政系統郵政編碼識别的問題。手寫數字識别常用的資料集是 mnist,該資料集是入門機器學習、深度學習 經典的資料集。

MNIST 資料集可在 http://yann.lecun.com/exdb/mnist/ 擷取, 它包含了四個部分:

Training set images: train-images-idx3-ubyte.gz (9.9 MB, 解壓後 47 MB, 包含 60,000 個樣本)

Training set labels: train-labels-idx1-ubyte.gz (29 KB, 解壓後 60 KB, 包含 60,000 個标簽)

Test set images: t10k-images-idx3-ubyte.gz (1.6 MB, 解壓後 7.8 MB, 包含 10,000 個樣本)

Test set labels: t10k-labels-idx1-ubyte.gz (5KB, 解壓後 10 KB, 包含 10,000 個标簽)

MNIST 資料集來自美國國家标準與技術研究所, National Institute of Standards and Technology (NIST). 訓練集 (training set) 由來自 250 個不同人手寫的數字構成, 其中 50% 是高中學生, 50% 來自人口普查局 (the Census Bureau) 的從業人員. 測試集(test set) 也是同樣比例的手寫數字資料.

深度學習經典入門項目—手寫數字識别

MNIST資料集的釋出,吸引了大量科學家訓練模型。1998年,LeCun分别用單層線性分類器、多層感覺器(Multilayer Perceptron, MLP)和多層卷積神經網絡LeNet進行實驗,使得測試集的誤差不斷下降(從12%下降到0.7%)。在研究過程中,LeCun提出了卷積神經網絡(Convolutional Neural Network,CNN),大幅度地提高了手寫字元的識别能力,也是以成為了深度學習領域的創始者之一。

  • 任務輸入:一系列手寫數字圖檔,其中每張圖檔都是28x28的像素矩陣。
  • 任務輸出:經過了大小歸一化和居中處理,輸出對應的0~9的數字标簽。

建構手寫數字識别的神經網絡模型

使用飛槳完成手寫數字識别模型任務的代碼結構如圖所示,與使用飛槳完成房價預測模型任務的流程一緻,下面的章節中我們将詳細介紹每個步驟的具體實作方法和優化思路。

深度學習經典入門項目—手寫數字識别

這裡的每個子產品都有可以配置的不同選項,類似于小朋友插積木,在這個模式固定的架構上更換各種元件,來适應不同需求。

下面來看一下各個子產品的可以插拔的元件。

深度學習的代碼結構是“套路”型的。按照固定的套路,替換部分部件就可以完成對應功能,系統的結構如下圖所示:

深度學習經典入門項目—手寫數字識别

接下來我們展示手寫數字識别的代碼

#加載飛槳、Numpy和相關類庫
import os
import random
import paddle
import paddle.fluid as fluid
from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear
import numpy as np
from PIL import Image

import gzip
import      

資料處理

手寫數字的資料處理

MNIST資料集以json格式存儲在本地,其資料存儲結構如圖所示。

深度學習經典入門項目—手寫數字識别

data包含三個元素的清單:train_set、val_set、 test_set,包括50000條訓練樣本、10000條驗證樣本、10000條測試樣本。每個樣本包含手寫數字圖檔和對應的标簽。

  • train_set(訓練集):用于确定模型參數。
  • val_set(驗證集):用于調節模型超參數(如多個網絡結構、正則化權重的最優選擇)。
  • test_set(測試集):用于估計應用效果(沒有在模型中應用過的資料,更貼近模型在真實場景應用的效果)。

train_set包含兩個元素的清單:train_images、train_labels。

  • train_images:[50000, 784]的二維清單,包含50000張圖檔。每張圖檔用一個長度為784的向量表示,内容是28*28尺寸的像素灰階值(黑白圖檔)。
  • train_labels:[50000, ]的清單,表示這些圖檔對應的分類标簽,即0-9之間的一個數字。
# 定義資料集讀取器
def load_data(mode='train'):

    # 資料檔案
    datafile = './work/mnist.json.gz'
    print('loading mnist dataset from {} ......'.format(datafile))
    data = json.load(gzip.open(datafile))
    train_set, val_set, eval_set = data

    # 資料集相關參數,圖檔高度IMG_ROWS, 圖檔寬度IMG_COLS
    IMG_ROWS = 28
    IMG_COLS = 28

    if mode == 'train':
        imgs = train_set[0]
        labels = train_set[1]
    elif mode == 'valid':
        imgs = val_set[0]
        labels = val_set[1]
    elif mode == 'eval':
        imgs = eval_set[0]
        labels = eval_set[1]

    imgs_length = len(imgs)

    # 校驗
    assert len(imgs) == len(labels), \
          "length of train_imgs({}) should be the same as train_labels({})".format(
                  len(imgs), len(labels))

    print('資料集校驗正常')
    index_list = list(range(imgs_length))

    # 讀入資料時用到的batchsize
    BATCHSIZE = 100 #調試修改,調參 128/64

    # 定義資料生成器
    def data_generator():
        if mode == 'train':
            random.shuffle(index_list)
        imgs_list = []
        labels_list = []
        for i in index_list:
            img = np.reshape(imgs[i], [1, IMG_ROWS, IMG_COLS]).astype('float32')
            label = np.reshape(labels[i], [1]).astype('int64')
            imgs_list.append(img) 
            labels_list.append(label)
            if len(imgs_list) == BATCHSIZE:
                yield np.array(imgs_list), np.array(labels_list)
                imgs_list = []
                labels_list = []

        # 如果剩餘資料的數目小于BATCHSIZE,
        # 則剩餘資料一起構成一個大小為len(imgs_list)的mini-batch
        if len(imgs_list) > 0:
            yield np.array(imgs_list), np.array(labels_list)

    return      

手寫數字的資料下載下傳

模型設計

房價預測的網絡結構
# 多層全連接配接神經網絡實作
class MNIST(fluid.dygraph.Layer):
    def __init__(self):
        super(MNIST, self).__init__()
        # 定義兩層全連接配接隐含層,輸出次元是10,激活函數為sigmoid
        self.fc1 = Linear(input_dim=784, output_dim=10, act='sigmoid') # 隐含層節點為10,可根據任務調整
        self.fc2 = Linear(input_dim=10, output_dim=10, act='sigmoid')
        # 定義一層全連接配接輸出層,輸出次元是1,不使用激活函數
        self.fc3 = Linear(input_dim=10, output_dim=1, act=None)
    
    # 定義網絡的前向計算
    def forward(self, inputs, label=None):
        inputs = fluid.layers.reshape(inputs, [inputs.shape[0], 784])
        outputs1 = self.fc1(inputs)
        outputs2 = self.fc2(outputs1)
        outputs_final = self.fc3(outputs2)
        return      
手寫數字識别的網絡結構

定義模型結構

class MNIST(fluid.dygraph.Layer):
     def __init__(self):
         super(MNIST, self).__init__()
         
         # 定義一個卷積層,使用relu激活函數
         self.conv1 = Conv2D(num_channels=1, num_filters=1, filter_size=1, stride=1, padding=1, act='relu')
         # 定義一個池化層,池化核為2,步長為2,使用最大池化方式
         self.pool1 = Pool2D(pool_size=1, pool_stride=1, pool_type='max')
         
         self.fc = Linear(input_dim=980, output_dim=10, act='softmax')
    # 定義網絡的前向計算過程
     def forward(self, inputs):
         x = self.conv1(inputs)
         x = self.pool1(x)
         x = fluid.layers.reshape(x, [x.shape[0], 980])
         x = self.fc(x)
         return      
# 請輸入 卷積網絡結構
##
##
##
##
class MNIST(fluid.dygraph.Layer):
    def __init__(self):
        super(MNIST,self).__init__()
        self.conv1=Conv2D(num_channels=1,num_filters=20,filter_size=5,stride=1,padding=2,act='relu')
        self.pool1=Pool2D(pool_size=1,pool_stride=2,pool_type='max')
        self.conv2=Conv2D(num_channels=20,num_filters=20,filter_size=5,stride=1,padding=2,act='relu')
        self.pool2=Pool2D(pool_size=2,pool_stride=2,pool_type='max')
        self.fc=Linear(input_dim=980,output_dim=10,act='softmax')
    def forward(self,inputs):
        x=self.conv1(inputs)
        x=self.pool1(x)
        x=self.conv2(x)
        x=self.pool2(x)
        x=fluid.layers.reshape(x,[x.shape[0],980])
        x=self.fc(x)
        return      

配置

with fluid.dygraph.guard():    
    # 聲明定義好的線性回歸模型
    model = MNIST()
    # 開啟模型訓練模式
    model.train()
    #調用加載資料的函數
    train_loader = load_data('train')
    # 定義優化算法,這裡使用随機梯度下降-SGD
    # 學習率設定為0.01
    optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.01, parameter_list=model.parameters())      

訓練

#僅修改計算損失的函數,從均方誤差(常用于回歸問題)到交叉熵誤差(常用于分類問題)
with fluid.dygraph.guard():
    EPOCH_NUM = 5
    for epoch_id in range(EPOCH_NUM):
        for batch_id, data in enumerate(train_loader()):
            #準備資料,變得更加簡潔
            image_data, label_data = data
            image = fluid.dygraph.to_variable(image_data)
            label = fluid.dygraph.to_variable(label_data)
            
            #前向計算的過程
            predict = model(image)
            
            #計算損失,使用交叉熵損失函數,取一個批次樣本損失的平均值
            loss = fluid.layers.cross_entropy(predict, label)
            avg_loss = fluid.layers.mean(loss)
            
            #每訓練了200批次的資料,列印下目前Loss的情況
            if batch_id % 50 == 0:
                print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))
            
            #後向傳播,更新參數的過程
            avg_loss.backward()
            optimizer.minimize(avg_loss)
            model.clear_gradients()

    #儲存模型參數
    fluid.save_dygraph(model.state_dict(), 'mnist')      

測試

資料處理

# 讀取一張本地的樣例圖檔,轉變成模型輸入的格式
def load_image(img_path):
    # 從img_path中讀取圖像,并轉為灰階圖
    im = Image.open(img_path).convert('L')
    im.show()
    im = im.resize((28, 28), Image.ANTIALIAS)
    im = np.array(im).reshape(1, 1, 28, 28).astype(np.float32)
    # 圖像歸一化
    im = 1.0 - im / 255.
    return      

預測

# 定義預測過程
with fluid.dygraph.guard():
    model = MNIST()
    params_file_path = 'mnist'
    img_path = './work/example_0.jpg'
    # 加載模型參數
    model_dict, _ = fluid.load_dygraph("mnist")
    model.load_dict(model_dict)
    
    model.eval()
    tensor_img = load_image(img_path)
    #模型回報10個分類标簽的對應機率
    results = model(fluid.dygraph.to_variable(tensor_img))
    #取機率最大的标簽作為預測輸出
    lab = np.argsort(results.numpy())
    print("本次預測的數字是: ", lab[0][-1])      
# 請編寫 預測統計 正确率的代碼 并 輸出正确率
datafile = './work/mnist.json.gz'
print('loading mnist dataset from {} ......'.format(datafile))
data = json.load(gzip.open(datafile))
train_set, val_set, eval_set = data
IMG_ROWS = 28
IMG_COLS = 28
with fluid.dygraph.guard():
    model = MNIST()
    params_file_path = 'mnist'
    img_path = './work/example_0.jpg'
    # 加載模型參數
    model_dict, _ = fluid.load_dygraph("mnist")
    model.load_dict(model_dict)
    
    model.eval()
    imags=val_set[0]
    lables=val_set[1]
    #自增長數
    count=0
    for i in range(len(imags)):
        imag=np.reshape(imags[i], [1,1, IMG_ROWS, IMG_COLS]).astype('float32')
        label = np.reshape(lables[i], [1]).astype('int64')
        #模型回報10個分類标簽的對應機率
        results = model(fluid.dygraph.to_variable(imag))
        #取機率最大的标簽作為預測輸出
        lab = np.argsort(results.numpy())
        if(lab[0][-1] == label):
            count=count+1
        # print("本次預測的數字是: ", lab[0][-1])
print("圖檔識别率為%f"%(count/len(imags)))