『跟著雨哥學AI』系列之四:詳解飛槳框架高階用法
課程簡介:
“跟著雨哥學AI”是百度飛槳開源框架近期針對高層API推出的系列課。本課程由多位資深飛槳工程師精心打造,不僅提供了從資料處理、到模型組網、模型訓練、模型評估和推理部署全流程講解;還提供了豐富的趣味案例,旨在幫助開發者更全面清晰地掌握百度飛槳框架的用法,並能夠舉一反三、靈活使用飛槳框架進行深度學習實踐。
回顧前幾節課,我們學會了資料的預處理及資料的載入、模型的組建以及模型的訓練。基於前三節課知識的積累,面對一個簡單任務,我們已經完全可以自己實現了。然而,在現實場景中,常常出現很多現成的方法無法滿足我們真實需求,因此接下來這節課程,我們將一起進階,學習高層API的高階用法,包括自定義損失
本次課程連結:
https://aistudio.baidu.com/aistudio/projectdetail/1409209
如果大家對哪部分有疑問,歡迎留言,我們會一一解答。下面就讓我們進入今天的內容吧。
1. 自定義損失函式
1.1 什麼是損失函式?
在深度學習中,損失函式是用來評估模型的預測結果與真實結果之間的差距,一般用L表示,損失函式越小,模型的魯棒性就越好。
模型訓練的過程其實是對損失函式的函式圖形採用梯度下降的方法來使得損失函式不斷減小到區域性最優值,來得到對任務來說比較合理的模型引數。
一般在深度學習框架中,有許多常用的損失函式,例如在影象分類任務中,我們常常使用交叉熵損失,在目標檢測任務中,常常使用Focal loss、L1/L2損失函式等,在影象識別任務中,我們經常會使用到Triplet Loss以及Center Loss等。然而,在現實世界中,這些“現成的”損失函式有的時候可能不太適合我們試圖解決的業務問題,因此自定義損失函式應運而生。
1.2 如何自定義損失函式?
飛槳除了提供常見場景需要的內建損失函式,還支援使用者根據自己的實際場景,完成損失函式的自定義。我們這裡就會講解介紹一下如何進行Loss的自定義操作,首先來看下面的程式碼:
importpaddle
paddle.__version__
'2.0.0-rc1'
classSelfDefineLoss(paddle.nn.Layer): """ 1.繼承paddle.nn.Layer """ def__init__(self): """ 2.建構函式根據自己的實際演算法需求和使用需求進行引數定義即可 """ super(SelfDefineLoss,self).__init__() defforward(self,input,label): """ 3. 實現forward函式,forward在呼叫時會傳遞兩個引數:input和label - input:單個或批次訓練資料經過模型前向計算輸出結果 - label:單個或批次訓練資料對應的標籤資料 介面返回值是一個Tensor,根據自定義的邏輯加和或計算均值後的損失 """ #使用Paddle中相關API自定義的計算邏輯 #output=xxxxx #returnoutput
那麼從程式碼 層面總結起來看,我們如何自定義損失函式呢?我們可以參考如下固定步驟:
-
實現一個Loss類,繼承paddle.nn.Layer
-
定義類初始化__init__方法,可以根據自己的場景需求來做初始化程式碼實現
-
在forward中實現損失函式的計算方法
看到這裡同學們是否學會如何編寫自定義損失函數了呢?如果還不理解,我們就看一個實際的例子吧,以影象分割為例,下面是在影象分割示例程式碼中寫的一個自定義損失,當時主要是想使用自定義的softmax計算維度。
classSoftmaxWithCrossEntropy(paddle.nn.Layer):
def__init__(self):
super(SoftmaxWithCrossEntropy,self).__init__()
defforward(self,input,label):
"""
這裡是呼叫了paddle提供的一個functional方法來實現自定義的axis維度計算帶softmax的crossentropy
"""
loss=F.softmax_with_cross_entropy(input,label,return_softmax=False,axis=1)
returnpaddle.mean(loss)
2. 自定義評估指標
2.1 什麼是評估指標?
評估指標用英文表示是Metrics,有的時候也成為效能指標,用來衡量反饋一個模型的實際效果好壞,一般是通過計算模型的預測結果和真實結果之間的某種【距離】得出。
和損失函式型別,我們一般會在不同的任務場景中選擇不同的評估指標來做模型評估,例如在分類任務中,比較常見的評估指標包括了Accuracy、Recall、Precision和AUC等,在迴歸中有MAE和MSE等等。
這些常見的評估指標在飛槳框架中都會有對應的API實現,直接使用即可。那麼如果我們遇到一些想要做個性化實現的操作時,該怎麼辦呢?那麼這裡就涉及到了如何自定義評估指標?
2.2 如何自定義評估指標?
對於如何自定義評估指標,我們總結了一個模板式的方法來給大家做引導學習實現,可以先通過下面的程式碼和註釋來了解一下:
classSelfDefineMetric(paddle.metric.Metric):
"""
1.繼承paddle.metric.Metric
"""
def__init__(self):
"""
2.建構函式實現,自定義引數即可
"""
super(SelfDefineMetric,self).__init__()
defname(self):
"""
3.實現name方法,返回定義的評估指標名字
"""
return'自定義評價指標的名字'
defcompute(self,*kwargs):
"""
4. 本步驟可以省略,實現compute方法,這個方法主要用於`update`的加速,可以在這個方法中呼叫一些paddle實現好的Tensor計算API,編譯到模型網路中一起使用低層C++ OP計算。
"""
return'自己想要返回的資料,會做為update的引數傳入。'
defupdate(self,*kwargs):
"""
5. 實現update方法,用於單個batch訓練時進行評估指標計算。
-當`compute`類函式未實現時,會將模型的計算輸出和標籤資料的展平作為`update`的引數傳入。
-當`compute`類函式做了實現時,會將compute的返回結果作為`update`的引數傳入。
"""
return'accvalue'
defaccumulate(self):
"""
6. 實現accumulate方法,返回歷史batch訓練積累後計算得到的評價指標值。
每次`update`呼叫時進行資料積累,`accumulate`計算時對積累的所有資料進行計算並返回。
結算結果會在`fit`介面的訓練日誌中呈現。
"""
#利用update中積累的成員變數資料進行計算後返回
return'accumulatedaccvalue'
defreset(self):
"""
7. 實現reset方法,每個Epoch結束後進行評估指標的重置,這樣下個Epoch可以重新進行計算。
"""
#doresetaction
那麼總結起來一共7個步驟:
-
實現自己的評估指標類,繼承paddle.metric.Metric
-
初始化函式__init__實現,自定義引數即可
-
實現name()方法,返回定義的評估指標名字
-
實現compute()方法,本步驟也可以省略,這個方法主要用於update()的加速,可以在這個方法中呼叫一些paddle實現好的Tensor計算API,編譯到模型網路中一起使用低層C++ OP計算
-
實現update()方法,用於單個batch訓練時進行評估指標計算
-
實現accumulate()方法,返回歷史batch訓練積累後計算得到的評價指標值
-
實現reset()方法,每個Epoch結束後進行評估指標的重置,這樣下個Epoch可以重新進行計算
為了方便同學們的理解,我們拿框架中已提供的一個評估指標計算介面作為例子展示一下,程式碼如下:
frompaddle.metricimportMetric
classPrecision(Metric):
"""
Precision(alsocalledpositivepredictivevalue)isthefractionof
relevantinstancesamongtheretrievedinstances.Referto
https://en.wikipedia.org/wiki/Evaluation_of_binary_classifiers
Notedthatthisclassmanagestheprecisionscoreonlyforbinary
classificationtask.
......
"""
def__init__(self,name='precision',*args,**kwargs):
super(Precision,self).__init__(*args,**kwargs)
self.tp=0#truepositive
self.fp=0#falsepositive
self._name=name
defupdate(self,preds,labels):
"""
Updatethestatesbasedonthecurrentmini-batchpredictionresults.
Args:
preds(numpy.ndarray):Thepredictionresult,usuallytheoutput
oftwo-classsigmoidfunction.Itshouldbeavector(column
vectororrowvector)withdatatype:'float64'or'float32'.
labels(numpy.ndarray):Thegroundtruth(labels),
theshapeshouldkeepthesameaspreds.
Thedatatypeis'int32'or'int64'.
"""
ifisinstance(preds,paddle.Tensor):
preds=preds.numpy()
elifnot_is_numpy_(preds):
raiseValueError("The'preds'mustbeanumpyndarrayorTensor.")
ifisinstance(labels,paddle.Tensor):
labels=labels.numpy()
elifnot_is_numpy_(labels):
raiseValueError("The'labels'mustbeanumpyndarrayorTensor.")
sample_num=labels.shape[0]
preds=np.floor(preds+0.5).astype("int32")
foriinrange(sample_num):
pred=preds[i]
label=labels[i]
ifpred==1:
ifpred==label:
self.tp+=1
else:
self.fp+=1
defreset(self):
"""
Resetsallofthemetricstate.
"""
self.tp=0
self.fp=0
defaccumulate(self):
"""
Calculatethefinalprecision.
Returns:
Ascalerfloat:resultsofthecalculatedprecision.
"""
ap=self.tp+self.fp
returnfloat(self.tp)/apifap!=0else.0
defname(self):
"""
Returnsmetricname
"""
returnself._name
3. 自定義執行過程回撥函式
3.1 什麼是執行過程回撥函式?
同學們是否記得在上節課中,我們學習了一個model.fit全流程介面,model.fit介面有一個callback引數來支援我們傳一個Callback類例項,用來在每輪訓練和每個batch訓練前後進行一些自定義操作呼叫,可以通過callback收集到訓練過程中的一些資料和引數,或者實現一些自定義操作。
3.2 如何自定義執行過程回撥函式?
我們也為大家準備了一個用於模板化學習的程式碼,如下所示,大家可以來檢視學習一下:
classSelfDefineCallback(paddle.callbacks.Callback):
"""
1.繼承paddle.callbacks.Callback
2. 按照自己的需求實現以下類成員方法:
defon_train_begin(self,logs=None)訓練開始前,`Model.fit`介面中呼叫
defon_train_end(self,logs=None)訓練結束後,`Model.fit`介面中呼叫
defon_eval_begin(self,logs=None)評估開始前,`Model.evaluate`介面呼叫
defon_eval_end(self,logs=None)評估結束後,`Model.evaluate`介面呼叫
defon_predict_begin(self,logs=None)預測測試開始前,`Model.predict`介面中呼叫
defon_predict_end(self,logs=None)預測測試結束後,`Model.predict`介面中呼叫
defon_epoch_begin(self,epoch,logs=None)每輪訓練開始前,`Model.fit`介面中呼叫
defon_epoch_end(self,epoch,logs=None)每輪訓練結束後,`Model.fit`介面中呼叫
defon_train_batch_begin(self,step,logs=None)單個Batch訓練開始前,`Model.fit`和`Model.train_batch`介面中呼叫
defon_train_batch_end(self,step,logs=None)單個Batch訓練結束後,`Model.fit`和`Model.train_batch`介面中呼叫
defon_eval_batch_begin(self,step,logs=None)單個Batch評估開始前,`Model.evalute`和`Model.eval_batch`介面中呼叫
defon_eval_batch_end(self,step,logs=None)單個Batch評估結束後,`Model.evalute`和`Model.eval_batch`介面中呼叫
defon_predict_batch_begin(self,step,logs=None)單個Batch預測測試開始前,`Model.predict`和`Model.predict_batch`介面中呼叫
defon_predict_batch_end(self,step,logs=None)單個Batch預測測試結束後,`Model.predict`和`Model.predict_batch`介面中呼叫
"""
def__init__(self):
super(SelfDefineCallback,self).__init__()
#按照需求定義自己的類成員方法
對於Callback的實現比較簡單,我們只需要繼承paddle.callbacks.Callback即可,剩下就是按照自己的callback使用需求實現不同的類成員方法,舉個栗子:比如我們想要在每個epoch開始和結束分別實現不同的操作,那麼就實現對應的on_epoch_begin和on_epoch_end即可;如果我們想要在訓練的每個batch開始前做一些操作或收集一些資料,那麼實現on_train_batch_begin介面即可。
結合上述的實現方法,我們具體看一個框架中的實際例子吧。這是一個框架自帶的ModelCheckpoint回撥函式,方便使用者在fit訓練模型時自動儲存每輪訓練得到的模型,我們嘗試使用自定義方式實現:
classModelCheckpoint(Callback):
def__init__(self,save_freq=1,save_dir=None):
self.save_freq=save_freq
self.save_dir=save_dir
defon_epoch_begin(self,epoch=None,logs=None):
self.epoch=epoch
def_is_save(self):
returnself.modelandself.save_dirandParallelEnv().local_rank==0
defon_epoch_end(self,epoch,logs=None):
ifself._is_save()andself.epoch%self.save_freq==0:
path='{}/{}'.format(self.save_dir,epoch)
print('savecheckpointat{}'.format(os.path.abspath(path)))
self.model.save(path)
defon_train_end(self,logs=None):
ifself._is_save():
path='{}/final'.format(self.save_dir)
print('savecheckpointat{}'.format(os.path.abspath(path)))
self.model.save(path)
4. 視覺化分析工具VisualDL
我們知道深度學習在各個應用領域產生了巨大的影響,但是我們常常因為無法很清晰地解釋深度網路的來龍去脈而感到困惑。由於人類對於世界的認知和感受主要來自於視覺,良好的視覺化可以有效的幫助人們理解深度網路,並進行有效的優化和調節,因此飛槳提供了視覺化分析工具--VisualDL,該工具以豐富的圖表呈現訓練引數變化趨勢、模型結構、資料樣本、直方圖、PR曲線及高維資料分佈,可幫助使用者更清晰直觀地理解深度學習模型訓練過程及模型結構,進而實現高效的模型優化。
Note: VisualDL支援瀏覽器種類:Chrome(81和83)、Safari 13、FireFox(77和78)、Edge(Chromium版),原生支援python的使用,通過在模型的Python配置中新增幾行程式碼,便可為訓練過程提供豐富的視覺化支援。
4.1 安裝
我們在使用VisualDL前需要先做下安裝,如果您已經安裝過了那麼可以跳過這個步驟。
使用pip來安裝我們的VisualDL,如下所示:
pipinstall--upgrade--previsualdl
4.2 在高層API中如何使用VisualDL?
在高層API中使用VisualDL可以用來呈現訓練過程中的Loss和Metric等訓練過程資料資訊,那麼使用起來也是比較方便,我們只需要應用paddle.callbacks.VisualDL回撥函式介面即可,這個回撥介面只有一個引數,就是我們的日誌儲存目錄,用於將訓練過程中的資料吸入到對應目錄的檔案內,後續通過VisualDL展示端來對應讀取呈現。
我們來檢視一個例子:
importpaddle importpaddle.vision.transformsasT frompaddle.staticimportInputSpec inputs=[InputSpec([-1,1,28,28],'float32','image')] labels=[InputSpec([None,1],'int64','label')] transform=T.Compose([ T.Transpose(), T.Normalize([127.5],[127.5]) ]) train_dataset=paddle.vision.datasets.MNIST(mode='train',transform=transform) eval_dataset=paddle.vision.datasets.MNIST(mode='test',transform=transform) net=paddle.vision.LeNet() model=paddle.Model(net,inputs,labels) optim=paddle.optimizer.Adam(0.001,parameters=net.parameters()) model.prepare(optimizer=optim, loss=paddle.nn.CrossEntropyLoss(), metrics=paddle.metric.Accuracy()) #只需要定義一個VisualDL的Callback,然後傳遞給fit介面即可 callback=paddle.callbacks.VisualDL(log_dir='visualdl_log_dir') model.fit(train_dataset,eval_dataset,batch_size=64,callbacks=callback)
Cachefile/home/aistudio/.cache/paddle/dataset/mnist/train-images-idx3-ubyte.gznotfound,downloadinghttps://dataset.bj.bcebos.com/mnist/train-images-idx3-ubyte.gz Begintodownload Downloadfinished Cachefile/home/aistudio/.cache/paddle/dataset/mnist/train-labels-idx1-ubyte.gznotfound,downloadinghttps://dataset.bj.bcebos.com/mnist/train-labels-idx1-ubyte.gz Begintodownload ........ Downloadfinished Cachefile/home/aistudio/.cache/paddle/dataset/mnist/t10k-images-idx3-ubyte.gznotfound,downloadinghttps://dataset.bj.bcebos.com/mnist/t10k-images-idx3-ubyte.gz Begintodownload Downloadfinished Cachefile/home/aistudio/.cache/paddle/dataset/mnist/t10k-labels-idx1-ubyte.gznotfound,downloadinghttps://dataset.bj.bcebos.com/mnist/t10k-labels-idx1-ubyte.gz Begintodownload .. Downloadfinished Thelossvalueprintedinthelogisthecurrentstep,andthemetricistheaveragevalueofpreviousstep. Epoch1/1 step10/938-loss:2.1055-acc:0.1953-25ms/step step20/938-loss:1.0578-acc:0.3617-17ms/step ... ...
... ...
step938/938-loss:0.0785-acc:0.9313-10ms/step
Evalbegin... Thelossvalueprintedinthelogisthecurrentbatch,andthemetricistheaveragevalueofpreviousstep. step10/157-loss:0.1088-acc:0.9766-10ms/step step20/157-loss:0.2529-acc:0.9680-9ms/step
... ...
... ...
step157/157-loss:9.7858e-04-acc:0.9731-7ms/step Evalsamples:10000
4.3 如何可視檢視訓練過程中儲存的日誌呢?
4.3.1 在AIStudio中使用
通過Notebook中最左側的選單中【視覺化】來完成。可以參考使用說明:https://ai.baidu.com/ai-doc/AISTUDIO/Dk3e2vxg9#visualdl工具
4.3.2 在本機上使用
在本機上使用VisualDL可以參考使用說明:
https://github.com/PaddlePaddle/VisualDL/blob/develop/docs/components/README.md#Scalar--折線圖元件
4.4 在高層API之外如何使用VisualDL
-
記錄日誌
VisualDL的後端提供了Python SDK,可通過LogWriter定製一個日誌記錄器,介面程式碼如下所示:
classLogWriter(logdir=None,
comment='',
max_queue=10,
flush_secs=120,
filename_suffix='',
write_to_disk=True,
**kwargs)
介面引數解釋和可以參見:
https://github.com/PaddlePaddle/VisualDL#1-log
我們來看一個例子,設定日誌檔案並記錄標量資料:
fromvisualdlimportLogWriter
#在`./log/scalar_test/train`路徑下建立日誌檔案
withLogWriter(logdir="./log/scalar_test/train")aswriter:
#使用scalar元件記錄一個標量資料
writer.add_scalar(tag="acc",step=1,value=0.5678)
writer.add_scalar(tag="acc",step=2,value=0.6878)
writer.add_scalar(tag="acc",step=3,value=0.9878)
-
啟動面板
在上述示例中,日誌已記錄三組標量資料,現可啟動VisualDL面板檢視日誌的視覺化結果,共有兩種啟動方式,一種為命令列啟動,使用命令列啟動VisualDL面板;另一種為在python指令碼中啟動,在使用任意一種方式啟動VisualDL面板後,開啟瀏覽器訪問VisualDL面板,即可檢視日誌的視覺化結果。
1)使用命令列啟動VisualDL面板,命令格式如下:
visualdl--logdir<dir_1,dir_2,...,dir_n>--host<host>--port<port>--cache-timeout<cache_timeout>--language<language>--public-path<public_path>--api-only
引數詳情參見:
https://github.com/PaddlePaddle/VisualDL#2-launch-panel
針對上一步生成的日誌,啟動命令為:
visualdl--logdir./log
2)在Python指令碼中啟動VisualDL面板,介面如下:
visualdl.server.app.run(logdir,
host="127.0.0.1",
port=8080,
cache_timeout=20,
language=None,
public_path=None,
api_only=False,
open_browser=False)
Note:請注意:除logdir外,其他引數均為不定引數,傳遞時請指明引數名。
引數詳情見:
https://github.com/PaddlePaddle/VisualDL#launch-in-python-script
針對上一步生成的日誌,我們的啟動指令碼為:
fromvisualdl.serverimportapp
app.run(logdir="./log")
總結
本節課為大家介紹了我們在日常研發測試過程中涉及到需要掌握的一些高層API的高階用法,包括自定義損失函式,自定義評估指標,自定義執行過程回撥函式以及視覺化分析工具VisualDL。到這裡,我們先暫時結束了飛槳高層API的一些基礎知識學習。下節課我們將進入到趣味案例部分,我們使用前面4節課中學習的API來實現我們的趣味案例,大家如果有什麼感興趣的趣味案例都可以在評論區留言,我們將會在後續的課程中給大家安排上!
回顧往期: