正文之前
本文純粹是為了記筆記而生,同時也算是對官方文檔的代碼的驗證,如果有朋友遇到問題可以來找我討論。內(nèi)容是官方文檔的精簡版。另外,還有一個大佬寫的關(guān)于paddle的教程:
《PaddlePaddle從入門到煉丹》十一——自定義圖像數(shù)據(jù)集識別
正文
當我們學習編程的時候,編寫的第一個程序一般是實現(xiàn)打印"Hello World"。而機器學習(或深度學習)的入門教程,一般都是 MNIST 數(shù)據(jù)庫上的手寫識別問題。原因是手寫識別屬于典型的圖像分類問題,比較簡單,同時MNIST數(shù)據(jù)集也很完備。MNIST數(shù)據(jù)集作為一個簡單的計算機視覺數(shù)據(jù)集,包含一系列如圖1所示的手寫數(shù)字圖片和對應(yīng)的標簽。圖片是28x28的像素矩陣,標簽則對應(yīng)著0~9的10個數(shù)字。每張圖片都經(jīng)過了大小歸一化和居中處理。
本教程中,我們從簡單的Softmax回歸模型開始,帶大家了解手寫字符識別,并向大家介紹如何改進模型,利用多層感知機(MLP)和卷積神經(jīng)網(wǎng)絡(luò)(CNN)優(yōu)化識別效果。
數(shù)據(jù)定義:
X是輸入:MNIST圖片是28×28 的二維圖像,為了進行計算,我們將其轉(zhuǎn)化為784維向量,即X=(x0,x1,…,x783)。
Y是輸出:分類器的輸出是10類數(shù)字(0-9),即Y=(y0,y1,…,y9),每一維yi代表圖片分類為第ii類數(shù)字的概率。
Label 是圖片的真實標簽:Label=(l0,l1,…,l9)也是10維,但只有一維為1,其他都為0。例如某張圖片上的數(shù)字為2,則它的標簽為(0,0,1,0,…,0)
一、最簡單的Softmax回歸模型是先將輸入層經(jīng)過一個全連接層得到特征,然后直接通過 softmax 函數(shù)計算多個類別的概率并輸出[9]。
圖為softmax回歸的網(wǎng)絡(luò)圖,圖中權(quán)重用藍線表示、偏置用紅線表示、+1代表偏置參數(shù)的系數(shù)為1。
在分類問題中,我們一般采用交叉熵代價損失函數(shù)(cross entropy loss),公式如下:
二、多層感知機(Multilayer Perceptron, MLP)?
Softmax回歸模型采用了最簡單的兩層神經(jīng)網(wǎng)絡(luò),即只有輸入層和輸出層,因此其擬合能力有限。為了達到更好的識別效果,我們考慮在輸入層和輸出層中間加上若干個隱藏層[10]。
經(jīng)過第一個隱藏層,可以得到 H1=?(W1X+b1),其中??代表激活函數(shù),常見的有sigmoid、tanh或ReLU等函數(shù)。
經(jīng)過第二個隱藏層,可以得到 H2=?(W2H1+b2)。
最后,再經(jīng)過輸出層,得到的Y=softmax(W3H2+b3),即為最后的分類結(jié)果向量。
圖3為多層感知器的網(wǎng)絡(luò)結(jié)構(gòu)圖,圖中權(quán)重用藍線表示、偏置用紅線表示、+1代表偏置參數(shù)的系數(shù)為1。
圖3. 多層感知器網(wǎng)絡(luò)結(jié)構(gòu)圖
三、卷積神經(jīng)網(wǎng)絡(luò)(Convolutional Neural Network, CNN)?
在多層感知器模型中,將圖像展開成一維向量輸入到網(wǎng)絡(luò)中,忽略了圖像的位置和結(jié)構(gòu)信息,而卷積神經(jīng)網(wǎng)絡(luò)能夠更好的利用圖像的結(jié)構(gòu)信息。LeNet-5是一個較簡單的卷積神經(jīng)網(wǎng)絡(luò)。圖4顯示了其結(jié)構(gòu):輸入的二維圖像,先經(jīng)過兩次卷積層到池化層,再經(jīng)過全連接層,最后使用softmax分類作為輸出層。下面我們主要介紹卷積層和池化層。
圖4. LeNet-5卷積神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)
卷積層?
卷積層是卷積神經(jīng)網(wǎng)絡(luò)的核心基石。在圖像識別里我們提到的卷積是二維卷積,即離散二維濾波器(也稱作卷積核)與二維圖像做卷積操作,簡單的講是二維濾波器滑動到二維圖像上所有位置,并在每個位置上與該像素點及其領(lǐng)域像素點做內(nèi)積。卷積操作被廣泛應(yīng)用與圖像處理領(lǐng)域,不同卷積核可以提取不同的特征,例如邊沿、線性、角等特征。在深層卷積神經(jīng)網(wǎng)絡(luò)中,通過卷積操作可以提取出圖像低級到復雜的特征。
圖5. 卷積層圖片
圖5給出一個卷積計算過程的示例圖,輸入圖像大小為H=5,W=5,D=3,即5×5大小的3通道(RGB,也稱作深度)彩色圖像。
這個示例圖中包含兩(用KK表示)組卷積核,即圖中FilterW0 和 FilterW1。在卷積計算中,通常對不同的輸入通道采用不同的卷積核,如圖示例中每組卷積核包含(D=3))個3×3(用F×F表示)大小的卷積核。另外,這個示例中卷積核在圖像的水平方向(WW方向)和垂直方向(H方向)的滑動步長為2(用S表示);對輸入圖像周圍各填充1(用P表示)個0,即圖中輸入層原始數(shù)據(jù)為藍色部分,灰色部分是進行了大小為1的擴展,用0來進行擴展。經(jīng)過卷積操作得到輸出為3×3×2(用Ho×Wo×K表示)大小的特征圖,即3×3大小的2通道特征圖,其中Ho計算公式為:Ho=(H?F+2×P)/S+1,Wo同理。 而輸出特征圖中的每個像素,是每組濾波器與輸入圖像每個特征圖的內(nèi)積再求和,再加上偏置bo,偏置通常對于每個輸出特征圖是共享的。輸出特征圖o[:,:,0]中的最后一個?2計算如圖5右下角公式所示。
局部連接:每個神經(jīng)元僅與輸入神經(jīng)元的一塊區(qū)域連接,這塊局部區(qū)域稱作感受野(receptive field)。在圖像卷積操作中,即神經(jīng)元在空間維度(spatial dimension,即上圖示例H和W所在的平面)是局部連接,但在深度上是全部連接。對于二維圖像本身而言,也是局部像素關(guān)聯(lián)較強。這種局部連接保證了學習后的過濾器能夠?qū)τ诰植康妮斎胩卣饔凶顝姷捻憫?yīng)。局部連接的思想,也是受啟發(fā)于生物學里面的視覺系統(tǒng)結(jié)構(gòu),視覺皮層的神經(jīng)元就是局部接受信息的。
權(quán)重共享:計算同一個深度切片的神經(jīng)元時采用的濾波器是共享的。例如圖5中計算o[:,:,0]o[:,:,0]的每個每個神經(jīng)元的濾波器均相同,都為W0W0,這樣可以很大程度上減少參數(shù)。共享權(quán)重在一定程度上講是有意義的,例如圖片的底層邊緣特征與特征在圖中的具體位置無關(guān)。但是在一些場景中是無意的,比如輸入的圖片是人臉,眼睛和頭發(fā)位于不同的位置,希望在不同的位置學到不同的特征 (參考斯坦福大學公開課)。請注意權(quán)重只是對于同一深度切片的神經(jīng)元是共享的,在卷積層,通常采用多組卷積核提取不同特征,即對應(yīng)不同深度切片的特征,不同深度切片的神經(jīng)元權(quán)重是不共享。另外,偏重對同一深度切片的所有神經(jīng)元都是共享的。
池化層?
圖6. 池化層圖片
池化是非線性下采樣的一種形式,主要作用是通過減少網(wǎng)絡(luò)的參數(shù)來減小計算量,并且能夠在一定程度上控制過擬合。通常在卷積層的后面會加上一個池化層。池化包括最大池化、平均池化等。其中最大池化是用不重疊的矩形框?qū)⑤斎雽臃殖刹煌膮^(qū)域,對于每個矩形框的數(shù)取最大值作為輸出層,如圖6所示。
常見激活函數(shù)介紹?
- sigmoid激活函數(shù):
- tanh激活函數(shù):
實際上,tanh函數(shù)只是規(guī)模變化的sigmoid函數(shù),將sigmoid函數(shù)值放大2倍之后再向下平移1個單位:tanh(x) = 2sigmoid(2x) - 1 。
- ReLU激活函數(shù): f(x)=max(0,x)
激活函數(shù)是用來加入非線性因素的,因為線性模型的表達能力不夠。
在下面的代碼示例中,我們將深入了解上述內(nèi)容:
加載 PaddlePaddle 的 Fluid API 包。
from __future__ import print_function # 將python3中的print特性導入當前版本
import os
from PIL import Image # 導入圖像處理模塊
import matplotlib.pyplot as plt
import numpy
import paddle # 導入paddle模塊
import paddle.fluid as fluid
Program Functions 配置
def softmax_regression():
"""
定義softmax分類器:
一個以softmax為激活函數(shù)的全連接層
Return:
predict_image -- 分類的結(jié)果
"""
# 輸入的原始數(shù)據(jù),28 * 28 * 1
img = fluid.layers.data(name='img', shape=[1,28,28], dtype='float32')
# 以softmax為激活函數(shù)的全連接層,輸出層的大小必須為數(shù)字的個數(shù)10
predict = fluid.layers.fc(
input='img', size=10, act='softmax'
)
return predict
def multilayer_perceptron():
"""
定義多層感知機分類器:
含有兩個隱藏層(全連接層)的多層感知器
其中前兩個隱藏層的激活函數(shù)采用 ReLU,輸出層的激活函數(shù)用 Softmax
Return:
predict_image -- 分類的結(jié)果
"""
# 輸入的原始圖像數(shù)據(jù),大小為28*28*1
img = fluid.layers.data(name='img', shape=[1, 28, 28], dtype='float32')
# 第一個全連接層,激活函數(shù)為ReLU
hidden = fluid.layers.fc(input=img, size=200, act='relu')
# 第二個全連接層,激活函數(shù)為ReLU
hidden = fluid.layers.fc(input=hidden, size=200, act='relu')
# 以softmax為激活函數(shù)的全連接輸出層,輸出層的大小必須為數(shù)字的個數(shù)10
prediction = fluid.layers.fc(input=hidden, size=10, act='softmax')
return prediction
def convolutional_neural_network():
"""
定義卷積神經(jīng)網(wǎng)絡(luò)分類器:
輸入的二維圖像,經(jīng)過兩個卷積-池化層,使用以softmax為激活函數(shù)的全連接層作為輸出層
Return:
predict -- 分類的結(jié)果
"""
# 輸入的原始圖像數(shù)據(jù),大小為28*28*1
img = fluid.layers.data(name='img', shape=[1, 28, 28], dtype='float32')
# 第一個卷積-池化層
# 使用20個5*5的濾波器,池化大小為2,池化步長為2,激活函數(shù)為Relu
conv_pool_1 = fluid.nets.simple_img_conv_pool(
input=img,
filter_size=5,
num_filters=20,
pool_size=2,
pool_stride=2,
act="relu")
conv_pool_1 = fluid.layers.batch_norm(conv_pool_1)
# 第二個卷積-池化層
# 使用50個5*5的濾波器,池化大小為2,池化步長為2,激活函數(shù)為Relu
conv_pool_2 = fluid.nets.simple_img_conv_pool(
input=conv_pool_1,
filter_size=5,
num_filters=50,
pool_size=2,
pool_stride=2,
act="relu")
# 以softmax為激活函數(shù)的全連接輸出層,輸出層的大小必須為數(shù)字的個數(shù)10
prediction = fluid.layers.fc(input=conv_pool_2, size=10, act='softmax')
return prediction
Train Program 配置
然后我們需要設(shè)置訓練程序 train_program。它首先從分類器中進行預測。 在訓練期間,它將從預測中計算 avg_cost。
注意: 訓練程序應(yīng)該返回一個數(shù)組,第一個返回參數(shù)必須是 avg_cost。訓練器使用它來計算梯度。
def train_program():
"""
配置train_program
:return:
predict -- 分類結(jié)果
avg-cost -- 平均損失
acc -- 分類的準確率
"""
# 標簽層 label,對應(yīng)輸入圖片的類別
label = fluid.layers.data(name='label', shape=[1], dtype='int64')
# predict = softmax_regression() # 取消注釋將使用 Softmax回歸
# Best pass is 4, testing Avgcost is 0.0630938182697093
# The classification accuracy is 97.91%
# predict = multilayer_perceptron() # 取消注釋將使用 多層感知器
# Best pass is 4, testing Avgcost is 0.0609021570576521
# The classification accuracy is 97.99%
predict = convolutional_neural_network() # 取消注釋將使用 LeNet5卷積神經(jīng)網(wǎng)絡(luò)
# Best pass is 4, testing Avgcost is 0.018020916773774882
# The classification accuracy is 99.31%
# 使用交叉熵函數(shù)計算predict和label之間的損失函數(shù)
cost = fluid.layers.cross_entropy(input = predict, label = label)
# 計算平均損失
avg_cost = fluid.layers.mean(cost)
acc = fluid.layers.accuracy(input=predict, label=label)
return predict, [ avg_cost, acc]
Optimizer Function 配置
在下面的 Adam optimizer,learning_rate 是學習率,它的大小與網(wǎng)絡(luò)的訓練收斂速度有關(guān)系。
def optimizer_program():
return fluid.optimizer.Adam(learning_rate=0.001)
數(shù)據(jù)集 Feeders 配置
下一步,我們開始訓練過程。paddle.dataset.mnist.train()和paddle.dataset.mnist.test()分別做訓練和測試數(shù)據(jù)集。這兩個函數(shù)各自返回一個reader——PaddlePaddle中的reader是一個Python函數(shù),每次調(diào)用的時候返回一個Python yield generator。
下面shuffle是一個reader decorator,它接受一個reader A,返回另一個reader B。reader B 每次讀入buffer_size條訓練數(shù)據(jù)到一個buffer里,然后隨機打亂其順序,并且逐條輸出。
batch是一個特殊的decorator,它的輸入是一個reader,輸出是一個batched reader。在PaddlePaddle里,一個reader每次yield一條訓練數(shù)據(jù),而一個batched reader每次yield一個minibatch。
# 一個minibatch中有64個數(shù)據(jù)
BATCH_SIZE = 64
# 每次讀取訓練集中的500個數(shù)據(jù)并隨機打亂,傳入batched reader中,batched reader 每次 yield 64個數(shù)據(jù)
train_reader = paddle.batch(
paddle.reader.shuffle(
paddle.dataset.mnist.train(), buf_size=500
),
batch_size=BATCH_SIZE
)
# 讀取測試集的數(shù)據(jù),每次 yield 64個數(shù)據(jù)
test_reader = paddle.batch(
paddle.reader.shuffle(
paddle.dataset.mnist.test(), buf_size=500
),
batch_size=BATCH_SIZE
)
Event Handler 配置
我們可以在訓練期間通過調(diào)用一個handler函數(shù)來監(jiān)控訓練進度。 我們將在這里演示兩個 event_handler 程序。請隨意修改 Jupyter Notebook ,看看有什么不同。
event_handler 用來在訓練過程中輸出訓練結(jié)果
def event_handler(pass_id, batch_id, cost):
# 打印訓練的中間結(jié)果,訓練輪次,batch數(shù),損失函數(shù)
print("Pass %d, Batch %d, Cost %f" % (pass_id,batch_id, cost))
from paddle.utils.plot import Ploter
train_prompt = "Train cost"
test_prompt = "Test cost"
cost_ploter = Ploter(train_prompt, test_prompt)
# 將訓練過程繪圖表示
def event_handler_plot(ploter_title, step, cost):
cost_ploter.append(ploter_title, step, cost)
cost_ploter.plot()
開始訓練
- 可以加入我們設(shè)置的 event_handler 和 data reader,然后就可以開始訓練模型了。
- 設(shè)置一些運行需要的參數(shù),配置數(shù)據(jù)描述 feed_order 用于將數(shù)據(jù)目錄映射到 train_program
- 創(chuàng)建一個反饋訓練過程中誤差的train_test
# 該模型運行在單個CPU上
use_cuda = False
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
# 調(diào)用train_program 獲取預測值和損失值
prediction, [avg_loss, acc] = train_program()
# 輸入的原始圖像名稱,img 28*28*1
# 標簽層,名稱為label,對應(yīng)輸入圖片的類別標簽
# 告知網(wǎng)絡(luò)傳入的數(shù)據(jù)分為兩部分,第一部分是img值,第二部分是label值
feeder = fluid.DataFeeder(feed_list=['img', 'label'], place = place)
# 選擇Adam優(yōu)化器
optimizer = optimizer_program()
optimizer.minimize(avg_loss)
# 訓練五輪
EPOCH_NUM = 5
epochs = [epoch_id for epoch_id in range(EPOCH_NUM)]
# 將模型參數(shù)保存在save_dirname中
save_dirname = "recognize_digits.inference.model"
exe = fluid.Executor(place)
exe.run(fluid.default_startup_program())
main_program = fluid.default_main_program()
test_program = main_program.clone(for_test=True)
def tarin_test(train_test_program,
train_test_feed, train_test_reader):
# 將分類準確率存儲在acc_set中
acc_set = []
# 將平均損失存儲在avg_loss_set中
avg_loss_set = []
# 將測試 reader yield 出的每一個數(shù)據(jù)傳入網(wǎng)絡(luò)中進行訓練
for test_data in train_test_reader():
acc_np, avg_loss_np = exe.run(
program = train_test_program,
feed = train_test_feed.feed(test_data),
fetch_list=[acc, avg_loss]
)
acc_set.append(float(acc_np))
avg_loss_set.append(float(float(avg_loss_np)))
# 獲得測試數(shù)據(jù)上的準確率和損失值
acc_val_mean = numpy.array(acc_set).mean()
avg_loss_val_mean = numpy.array(avg_loss_set).mean()
# 返回平均損失值,平均準確率
return avg_loss_val_mean, acc_val_mean
開始訓練
lists = []
step = 0
for epoch_id in epochs:
metric = 0
for step_id, data in enumerate(train_reader()):
metrics = exe.run(
program = main_program,
feed = feeder.feed(data),
fetch_list = [avg_loss, acc]
)
if step % 100 == 0: #每訓練100次 打印一次log
event_handler_plot(train_prompt, step, metrics[0])
print("Pass %d, Batch %d, Cost %f" % (step, epoch_id, metrics[0]))
step += 1
metric = metrics[0]
# 測試每個epoch分類的效果
avg_loss_val, acc_val = tarin_test(train_test_program=test_program,
train_test_reader=test_reader,
train_test_feed=feeder)
print("Test with Epoch %d, avg_cost: %s, acc: %s" %(epoch_id, avg_loss_val, acc_val))
event_handler_plot(test_prompt, step, metric)
lists.append((epoch_id, avg_loss_val, acc_val))
# 保存訓練好的模型參數(shù)用于后續(xù)的預測
if save_dirname is not None:
fluid.io.save_inference_model(save_dirname,
["img"], [prediction], exe,
model_filename = None,
params_filename=None)
best = sorted(lists, key=lambda list: float(list[1]))[0]
print('Best pass is %s, testing Avgcost is %s' % (best[0], best[1]))
print('The classification accuracy is %.2f%%' % (float(best[2]) * 100))
Best pass is 3, testing Avgcost is 0.0381211013488268
The classification accuracy is 98.76%
<Figure size 432x288 with 0 Axes>
應(yīng)用模型
可以使用訓練好的模型對手寫體數(shù)字圖片進行分類,下面程序展示了如何使用訓練好的模型進行推斷。
生成預測輸入數(shù)據(jù)
infer_5.jpeg 是數(shù)字 3 的一個示例圖像。把它變成一個 numpy 數(shù)組以匹配數(shù)據(jù)feed格式。
def load_image(file):
# 讀取圖片文件,轉(zhuǎn)化為灰度圖
im = Image.open(file).convert('L')
# 將輸入圖片調(diào)整為28*28的高質(zhì)量圖
im = im.resize((28,28), Image.ANTIALIAS)
# 將圖片轉(zhuǎn)化為我numpy的數(shù)組
im = numpy.array(im).reshape(1,1,28,28).astype(numpy.float32)
#對數(shù)據(jù)做歸一化處理
im = im / 255.0 * 2.0 -1
return im
cur_dir = os.getcwd()
tensor_img = load_image(cur_dir + '/image/infer_5.jpeg')
tensor_img = load_image(cur_dir + '/image/infer_5.jpeg')
Inference 創(chuàng)建及預測
通過load_inference_model來設(shè)置網(wǎng)絡(luò)和經(jīng)過訓練的參數(shù)。我們可以簡單地插入在此之前定義的分類器。
inference_scope = fluid.core.Scope()
with fluid.scope_guard(inference_scope):
# 使用 fluid.io.load_inference_model 獲取 inference program desc,
# feed_target_names 用于指定需要傳入網(wǎng)絡(luò)的變量名
# fetch_targets 指定希望從網(wǎng)絡(luò)中fetch出的變量名
[inference_program, feed_target_names,
fetch_targets] = fluid.io.load_inference_model(
save_dirname, exe, None, None
)
# 將feed構(gòu)建成字典 { feed_target_names: feed_target_data}
# 結(jié)果將包含一個與fetch——targets對應(yīng)的數(shù)據(jù)列表
results = exe.run(inference_program,
feed = {feed_target_names[0] : tensor_img},
fetch_list=fetch_targets)
lab = numpy.argsort(results)
# 打印infer_圖片的預測結(jié)果
img = Image.open('image/infer_5.jpeg')
plt.show(img)
print("Inference result of image/infer_5.jpeg is: %d" % lab[0][0][-1])
Inference result of image/infer_5.jpeg is: 5
正文之后
附贈我用于測試的圖片,講道理,準確度感人。。。