本系列是針對於百度飛槳深度學習框架課程的筆記,主要是對百度官方課程資料的總結,內容是以入門項目——手寫數字識別為例介紹深度學習模型的搭建和飛槳框架的使用方法。由於水平實在有限,不免產生謬誤,歡迎讀者多多批評指正。如需要轉載請與博主聯系,謝謝
用飛槳搭建模型——以手寫數字識別為例
整體思路
利用飛槳搭建手寫數字識別模型的整體思路如下圖所示:
整個模型的代碼可以大致分為5個部分,即數據處理、模型設計、訓練配置、模型訓練、模型保存和測試,每個部分中又有着多個需要考慮的任務,這就大概總結了深度學習模型搭建所需的步驟。下面是針對每個部分的具體介紹。
數據處理
數據的預處理是模型解決問題的第一步,干凈而充實的訓練數據是獲得預測效果良好的模型的基本保證,實際工作中要根據所研究的任務不同而選擇合適的處理方式。一般來說需要考慮數據讀入、訓練/驗證集划分,生成批次數據、訓練樣本亂序及數據有效性校驗等問題。
MNIST手寫識別數據集分為訓練集train_set、驗證集val_set和測試集test_set,其中訓練集又包含訓練圖像train_images和訓練標簽train_labels兩個列表。train_image為[50000, 784]的二維列表,包含50000張圖片,每張圖片用一個長度為784的向量表示,內容是28*28尺寸的像素灰度值(黑白圖片)。train_labels為[50000, ]的列表,表示這些圖片對應的分類標簽,即0-9之間的一個數字。
相關實現代碼如下:
# 加載飛槳和相關數據處理的庫
import paddle
import paddle.fluid as fluid
from paddle.fluid.dygraph.nn import Linear
import numpy as np
import os
import gzip
import json
import random
# 數據讀取部分
# 聲明數據集文件位置
datafile = './work/mnist.json.gz'
print('loading mnist dataset from {} ......'.format(datafile))
# 加載json數據文件
data = json.load(gzip.open(datafile))
print('mnist dataset load done')
# 讀取到的數據區分訓練集,驗證集,測試集
train_set, val_set, eval_set = data
# 數據集相關參數,圖片高度IMG_ROWS, 圖片寬度IMG_COLS
IMG_ROWS = 28
IMG_COLS = 28
# 分批和亂序
imgs, labels = train_set[0], train_set[1]
# 獲得數據集長度
imgs_length = len(imgs)
# 定義數據集每個數據的序號,根據序號讀取數據
index_list = list(range(imgs_length))
# 讀入數據時用到的批次大小
BATCHSIZE = 100
# 隨機打亂訓練數據的索引序號
random.shuffle(index_list)
# 定義數據生成器,返回批次數據
def data_generator():
imgs_list = []
labels_list = []
for i in index_list:
# 將數據處理成期望的格式,比如類型為float32,shape為[1, 28, 28]
img = np.reshape(imgs[i], [1, IMG_ROWS, IMG_COLS]).astype('float32')
label = np.reshape(labels[i], [1]).astype('float32')
imgs_list.append(img)
labels_list.append(label)
if len(imgs_list) == BATCHSIZE:
# 獲得一個batchsize的數據,並返回
yield np.array(imgs_list), np.array(labels_list)
# 清空數據讀取列表
imgs_list = []
labels_list = []
# 如果剩余數據的數目小於BATCHSIZE,
# 則剩余數據一起構成一個大小為len(imgs_list)的mini-batch
if len(imgs_list) > 0:
yield np.array(imgs_list), np.array(labels_list)
return data_generator
# 數據校驗
# 聲明數據讀取函數,從訓練集中讀取數據
train_loader = data_generator
# 以迭代的形式讀取數據(這里只是校驗下圖像和標簽是否對應,一般在模型訓練時讀取)
for batch_id, data in enumerate(train_loader()):
image_data, label_data = data
if batch_id == 0: # 這里只看一組,如果自動校驗可以遍歷所有數據
# 人工校驗
print("打印第一個batch數據的維度:")
print("圖像維度: {}, 標簽維度: {}, 圖像數據類型: {}, 標簽數據類型: {}".format(image_data.shape, label_data.shape, type(image_data), type(label_data)))
# 自動校驗(與人工校驗目的類似)
assert len(image_data.shape[0]) == len(label_data.shape[0]), \
"length of train_imgs({}) should be the same as train_labels({})".format(image_data.shape[0], label_data.shape[0])
break
在實際工程中我們通常將上述數據預處理步驟提前封裝成函數,便於在訓練部分直接調用。這里僅對數據讀取部分進行封裝,並預留train/val/test三種模式。此外所需的批次數據生成函數data_generator前面已定義,就不再重復了。
def load_data(mode='train'):
datafile = './work/mnist.json.gz'
print('loading mnist dataset from {} ......'.format(datafile))
# 加載json數據文件
data = json.load(gzip.open(datafile))
print('mnist dataset load done')
# 讀取到的數據區分訓練集,驗證集,測試集
train_set, val_set, eval_set = data
if mode=='train':
# 獲得訓練數據集
imgs, labels = train_set[0], train_set[1]
elif mode=='valid':
# 獲得驗證數據集
imgs, labels = val_set[0], val_set[1]
elif mode=='eval':
# 獲得測試數據集
imgs, labels = eval_set[0], eval_set[1]
else:
raise Exception("mode can only be one of ['train', 'valid', 'eval']")
print("訓練數據集數量: ", len(imgs))
# 校驗數據
imgs_length = len(imgs)
assert len(imgs) == len(labels), \
"length of train_imgs({}) should be the same as train_labels({})".format(len(imgs), len(label))
# 獲得數據集長度
imgs_length = len(imgs)
# 定義數據集每個數據的序號,根據序號讀取數據
index_list = list(range(imgs_length))
# 讀入數據時用到的批次大小
BATCHSIZE = 100
為了提升模型整體運行效率,我們通常采取異步策略讀取數據,即設立一個異步隊列作為緩存區,將新讀取的數據不斷存入,而模型則從中取出前期存儲的數據進行訓練,從而實現數據讀取和模型訓練的並行運行。飛槳中相關實現方法如下:
# 定義數據讀取后存放的位置,CPU或者GPU,這里使用CPU,place = fluid.CUDAPlace(0) 時,數據才讀取到GPU上
place = fluid.CPUPlace()
with fluid.dygraph.guard(place):
# 聲明數據加載函數,使用訓練模式
train_loader = load_data(mode='train')
# 定義DataLoader對象用於加載Python生成器產生的數據,數據會由Python線程預先讀取,並異步送入一個隊列中(如果去掉下面兩句則代碼變為同步讀取策略)
# 參數capacity表示在DataLoader中維護的隊列容量,如果讀取數據的速度很快,建議設置為更大的值;return_list:在動態圖模式下需要設置為“True”,詳細參數介紹見飛槳技術文檔
data_loader = fluid.io.DataLoader.from_generator(capacity=5, return_list=True)
# 設置數據生成器,輸入的參數是一個Python數據生成器train_loader和服務器資源類型place(標明CPU還是GPU)
data_loader.set_batch_generator(train_loader, places=place)
# 迭代的讀取數據並打印數據的形狀
for i, data in enumerate(data_loader):
image_data, label_data = data
print(i, image_data.shape, label_data.shape)
if i>=5:
break
利用上述數據讀取部分,我們可以搭建一個最簡單的一層神經網絡進行訓練,以此展示讀取的數據應當如何使用。即使今后搭建非常復雜的模型,整體思路也是一樣的。
class MNIST(fluid.dygraph.Layer): # 定義神經網絡模型
def __init__(self):
super(MNIST, self).__init__()
self.fc = Linear(input_dim=784, output_dim=1, act=None)
def forward(self, inputs):
inputs = fluid.layers.reshape(inputs, (-1, 784))
outputs = self.fc(inputs)
return outputs
with fluid.dygraph.guard():
model = MNIST()
model.train()
# 調用加載數據的函數
train_loader = load_data('train')
# 創建異步數據讀取器
place = fluid.CPUPlace()
data_loader = fluid.io.DataLoader.from_generator(capacity=5, return_list=True)
data_loader.set_batch_generator(train_loader, places=place)
optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.001, parameter_list=model.parameters())
EPOCH_NUM = 3
for epoch_id in range(EPOCH_NUM):
for batch_id, data in enumerate(data_loader):
# 准備數據,變得更加簡潔
image_data, label_data = data
image = fluid.dygraph.to_variable(image_data)
label = fluid.dygraph.to_variable(label_data)
# 前向計算的過程
predict = model(image)
# 計算損失,取一個批次樣本損失的平均值
loss = fluid.layers.square_error_cost(predict, label)
avg_loss = fluid.layers.mean(loss)
# 每訓練了200批次的數據,打印下當前Loss的情況
if batch_id % 200 == 0:
print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))
# 后向傳播,更新參數的過程
avg_loss.backward()
optimizer.minimize(avg_loss)
model.clear_gradients()
#保存模型參數
fluid.save_dygraph(model.state_dict(), 'mnist')
參考資料: