一、前言
本文基於百度PaddlePaddle 的教程:
https://aistudio.baidu.com/aistudio/projectdetail/349025
上一篇我們介紹了什麼是深度學習 👉 百度PaddlePaddle >>> 2. 什麼是深度學習?
這次我們來寫個手寫數字識別的深度學習程序!
二、開始
1. 準備數據
MNIST數據集包含60000個訓練集和10000測試數據集。分爲圖片和標籤,圖片是28*28的像素矩陣,標籤爲0~9共10個數字。
定義讀取MNIST數據集的train_reader
和test_reader
,指定一個Batch的大小爲128,也就是一次訓練或驗證128張圖像。
paddle.dataset.mnist.train()
或test()
接口已經爲我們對圖片進行了灰度處理、歸一化、居中處理等處理。
import numpy as np
import paddle as paddle
import paddle.fluid as fluid
from PIL import Image
import matplotlib.pyplot as plt
import os
train_reader = paddle.batch(paddle.reader.shuffle(paddle.dataset.mnist.train(),
buf_size=512),
batch_size=128)
test_reader = paddle.batch(paddle.dataset.mnist.test(),
batch_size=128)
附MNIST 數據集下載鏈接:http://yann.lecun.com/exdb/mnist/
2. 配置網絡
先定義一個簡單的多層感知器,一共有三層,兩個大小爲100的隱層和一個大小爲10的輸出層,因爲MNIST數據集是手寫0到9的灰度圖像,類別有10個,所以最後的輸出大小是10。
最後輸出層的激活函數是Softmax,所以最後的輸出層相當於一個分類器。加上一個輸入層的話,多層感知器的結構是:輸入層–>>隱層–>>隱層–>>輸出層。
def multilayer_perceptron(input):
# 第一個全連接層,激活函數爲ReLU
hidden1 = fluid.layers.fc(input=input, size=100, act='relu')
# 第二個全連接層,激活函數爲
hidden2 = fluid.layers.fc(input=hidden1, size=100, act='relu')
# 以softmax 爲激活函數的全連接輸出層,大小爲10
prediction = fluid.layers.fc(input=hidden2, size=10, act='softmax')
return prediction
再定義一個輸入層,輸入的是圖像數據。圖像是28*28的灰度圖,所以輸入的形狀是[1, 28, 28]。
image = fluid.layers.data(name='image', shape=[1, 28, 28], dtype='float32')
# 圖片標籤
label = fluid.layers.data(name='label', shape=[1], dtype='int64')
然後調用定義好的網絡來獲取分類器:
model = multilayer_perceptron(image)
接着是定義損失函數,這次使用的是交叉熵損失函數。
定義了一個損失函數之後,還有對它求平均值,因爲定義的是一個Batch的損失值。同時我們還可以定義一個準確率函數,這個可以在我們訓練的時候輸出分類的準確率。
cost = fluid.layers.cross_entropy(input=model, label=label)
avg_cost = fluid.layers.mean(cost)
acc = fluid.layers.accuracy(input=model, label=label)
再然後是定義優化方法,這次我們使用的是Adam優化方法,同時指定學習率爲0.001:
optimizer = fluid.optimizer.AdamOptimizer(learning_rate=0.001)
opts = optimizer.minimize(avg_cost)
3. 模型訓練 和 模型評估
定義一個解析器和初始化參數:
# 定義一個使用CPU 的解析器
place = fluid.CPUPlace()
exe = fluid.Executor(place)
# 進行參數初始化
exe.run(fluid.default_startup_program())
輸入的數據維度是圖像數據和圖像對應的標籤,每個類別的圖像都要對應一個標籤,這個標籤是從0遞增的整型數值。
# 定義輸入數據維度
feeder = fluid.DataFeeder(place=place, feed_list=[image, label])
開始訓練!
開始訓練和測試
for pass_id in range(5):
# 進行訓練
for batch_id, data in enumerate(train_reader()): # 遍歷train_reader
train_cost, train_acc = exe.run(program=fluid.default_main_program(), # 運行主程序
feed=feeder.feed(data), # 給模型喂入數據
fetch_list=[avg_cost, acc]) # fetch 誤差、準確率
# 每100個batch打印一次信息,誤差、準確率
if batch_id % 100 == 0:
print('Pass:%d, Batch:%d, Cost:%0.5f, Accuracy:%0.5f' %
(pass_id, batch_id, train_cost[0], train_acc[0]))
# 進行測試
test_accs = []
test_costs = []
# 每訓練一輪,進行一次測試
for batch_id, data in enumerate(test_reader()): # 遍歷test_reader
test_cost, test_acc = exe.run(program=fluid.default_main_program(), # 執行訓練程序
feed=feeder.feed(data), # 喂入數據
fetch_list=[avg_cost, acc]) # fetch 誤差、準確率
test_accs.append(test_acc[0]) # 每個batch的準確率
test_costs.append(test_cost[0]) # 每個batch的誤差
# 求測試結果的平均值
test_cost = (sum(test_costs) / len(test_costs)) # 每輪的平均誤差
test_acc = (sum(test_accs) / len(test_accs)) # 每輪的平均準確率
print('Test:%d, Cost:%0.5f, Accuracy:%0.5f' % (pass_id, test_cost, test_acc))
# 保存模型
# 如果保存路徑不存在就創建
if not os.path.exists(model_save_dir):
os.makedirs(model_save_dir)
print ('save models to %s' % (model_save_dir))
# 保存推理model的路徑,推理需要feed的數據,保存推理結果的Variables
fluid.io.save_inference_model(model_save_dir,
['image'],
[model],
exe)
訓練結果:
4. 模型預測
在預測之前,要對圖像進行預處理,處理方式要跟訓練的時候一樣。首先進行灰度化,然後壓縮圖像大小爲28*28,接着將圖像轉換成一維向量,最後再對一維向量進行歸一化處理。
def load_image(file):
im = Image.open(file).convert('L') # 將RGB轉化爲灰度圖像,L代表灰度圖像,灰度圖像的像素值在0-255之間
im = im.resize((28, 28), Image.ANTIALIAS) # resize image with high-quality 圖像大小爲28*28
im = np.array(im).reshape(1, 1, 28, 28).astype(np.float32) # 返回新形狀的數組,把它變成一個numpy數組以匹配數據饋送格式
im = im/255.0*2.0-1.0
# print(im)
return im
infer_exe = fluid.Executor(place)
inference_scope = fluid.core.Scope()
將圖像轉換成一維向量並進行預測,數據從feed中的image傳入。fetch_list的值是網絡模型的最後一層分類器,所以輸出的結果是10個標籤的概率值,這些概率值的總和爲1。
# 加載數據並開始預測
with fluid.scope_guard(inference_scope):
# 獲取訓練好的模型
# 從指定目錄中加載 推理model(inference model)
[inference_program, # 推理Program
feed_target_names, # 是一個str列表,它包含需要在推理Program中提供數據的變量的名稱
fetch_targets] = fluid.io.load_inference_model(model_save_dir, # fetch_targets: 是一個Variable列表,從中我們可以得到推斷結果。model_save_dir:模型保存的路徑
infer_exe) # infer_exe: 運行inference model 的executor
img = load_image(img_path)
results = exe.run(program=inference_program, # 運行預測程序
feed={feed_target_names[0]:img}, # 喂入需要預測的img
fetch_list=fetch_targets) # 得到推測結果
拿到每個標籤的概率值之後,我們要獲取概率最大的標籤,並打印出來。
# 獲取概率最大的label
lab = np.argsort(results) # argsort函數返回的是result數組值從小到大的索引值
print("該圖片的預測結果的label爲: %d" % lab[0][0][-1]) # -1代表讀取數組中倒數第一列
三、結果
上面兩張照片都能夠正確識別👇
最後我自己寫了幾張,也可以正確識別: