百度PaddlePaddle入門-9(建模)


本節介紹使用飛槳快速實現“手寫數字識別”的建模方法。

與“房價預測”的案例類似,我們以同樣的標准結構實現“手寫數字識別”的建模。在后續的課程中,該標准結構會反復出現,逐漸加深我們對深度學習模型的理解。深度學習模型的標准結構分如下五個步驟:

  1. 數據處理:讀取數據和預處理操作。
  2. 模型設計:搭建神經網絡結構。
  3. 訓練配置:配置優化器、學習率、訓練參數。
  4. 訓練過程:循環調用訓練過程,循環執行“前向計算 + 損失函數 + 反向傳播”。
  5. 保存模型並測試:將訓練好的模型保存並評估測試。

下面我們使用飛槳框架,按照五個步驟寫“手寫數字識別”模型,體會下使用飛槳框架的感覺。

在數據處理前,首先要加載飛槳平台、與“手寫數字識別”模型相關類庫,代碼如下:

1 #加載飛槳和相關類庫
2 import paddle
3 import paddle.fluid as fluid
4 from paddle.fluid.dygraph.nn import FC
5 import numpy as np
6 import os
7 from PIL import Image

1. 數據處理

飛槳提供了多個封裝好的數據集API,覆蓋計算機視覺、自然語言處理、推薦系統等多個領域,可以幫助我們快速完成機器學習任務。比如,在“手寫數字識別”模型中,我們可以通過調用paddle.dataset.mnist的train函數和test函數,直接獲取處理好的MNIST訓練集和測試集。

定義數據讀取器

用戶可以通過如下代碼定義數據讀取器:

1 # 如果~/.cache/paddle/dataset/mnist/目錄下沒有MNIST數據,API會自動將MINST數據下載到該文件夾下
2 # 設置數據讀取器,讀取MNIST數據訓練集
3 trainset = paddle.dataset.mnist.train()
4 testset = paddle.dataset.mnist.test()
5 # 包裝數據讀取器,每次讀取的數據數量設置為batch_size=8
6 train_reader = paddle.batch(trainset, batch_size=8)
7 test_reader = paddle.batch(testset,batch_size=8)

讀取數據,並打印觀察

paddle.batch函數將MNIST數據集拆分成多個批次,我們可以用下面的代碼讀取第一個批次的數據內容(因為for循環結尾處有一個break,運行一個循環后就立即退出),並觀察數據結果。

 1 # 以迭代的形式讀取數據
 2 for batch_id, data in enumerate(train_reader()):
 3     # 獲得圖像數據,並轉為float32類型的數組
 4     img_data = np.array([x[0] for x in data]).astype('float32')
 5     # 獲得圖像標簽數據,並轉為float32類型的數組
 6     label_data = np.array([x[1] for x in data]).astype('float32')
 7     # 打印數據形狀
 8     #print("圖像數據形狀和對應數據為:", img_data.shape, img_data[0])
 9     print("圖像數據形狀和對應數據為:", img_data.shape)
10     print("圖像標簽形狀和對應數據為:", label_data.shape, label_data[0])
11     break
12 
13 print("\n打印第一個batch的第一個圖像,對應標簽數字為{}".format(label_data[0]))
14 # 顯示第一batch的第一個圖像(可以在程序任一個地方引用庫)
15 import matplotlib.pyplot as plt
16 #img=np.array(img_data[0])  #這兩句話效果相同
17 img = np.array(img_data[0]+1)*127.5
18 img = np.reshape(img, [28, 28]).astype(np.uint8)
19 
20 plt.figure("Image") # 圖像窗口名稱
21 plt.imshow(img)
22 plt.axis('on') # 關掉坐標軸為 off
23 plt.title('image') # 圖像題目
24 plt.show()
圖像數據形狀和對應數據為: (8, 784)
圖像標簽形狀和對應數據為: (8,) 5.0

打印第一個batch的第一個圖像,對應標簽數字為5.0
上面得到的數據img_data[0]輸出的是一個矩陣,每個數據值在【-1,1】之間。

從代碼的輸出來看,我們從數據加載器train_loader()中讀取一次數據,可以得到形狀為 (8, 784)的圖像數據和形狀為(8,)的標簽數據。其中,8與設置的batch大小對應784為mnist數據集中每個圖像的像素數量(28*28)

另外,從打印的圖像數據來看,圖像數據的范圍是[-1, 1],表明這是已經完成圖像歸一化后的圖像數據,且背景部分的值是-1。我們可以將圖像數據反歸一化(就是+1,再乘以127),並使用matplotlib工具包將其顯示出來。顯示的數字是5,和對應標簽數字一致。

說明:

飛槳將維度是28*28的手寫數字數據圖像轉成向量形式存儲,因此,使用飛槳數據讀取到的手寫數字圖像是長度為784(28*28)的向量。


 

2. 模型設計

在“房價預測”深度學習任務中,我們使用了單層且沒有非線性變換的模型,取得了理想的預測效果。在“手寫數字識別”中,我們依然使用這個模型預測輸入的圖形數字值。其中,模型的輸入為784維(28*28)數據,輸出為1維數據,如圖1所示。

 1 # 定義mnist數據識別網絡結構,同房價預測網絡
 2 class MNIST(fluid.dygraph.Layer):
 3     def __init__(self, name_scope):
 4         super(MNIST, self).__init__(name_scope)
 5         name_scope = self.full_name()
 6         # 定義一層全連接層,輸出維度是1,激活函數為None,即不使用激活函數
 7         #hidden1=fluid.layers.fc(input=name_scope,size=100,act='relu')
 8         #hidden2=FC(input=hidden1,size=100,act='relu')
 9         self.fc = FC(name_scope, size=1, act='relu')
10         
11     # 定義網絡結構的前向計算過程
12     def forward(self, inputs):
13         outputs = self.fc(inputs)
14         return outputs

3. 訓練配置

訓練配置負責神經網絡訓練前的准備,包括:

  1. 聲明定義好的模型。
  2. 加載訓練數據和測試數據。
  3. 設置優化算法和學習率,本次實驗優化算法使用隨機梯度下降SGD,學習率使用 0.01。
 1 # 定義飛槳動態圖工作環境
 2 with fluid.dygraph.guard():
 3     # 聲明網絡結構
 4     model = MNIST("mnist")
 5     # 啟動訓練模式
 6     model.train()
 7     # 定義數據讀取函數,數據讀取batch_size設置為16
 8     train_loader = paddle.batch(paddle.dataset.mnist.train(), batch_size=16)
 9     # 定義優化器,使用隨機梯度下降SGD優化器,學習率設置為0.001
10     optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.001)

上面中train()函數沒有發現在MNIST類中有定義,很是奇怪,需要找到train()函數的原始定義。


4. 訓練過程

完成訓練配置后,可啟動訓練過程。采用二層循環嵌套方式:

  • 內層循環負責整個數據集的一次遍歷,遍歷數據集采用分批次(batch)方式。
  • 外層循環定義遍歷數據集的次數,本次訓練中外層循環10次,通過參數EPOCH_NUM設置。
 1 # 通過with語句創建一個dygraph運行的context,
 2 # 動態圖下的一些操作需要在guard下進行
 3 with fluid.dygraph.guard():
 4     model = MNIST("mnist")
 5     model.train()
 6     train_loader = paddle.batch(paddle.dataset.mnist.train(), batch_size=16)
 7     optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.001)
 8     EPOCH_NUM = 10
 9     for epoch_id in range(EPOCH_NUM):
10         for batch_id, data in enumerate(train_loader()):
11             #准備數據,格式需要轉換成符合框架要求的
12             image_data = np.array([x[0] for x in data]).astype('float32')
13             label_data = np.array([x[1] for x in data]).astype('float32').reshape(-1, 1)
14             # 將數據轉為飛槳動態圖格式
15             image = fluid.dygraph.to_variable(image_data)
16             label = fluid.dygraph.to_variable(label_data)
17             
18             #前向計算的過程
19             predict = model(image)
20             
21             #計算損失,取一個批次樣本損失的平均值
22             loss = fluid.layers.square_error_cost(predict, label)
23             avg_loss = fluid.layers.mean(loss)
24             
25             #每訓練了1000批次的數據,打印下當前Loss的情況
26             if batch_id !=0 and batch_id  % 1000 == 0:
27                 print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))
28             
29             #后向傳播,更新參數的過程
30             avg_loss.backward()
31             optimizer.minimize(avg_loss)
32             model.clear_gradients()
33 
34     # 保存模型
35     fluid.save_dygraph(model.state_dict(), 'mnist')
epoch: 0, batch: 1000, loss is: [1.8736392]
epoch: 0, batch: 2000, loss is: [4.0054626]
epoch: 0, batch: 3000, loss is: [3.7705934]
epoch: 1, batch: 1000, loss is: [1.8645047]
epoch: 1, batch: 2000, loss is: [3.8951108]
epoch: 1, batch: 3000, loss is: [3.5067868]
epoch: 2, batch: 1000, loss is: [1.8366505]
epoch: 2, batch: 2000, loss is: [3.778401]
epoch: 2, batch: 3000, loss is: [3.4168165]
epoch: 3, batch: 1000, loss is: [1.8329564]
epoch: 3, batch: 2000, loss is: [3.7081861]
epoch: 3, batch: 3000, loss is: [3.3437557]
epoch: 4, batch: 1000, loss is: [1.8373424]
epoch: 4, batch: 2000, loss is: [3.6615422]
epoch: 4, batch: 3000, loss is: [3.2822015]
epoch: 5, batch: 1000, loss is: [1.8455799]
epoch: 5, batch: 2000, loss is: [3.6457105]
epoch: 5, batch: 3000, loss is: [3.2266264]
epoch: 6, batch: 1000, loss is: [1.8546844]
epoch: 6, batch: 2000, loss is: [3.6325989]
epoch: 6, batch: 3000, loss is: [3.1794245]
epoch: 7, batch: 1000, loss is: [1.863614]
epoch: 7, batch: 2000, loss is: [3.6269343]
epoch: 7, batch: 3000, loss is: [3.129442]
epoch: 8, batch: 1000, loss is: [1.8726021]
epoch: 8, batch: 2000, loss is: [3.6225967]
epoch: 8, batch: 3000, loss is: [3.0918531]
epoch: 9, batch: 1000, loss is: [1.880715]
epoch: 9, batch: 2000, loss is: [3.6216624]
epoch: 9, batch: 3000, loss is: [3.0604477]
上面為訓練的結果輸出。
同樣的問題:predict = model(image)這句中,沒有調用model的forward()函數就完成了前向計算,也理解不了。
通過觀察訓練過程中損失所發生的變化,可以發現雖然損失整體上在降低,但到訓練的最后一輪,損失函數值依然較高。可以猜測,“手寫數字識別”完全復用“房價預測”的代碼,訓練效果並不好。接下來我們通過模型測試,獲取模型訓練的真實效果。

5. 模型測試

模型測試的主要目的是驗證訓練好的模型是否能正確識別出數字。測試模型包括以下三步:

  • 從'./demo/example_0.jpg'目錄下讀取樣例圖片。
  • 加載模型並將模型的狀態設置為校驗狀態(eval),顯式告訴框架我們接下來只會使用前向計算的流程,不會計算梯度和梯度反向傳播,這將減少內存的消耗
  • 將測試樣本傳入模型,獲取預測結果,取整后作為預測標簽輸出。
1 # 導入圖像讀取第三方庫
2 import matplotlib.image as mpimg
3 import matplotlib.pyplot as plt
4 # 讀取圖像
5 example = mpimg.imread('./work/example_0.png')
6 # 顯示圖像
7 plt.imshow(example)

 1 # 讀取一張本地的樣例圖片,轉變成模型輸入的格式
 2 def load_image(img_path):
 3     # 從img_path中讀取圖像,並轉為灰度圖
 4     im = Image.open(img_path).convert('L')
 5     print(np.array(im))
 6     im = im.resize((28, 28), Image.ANTIALIAS)
 7     im = np.array(im).reshape(1, -1).astype(np.float32)
 8     # 圖像歸一化,保持和數據集的數據范圍一致
 9     im = 2 - im / 127.5
10     return im
11 
12 # 定義預測過程
13 with fluid.dygraph.guard():
14     model = MNIST("mnist")
15     params_file_path = 'mnist'
16     img_path = './work/example_0.png'
17     # 加載模型參數
18     model_dict, _ = fluid.load_dygraph("mnist")
19     model.load_dict(model_dict)
20     
21     model.eval()
22     tensor_img = load_image(img_path)
23     result = model(fluid.dygraph.to_variable(tensor_img))
24     #預測輸出取整,即為預測的數字
25     print("本次預測的數字是", result.numpy().astype('int32'))
[[255 255 255 ... 255 255 255]
 [255 255 255 ... 255 255 255]
 [255 255 255 ... 255 255 255]
 ...
 [255 255 255 ... 255 255 255]
 [255 255 255 ... 255 255 255]
 [255 255 255 ... 255 255 255]]
本次預測的數字是 [[1]]
model.load_dict()是加載模型,model.eval()是去掉訓練過程(也就是沒有了反向計算過程),只是測試。
如上可見,模型錯誤預測樣例圖片中的數字是1,實際應該預測的結果是0。但如果我們嘗試更多的樣本,會發現很多數字圖片識別結果是錯誤的,完全復用房價預測的實驗並不適用於手寫數字識別任務,接下來我們會對該實驗進行逐一改進,直到獲得令人滿意的結果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM