神經網絡第四篇:推理處理之手寫數字識別

到目前爲止,我們已經介紹完了神經網絡的基本結構,現在用一個圖像識別示例對前面的知識作整體的總結。本專題知識點如下:

  • MNIST數據集
  • 圖像數據轉圖像
  • 神經網絡的推理處理
  • 批處理

  •  MNIST數據集         
mnist數據圖像

MNIST數據集由0到9的數字圖像構成。像素取值在0到255之間。每個圖像數據都相應地標有“7”、“2”、“1”等數字標籤。MNIST數據集中,訓練數據有6萬張,測試圖像有1萬張。一般先用訓練數據進行學習,再用學習到的模型(參數)對測試圖像進行識別分類。MNIST數據集可以從官網下載,這裏我們用python獲取已經下載好並做過處理的MNIST數據集的相關信息:

#數據文件:mnist.pkl,大小約54M。
#文件讀取,位置
import pandas as pd
network=pd.read_pickle('F:/deep-learning with python/dataset/mnist.pkl')
type(network) #類型:字典
network.keys() #關鍵字['train_label', 'train_img', 'test_img', 'test_label']
#訓練數據形狀,(60000, 784),6萬個樣本,每個樣本由784個數據組成(1·28·28)
network['train_img'].shape
network['train_img'][0,:].max()  #第一個樣本的最大值255
network['train_img'][0,:].min()  #第一個樣本的最小值0,
network['train_label'].shape #訓練標籤形狀(60000,),由0至9組成的6萬個數據
network['train_label'].max() #最大值9
network['train_label'].min() #最小值0
network['train_label']  #訓練標籤:0~9
network['test_img'].shape #測試數據形狀(10000, 784),1萬個樣本
network['test_label'].shape#測試數據標籤形狀(10000,)
network['test_label']  #測試標籤:0~9

MNIST數據集保存在mnist.pkl,讀者可點擊:mnist數據集及權重參數下載 進行下載,mnist數據集下載的源碼如下:

# coding: utf-8
try:
    import urllib.request
except ImportError:
    raise ImportError('You should use Python 3.x')
import os.path
import gzip
import pickle
import os
import numpy as np


url_base = 'http://yann.lecun.com/exdb/mnist/'
key_file = {
    'train_img':'train-images-idx3-ubyte.gz',
    'train_label':'train-labels-idx1-ubyte.gz',
    'test_img':'t10k-images-idx3-ubyte.gz',
    'test_label':'t10k-labels-idx1-ubyte.gz'
}

dataset_dir = os.path.dirname(os.path.abspath(__file__))
save_file = dataset_dir + "/mnist.pkl"

train_num = 60000
test_num = 10000
img_dim = (1, 28, 28)
img_size = 784


def _download(file_name):
    file_path = dataset_dir + "/" + file_name
    
    if os.path.exists(file_path):
        return

    print("Downloading " + file_name + " ... ")
    urllib.request.urlretrieve(url_base + file_name, file_path)
    print("Done")
    
def download_mnist():
    for v in key_file.values():
       _download(v)
        
def _load_label(file_name):
    file_path = dataset_dir + "/" + file_name
    
    print("Converting " + file_name + " to NumPy Array ...")
    with gzip.open(file_path, 'rb') as f:
            labels = np.frombuffer(f.read(), np.uint8, offset=8)
    print("Done")
    
    return labels

def _load_img(file_name):
    file_path = dataset_dir + "/" + file_name
    
    print("Converting " + file_name + " to NumPy Array ...")    
    with gzip.open(file_path, 'rb') as f:
            data = np.frombuffer(f.read(), np.uint8, offset=16)
    data = data.reshape(-1, img_size)
    print("Done")
    
    return data
    
def _convert_numpy():
    dataset = {}
    dataset['train_img'] =  _load_img(key_file['train_img'])
    dataset['train_label'] = _load_label(key_file['train_label'])    
    dataset['test_img'] = _load_img(key_file['test_img'])
    dataset['test_label'] = _load_label(key_file['test_label'])
    
    return dataset

def init_mnist():
    download_mnist()
    dataset = _convert_numpy()
    print("Creating pickle file ...")
    with open(save_file, 'wb') as f:
        pickle.dump(dataset, f, -1)
    print("Done!")

def _change_one_hot_label(X):
    T = np.zeros((X.size, 10))
    for idx, row in enumerate(T):
        row[X[idx]] = 1
        
    return T
    

def load_mnist(normalize=True, flatten=True, one_hot_label=False):
    """讀入MNIST數據集
    
    Parameters
    ----------
    normalize : 將圖像的像素值正規化爲0.0~1.0
    one_hot_label : 
        one_hot_label爲True的情況下,標籤作爲one-hot數組返回
        one-hot數組是指[0,0,1,0,0,0,0,0,0,0]這樣的數組
    flatten : 是否將圖像展開爲一維數組
    
    Returns
    -------
    (訓練圖像, 訓練標籤), (測試圖像, 測試標籤)
    """
    if not os.path.exists(save_file):
        init_mnist()
        
    with open(save_file, 'rb') as f:
        dataset = pickle.load(f)
    
    if normalize:
        for key in ('train_img', 'test_img'):
            dataset[key] = dataset[key].astype(np.float32)
            dataset[key] /= 255.0
            
    if one_hot_label:
        dataset['train_label'] = _change_one_hot_label(dataset['train_label'])
        dataset['test_label'] = _change_one_hot_label(dataset['test_label'])
    
    if not flatten:
         for key in ('train_img', 'test_img'):
            dataset[key] = dataset[key].reshape(-1, 1, 28, 28)

    return (dataset['train_img'], dataset['train_label']), (dataset['test_img'], dataset['test_label']) 


if __name__ == '__main__':
    init_mnist()

爲方便訓練和預測,一般以(訓練數據,訓練標籤),(測試數據,測試標籤)的形式修改數據格式。


  • 圖像數據轉圖像                                                                                                                                       

​​​​​MNIST數據集是圖像數據,每一張圖的大小爲28像素×28像素,像素值在0至255之間。在MNIST數據集中的形狀是以一列數組的形式保存的(784個像素),因此要顯示爲圖像時需要對數據進行相關轉換。PIL是Python的圖像庫,可用於顯示圖像,這裏我們用它來將MNIST圖像數據轉換爲圖像進行顯示,方便大家對本主題知識的理解 。

import numpy as np
from PIL import Image
img_data=network['train_img'][1]     #訓練數據中第二個樣本的圖像數據,(784,)
img_label=network['train_label'][1] #訓練數據中第2個樣本的標籤爲
print(img_label)                     #  0
img=img_data.reshape(28,28)#轉爲圖像尺寸
print(img.shape)           #(28, 28)
show_img=Image.fromarray(np.uint8(img)) #轉換爲PIL顯示圖像的數據格式
show_img.show()                         #圖像顯示,0
第二張訓練圖像數據的圖像顯示

  • 神經網絡的推理處理 

前面介紹的PIL庫顯示的訓練數據中的第二個樣本的圖形是“0”,實際標籤也是“0”。所謂神經網絡的推理,即利用神經網絡對訓練數據進行學習(這裏我們先直接使用學到的參數,保存在sample_weight.pkl文件中),利用學到的參數(w,b)對測試數據(test_img)進行識別,然後將識別結果與實際標籤(test_label)進行比較,判斷推理是否正確。原理如下圖:

具體的推理處理過程爲:

圖像數據有784個像素(28×28),即輸入層有784個神經元,推理的結果是識別數字0到9,因此輸出層有10個神經元。此外,爲和前面知識點相連,我們在這個神經網絡中添加了2個隱藏層,第一個隱藏層有50個神經元,第二個隱藏層有100個神經元,隱藏層中神經元的個數可自己設置。由於本主題不涉及參數的學習,因此我們直接使用已學習到的參數,它保存在文件sample_weight.pkl中,根據我們設計的神經網絡的結構,我們應該知道參數的結構。下面是神經網絡推理的代碼:

parms=pd.read_pickle('F:/deep-learning with python/ch03/sample_weight.pkl')
type(parms)  #字典
parms.keys() #['b1', 'W1', 'b2', 'W2', 'b3', 'W3']
parms['b1'].shape #(50,)
parms['W1'].shape #(784,50)
parms['b2'].shape #(100,)
parms['W2'].shape #(50,100)
parms['b3'].shape #(10,)
parms['W3'].shape #(100,10)     
def predict(parms,x):
    """
      代碼同三層神經網絡的實現一樣,只是將隨機參數改爲實際學到的參數 
          6激活函數在前面專題已給出
    """
    W1,W2,W3=parms['W1'],parms['W2'],parms['W3']
    b1,b2,b3=parms['b1'],parms['b2'],parms['b3']
    a1=np.dot(x,W1)+b1
    z1=sigmoid(a1)  #激活函數可在以前的文章中找到
    a2=np.dot(z1,W2)+b2
    z2=sigmoid(a2)
    a3=np.dot(z2,W3)+b3
    y=softmax(a3)   #激活函數可在以前的文章中找到
    return y


test_img=network['test_img']  #測試數據
test_label=network['test_label'] #測試標籤
accuracy_cnt=0
for i in range(len(test_img)):
    y=predict(parms,test_img[i])
    p=np.argmax(y)#獲取概率最高的元素的索引
    if p==test_label[i]:
        accuracy_cnt+=1
print("識別精度:"+str(float(accuracy_cnt)/len(test_img)))  #0.9352

下面我們對代碼做簡單介紹,首先提取測試數據和測試標籤。接着用for循環逐一取出測試數據中的圖像數據,然後用predict()函數進行分類,該函數輸出各個標籤對應的概率,比如輸出[0.1,0.2,0.4…,0.03],表示“0”的概率爲0.1,1的概率爲0.2,9的概率爲0.03。然後我們取出這個概率列表中的最大值的索引即爲分類結果。最後比較神經網絡預測的結果和正確解標籤(test_label),對1萬張圖預測正確的概率作爲識別精度(93.52%)。         

在機器學習領域中,一般需要考慮數據預處理,這裏我們可將像素0至255可縮小到0至1的範圍內(即對所有數據均除以255),然後再輸入至神經網絡中,這種將數據限制在某個範圍內的處理稱爲正則化,一般情況下,預處理會改善機器學習模型。讀者可比較一下圖像數據正則化後神經網絡的識別精度。


  • 批處理

 上面只介紹了輸入一張圖像數據時的處理流程。即每次向神經網絡中輸入一個由784個元素(原本是一個28·28的二維數組)構成的一維數組後,輸出一個有10個元素的一維數組。其數據形狀如下:

現在我們想predict()函數一次性打包處理100張圖像。爲此可把X的形狀改爲100×784,將100張圖片打包作爲輸入數據。數據形狀如下:

批處理數據形狀

可見,輸入數據的形狀爲100×784,輸出數據的形狀爲100×100,這說明了輸入的100張圖像的推理結果被一次性輸出了。例如x[0]、x[1]....x[99]和y[0]、y[1]....y[99]保存了第1、2....到100張圖像的圖像數據及其推理結果。這種被打包的輸入數據被稱爲(batch),批處理主要集中在數據計算上,而不是數據讀入,因此批處理可縮短時間開銷。下面用代碼實現如下:

test_img=network['test_img']  #測試數據
test_label=network['test_label'] #測試標籤

batch_size=100 #批數量
accuracy_cnt=0 #初始識別精度

for i in range(0,len(test_img),batch_size):
    x_batch=test_img[i:i+batch_size]
    y_batch=predict(parms,x_batch)
    p=np.argmax(y_batch,axis=1)#取每列最大值
    accuracy_cnt+=np.sum(p==t[i:i+batch_size])
print("識別精度:"+str(float(accuracy_cnt)/len(test_img)))

批處理代碼核心在於for循環語句添加了步數batch_size,輸入predict()函數的參數x由以前的單條數據變爲x_batch表示的100條數據,尋找二維數組中每行的最大值所在的列位置使用了參數axis=1。

至此,神經網絡的基本知識已經講解完了,後面的內容主要講解權重參數的學習!歡迎關注微信公衆號“Python生態智聯”,學知識,享生活!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章