11月1日,百度發佈了Paddle Fluid的1.1版本,做爲國內首個深度學習框架,PaddlePaddle對中文社區很是友好,有完善的中文社區、項目爲導向的中文教程,可讓更多中文使用者更方便地進行深度學習、機器學習相關的研究和實踐。我本人也很是但願PaddlePaddle可以不斷髮展壯大,畢竟這是國內公司爲開源社區作出的一項很是有意義的貢獻。爲了一探Paddle Fluid 1.1版本究竟作了哪些方面的更新,筆者第一時間安裝了新發布的版本,用一個基於SSD的目標檢測任務來測試一下新版PaddlePaddle的表現。html
圖像識別對於作視覺的同窗來講應該是一個很是熟悉的任務了,最初深度學習就是是應用於圖像識別任務的,舉例來講,給計算機一張汽車圖片,讓它判斷這圖片裏有沒有汽車。python
對於背景乾淨的圖片來講,這樣作頗有意義也比較容易。可是若是是一張包含豐富元素的圖片,不只識別難度大大提升,僅僅判斷出裏面有沒有圖片的意義也不大了,咱們須要找到到底在讀片的什麼位置出現了一輛汽車,這就提出了一個新的任務和需求——目標檢測。git
咱們的任務就是給定一張圖像或是一個視頻幀,讓計算機找出其中全部目標的位置,並給出每一個目標的具體類別。對於人類來講,目標檢測是一個很是簡單的任務。然而,計算機可以「看到」的是圖像被編碼以後的數字,很難解圖像或是視頻幀中出現了人或是物體這樣的高層語義概念,也就更加難以定位目標出如今圖像中哪一個區域。github
與此同時,因爲目標會出如今圖像或是視頻幀中的任何位置,目標的形態變幻無窮,圖像或是視頻幀的背景千差萬別,諸多因素都使得目標檢測對計算機來講是一個具備挑戰性的問題。目前主流的方法是FasterRCNN、YOLO和SSD,本文使用SSD進行實驗。算法
第一次聽到PaddlePaddle是在CCF前線研討會上,當時幾我的聊起來關於機器學習算法平臺的事情,有一位小夥伴提起了這個名字,因此一段時間以來我一直認爲這是一個機器學習算法平臺。直到16年百度開源了PaddlePaddle我才知道,原來這是一個能夠跟TensorFlow媲美的深度學習框架,主打「易用、高效、靈活、可擴展」。因此,簡單來講,PaddlePaddle就是百度自研的一套深度學習框架(看過發佈會後瞭解到,百度爲此創建了一套覆蓋面很是廣的生態,包括金融、推薦、決策等,但筆者主要是對PaddlePaddle的核心框架進行測評,不在此浪費過多筆墨了)。docker
筆者的工做站是Ubuntu 16.04系統,PaddlePaddle在CentOS和Ubuntu都支持pip安裝和docker安裝,GPU版本在Linux下也能夠完美適配。下面來看一下具體的安裝步驟。編程
首先咱們使用cat /proc/cpuinfo | grep avx2來查看咱們的Ubuntu系統是否支持avx2指令集,若是發現系統返回了以下一系列信息,就說明系統是支持avx2指令集的,能夠放心進行後續安裝。若是不支持也不要緊,在官網上能夠直接下載no_avx的whl包進行安裝。json
接下來使用pip安裝最新的Fluid v1.1版本的PaddlePaddle(GPU),在安裝前注意,須要在機器上安裝python3.5-dev才能夠用pip安裝PaddlePaddle。下載速度會比較慢,須要20分鐘左右的下載時間。api
安裝完成後,在python裏import paddle測試一下,若是成功導入則說明安裝成功!服務器
在更新的Paddle Fluid v1.1版本中還特地優化了對MacOS的支持,能夠直接經過pip安裝,也能夠用源碼編譯安裝。具體細節可參考:http://www.paddlepaddle.org/documentation/docs/zh/1.1/beginners_guide/install/Start.html
框架的計算描述方式是深度學習項目開發者很是關注的一個問題。計算的描述方式經歷了從Caffe1.0時代的一組連續執行的layers到TensorFlow的變量和操做構成的計算圖再到PaddlePaddle Fluid[1]提出再也不有模型的概念一系列的演變。那麼PaddlePaddle如今是怎麼描述計算的呢?
PaddlePaddle使用Program來描述模型和優化過程,能夠把它簡單理解爲數據流的控制過程。Program由Block、Operator和Variable構成,variable和operator被組織成爲多個能夠嵌套的block。具體的,若是要實現一個神經網絡,咱們只須要經過添加必要的variable、operator來定義網絡的前向計算,而反向計算、內存管理、block建立都由框架來完成。下面展現一下如何在PaddlePaddle中定義program:
以一個簡單的線性迴歸爲例,咱們這樣定義前向計算邏輯:
#定義輸入數據類型
x=fluid.layers.data(name="x",shape=[1],dtype='float32')
#搭建全鏈接網絡
y_predict=fluid.layers.fc(input=x,size=1,act=None)
定義好計算邏輯後,與TensorFlow同樣,下一步就須要定義損失函數,feed數據,開始訓練,feed數據也是在執行運算的時候進行,咱們先定義一下數據,這裏train_data 就是咱們的輸入數據,y_true是label:
train_data=numpy.array([[1.0],[2.0],[3.0],[4.0]]).astype('float32')
y_true = numpy.array([[2.0],[4.0],[6.0],[8.0]]).astype('float32')
添加均方偏差損失函數(MSE),框架會自動完成反向計算:
cost = fluid.layers.square_error_cost(input=y_predict,label=y)
avg_cost = fluid.layers.mean(cost)
執行咱們定義的上述Program:
cpu = fluid.core.CPUPlace()
exe = fluid.Executor(cpu)
exe.run(fluid.default_startup_program())
#開始訓練
outs = exe.run(
feed={'x':train_data,'y':y_true},
fetch_list=[y_predict.name,avg_cost.name])
#觀察結果
print outs
輸出結果:
[array([[0.9010564],
[1.8021128],
[2.7031693],
[3.6042256]], dtype=float32), array([9.057577], dtype=float32)]
這樣就用PaddlePaddle實現了簡單的計算流程,我的感受使用起來跟TensorFlow的類似度較高,習慣在TensorFlow上跑模型的小夥伴應該很容易適應PaddlePaddle的這一套生態。
關於PaddlePaddle計算描述的詳情能夠參考Fluid編程指南:http://www.paddlepaddle.org/documentation/docs/zh/1.1/beginners_guide/programming_guide/programming_guide.html
PaddlePaddle的核心框架內置了很是多的經典模型和網絡,涵蓋了幾乎全部主流的機器學習/深度學習任務,包括圖像、語音、天然語言處理、推薦等諸多方面。由於本文是作目標檢測,因此主要調研了一下圖像方面的模型庫,在此大體介紹一下。
分類任務中的模型庫是最全面的,AlexNet、VGG、GoogleNet、ResNet、Inception、MobileNet、Dual Path Network以及SE-ResNeXt,2012年以來的經典圖像識別網絡都包含其中,每一個網絡模型是一個獨立的py文件,裏面是這個網絡模型的類,類裏面公用的方法是net(),在調用時初始化對應的類以後調用.net()方法,就能夠獲得對應網絡的Program描述,以後只須要給網絡feed數據、定義損失函數、優化方法等就能夠輕鬆使用了。分類模型做爲圖像任務的基礎任務,在目標檢測、語義分割等任務中都會重複利用這些模型,因此這樣一個模型庫能夠爲大大簡化後續任務的開發工做。這部分的模型庫裏的寫法比較統一,只要瞭解網絡結構,用.net()方法調用就能夠,這裏就不一一介紹了,具體能夠參考:https://github.com/PaddlePaddle/models/tree/develop/fluid/PaddleCV/image_classification/models。
SSD
Single Shot MultiBox Detector (SSD) 是一種單階段的目標檢測器。與兩階段的檢測方法不一樣,單階段目標檢測並不進行區域推薦,而是直接從特徵圖迴歸出目標的邊界框和分類機率。SSD 運用了這種單階段檢測的思想,而且對其進行改進:在不一樣尺度的特徵圖上檢測對應尺度的目標。以下圖所示,SSD 在六個尺度的特徵圖上進行了不一樣層級的預測。每一個層級由兩個3x3卷積分別對目標類別和邊界框偏移進行迴歸。所以對於每一個類別,SSD 的六個層級一共會產生38x38x4 + 19x19x6 + 10x10x6 + 5x5x6 + 3x3x4 + 1x1x4 = 8732 個檢測結果。
SSD 目標檢測模型
SSD 能夠方便地插入到任何一種標準卷積網絡中,好比VGG、ResNet 或者MobileNet,這些網絡被稱做檢測器的基網絡。PaddlePaddle裏的SSD使用Google的MobileNet做爲基網絡。
目標檢測模型庫不一樣於分類模型庫,PaddlePaddle是以一個工程的形式提供SSD的模型庫。工程裏面包含以下文件:
其中,train.py、reader.py、mobilenet_ssd.py是與網絡訓練相關的文件,包括數據讀取、網絡結構、訓練參數等過程的定義都在這3個文件中;eval.py、eval_coco_map.py是網絡預測評估相關文件;infer.py是可視化預測結果相關文件。Data文件夾用於存儲數據集,使用時能夠把訓練集、測試集、驗證集放在data目錄下,reader會在data目錄下尋找圖片數據加載;pretrained目錄存放預訓練模型,若是不想從頭訓練一個SSD,能夠把預訓練好的模型放在這個目錄下,方便進行遷移學習。
有了上述的一些基礎,咱們就能夠輕鬆使用PaddlePaddle上手一些項目了。如今咱們就來實現一個基於SSD的目標檢測任務。
系統:Ubuntu 16.04
GPU:NVIDIA GTX 1080*4 顯存:8GB
環境:python 3.5
Paddle Fluid v1.1 GPU版本
咱們使用微軟的COCO2017數據集來預訓練模型(PaddlePaddle提供了一個基於COCO的預訓練模型,能夠直接使用),COCO數據集是微軟團隊獲取的一個能夠用來圖像recognition+segmentation+captioning 數據集,其官方說明網址:http://mscoco.org/。微軟在ECCV Workshops裏發表文章《Microsoft COCO: Common Objects in Context》更充分地介紹了該數據集。COCO以場景理解爲目標,從複雜場景中截取了328,000張影像,包括了91類目標和2,500,000個label。整個COCO2017數據集20G,官網下載很是慢,能夠在國內找一些鏡像站下載,數據集裏分好了訓練集、測試集和驗證集,標註和file_list用json文件保存。
拿到預訓練數據集後,咱們在Pascal VOC數據集上對模型進行進一步訓練,作一下微調。Pascal VOC數據集相較COCO數據集來講圖片數量和種類小不少,共計20類,11540張訓練圖片,標註採用xml格式文件保存。
圖片格式爲jpg,須要對圖像進行轉碼讀取,SSD中的reader.py文件幫助咱們實現了這個功能,內置的數據讀取使用了一個生成器來逐個batch讀取圖片並轉碼,這樣內存佔用率很是低。因爲咱們機器內存不大,設置的batch爲32,在此狀況下load十萬張圖片的annotation只須要17秒左右,每個batch的load+train時間只須要0.3秒左右。
能夠看一下這個reader的核心代碼:
defreader():
ifmode =='train'andshuffle:
np.random.shuffle(images)
batch_out =[]
forimage inimages:
image_name =image['file_name']
image_path =os.path.join(settings.data_dir,image_name)
im =Image.open(image_path)
ifim.mode =='L':
im =im.convert('RGB')
im_width,im_height =im.size
im_id =image['id']
# layout: category_id | xmin | ymin | xmax | ymax | iscrowd
bbox_labels =[]
annIds =coco.getAnnIds(imgIds=image['id'])
anns =coco.loadAnns(annIds)
forann inanns:
bbox_sample =[]
# start from 1, leave 0 to background
bbox_sample.append(float(ann['category_id']))
bbox =ann['bbox']
xmin,ymin,w,h =bbox
xmax =xmin +w
ymax =ymin +h
bbox_sample.append(float(xmin)/im_width)
bbox_sample.append(float(ymin)/im_height)
bbox_sample.append(float(xmax)/im_width)
bbox_sample.append(float(ymax)/im_height)
bbox_sample.append(float(ann['iscrowd']))
bbox_labels.append(bbox_sample)
im,sample_labels =preprocess(im,bbox_labels,mode,settings)
sample_labels =np.array(sample_labels)
iflen(sample_labels)==0:continue
im =im.astype('float32')
boxes =sample_labels[:,1:5]
lbls =sample_labels[:,0].astype('int32')
iscrowd =sample_labels[:,-1].astype('int32')
if'cocoMAP'insettings.ap_version:
batch_out.append((im,boxes,lbls,iscrowd,
[im_id,im_width,im_height]))
else:
batch_out.append((im,boxes,lbls,iscrowd))
iflen(batch_out)==batch_size:
yieldbatch_out
batch_out =[]
能夠看到,這裏的reader是一個生成器,逐個batch把數據load進內存。在數據讀取過程當中,須要注意一下幾點:
1. 數據集須要放在項目的data目錄下,reader經過annotations下的instances_train2017.json文件區分訓練集和驗證集,不須要在data目錄下用文件夾區分訓練集和驗證集。
2. 若是數據沒有按要求保存,則須要在reader.py修改數據路徑:
classSettings(object):
def__init__(self,
dataset=None,
data_dir=None,
label_file=None,
resize_h=300,
resize_w=300,
mean_value=[127.5,127.5,127.5],
apply_distort=True,
apply_expand=True,
ap_version='11point'):
self._dataset =dataset
self._ap_version =ap_version
# 把data_dir替換爲數據所在路徑
self._data_dir =data_dir
if'pascalvoc'indataset:
self._label_list =[]
label_fpath =os.path.join(data_dir,label_file)
forline inopen(label_fpath):
self._label_list.append(line.strip())
3. 若是遇到NoneType is not iterable的錯誤,通常是因爲數據讀取錯誤致使的,仔細檢查文件路徑應該能夠解決。
4. 讀取PascalVOC數據集用reader.py文件中的pascalvoc()函數,兩個數據集的文件結構和標註不太同樣,Paddle爲咱們寫好了兩個版本數據集的讀取方法,能夠直接調用。
數據讀取完成後,就能夠着手開始模型的訓練了,這裏直接使用PaddlePaddle SSD model裏面的train.py進行訓練:
python-u train.py
train.py裏爲全部的超參數都設置了缺省值,不熟悉PaddlePaddle參數調整的工程師能夠直接用缺省參數進行訓練,很是方便。若是須要,能夠根據下表進行對應超參數的修改:
參數名 |
類型 |
意義 |
learning_rate |
Float |
學習率 |
batch_size |
Int |
Batch大小 |
epoc_num |
Int |
迭代次數 |
use_gpu |
Bool |
是否使用GPU訓練 |
parallel |
Bool |
是否使用多卡訓練 |
dataset |
Str |
數據集名稱 |
model_save_dir |
Str |
模型保存路徑 |
pretrained_model |
Str |
預訓練模型路徑(若是使用) |
image_shape |
Str |
輸入圖片尺寸 |
data_dir |
Str |
數據集路徑 |
在執行腳本時,傳入相應的參數值便可,例如:
python-u train.py --batch_size=16--epoc_num=1--dataset='pascalvoc'--pretrained_model='pretrain/ssd_mobilenet_v1_coco/'
單機多卡的配置相較於多機多卡配置較爲簡單,參數須要先在GPU0上初始化,再經由fluid.ParallelExecutor() 分發到多張顯卡上。這裏可使用fluid.core.get_cuda_device_count()獲得可用顯卡數量,也能夠本身定義用幾張顯卡。
train_exe=fluid.ParallelExecutor(use_cuda=True,loss_name=loss.name,
main_program=fluid.default_main_program())
train_exe.run(fetch_list=[loss.name],feed={...})
PaddlePaddle這一套SSD模型給了使用者很是大的自由度,能夠對網絡結構、損失函數、優化方法等多個角度對模型進行調整。本文采用的是基於MobileNet的SSD,若是想使用基於VGG的SSD,能夠本身修改工程中的mobilenet_ssd.py文件,把裏面定義的MobileNet Program更改成VGG的Program描述就能夠了;若是須要修改損失函數或優化方法,則在train.py中找到build_program()函數,在
withfluid.unique_name.guard("train"):
loss =fluid.layers.ssd_loss(locs,confs,gt_box,gt_label,box,
box_var)
loss =fluid.layers.reduce_sum(loss)
optimizer =optimizer_setting(train_params)
optimizer.minimize(loss)
裏修改損失函數或優化器便可;修改batch_num、epoch_num、learning rate等參數能夠直接在train.py傳入參數中進行。
模型在COCO數據集上訓練完後,能夠用fluid.io.save_persistables()方法將模型保存下來,咱們實現了以下save_model()函數來將模型保存到指定路徑。
defsave_model(postfix,main_prog,model_path):
model_path =os.path.join(model_save_dir,postfix)
ifos.path.isdir(model_path):
shutil.rmtree(model_path)
print('save models to %s'%(model_path))
fluid.io.save_persistables(exe,model_path,main_program=main_prog)
訓練過程有時候會被打斷,只要每一個過幾個batch保存一下模型,咱們就能夠經過load_vars()方法來恢復已經保存的模型來繼續訓練或者用於預測。文中提到的這些API,你們能夠去PaddlePaddle的官網教程上進行更系統的學習和查看,PaddlePaddle提供了大量的中文文檔和使用教程,對中文使用者能夠說是很是友好的了。
fluid.io.load_vars(exe,pretrained_model,main_program=train_prog,predicate=if_exist)
訓練速度:在COCO2017數據集上單卡訓練,迭代1個epoch耗時3 min33s;單機4卡訓練,迭代1個epoch耗時1min02s。
CPU/GPU佔用率:正常訓練狀況下CPU佔用率在40%-60%之間,GPU佔用率穩定在50%左右。
CPU/GPU使用狀況
在PaddlePaddle的SSD模型中,可使用eval.py腳本進行模型評估,能夠選擇11point、integral等方法來計算模型在驗證集上的mAP。
python eval.py --dataset='pascalvoc'--model_dir='train_pascal_model/best_model'--data_dir='data/pascalvoc'--test_list='test.txt'--ap_version='11point'--nms_threshold=0.45
其中,model_dir是咱們訓練好的模型的保存目錄,data_dir是數據集目錄,test_list是做爲驗證集的文件列表(txt文件),前提是這些文件必需要有對應的標籤文件,ap_version是計算mAP的方法,nms_threshold是分類閾值。最後咱們獲得PaddlePaddle SSD模型在Pascal VOC數據集上的mAP爲73.32%[2]
模型 |
預訓練模型 |
訓練數據 |
測試數據 |
mAP |
MobileNet-v1-SSD 300x300 |
COCO MobileNet SSD |
VOC07+12 trainval |
VOC07 test |
73.32% |
模型訓練完成後,用test_program = fluid.default_main_program().clone(for_test=True)將Program轉換到test模式,而後把要預測的數據feed進Executor執行Program就能夠計算獲得圖像的分類標籤、目標框的得分、xmin、ymin、xmax、ymax。具體過程以下:
test_program=fluid.default_main_program().clone(for_test=True)
image=fluid.layers.data(name='image',shape=image_shape,dtype='float32')
locs,confs,box,box_var =mobile_net(num_classes,image,image_shape)
nmsed_out=fluid.layers.detection_output(
locs,confs,box,box_var,nms_threshold=args.nms_threshold)
place=fluid.CUDAPlace(0)ifargs.use_gpu elsefluid.CPUPlace()
exe =fluid.Executor(place)
nmsed_out_v,=exe.run(test_program,
feed=feeder.feed([[data]]),
fetch_list=[nmsed_out],
return_numpy=False)
nmsed_out_v=np.array(nmsed_out_v)
對於目標檢測任務,咱們一般須要對預測結果進行可視化進而得到對結果的感性認識。咱們能夠編寫一個程序,讓它在原圖像上畫出預測框,核心代碼以下:
defdraw_bounding_box_on_image(image_path,nms_out,confs_threshold,
label_list):
image =Image.open(image_path)
draw =ImageDraw.Draw(image)
im_width,im_height =image.size
fordt innms_out:
ifdt[1]<confs_threshold:
continue
category_id =dt[0]
bbox =dt[2:]
xmin,ymin,xmax,ymax =clip_bbox(dt[2:])
(left,right,top,bottom)=(xmin *im_width,xmax *im_width,
ymin *im_height,ymax *im_height)
draw.line(
[(left,top),(left,bottom),(right,bottom),(right,top),
(left,top)],
width=4,
fill='red')
ifimage.mode =='RGB':
draw.text((left,top),label_list[int(category_id)],(255,255,0))
image_name =image_path.split('/')[-1]
print("image with bbox drawed saved as {}".format(image_name))
image.save(image_name)
這樣,咱們能夠很直觀的看到預測結果:
使人欣喜的是,PaddlePaddle的SSD模型中幫咱們實現了完整的一套預測流程,咱們能夠直接運行SSD model下的infer.py腳本使用訓練好的模型對圖片進行預測:
python infer.py --dataset='coco'--nms_threshold=0.45--model_dir='pretrained/ssd_mobilenet_v1_coco'--image_path='./data/ pascalvoc/VOCdevkit/VOC2012/JPEGImages/2007_002216.jpg'
PaddlePaddle的模型部署須要先安裝編譯C++預測庫,能夠在http://www.paddlepaddle.org/documentation/docs/zh/1.1/user_guides/howto/inference/build_and_install_lib_cn.html下載安裝。預測庫中提供了Paddle的預測API,預測部署過程大體分爲三個步驟:1.建立PaddlePredictor;2.建立PaddleTensor傳入PaddlePredictor中;3.獲取輸出PaddleTensor,輸出結果。這部分操做也並不複雜,並且Paddle的教程中也提供了一份部署詳細代碼參考,你們能夠很快地利用這個模板完成模型部署(https://github.com/PaddlePaddle/Paddle/tree/develop/paddle/fluid/inference/api/demo_ci)
ü 中文社區支持好
在搭建SSD過程當中,遇到了一些問題,例如segmentation fault、NoneType等,筆者直接在paddle的GitHub上提了相關issue,很快就獲得了contributor的回覆,問題很快獲得瞭解決。
ü 教程完善
PaddlePaddle的官網上提供了很是詳盡的中英文教程,相較於以前學TensorFlow的時候常常看文檔看半天才能理解其中的意思,PaddlePaddle對於中文使用者真是一大福音。
ü 相比較TensorFlow,總體架構簡明清晰,沒有太多難以理解的概念。
ü 模型庫豐富
內置了CV、NLP、Recommendation等多種任務經常使用經典的模型,能夠快速開發迭代AI產品。
ü 性能優越,生態完整
從此次實驗的結果來看,PaddlePaddle在性能上與TensorFlow等主流框架的性能差異不大,訓練速度、CPU/GPU佔用率等方面均表現優異,並且PaddlePaddle已經佈局了一套完整的生態,前景很是好。
總體來講,PaddlePaddle是一個不錯的框架。因爲設計簡潔加之文檔、社區作的很好,很是容易上手,在使用過程當中也沒有很是難理解的概念,用fluid Program定義網絡結構很方便,對於以前使用過TensorFlow的工程師來講能夠比較快速的遷移到PaddlePaddle上。此次實驗過程當中,仍是發現了一些PaddlePaddle的問題,訓練過程若是意外終止,Paddle的訓練任務並無被徹底kill掉,依然會佔用CPU和GPU大量資源,內存和顯存的管理還須要進一步的提升。不過,實驗也證明了,正常狀況下PaddlePaddle在SSD模型上的精度、速度等性能與TensorFlow差很少,在數據讀取操做上比TensorFlow要更加簡潔明瞭。