『跟着雨哥學AI』系列之四:詳解飛槳框架高階用法

課程簡介:python

「跟着雨哥學AI」是百度飛槳開源框架近期針對高層API推出的系列課。本課程由多位資深飛槳工程師精心打造,不只提供了從數據處理、到模型組網、模型訓練、模型評估和推理部署全流程講解;還提供了豐富的趣味案例,旨在幫助開發者更全面清晰地掌握百度飛槳框架的用法,並可以觸類旁通、靈活使用飛槳框架進行深度學習實踐。git

下載安裝命令

## CPU版本安裝命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle

## GPU版本安裝命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu

圖片

回顧前幾節課,咱們學會了數據的預處理及數據的加載、模型的組建以及模型的訓練。基於前三節課知識的積累,面對一個簡單任務,咱們已經徹底能夠本身實現了。然而,在現實場景中,經常出現不少現成的方法沒法知足咱們真實需求,所以接下來這節課程,咱們將一塊兒進階,學習高層API的高階用法,包括自定義損失自定義評估方法自定義執行過程回調以及飛槳框架提供的可視化工具VisualDLgithub

本次課程連接:算法

https://aistudio.baidu.com/aistudio/projectdetail/1409209後端

若是你們對哪部分有疑問,歡迎留言,咱們會一一解答。下面就讓咱們進入今天的內容吧。api

1. 自定義損失函數

1.1 什麼是損失函數?

在深度學習中,損失函數是用來評估模型的預測結果與真實結果之間的差距,通常用L表示,損失函數越小,模型的魯棒性就越好。瀏覽器

模型訓練的過程實際上是對損失函數的函數圖形採用梯度降低的方法來使得損失函數不斷減少到局部最優值,來獲得對任務來講比較合理的模型參數。網絡

通常在深度學習框架中,有許多經常使用的損失函數,例如在圖像分類任務中,咱們經常使用交叉熵損失,在目標檢測任務中,經常使用Focal loss、L1/L2損失函數等,在圖像識別任務中,咱們常常會使用到Triplet Loss以及Center Loss等。然而,在現實世界中,這些「現成的」損失函數有的時候可能不太適合咱們試圖解決的業務問題,所以自定義損失函數應運而生。app

1.2 如何自定義損失函數?

飛槳除了提供常見場景須要的內置損失函數,還支持用戶根據本身的實際場景,完成損失函數的自定義。咱們這裏就會講解介紹一下如何進行Loss的自定義操做,首先來看下面的代碼:框架

import paddle
paddle.__version__

'2.0.0-rc1'

 

class SelfDefineLoss(paddle.nn.Layer):
    """
    1. 繼承paddle.nn.Layer
    """
    def __init__(self):
        """
        2. 構造函數根據本身的實際算法需求和使用需求進行參數定義便可
        """
        super(SelfDefineLoss, self).__init__()

    def forward(self, input, label):
        """
        3. 實現forward函數,forward在調用時會傳遞兩個參數:input和label
           - input:單個或批次訓練數據通過模型前向計算輸出結果
           - label:單個或批次訓練數據對應的標籤數據
           接口返回值是一個Tensor,根據自定義的邏輯加和或計算均值後的損失
        """
       # 使用Paddle中相關API自定義的計算邏輯
       # output = xxxxx
       # return output

那麼從代碼 層面總結起來看,咱們如何自定義損失函數呢?咱們能夠參考以下固定步驟:

  1. 實現一個Loss類,繼承paddle.nn.Layer

  2. 定義類初始化__init__方法,能夠根據本身的場景需求來作初始化代碼實現

  3. 在forward中實現損失函數的計算方法

看到這裏同窗們是否學會如何編寫自定義損失函數了呢?若是還不理解,咱們就看一個實際的例子吧,以圖像分割爲例,下面是在圖像分割示例代碼中寫的一個自定義損失,當時主要是想使用自定義的softmax計算維度。

class SoftmaxWithCrossEntropy(paddle.nn.Layer):
    def __init__(self):
        super(SoftmaxWithCrossEntropy, self).__init__()

    def forward(self, input, label):
        """
        這裏是調用了paddle提供的一個functional方法來實現自定義的axis維度計算帶softmax的cross entropy
        """
        loss = F.softmax_with_cross_entropy(input, label, return_softmax=False, axis=1)

        return paddle.mean(loss)

2. 自定義評估指標

2.1 什麼是評估指標?

評估指標用英文表示是Metrics,有的時候也成爲性能指標,用來衡量反饋一個模型的實際效果好壞,通常是經過計算模型的預測結果和真實結果之間的某種【距離】得出。

和損失函數類型,咱們通常會在不一樣的任務場景中選擇不一樣的評估指標來作模型評估,例如在分類任務中,比較常見的評估指標包括了Accuracy、Recall、Precision和AUC等,在迴歸中有MAE和MSE等等。

這些常見的評估指標在飛槳框架中都會有對應的API實現,直接使用便可。那麼若是咱們遇到一些想要作個性化實現的操做時,該怎麼辦呢?那麼這裏就涉及到了如何自定義評估指標?

2.2 如何自定義評估指標?

對於如何自定義評估指標,咱們總結了一個模板式的方法來給你們作引導學習實現,能夠先經過下面的代碼和註釋來了解一下:

class SelfDefineMetric(paddle.metric.Metric):
    """
    1. 繼承paddle.metric.Metric
    """
    def __init__(self):
        """
        2. 構造函數實現,自定義參數便可
        """
        super(SelfDefineMetric, self).__init__()

    def name(self):
        """
        3. 實現name方法,返回定義的評估指標名字
        """
        return '自定義評價指標的名字'

    def compute(self, *kwargs):
        """
        4. 本步驟能夠省略,實現compute方法,這個方法主要用於`update`的加速,能夠在這個方法中調用一些paddle實現好的Tensor計算API,編譯到模型網絡中一塊兒使用低層C++ OP計算。
        """
        return '本身想要返回的數據,會作爲update的參數傳入。'

    def update(self, *kwargs):
        """
        5. 實現update方法,用於單個batch訓練時進行評估指標計算。
        - 當`compute`類函數未實現時,會將模型的計算輸出和標籤數據的展平做爲`update`的參數傳入。
        - 當`compute`類函數作了實現時,會將compute的返回結果做爲`update`的參數傳入。
        """
        return 'acc value'

    def accumulate(self):
        """
        6. 實現accumulate方法,返回歷史batch訓練積累後計算獲得的評價指標值。
        每次`update`調用時進行數據積累,`accumulate`計算時對積累的全部數據進行計算並返回。
        結算結果會在`fit`接口的訓練日誌中呈現。
        """
        # 利用update中積累的成員變量數據進行計算後返回
        return 'accumulated acc value'

    def reset(self):
        """
        7. 實現reset方法,每一個Epoch結束後進行評估指標的重置,這樣下個Epoch能夠從新進行計算。
        """
        # do reset action

那麼總結起來一共7個步驟:

  1. 實現本身的評估指標類,繼承paddle.metric.Metric

  2. 初始化函數__init__實現,自定義參數便可

  3. 實現name()方法,返回定義的評估指標名字

  4. 實現compute()方法,本步驟也能夠省略,這個方法主要用於update()的加速,能夠在這個方法中調用一些paddle實現好的Tensor計算API,編譯到模型網絡中一塊兒使用低層C++ OP計算

  5. 實現update()方法,用於單個batch訓練時進行評估指標計算

  6. 實現accumulate()方法,返回歷史batch訓練積累後計算獲得的評價指標值

  7. 實現reset()方法,每一個Epoch結束後進行評估指標的重置,這樣下個Epoch能夠從新進行計算

爲了方便同窗們的理解,咱們拿框架中已提供的一個評估指標計算接口做爲例子展現一下,代碼以下:

from paddle.metric import Metric

class Precision(Metric):
    """
    Precision (also called positive predictive value) is the fraction of
    relevant instances among the retrieved instances. Refer to
    https://en.wikipedia.org/wiki/Evaluation_of_binary_classifiers
    Noted that this class manages the precision score only for binary
    classification task.

    ......
    """

    def __init__(self, name='precision', *args, **kwargs):
        super(Precision, self).__init__(*args, **kwargs)
        self.tp = 0  # true positive
        self.fp = 0  # false positive
        self._name = name

    def update(self, preds, labels):
        """
        Update the states based on the current mini-batch prediction results.
        Args:
            preds (numpy.ndarray): The prediction result, usually the output
               of two-class sigmoid function. It should be a vector (column
               vector or row vector) with data type: 'float64' or 'float32'.
           labels (numpy.ndarray): The ground truth (labels),
               the shape should keep the same as preds.
               The data type is 'int32' or 'int64'.
        """
        if isinstance(preds, paddle.Tensor):
            preds = preds.numpy()
        elif not _is_numpy_(preds):
            raise ValueError("The 'preds' must be a numpy ndarray or Tensor.")
        if isinstance(labels, paddle.Tensor):
            labels = labels.numpy()
        elif not _is_numpy_(labels):
            raise ValueError("The 'labels' must be a numpy ndarray or Tensor.")

        sample_num = labels.shape[0]
        preds = np.floor(preds + 0.5).astype("int32")

        for i in range(sample_num):
            pred = preds[i]
            label = labels[i]
            if pred == 1:
                if pred == label:
                    self.tp += 1
                else:
                    self.fp += 1

    def reset(self):
        """
        Resets all of the metric state.
        """
        self.tp = 0
        self.fp = 0

    def accumulate(self):
        """
        Calculate the final precision.

        Returns:
           A scaler float: results of the calculated precision.
        """
        ap = self.tp + self.fp
        return float(self.tp) / ap if ap != 0 else .0

    def name(self):
        """
        Returns metric name
        """
        return self._name

3. 自定義執行過程回調函數

3.1 什麼是執行過程回調函數?

同窗們是否記得在上節課中,咱們學習了一個model.fit全流程接口,model.fit接口有一個callback參數來支持咱們傳一個Callback類實例,用來在每輪訓練和每一個batch訓練先後進行一些自定義操做調用,能夠經過callback收集到訓練過程當中的一些數據和參數,或者實現一些自定義操做。

3.2 如何自定義執行過程回調函數?

咱們也爲你們準備了一個用於模板化學習的代碼,以下所示,你們能夠來查看學習一下:

class SelfDefineCallback(paddle.callbacks.Callback):
    """
    1. 繼承paddle.callbacks.Callback
    2. 按照本身的需求實現如下類成員方法:
        def on_train_begin(self, logs=None)                 訓練開始前,`Model.fit`接口中調用
        def on_train_end(self, logs=None)                   訓練結束後,`Model.fit`接口中調用
        def on_eval_begin(self, logs=None)                  評估開始前,`Model.evaluate`接口調用
        def on_eval_end(self, logs=None)                    評估結束後,`Model.evaluate`接口調用
        def on_predict_begin(self, logs=None)               預測測試開始前,`Model.predict`接口中調用
        def on_predict_end(self, logs=None)                 預測測試結束後,`Model.predict`接口中調用
        def on_epoch_begin(self, epoch, logs=None)          每輪訓練開始前,`Model.fit`接口中調用
        def on_epoch_end(self, epoch, logs=None)            每輪訓練結束後,`Model.fit`接口中調用
        def on_train_batch_begin(self, step, logs=None)     單個Batch訓練開始前,`Model.fit`和`Model.train_batch`接口中調用
        def on_train_batch_end(self, step, logs=None)       單個Batch訓練結束後,`Model.fit`和`Model.train_batch`接口中調用
        def on_eval_batch_begin(self, step, logs=None)      單個Batch評估開始前,`Model.evalute`和`Model.eval_batch`接口中調用
        def on_eval_batch_end(self, step, logs=None)        單個Batch評估結束後,`Model.evalute`和`Model.eval_batch`接口中調用
        def on_predict_batch_begin(self, step, logs=None)   單個Batch預測測試開始前,`Model.predict`和`Model.predict_batch`接口中調用
        def on_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訓練模型時自動存儲每輪訓練獲得的模型,咱們嘗試使用自定義方式實現:

class ModelCheckpoint(Callback):
    def __init__(self, save_freq=1, save_dir=None):
        self.save_freq = save_freq
        self.save_dir = save_dir

    def on_epoch_begin(self, epoch=None, logs=None):
        self.epoch = epoch

    def _is_save(self):
        return self.model and self.save_dir and ParallelEnv().local_rank == 0

    def on_epoch_end(self, epoch, logs=None):
        if self._is_save() and self.epoch % self.save_freq == 0:
            path = '{}/{}'.format(self.save_dir, epoch)
            print('save checkpoint at {}'.format(os.path.abspath(path)))
            self.model.save(path)

    def on_train_end(self, logs=None):
        if self._is_save():
            path = '{}/final'.format(self.save_dir)
            print('save checkpoint at {}'.format(os.path.abspath(path)))
            self.model.save(path)

4. 可視化分析工具VisualDL

咱們知道深度學習在各個應用領域產生了巨大的影響,可是咱們經常由於沒法很清晰地解釋深度網絡的前因後果而感到困惑。因爲人類對於世界的認知和感覺主要來自於視覺,良好的可視化能夠有效的幫助人們理解深度網絡,並進行有效的優化和調節,所以飛槳提供了可視化分析工具--VisualDL,該工具以豐富的圖表呈現訓練參數變化趨勢、模型結構、數據樣本、直方圖、PR曲線及高維數據分佈,可幫助用戶更清晰直觀地理解深度學習模型訓練過程及模型結構,進而實現高效的模型優化。

Note: VisualDL支持瀏覽器種類:Chrome(81和83)、Safari 1三、FireFox(77和78)、Edge(Chromium版),原生支持python的使用,經過在模型的Python配置中添加幾行代碼,即可爲訓練過程提供豐富的可視化支持。

4.1 安裝

咱們在使用VisualDL前須要先作下安裝,若是您已經安裝過了那麼能夠跳過這個步驟。

使用pip來安裝咱們的VisualDL,以下所示:

pip install --upgrade --pre visualdl

4.2 在高層API中如何使用VisualDL?

在高層API中使用VisualDL能夠用來呈現訓練過程當中的Loss和Metric等訓練過程數據信息,那麼使用起來也是比較方便,咱們只須要應用paddle.callbacks.VisualDL回調函數接口便可,這個回調接口只有一個參數,就是咱們的日誌存儲目錄,用於將訓練過程當中的數據吸入到對應目錄的文件內,後續經過VisualDL展現端來對應讀取呈現。

咱們來查看一個例子:




import paddle
import paddle.vision.transforms as T
from paddle.static import InputSpec

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)Cache file /home/aistudio/.cache/paddle/dataset/mnist/train-images-idx3-ubyte.gz not found, downloading https://dataset.bj.bcebos.com/mnist/train-images-idx3-ubyte.gz 
Begin to download

Download finished
Cache file /home/aistudio/.cache/paddle/dataset/mnist/train-labels-idx1-ubyte.gz not found, downloading https://dataset.bj.bcebos.com/mnist/train-labels-idx1-ubyte.gz 
Begin to download
........
Download finished
Cache file /home/aistudio/.cache/paddle/dataset/mnist/t10k-images-idx3-ubyte.gz not found, downloading https://dataset.bj.bcebos.com/mnist/t10k-images-idx3-ubyte.gz 
Begin to download

Download finished
Cache file /home/aistudio/.cache/paddle/dataset/mnist/t10k-labels-idx1-ubyte.gz not found, downloading https://dataset.bj.bcebos.com/mnist/t10k-labels-idx1-ubyte.gz 
Begin to download
..
Download finished
The loss value printed in the log is the current step, and the metric is the average value of previous step.
Epoch 1/1
step  10/938 - loss: 2.1055 - acc: 0.1953 - 25ms/step
step  20/938 - loss: 1.0578 - acc: 0.3617 - 17ms/step
... ...... ...step 938/938 - loss: 0.0785 - acc: 0.9313 - 10ms/stepEval begin...
The loss value printed in the log is the current batch, and the metric is the average value of previous step.
step  10/157 - loss: 0.1088 - acc: 0.9766 - 10ms/step
step  20/157 - loss: 0.2529 - acc: 0.9680 - 9ms/step... ...... ...step 157/157 - loss: 9.7858e-04 - acc: 0.9731 - 7ms/step
Eval samples: 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定製一個日誌記錄器,接口代碼以下所示:

class LogWriter(logdir=None,
                comment='',
                max_queue=10,
                flush_secs=120,
                filename_suffix='',
                write_to_disk=True,
                **kwargs)

接口參數解釋和能夠參見:

https://github.com/PaddlePaddle/VisualDL#1-log

咱們來看一個例子,設置日誌文件並記錄標量數據:

from visualdl import LogWriter

# 在`./log/scalar_test/train`路徑下創建日誌文件
with LogWriter(logdir="./log/scalar_test/train") as writer:
    # 使用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

針對上一步生成的日誌,咱們的啓動腳本爲:

from visualdl.server import app
app.run(logdir="./log")

總結

本節課爲你們介紹了咱們在平常研發測試過程當中涉及到須要掌握的一些高層API的高階用法,包括自定義損失函數,自定義評估指標,自定義執行過程回調函數以及可視化分析工具VisualDL。到這裏,咱們先暫時結束了飛槳高層API的一些基礎知識學習。下節課咱們將進入到趣味案例部分,咱們使用前面4節課中學習的API來實現咱們的趣味案例,你們若是有什麼感興趣的趣味案例均可以在評論區留言,咱們將會在後續的課程中給你們安排上!

下載安裝命令

## CPU版本安裝命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle

## GPU版本安裝命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu

回顧往期:

第一篇:『跟着雨哥學AI』系列:詳解飛槳框架數據管道

第二篇:『跟着雨哥學AI』系列之二:詳解飛槳框架模型組網

第三篇:『跟着雨哥學AI』系列之三:詳解飛槳框架模型訓練

本文同步分享在 博客「飛槳PaddlePaddle」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索