人工智能 - paddlepaddle飞桨 - 深度学习基础教程 - 数字识别
人工智能 - paddlepaddle飛槳 - 深度學習基礎教程 - 數字識別
?
本教程源代碼目錄在book/recognize_digits,初次使用請您參考Book文檔使用說明。
說明:?
背景介紹?
當我們學習編程的時候,編寫的第一個程序一般是實現打印"Hello World"。而機器學習(或深度學習)的入門教程,一般都是?MNIST?數據庫上的手寫識別問題。原因是手寫識別屬于典型的圖像分類問題,比較簡單,同時MNIST數據集也很完備。MNIST數據集作為一個簡單的計算機視覺數據集,包含一系列如圖1所示的手寫數字圖片和對應的標簽。圖片是28x28的像素矩陣,標簽則對應著0~9的10個數字。每張圖片都經過了大小歸一化和居中處理。
圖1. MNIST圖片示例
MNIST數據集是從?NIST?的Special Database 3(SD-3)和Special Database 1(SD-1)構建而來。由于SD-3是由美國人口調查局的員工進行標注,SD-1是由美國高中生進行標注,因此SD-3比SD-1更干凈也更容易識別。Yann LeCun等人從SD-1和SD-3中各取一半作為MNIST的訓練集(60000條數據)和測試集(10000條數據),其中訓練集來自250位不同的標注員,此外還保證了訓練集和測試集的標注員是不完全相同的。
MNIST吸引了大量的科學家基于此數據集訓練模型,1998年,LeCun分別用單層線性分類器、多層感知器(Multilayer Perceptron, MLP)和多層卷積神經網絡LeNet進行實驗,使得測試集上的誤差不斷下降(從12%下降到0.7%)[1]。在研究過程中,LeCun提出了卷積神經網絡(Convolutional Neural Network),大幅度地提高了手寫字符的識別能力,也因此成為了深度學習領域的奠基人之一。此后,科學家們又基于K近鄰(K-Nearest Neighbors)算法[2]、支持向量機(SVM)[3]、神經網絡[4-7]和Boosting方法[8]等做了大量實驗,并采用多種預處理方法(如去除歪曲、去噪、模糊等)來提高識別的準確率。
如今的深度學習領域,卷積神經網絡占據了至關重要的地位,從最早Yann LeCun提出的簡單LeNet,到如今ImageNet大賽上的優勝模型VGGNet、GoogLeNet、ResNet等(請參見圖像分類?教程),人們在圖像分類領域,利用卷積神經網絡得到了一系列驚人的結果。
本教程中,我們從簡單的Softmax回歸模型開始,帶大家了解手寫字符識別,并向大家介紹如何改進模型,利用多層感知機(MLP)和卷積神經網絡(CNN)優化識別效果。
模型概覽?
基于MNIST數據集訓練一個分類器,在介紹本教程使用的三個基本圖像分類網絡前,我們先給出一些定義:
- XX是輸入:MNIST圖片是28×2828×28?的二維圖像,為了進行計算,我們將其轉化為784784維向量,即X=(x0,x1,…,x783)X=(x0,x1,…,x783)。
- YY是輸出:分類器的輸出是10類數字(0-9),即Y=(y0,y1,…,y9)Y=(y0,y1,…,y9),每一維yiyi代表圖片分類為第ii類數字的概率。
- LabelLabel是圖片的真實標簽:Label=(l0,l1,…,l9)Label=(l0,l1,…,l9)也是10維,但只有一維為1,其他都為0。例如某張圖片上的數字為2,則它的標簽為(0,0,1,0,…,0)(0,0,1,0,…,0)
Softmax回歸(Softmax Regression)?
最簡單的Softmax回歸模型是先將輸入層經過一個全連接層得到特征,然后直接通過 softmax 函數計算多個類別的概率并輸出[9]。
輸入層的數據XX傳到輸出層,在激活操作之前,會乘以相應的權重?WW?,并加上偏置變量?bb?,具體如下:
其中
圖2為softmax回歸的網絡圖,圖中權重用藍線表示、偏置用紅線表示、+1代表偏置參數的系數為1。
圖2. softmax回歸網絡結構圖
對于有?NN?個類別的多分類問題,指定?NN?個輸出節點,NN?維結果向量經過softmax將歸一化為?NN?個[0,1]范圍內的實數值,分別表示該樣本屬于這?NN?個類別的概率。此處的?yiyi?即對應該圖片為數字?ii?的預測概率。
在分類問題中,我們一般采用交叉熵代價損失函數(cross entropy loss),公式如下:
多層感知機(Multilayer Perceptron, MLP)?
Softmax回歸模型采用了最簡單的兩層神經網絡,即只有輸入層和輸出層,因此其擬合能力有限。為了達到更好的識別效果,我們考慮在輸入層和輸出層中間加上若干個隱藏層[10]。
圖3為多層感知器的網絡結構圖,圖中權重用藍線表示、偏置用紅線表示、+1代表偏置參數的系數為1。
圖3. 多層感知器網絡結構圖
卷積神經網絡(Convolutional Neural Network, CNN)?
在多層感知器模型中,將圖像展開成一維向量輸入到網絡中,忽略了圖像的位置和結構信息,而卷積神經網絡能夠更好的利用圖像的結構信息。LeNet-5是一個較簡單的卷積神經網絡。圖4顯示了其結構:輸入的二維圖像,先經過兩次卷積層到池化層,再經過全連接層,最后使用softmax分類作為輸出層。下面我們主要介紹卷積層和池化層。
圖4. LeNet-5卷積神經網絡結構
卷積層?
卷積層是卷積神經網絡的核心基石。在圖像識別里我們提到的卷積是二維卷積,即離散二維濾波器(也稱作卷積核)與二維圖像做卷積操作,簡單的講是二維濾波器滑動到二維圖像上所有位置,并在每個位置上與該像素點及其領域像素點做內積。卷積操作被廣泛應用與圖像處理領域,不同卷積核可以提取不同的特征,例如邊沿、線性、角等特征。在深層卷積神經網絡中,通過卷積操作可以提取出圖像低級到復雜的特征。
圖5. 卷積層圖片
圖5給出一個卷積計算過程的示例圖,輸入圖像大小為H=5,W=5,D=3H=5,W=5,D=3,即5×55×5大小的3通道(RGB,也稱作深度)彩色圖像。
這個示例圖中包含兩(用KK表示)組卷積核,即圖中FilterW0FilterW0?和?FilterW1FilterW1。在卷積計算中,通常對不同的輸入通道采用不同的卷積核,如圖示例中每組卷積核包含(D=3)D=3)個3×33×3(用F×FF×F表示)大小的卷積核。另外,這個示例中卷積核在圖像的水平方向(WW方向)和垂直方向(HH方向)的滑動步長為2(用SS表示);對輸入圖像周圍各填充1(用PP表示)個0,即圖中輸入層原始數據為藍色部分,灰色部分是進行了大小為1的擴展,用0來進行擴展。經過卷積操作得到輸出為3×3×23×3×2(用Ho×Wo×KHo×Wo×K表示)大小的特征圖,即3×33×3大小的2通道特征圖,其中HoHo計算公式為:Ho=(H?F+2×P)/S+1Ho=(H?F+2×P)/S+1,WoWo同理。 而輸出特征圖中的每個像素,是每組濾波器與輸入圖像每個特征圖的內積再求和,再加上偏置bobo,偏置通常對于每個輸出特征圖是共享的。輸出特征圖o[:,:,0]o[:,:,0]中的最后一個?2?2計算如圖5右下角公式所示。
在卷積操作中卷積核是可學習的參數,經過上面示例介紹,每層卷積的參數大小為D×F×F×KD×F×F×K。在多層感知器模型中,神經元通常是全部連接,參數較多。而卷積層的參數較少,這也是由卷積層的主要特性即局部連接和共享權重所決定。
- 局部連接:每個神經元僅與輸入神經元的一塊區域連接,這塊局部區域稱作感受野(receptive field)。在圖像卷積操作中,即神經元在空間維度(spatial dimension,即上圖示例H和W所在的平面)是局部連接,但在深度上是全部連接。對于二維圖像本身而言,也是局部像素關聯較強。這種局部連接保證了學習后的過濾器能夠對于局部的輸入特征有最強的響應。局部連接的思想,也是受啟發于生物學里面的視覺系統結構,視覺皮層的神經元就是局部接受信息的。
- 權重共享:計算同一個深度切片的神經元時采用的濾波器是共享的。例如圖5中計算o[:,:,0]o[:,:,0]的每個每個神經元的濾波器均相同,都為W0W0,這樣可以很大程度上減少參數。共享權重在一定程度上講是有意義的,例如圖片的底層邊緣特征與特征在圖中的具體位置無關。但是在一些場景中是無意的,比如輸入的圖片是人臉,眼睛和頭發位于不同的位置,希望在不同的位置學到不同的特征 (參考斯坦福大學公開課)。請注意權重只是對于同一深度切片的神經元是共享的,在卷積層,通常采用多組卷積核提取不同特征,即對應不同深度切片的特征,不同深度切片的神經元權重是不共享。另外,偏重對同一深度切片的所有神經元都是共享的。
通過介紹卷積計算過程及其特性,可以看出卷積是線性操作,并具有平移不變性(shift-invariant),平移不變性即在圖像每個位置執行相同的操作。卷積層的局部連接和權重共享使得需要學習的參數大大減小,這樣也有利于訓練較大卷積神經網絡。
關于卷積的更多內容可參考閱讀。
池化層?
圖6. 池化層圖片
池化是非線性下采樣的一種形式,主要作用是通過減少網絡的參數來減小計算量,并且能夠在一定程度上控制過擬合。通常在卷積層的后面會加上一個池化層。池化包括最大池化、平均池化等。其中最大池化是用不重疊的矩形框將輸入層分成不同的區域,對于每個矩形框的數取最大值作為輸出層,如圖6所示。
更詳細的關于卷積神經網絡的具體知識可以參考斯坦福大學公開課、Ufldl?和?圖像分類教程。
常見激活函數介紹?
- sigmoid激活函數:
- tanh激活函數:
實際上,tanh函數只是規模變化的sigmoid函數,將sigmoid函數值放大2倍之后再向下平移1個單位:tanh(x) = 2sigmoid(2x) - 1 。
- ReLU激活函數:?f(x)=max(0,x)f(x)=max(0,x)
更詳細的介紹請參考維基百科激活函數。
數據介紹?
PaddlePaddle在API中提供了自動加載MNIST數據的模塊paddle.dataset.mnist。加載后的數據位于/home/username/.cache/paddle/dataset/mnist下:
| train-images-idx3-ubyte | 訓練數據圖片,60,000條數據 |
| train-labels-idx1-ubyte | 訓練數據標簽,60,000條數據 |
| t10k-images-idx3-ubyte | 測試數據圖片,10,000條數據 |
| t10k-labels-idx1-ubyte | 測試數據標簽,10,000條數據 |
Fluid API 概述?
演示將使用最新的?Fluid API。Fluid API是最新的 PaddlePaddle API。它在不犧牲性能的情況下簡化了模型配置。 我們建議使用 Fluid API,它易學易用的特性將幫助您快速完成機器學習任務。。
下面是 Fluid API 中幾個重要概念的概述:
在下面的代碼示例中,我們將深入了解它們。
配置說明?
加載 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 fluidProgram Functions 配置?
我們需要設置?inference_program?函數。我們想用這個程序來演示三個不同的分類器,每個分類器都定義為 Python 函數。 我們需要將圖像數據輸入到分類器中。Paddle 為讀取數據提供了一個特殊的層?fluid.data?層。 讓我們創建一個數據層來讀取圖像并將其連接到分類網絡。
- Softmax回歸:只通過一層簡單的以softmax為激活函數的全連接層,就可以得到分類的結果。
- 多層感知器:下面代碼實現了一個含有兩個隱藏層(即全連接層)的多層感知器。其中兩個隱藏層的激活函數均采用ReLU,輸出層的激活函數用Softmax。
- 卷積神經網絡LeNet-5: 輸入的二維圖像,首先經過兩次卷積層到池化層,再經過全連接層,最后使用以softmax為激活函數的全連接層作為輸出層。
Train Program 配置?
然后我們需要設置訓練程序?train_program。它首先從分類器中進行預測。 在訓練期間,它將從預測中計算?avg_cost。
注意:?訓練程序應該返回一個數組,第一個返回參數必須是?avg_cost。訓練器使用它來計算梯度。
請隨意修改代碼,測試 Softmax 回歸?softmax_regression,?MLP?和 卷積神經網絡?convolutional?neural?network?分類器之間的不同結果。
def train_program():"""配置train_programReturn:predict -- 分類的結果avg_cost -- 平均損失acc -- 分類的準確率"""# 標簽層,名稱為label,對應輸入圖片的類別標簽label = fluid.data(name='label', shape=[None, 1], dtype='int64')# predict = softmax_regression() # 取消注釋將使用 Softmax回歸# predict = multilayer_perceptron() # 取消注釋將使用 多層感知器predict = convolutional_neural_network() # 取消注釋將使用 LeNet5卷積神經網絡# 使用類交叉熵函數計算predict和label之間的損失函數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?是學習率,它的大小與網絡的訓練收斂速度有關系。
def optimizer_program():return fluid.optimizer.Adam(learning_rate=0.001)數據集 Feeders 配置?
下一步,我們開始訓練過程。paddle.dataset.mnist.train()和paddle.dataset.mnist.test()分別做訓練和測試數據集。這兩個函數各自返回一個reader——PaddlePaddle中的reader是一個Python函數,每次調用的時候返回一個Python yield generator。
下面shuffle是一個reader decorator,它接受一個reader A,返回另一個reader B。reader B 每次讀入buffer_size條訓練數據到一個buffer里,然后隨機打亂其順序,并且逐條輸出。
batch是一個特殊的decorator,它的輸入是一個reader,輸出是一個batched reader。在PaddlePaddle里,一個reader每次yield一條訓練數據,而一個batched reader每次yield一個minibatch。
# 一個minibatch中有64個數據 BATCH_SIZE = 64# 每次讀取訓練集中的500個數據并隨機打亂,傳入batched reader中,batched reader 每次 yield 64個數據 train_reader = paddle.batch(paddle.reader.shuffle(paddle.dataset.mnist.train(), buf_size=500),batch_size=BATCH_SIZE) # 讀取測試集的數據,每次 yield 64個數據 test_reader = paddle.batch(paddle.dataset.mnist.test(), batch_size=BATCH_SIZE)構建訓練過程?
現在,我們需要構建一個訓練過程。將使用到前面定義的訓練程序?train_program,?place?和優化器?optimizer,并包含訓練迭代、檢查訓練期間測試誤差以及保存所需要用來預測的模型參數。
Event Handler 配置?
我們可以在訓練期間通過調用一個handler函數來監控訓練進度。 我們將在這里演示兩個?event_handler?程序。請隨意修改 Jupyter Notebook ,看看有什么不同。
event_handler?用來在訓練過程中輸出訓練結果
def event_handler(pass_id, batch_id, cost):# 打印訓練的中間結果,訓練輪次,batch數,損失函數print("Pass %d, Batch %d, Cost %f" % (pass_id,batch_id, cost)) from paddle.utils.plot import Plotertrain_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()event_handler_plot?可以用來在訓練過程中畫圖如下:
開始訓練?
可以加入我們設置的?event_handler?和?data?reader,然后就可以開始訓練模型了。 設置一些運行需要的參數,配置數據描述?feed_order?用于將數據目錄映射到?train_program?創建一個反饋訓練過程中誤差的train_test
定義網絡結構:
# 該模型運行在單個CPU上 use_cuda = False # 如想使用GPU,請設置為 True place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()# 調用train_program 獲取預測值,損失值, prediction, [avg_loss, acc] = train_program()# 輸入的原始圖像數據,名稱為img,大小為28*28*1 # 標簽層,名稱為label,對應輸入圖片的類別標簽 # 告知網絡傳入的數據分為兩部分,第一部分是img值,第二部分是label值 feeder = fluid.DataFeeder(feed_list=['img', 'label'], place=place)# 選擇Adam優化器 optimizer = optimizer_program() optimizer.minimize(avg_loss)設置訓練過程的超參:
PASS_NUM = 5 #訓練5輪 epochs = [epoch_id for epoch_id in range(PASS_NUM)]# 將模型參數存儲在名為 save_dirname 的文件中 save_dirname = "recognize_digits.inference.model" def train_test(train_test_program,train_test_feed, train_test_reader):# 將分類準確率存儲在acc_set中acc_set = []# 將平均損失存儲在avg_loss_set中avg_loss_set = []# 將測試 reader yield 出的每一個數據傳入網絡中進行訓練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(avg_loss_np))# 獲得測試數據上的準確率和損失值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創建執行器:
exe = fluid.Executor(place) exe.run(fluid.default_startup_program())設置 main_program 和 test_program :
main_program = fluid.default_main_program() test_program = fluid.default_main_program().clone(for_test=True)開始訓練:
lists = [] step = 0 for epoch_id in epochs:for step_id, data in enumerate(train_reader()):metrics = exe.run(main_program,feed=feeder.feed(data),fetch_list=[avg_loss, acc])if step % 100 == 0: #每訓練100次 打印一次logprint("Pass %d, Batch %d, Cost %f" % (step, epoch_id, metrics[0]))event_handler_plot(train_prompt, step, metrics[0])step += 1# 測試每個epoch的分類效果avg_loss_val, acc_val = train_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, metrics[0])lists.append((epoch_id, avg_loss_val, acc_val))# 保存訓練好的模型參數用于預測if save_dirname is not None:fluid.io.save_inference_model(save_dirname,["img"], [prediction], exe,model_filename=None,params_filename=None)# 選擇效果最好的pass 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))訓練過程是完全自動的,event_handler里打印的日志類似如下所示。
Pass表示訓練輪次,Batch表示訓練全量數據的次數,cost表示當前pass的損失值。
每訓練完一個Epoch后,計算一次平均損失和分類準確率。
Pass 0, Batch 0, Cost 0.125650 Pass 100, Batch 0, Cost 0.161387 Pass 200, Batch 0, Cost 0.040036 Pass 300, Batch 0, Cost 0.023391 Pass 400, Batch 0, Cost 0.005856 Pass 500, Batch 0, Cost 0.003315 Pass 600, Batch 0, Cost 0.009977 Pass 700, Batch 0, Cost 0.020959 Pass 800, Batch 0, Cost 0.105560 Pass 900, Batch 0, Cost 0.239809 Test with Epoch 0, avg_cost: 0.053097883707459624, acc: 0.9822850318471338訓練之后,檢查模型的預測準確度。用 MNIST 訓練的時候,一般 softmax回歸模型的分類準確率約為 92.34%,多層感知器為97.66%,卷積神經網絡可以達到 99.20%。
應用模型?
可以使用訓練好的模型對手寫體數字圖片進行分類,下面程序展示了如何使用訓練好的模型進行推斷。
生成預測輸入數據?
infer_3.png?是數字 3 的一個示例圖像。把它變成一個 numpy 數組以匹配數據feed格式。
def load_image(file):# 讀取圖片文件,并將它轉成灰度圖im = Image.open(file).convert('L')# 將輸入圖片調整為 28*28 的高質量圖im = im.resize((28, 28), Image.ANTIALIAS)# 將圖片轉換為numpyim = numpy.array(im).reshape(1, 1, 28, 28).astype(numpy.float32)# 對數據作歸一化處理im = im / 255.0 * 2.0 - 1.0return imcur_dir = os.getcwd() tensor_img = load_image(cur_dir + '/image/infer_3.png')Inference 創建及預測?
通過load_inference_model來設置網絡和經過訓練的參數。我們可以簡單地插入在此之前定義的分類器。
inference_scope = fluid.core.Scope() with fluid.scope_guard(inference_scope):# 使用 fluid.io.load_inference_model 獲取 inference program desc,# feed_target_names 用于指定需要傳入網絡的變量名# fetch_targets 指定希望從網絡中fetch出的變量名[inference_program, feed_target_names,fetch_targets] = fluid.io.load_inference_model(save_dirname, exe, None, None)# 將feed構建成字典 {feed_target_name: feed_target_data}# 結果將包含一個與fetch_targets對應的數據列表results = exe.run(inference_program,feed={feed_target_names[0]: tensor_img},fetch_list=fetch_targets)lab = numpy.argsort(results)# 打印 infer_3.png 這張圖片的預測結果img=Image.open('image/infer_3.png')plt.imshow(img)print("Inference result of image/infer_3.png is: %d" % lab[0][0][-1])預測結果?
如果順利,預測結果輸入如下:?Inference?result?of?image/infer_3.png?is:?3?, 說明我們的網絡成功的識別出了這張圖片!
總結?
本教程的softmax回歸、多層感知機和卷積神經網絡是最基礎的深度學習模型,后續章節中復雜的神經網絡都是從它們衍生出來的,因此這幾個模型對之后的學習大有裨益。同時,我們也觀察到從最簡單的softmax回歸變換到稍復雜的卷積神經網絡的時候,MNIST數據集上的識別準確率有了大幅度的提升,原因是卷積層具有局部連接和共享權重的特性。在之后學習新模型的時候,希望大家也要深入到新模型相比原模型帶來效果提升的關鍵之處。此外,本教程還介紹了PaddlePaddle模型搭建的基本流程,從dataprovider的編寫、網絡層的構建,到最后的訓練和預測。對這個流程熟悉以后,大家就可以用自己的數據,定義自己的網絡模型,并完成自己的訓練和預測任務了。
參考文獻?
本教程?由?PaddlePaddle?創作,采用?知識共享 署名-相同方式共享 4.0 國際 許可協議進行許可。
總結
以上是生活随笔為你收集整理的人工智能 - paddlepaddle飞桨 - 深度学习基础教程 - 数字识别的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微信网页游戏刷分BUG利用——抓包分析记
- 下一篇: 梳理百年深度学习发展史-七月在线机器学习