【飛槳PaddlePaddle2.0系列】初識Paddle2.0高層API

『跟着飛槳PM學AI』系列01:初識飛槳框架高層APIpython

在這裏插入圖片描述

下載安裝命令

## 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》。在飛槳框架2.0版本中開始全新推出高層API,對飛槳API的進一步封裝與升級,提供了更加簡潔易用的API,提高了飛槳的易學易用性,並加強飛槳的功能,下面咱們就帶你們來總體瞭解一下飛槳高層API。git


若是想直接運行本項目:請點擊https://aistudio.baidu.com/aistudio/projectdetail/1243085github


什麼是高層API

爲了簡化深度學習的學習過程、下降深度學習的開發效率,飛槳框架歷經幾個月的迭代,不斷的對飛槳框架API進行優化和開發者使用場景進行封裝,終於推出了飛槳高層API。飛槳高層API是飛槳框架推出的快速實現深度學習模型的API,旨在幫助開發者更快更好的完成深度學習模型的學習和開發。算法

簡單來講,飛槳API分爲兩類,一類是基礎API,另外一類是高層API。拿製做披薩舉例,通常有兩種方法:一種是咱們準備好麪粉、牛奶、火腿等食材,通過咱們精心進行加工,就能製做出美味的披薩;而第二種則是咱們買商家預烤制的披薩餅,以及調好的餡料,咱們直接加熱就能夠吃到披薩了。編程

那麼這兩種方法有什麼區別呢?採用方法一,本身準備食材,能夠爲所欲爲的搭配料理,製做醬料,從而知足咱們的不一樣口味,可是,這更適合」老司機」,若是是新人朋友,頗有可能翻車;而方法二,咱們用商家預烤制的披薩餅與餡料,直接加熱便可,能夠很是快速的完成披薩的製做;可是,相比於方法一,咱們會少一些披薩的選擇。api

那麼,用框架來類比,飛槳框架基礎API對應方法一,飛槳框架高層API對應方法二。使用基礎API,咱們能夠爲所欲爲的搭建本身的深度學習模型,不會受到任何限制;而使用方法二,咱們能夠很快的實現模型,達到本身想要的效果,缺點是少了一些自主性。可是,與製做披薩不一樣的是,飛槳框架能夠作到真正的」魚與熊掌」能夠兼得,咱們在飛槳框架中實現了API的高低融合,使咱們的開發者既能夠享受到基礎API的強大,又能夠兼顧高層API的快捷。瀏覽器

高層API的特色

首先,此次升級並非簡單的優化或者修改了幾個API,而是讓全部API變得更加體系化。飛槳基於對開發者使用習慣的洞察,以及對深度學習技術自己的理解和應用實踐,對已有的API進行了整理和優化,使得API作到了更加科學和一致,貼合開發者的使用習慣。網絡

其次,爲了幫助開發者實現低代碼快速建模,咱們提供了更適合低代碼編程的高層API,好比數據加強,或者是創建數據流水線等等API,這樣就可以幫助開發者進一步簡化工做流程;此外,一些很是經典的模型結構,咱們也將其封裝成了高層API,提供給開發者直接使用。高層API自己不是一個獨立的體系,它徹底能夠和基礎API互相配合使用,作到高低融合,從而使用起來會更加便捷。app

而後還有一個點是廣大老開發者最爲關心的問題,那就是兼容性,這一點你們能夠放心,咱們實現了對歷史版本的徹底兼容,不用擔憂已有的模型會出現不能使用的狀況,咱們的老開發者依然可使用原來的API進行模型開發,同時咱們會配備很是完善的教程,引導開發者去根據本身的偏好順利升級到新版本的API。框架

高層API全景圖

飛槳高層API的全景圖以下:

從上圖中能夠看出,目前飛槳高層API由五個模塊組成,分別是數據加載、模型組建、模型訓練、模型可視化和高階用法。咱們先經過一個深度學習中經典的手寫數字分類任務,來簡單瞭解飛槳高層API,而後再詳細的介紹每一個模塊中所包含的API。

import paddle
from paddle.vision.transforms import Compose, Normalize
from paddle.vision.datasets import MNIST
import paddle.nn as nn 

# 數據預處理,這裏用到了隨機調整亮度、對比度和飽和度
transform = Compose([Normalize(mean=[127.5],
                               std=[127.5],
                               data_format='CHW')])


# 數據加載,在訓練集上應用數據預處理的操做
train_dataset = MNIST(mode='train', transform=transform)
test_dataset = MNIST(mode='test')

# 模型組網
mnist = nn.Sequential(
    nn.Flatten(),
    nn.Linear(784, 512),
    nn.ReLU(),
    nn.Dropout(0.2),
    nn.Linear(512, 10)
)

# 模型封裝,用Model類封裝
model = paddle.Model(mnist)

# 模型配置:爲模型訓練作準備,設置優化器,損失函數和精度計算方式
model.prepare(optimizer=paddle.optimizer.Adam(parameters=model.parameters()),
              loss=nn.CrossEntropyLoss(),
              metrics=paddle.metric.Accuracy())

# 模型訓練,
model.fit(train_dataset,
          epochs=5,
          batch_size=64,
          verbose=1)

# 模型評估,
model.evaluate(test_dataset, verbose=1)
Epoch 1/5
step 938/938 [==============================] - loss: 0.3271 - acc: 0.9008 - 4ms/step         
Epoch 2/5
step 938/938 [==============================] - loss: 0.1028 - acc: 0.9505 - 5ms/step         
Epoch 3/5
step 938/938 [==============================] - loss: 0.0588 - acc: 0.9599 - 4ms/step         
Epoch 4/5
step 938/938 [==============================] - loss: 0.0092 - acc: 0.9643 - 5ms/step        
Epoch 5/5
step 938/938 [==============================] - loss: 0.1078 - acc: 0.9685 - 4ms/step         
Eval begin...
step 10000/10000 [==============================] - loss: 0.0000e+00 - acc: 0.9315 - 2ms/step        
Eval samples: 10000





{'loss': [0.0], 'acc': 0.9315}

經過上面十來行代碼,就能輕鬆完成一個MNIST分類器的訓練、評估與保存。能夠看出,飛槳框架高層對數據預處理、數據加載、模型組網、模型訓練、模型評估、模型保存等都進行了封裝,可以快速高效地完成模型的訓練。

在一些場景下,如初次學習深度學習框架時,使用飛槳高層API,能夠驕傲地說出"當你用傳統框架還在寫數據加載時,我用飛槳高層API已經開始訓練模型了"!

高層API詳解

下面以CV任務爲例,詳細介紹如何使用飛槳高層API完成模型的構建、訓練、保存等全流程操做。

一、數據預處理與數據加載

對於數據預處理與數據加載,飛槳框架提供了許多API,列表以下:

一、 飛槳框架內置數據集:paddle.vision.datasets內置包含了許多CV領域相關的數據集,直接調用API便可使用;

二、 飛槳框架數據預處理:paddle.vision.transforms飛槳框架對於圖像預處理的方式,能夠快速完成常見的圖像預處理的方式,如調整色調、對比度,圖像大小等;

三、 飛槳框架數據加載:paddle.io.Dataset與paddle.io.DataLoader飛槳框架標準數據加載方式,能夠」一鍵」完成數據的批加載與異步加載;

1.1 飛槳框架內置數據集

首先,飛槳框架將經常使用的數據集做爲領域API對用戶開放,對應API所在目錄爲paddle.vision.datasets包含的數據集以下所示。

print("飛槳框架CV領域內置數據集:" + str(paddle.vision.datasets.__all__))
飛槳框架CV領域內置數據集:['DatasetFolder', 'ImageFolder', 'MNIST', 'Flowers', 'Cifar10', 'Cifar100', 'VOC2012']

如上所示,飛槳提供的數據集API包含計算機視覺領域中常見的數據集,徹底能夠知足咱們在數據集方面的需求。
咱們給出一個數據集的加載示例方便理解。

train_dataset = paddle.vision.datasets.MNIST(mode='train')
test_dataset = paddle.vision.datasets.MNIST(mode='test')
# 取其中的一條數據看一下,如圖所示:

import numpy as np
import matplotlib.pyplot as plt
train_data0, train_label_0 = train_dataset[0][0],train_dataset[0][1]
train_data0 = train_data0.reshape([28,28])
%matplotlib inline
plt.figure(figsize=(2,2))
plt.imshow(train_data0, cmap=plt.cm.binary)
print('train_data0 label is: ' + str(train_label_0))
train_data0 label is: [5]


/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/numpy/lib/type_check.py:546: DeprecationWarning: np.asscalar(a) is deprecated since NumPy v1.16, use a.item() instead
  'a.item() instead', DeprecationWarning, stacklevel=1)

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-yLsKhvLC-1608009399859)(output_8_2.png)]

1.2 飛槳框架預處理方法

飛槳框架提供了20多種數據集預處理的接口,方便開發者快速實現數據加強,目前都集中在 paddle.vision.transforms 目錄下,具體包含的API以下:

print('視覺數據預處理方法:' + str(paddle.vision.transforms.__all__))
視覺數據預處理方法:['BaseTransform', 'Compose', 'Resize', 'RandomResizedCrop', 'CenterCrop', 'RandomHorizontalFlip', 'RandomVerticalFlip', 'Transpose', 'Normalize', 'BrightnessTransform', 'SaturationTransform', 'ContrastTransform', 'HueTransform', 'ColorJitter', 'RandomCrop', 'Pad', 'RandomRotation', 'Grayscale', 'ToTensor', 'to_tensor', 'hflip', 'vflip', 'resize', 'pad', 'rotate', 'to_grayscale', 'crop', 'center_crop', 'adjust_brightness', 'adjust_contrast', 'adjust_hue', 'to_grayscale', 'normalize']

飛槳框架的預處理方法實現了圖像的色調、對比度、飽和度、大小等各類數字圖像處理的方法。而這些數據預處理方法很是方便,只須要先建立一個數據預處理的transform,在其中存入須要進行的數據預處理方法,而後在數據加載的過程當中,將transform做爲參數傳入便可,具體以下:

# 首先,咱們建立一個transform, 用於存儲數據預處理的接口組合。
# 數據預處理
from paddle.vision.transforms import Compose, ColorJitter
from paddle.vision.datasets import Cifar10
transform = Compose([ColorJitter()]) # transform用於存儲數據預處理的接口組合 ColorJitter()實現隨機調整亮度、對比度和飽和度

# 數據加載,在訓練集上應用數據預處理的操做
train_dataset = Cifar10(mode='train', transform=transform)
test_dataset = Cifar10(mode='test')

隨機處理與未隨機處理的對比圖以下:

train_dataset = Cifar10(mode='train')
train_dataset_transform = Cifar10(mode='train', transform=transform)

train_data0 = train_dataset[326][0]
train_data0 = train_data0.reshape([3,32,32]).astype('float32') / 255.
train_data0 = train_data0.transpose(1, 2, 0)
plt.figure(figsize=(2,2))
plt.imshow(train_data0, cmap=plt.cm.binary)

train_data1 = train_dataset_transform[326][0]
train_data1 = train_data1.reshape([3,32,32]).astype('float32') / 255.
train_data1 = train_data1.transpose(1, 2, 0)
plt.figure(figsize=(2,2))
plt.imshow(train_data1, cmap=plt.cm.binary)
<matplotlib.image.AxesImage at 0x7f42937dd050>

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-yBsP8sBO-1608009399862)(output_14_1.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KlquEZmQ-1608009399864)(output_14_2.png)]

1.3 自定義數據集加載

飛槳框架標準數據加載方式,能夠」一鍵」完成數據的批加載與異步加載;
更多的時候咱們須要本身使用已有的相關數據來定義數據集,那麼這裏咱們經過一個案例來了解如何進行數據集的定義,飛槳爲用戶提供了paddle.io.Dataset基類,讓用戶經過類的集成來快速實現數據集定義。示例以下

from paddle.io import Dataset

class MyDataset(Dataset):
    """ 步驟一:繼承paddle.io.Dataset類 """
    def __init__(self, mode='train'):
        """ 步驟二:實現構造函數,定義數據讀取方式,劃分訓練和測試數據集 """
        super(MyDataset, self).__init__()

        if mode == 'train':
            self.data = [
                ['traindata1', 'label1'],
                ['traindata2', 'label2'],
                ['traindata3', 'label3'],
                ['traindata4', 'label4'],
            ]
        else:
            self.data = [
                ['testdata1', 'label1'],
                ['testdata2', 'label2'],
                ['testdata3', 'label3'],
                ['testdata4', 'label4'],
            ]

    def __getitem__(self, index):
        """ 步驟三:實現__getitem__方法,定義指定index時如何獲取數據,並返回單條數據(訓練數據,對應的標籤) """
        data = self.data[index][0]
        label = self.data[index][1]

        return data, label

    def __len__(self):
        """ 步驟四:實現__len__方法,返回數據集總數目 """
        return len(self.data)

# 測試定義的數據集
train_dataset = MyDataset(mode='train')
val_dataset = MyDataset(mode='test')

print('=============train dataset=============')
for data, label in train_dataset:
    print(data, label)

print('=============evaluation dataset=============')
for data, label in val_dataset:
    print(data, label)
=============train dataset=============
traindata1 label1
traindata2 label2
traindata3 label3
traindata4 label4
=============evaluation dataset=============
testdata1 label1
testdata2 label2
testdata3 label3
testdata4 label4

經過上述的方法,咱們就實現了一個本身的數據集,而後,將train_dataset 與 val_dataset 做爲參數,傳入到DataLoader中,便可得到一個數據加載器,完成訓練數據的加載。
對於數據集的定義上,飛槳框架同時支持map-style和interable-style兩種類型的數據集定義,只須要分別繼承paddle.io.Dataset和paddle.io.IterableDataset便可。

二、網絡構建

在網絡構建模塊,飛槳高層API與基礎API保持徹底的一致,都使用paddle.nn下的API進行組網。這也是儘量的減小須要暴露的概念,從而提高框架的易學性。飛槳框架 paddle.nn 目錄下包含了全部與模型組網相關的API,如卷積相關的 Conv1D、Conv2D、Conv3D,循環神經網絡相關的 RNN、LSTM、GRU 等。

對於組網方式,飛槳框架統一支持 Sequential 或 SubClass 的方式進行模型的組建。咱們根據實際的使用場景,來選擇最合適的組網方式。如針對順序的線性網絡結構咱們能夠直接使用 Sequential ,相比於 SubClass ,Sequential 能夠快速的完成組網。
若是是一些比較複雜的網絡結構,咱們可使用 SubClass 定義的方式來進行模型代碼編寫,在 init 構造函數中進行 Layer 的聲明,在 forward 中使用聲明的 Layer 變量進行前向計算。經過這種方式,咱們能夠組建更靈活的網絡結構。

2.1 Sequential 的組網方式

使用 Sequential 進行組網的實現以下:

# Sequential形式組網
mnist = nn.Sequential(
    nn.Flatten(),
    nn.Linear(784, 512),
    nn.ReLU(),
    nn.Dropout(0.2),
    nn.Linear(512, 10)
)

對於線性的網絡模型,咱們只須要按網絡模型的結構順序,一層一層的加到Sequential 後面便可,很是快速就能夠完成模型的組建。

2.2 SubClass 的組網方式

使用 SubClass 進行組網的實現以下:

# SubClass方式組網
class Mnist(paddle.nn.Layer):
    def __init__(self):
        super(Mnist, self).__init__()

        self.flatten = nn.Flatten()
        self.linear_1 = nn.Linear(784, 512)
        self.linear_2 = nn.Linear(512, 10)
        self.relu = nn.ReLU()
        self.dropout = nn.Dropout(0.2)

    def forward(self, inputs):
        y = self.flatten(inputs)
        y = self.linear_1(y)
        y = self.relu(y)
        y = self.dropout(y)
        y = self.linear_2(y)

        return y

上述的SubClass 組網的結果與Sequential 組網的結果徹底一致,能夠明顯看出,使用SubClass 組網會比使用Sequential 更復雜一些。不過,這帶來的是網絡模型結構的靈活性。咱們能夠設計不一樣的網絡模型結構來應對不一樣的場景。

2.3 飛槳框架內置模型

除了自定義模型結構外,飛槳框架還」貼心」的內置了許多模型,真正的一行代碼實現深度學習模型。目前,飛槳框架內置的模型都是CV領域領域的模型,在paddle.vision.models目錄下,具體包含以下的模型:

print("視覺相關模型: " + str(paddle.vision.models.__all__))
視覺相關模型: ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101', 'resnet152', 'VGG', 'vgg11', 'vgg13', 'vgg16', 'vgg19', 'MobileNetV1', 'mobilenet_v1', 'MobileNetV2', 'mobilenet_v2', 'LeNet']

使用上也是很是的簡單,只須要一行便可完成模型的構建,具體以下:

lenet = paddle.vision.models.LeNet()

這樣咱們就完成了Lenet模型的搭建,而後就能夠開始下一步的模型訓練了。

三、模型訓練

3.1 使用高層API在所有數據集上進行訓練

過去經常困擾深度學習開發者的一個問題是,模型訓練的代碼過於複雜,經常要寫好多步驟,才能正確的使程序運行起來,冗長的代碼使許多開發者望而卻步。

如今,飛槳高層API將訓練、評估與預測API都進行了封裝,直接使用Model.prepare()、Model.fit()、Model.evaluate()、Model.predict()完成模型的訓練、評估與預測。

對比傳統框架動輒一大塊的訓練代碼。使用飛槳高層API,能夠在3-5行內,完成模型的訓練,極大的簡化了開發的代碼量,對初學者用戶很是友好。具體代碼以下:

# 定義 數據集與模型

import paddle
from paddle.vision.transforms import Compose, ColorJitter
from paddle.vision.datasets import MNIST
import paddle.nn as nn 

# 數據預處理,這裏用到了隨機調整亮度、對比度和飽和度
transform = Compose([ColorJitter()])

# 數據加載,在訓練集上應用數據預處理的操做
train_dataset = MNIST(mode='train', transform=transform)
test_dataset = MNIST(mode='test')

mnist = nn.Sequential(
    nn.Flatten(),
    nn.Linear(784, 512),
    nn.ReLU(),
    nn.Dropout(0.2),
    nn.Linear(512, 10)
)
# 使用高層API訓練

# 將網絡結構用 Model類封裝成爲模型
model = paddle.Model(mnist)

# 爲模型訓練作準備,設置優化器,損失函數和精度計算方式
model.prepare(optimizer=paddle.optimizer.Adam(parameters=model.parameters()),
              loss=paddle.nn.CrossEntropyLoss(),
              metrics=paddle.metric.Accuracy())

# 啓動模型訓練,指定訓練數據集,設置訓練輪次,設置每次數據集計算的批次大小,設置日誌格式
model.fit(train_dataset,
          epochs=5,
          batch_size=64,
          verbose=1)

# 啓動模型評估,指定數據集,設置日誌格式
model.evaluate(test_dataset, verbose=1)

# 啓動模型測試,指定測試集 
model.predict(test_dataset)

3.2 使用高層API在一個批次的數據集上訓練、驗證與測試

有時咱們須要對數據按batch進行取樣,而後完成模型的訓練與驗證,這時,可使用 train_batch、eval_batch、predict_batch 完成一個批次上的訓練、驗證與測試,具體以下:

# 模型封裝,用Model類封裝
model = paddle.Model(mnist)

# 模型配置:爲模型訓練作準備,設置優化器,損失函數和精度計算方式
model.prepare(optimizer=paddle.optimizer.Adam(parameters=model.parameters()),
              loss=nn.CrossEntropyLoss(),
              metrics=paddle.metric.Accuracy())

# 構建訓練集數據加載器
train_loader = paddle.io.DataLoader(train_dataset, batch_size=64, shuffle=True)

# 使用train_batch 完成訓練
for batch_id, data in enumerate(train_loader()):
    model.train_batch([data[0]],[data[1]])

# 構建測試集數據加載器
test_loader = paddle.io.DataLoader(test_dataset, batch_size=64, shuffle=True)

# 使用 eval_batch 完成驗證
for batch_id, data in enumerate(test_loader()):
    model.eval_batch([data[0]],[data[1]])

# 使用 predict_batch 完成預測
for batch_id, data in enumerate(test_loader()):
    model.predict_batch([data[0]])

3.3 使用基礎API進行訓練

因爲飛槳高層API是對基礎API的封裝,因此咱們也能夠對其進行拆解,將高層API用基礎API實現。拆解的步驟以下面的代碼,這裏咱們只對fit,也就是訓練過程進行拆解。

import paddle.nn.functional as F

# 加載數據
train_loader = paddle.io.DataLoader(train_dataset, places=paddle.CPUPlace(), batch_size=64, shuffle=True)
# 加載訓練集 batch_size 設爲 64
def train(model):
    model.train()
    epochs = 5
    optim = paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters()) # 用Adam做爲優化函數
    for epoch in range(epochs):
        for batch_id, data in enumerate(train_loader()):
            x_data = data[0]
            y_data = data[1]
            predicts = model(x_data)
            loss = F.cross_entropy(predicts, y_data) # 計算損失
            acc = paddle.metric.accuracy(predicts, y_data) # 計算精度
            loss.backward() # 反向傳播
            if batch_id % 500 == 0:
                print("epoch: {}, batch_id: {}, loss is: {}, acc is: {}".format(epoch, batch_id, loss.numpy(), acc.numpy()))
            optim.step()  # 更新參數
            optim.clear_grad()  # 清除梯度
model = Mnist()
train(model)
epoch: 0, batch_id: 0, loss is: [56.63907], acc is: [0.046875]
epoch: 0, batch_id: 500, loss is: [0.7398033], acc is: [0.890625]
epoch: 1, batch_id: 0, loss is: [0.34674442], acc is: [0.90625]
epoch: 1, batch_id: 500, loss is: [1.0604179], acc is: [0.90625]
epoch: 2, batch_id: 0, loss is: [0.15792622], acc is: [0.9375]
epoch: 2, batch_id: 500, loss is: [0.35274628], acc is: [0.953125]
epoch: 3, batch_id: 0, loss is: [0.20840943], acc is: [0.921875]
epoch: 3, batch_id: 500, loss is: [0.64552385], acc is: [0.9375]
epoch: 4, batch_id: 0, loss is: [0.11157086], acc is: [0.96875]
epoch: 4, batch_id: 500, loss is: [0.7364702], acc is: [0.90625]

四、高層API進階用法

以上是飛槳高層API在常見任務中的使用方式,能夠快速高效的完成模型的訓練。除此以外,飛槳高層API還支持一些高階的玩法,如自定義Loss、自定義Metric、自定義Callback等。

4.1 自定義Loss

有時咱們會遇到特定任務的Loss計算方式在框架既有的Loss接口中不存在,或算法不符合本身的需求,那麼指望可以本身來進行Loss的自定義,咱們這裏就會講解介紹一下如何進行Loss的自定義操做,首先來看下面的代碼:

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

那麼瞭解完代碼層面若是編寫自定義代碼後咱們看一個實際的例子,下面是在圖像分割示例代碼中寫的一個自定義Loss,主要是想使用自定義的softmax計算維度。

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

    def forward(self, input, label):
        loss = F.softmax_with_cross_entropy(input,
                                            label,
                                            return_softmax=False,
                                            axis=1)
        return paddle.mean(loss)

4.2 自定義metric

和Loss同樣,若是遇到一些想要作個性化實現的操做時,咱們也能夠來經過框架完成自定義的評估計算方法,具體的實現方式以下:

### 僞代碼說明
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, ...)
        """ 4. 本步驟能夠省略,實現compute方法,這個方法主要用於`update`的加速,能夠在這個方法中調用一些paddle實現好的Tensor計算API,編譯到模型網絡中一塊兒使用低層C++ OP計算。 """

        return 本身想要返回的數據,會作爲update的參數傳入。

    def update(self, ...):
        """ 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

咱們看一個框架中的具體例子,這個是框架中已提供的一個評估指標計算接口,這裏就是按照上述說明中的實現方法進行了相關類繼承和成員函數實現。

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

4.3 自定義Callback

這裏咱們簡單介紹自定義Callback。

fit接口的callback參數支持咱們傳一個Callback類實例,用來在每一個epoch訓練和每一個batch訓練先後進行調用,以此收集訓練過程當中的一些數據和參數,或者實現一些自定義操做。如對於模型保存而言,正常狀況下,fit只會保存模型最後一次迭代的參數。然而實際狀況中,每每咱們須要保存多個模型,從中選擇效果最好的那個。

這時,咱們能夠經過框架預約義的ModelCheckpoint回調函數,能夠在fit訓練模型時自動存儲每輪訓練獲得的模型。

class ModelCheckpoint(paddle.callbacks.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.4 自定義執行過程回調

有時,咱們須要保存模型訓練過程當中loss降低的信息,繪成圖來分析網絡模型的優化過程,這個在高層API中該如何實現呢?其實,這裏也會用到上文提到的自定義Callback,咱們只須要自定義與loss相關的Callback,而後保存loss信息,最後將其轉化爲圖片便可。具體的實現過程以下:

# 定義 數據集與模型

import paddle
from paddle.vision.transforms import Compose, ColorJitter
from paddle.vision.datasets import MNIST
import paddle.nn as nn 

# 數據預處理,這裏用到了隨機調整亮度、對比度和飽和度
transform = Compose([ColorJitter()])

# 數據加載,在訓練集上應用數據預處理的操做
train_dataset = MNIST(mode='train', transform=transform)
test_dataset = MNIST(mode='test')

mnist = nn.Sequential(
    nn.Flatten(),
    nn.Linear(784, 512),
    nn.ReLU(),
    nn.Dropout(0.2),
    nn.Linear(512, 10)
)
# 模型封裝,用Model類封裝
model = paddle.Model(mnist)

# 模型配置:爲模型訓練作準備,設置優化器,損失函數和精度計算方式
model.prepare(optimizer=paddle.optimizer.Adam(parameters=model.parameters()),
              loss=nn.CrossEntropyLoss(),
              metrics=paddle.metric.Accuracy())
# 自定義Callback 記錄訓練過程當中的loss信息
class LossCallback(paddle.callbacks.Callback):

    def on_train_begin(self, logs={ 
 
   }):
        # 在fit前 初始化losses,用於保存每一個batch的loss結果
        self.losses = []

    def on_train_batch_end(self, step, logs={ 
 
   }):
        # 每一個batch訓練完成後調用,把當前loss添加到losses中
        self.losses.append(logs.get('loss'))

# 初始化一個loss_log 的實例,而後將其做爲參數傳遞給fit
loss_log = LossCallback()
model.fit(train_dataset,
          epochs=5,
          batch_size=32,
          # callbacks=loss_log,
          verbose=1)
# loss信息都保存在 loss_log.losses 中,可視化後獲得下圖
Epoch 1/5
step 1875/1875 [==============================] - loss: 0.6919 - acc: 0.8754 - 4ms/step         
Epoch 2/5
step 1875/1875 [==============================] - loss: 0.0950 - acc: 0.9046 - 4ms/step         
Epoch 3/5
step 1875/1875 [==============================] - loss: 0.3074 - acc: 0.9056 - 4ms/step         
Epoch 4/5
step 1875/1875 [==============================] - loss: 0.1570 - acc: 0.9142 - 4ms/step         
Epoch 5/5
step 1875/1875 [==============================] - loss: 0.4957 - acc: 0.9169 - 4ms/step

經過這樣的方式,咱們就能夠將訓練過程當中的loss信息保存,而後可視化獲得loss降低的曲線,根據loss降低的曲線,對模型進行下一步的優化迭代。此外,除了loss外,飛槳框架還能夠保存 「metrics」 等信息。

五、模型可視化

在咱們完成模型的構建後,有時還須要可視化模型的網絡結構與訓練過程,來直觀的瞭解深度學習模型與訓練過程,方便咱們更好地優化模型。飛槳框架高層API提供了一系列相關的API,來幫助咱們可視化模型與訓練過程,就讓咱們來看一下吧。

5.1 模型結構可視化

飛槳框架中,對於咱們組網的模型,只要咱們用Model進行模型的封裝後,只須要調用 model.summary 便可實現網絡模型的可視化,具體以下:

import paddle
mnist = paddle.nn.Sequential(
    paddle.nn.Flatten(),
    paddle.nn.Linear(784, 512),
    paddle.nn.ReLU(),
    paddle.nn.Dropout(0.2),
    paddle.nn.Linear(512, 10)
)

# 模型封裝,用Model類封裝
model = paddle.Model(mnist)
model.summary((1, 28, 28))
---------------------------------------------------------------------------
 Layer (type)       Input Shape          Output Shape         Param #    
===========================================================================
Flatten-134580     [[1, 28, 28]]           [1, 784]              0       
   Linear-31         [[1, 784]]            [1, 512]           401,920    
    ReLU-17          [[1, 512]]            [1, 512]              0       
  Dropout-13         [[1, 512]]            [1, 512]              0       
   Linear-32         [[1, 512]]            [1, 10]             5,130     
===========================================================================
Total params: 407,050
Trainable params: 407,050
Non-trainable params: 0
---------------------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.02
Params size (MB): 1.55
Estimated Total Size (MB): 1.57
---------------------------------------------------------------------------






{'total_params': 407050, 'trainable_params': 407050}

不只會給出每一層網絡的形狀,還會給出每層網絡的參數量與模型的總參數量,很是方便直觀的就能夠看到模型的所有信息。

經過這樣的方式,咱們就能夠將訓練過程當中的loss信息保存,而後可視化獲得loss降低的曲線,根據loss降低的曲線,對模型進行下一步的優化迭代。此外,除了loss外,還能夠保存 「metrics」 等信息。

5.2 使用VisualDL完成訓練過程的可視化

VisualDL是飛槳可視化分析工具,以豐富的圖表呈現訓練參數變化趨勢、模型結構、數據樣本、直方圖以及PR曲線等。可幫助用戶更清晰直觀地理解深度學習模型訓練過程及模型結構,進而實現高效的模型優化。是飛槳模型可視化的大殺器。飛槳高層API也作了與VisualDL的聯動,也僅僅須要一行代碼,就能夠輕鬆使用VisualDL完成模型訓練過程的分析。具體以下:

# 調用飛槳框架的VisualDL模塊,保存信息到目錄中。
callback = paddle.callbacks.VisualDL(log_dir='visualdl_log_dir')

# 模型配置:爲模型訓練作準備,設置優化器,損失函數和精度計算方式
model.prepare(optimizer=paddle.optimizer.Adam(parameters=model.parameters()),
              loss=nn.CrossEntropyLoss(),
              metrics=paddle.metric.Accuracy())
              
model.fit(train_dataset,
          epochs=5,
          batch_size=32,
          callbacks=callback,
          verbose=1)
Epoch 1/5
step 1875/1875 [==============================] - loss: 0.2165 - acc: 0.8795 - 5ms/step        
Epoch 2/5
step 1875/1875 [==============================] - loss: 0.3125 - acc: 0.9054 - 5ms/step         
Epoch 3/5
step 1875/1875 [==============================] - loss: 0.1470 - acc: 0.9126 - 5ms/step         
Epoch 4/5
step 1875/1875 [==============================] - loss: 0.1302 - acc: 0.9140 - 5ms/step         
Epoch 5/5
step 1875/1875 [==============================] - loss: 0.3279 - acc: 0.9175 - 5ms/step

而後咱們調用VisualDL工具,在命令行中輸入: visualdl --logdir ./visualdl_log_dir --port 8080,打開瀏覽器,輸入網址 http://127.0.0.1:8080 就能夠在瀏覽器中看到相關的訓練信息,具體以下:

上文以CV任務爲例,介紹了飛槳框架高層API的使用指南。後續,飛槳框架還計劃推出NLP領域專用的數據預處理模塊,如對數據進行padding、獲取數據集詞表等;在組網方面,也會實現NLP領域中組網專用的API,如組網相關的sequence_mask,評估指標相關的BLEU等;最後,針對NLP領域中的神器transformer,咱們也會對其進行特定的優化;待這些功能上線後,咱們會第一時間告訴你們,敬請期待吧~

下載安裝命令

## 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官方帳號:飛槳PaddleHapi

有任何問題能夠在本項目中評論或到飛槳Github倉庫(連接)提交Issue。

歡迎掃碼加入飛槳框架高層API技術交流羣

 

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

相關文章
相關標籤/搜索