小言_互联网的博客

【飞桨PaddlePaddle2.0系列】初识Paddle2.0高层API

269人阅读  评论(0)

『跟着飞桨PM学AI』系列01:初识飞桨框架高层API

前言: 嗨,大家好,欢迎大家来到《跟着雨哥学AI》。在飞桨框架2.0版本中开始全新推出高层API,对飞桨API的进一步封装与升级,提供了更加简洁易用的API,提升了飞桨的易学易用性,并增强飞桨的功能,下面我们就带大家来整体了解一下飞桨高层API。


如果想直接运行本项目:请点击https://aistudio.baidu.com/aistudio/projectdetail/1243085


什么是高层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进行模型开发,同时我们会配备非常完善的教程,引导开发者去根据自己的偏好顺利升级到新版本的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完成模型的构建、训练、保存等全流程操作。

1、数据预处理与数据加载

对于数据预处理与数据加载,飞桨框架提供了许多API,列表如下:

1、 飞桨框架内置数据集:paddle.vision.datasets内置包含了许多CV领域相关的数据集,直接调用API即可使用;

2、 飞桨框架数据预处理:paddle.vision.transforms飞桨框架对于图像预处理的方式,可以快速完成常见的图像预处理的方式,如调整色调、对比度,图像大小等;

3、 飞桨框架数据加载: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即可。

2、网络构建

在网络构建模块,飞桨高层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、模型训练

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]

4、高层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” 等信息。

5、模型可视化

在我们完成模型的构建后,有时还需要可视化模型的网络结构与训练过程,来直观的了解深度学习模型与训练过程,方便我们更好地优化模型。飞桨框架高层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,我们也会对其进行特定的优化;待这些功能上线后,我们会第一时间告诉大家,敬请期待吧~

欢迎关注飞桨框架高层API官方账号:飞桨PaddleHapi

有任何问题可以在本项目中评论或到飞桨Github仓库(链接)提交Issue。

欢迎扫码加入飞桨框架高层API技术交流群


转载:https://blog.csdn.net/PaddlePaddle/article/details/111203199
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场