上云无忧 > 文档中心 > 第二章:一个案例吃透深度学习(下) - 10 【手写数字识别】之动转静部署
飞桨PaddlePaddle开源深度学习平台
第二章:一个案例吃透深度学习(下) - 10 【手写数字识别】之动转静部署

文档简介:
动静转换: 动态图有诸多优点,比如易用的接口、Python风格的编程体验、友好的调试交互机制等。在动态图模式下,代码可以按照我们编写的顺序依次执行。这种机制更符合Python程序员的使用习惯,可以很方便地将脑海中的想法快速地转化为实际代码,也更容易调试。
*此产品及展示信息均由百度智能云官方提供。免费试用 咨询热线:400-826-7010,为您提供专业的售前咨询,让您快速了解云产品,助您轻松上云! 微信咨询
  免费试用、价格特惠

动静转换

动态图有诸多优点,比如易用的接口、Python风格的编程体验、友好的调试交互机制等。在动态图模式下,代码可以按照我们编写的顺序依次执行。这种机制更符合Python程序员的使用习惯,可以很方便地将脑海中的想法快速地转化为实际代码,也更容易调试。

但在性能方面,由于Python执行开销较大,与C++有一定差距,因此在工业界的许多部署场景中(如大型推荐系统、移动端)都倾向于直接使用C++进行提速。相比动态图,静态图在部署方面更具有性能的优势。静态图程序在编译执行时,先搭建模型的神经网络结构,然后再对神经网络执行计算操作。预先搭建好的神经网络可以脱离Python依赖,在C++端被重新解析执行,而且拥有整体网络结构也能进行一些网络结构的优化。

那么,有没有可能,深度学习框架实现一个新的模式,同时具备动态图高易用性与静态图高性能的特点呢?飞桨从2.0版本开始,新增新增支持动静转换功能,编程范式的选择更加灵活。用户依然使用动态图编写代码,只需添加一行装饰器 @paddle.jit.to_static,即可实现动态图转静态图模式运行,进行模型训练或者推理部署。在本章节中,将介绍飞桨动态图转静态图的基本用法和相关原理。

动态图转静态图训练

飞桨的动转静方式是基于源代码级别转换的ProgramTranslator实现,其原理是通过分析Python代码,将动态图代码转写为静态图代码,并在底层自动使用静态图执行器运行。其基本使用方法十分简便,只需要在要转化的函数(该函数也可以是用户自定义动态图Layer的forward函数)前添加一个装饰器 @paddle.jit.to_static。这种转换方式使得用户可以灵活使用Python语法及其控制流来构建神经网络模型。下面通过一个例子说明如何使用飞桨实现动态图转静态图训练。

import paddle # 定义手写数字识别模型 class MNIST(paddle.nn.Layer): def __init__(self): 
super(MNIST, self).__init__() # 定义一层全连接层,输出维度是1 self.fc = paddle.nn.Linear(in_
features=784, out_features=10) # 定义网络结构的前向计算过程  @paddle.jit.to_static #
 添加装饰器,使动态图网络结构在静态图模式下运行 def forward(self, inputs): outputs
 = self.fc(inputs) return outputs

上述代码构建了仅有一层全连接层的手写字符识别网络。特别注意,在forward函数之前加了装饰器@paddle.jit.to_static,要求模型在静态图模式下运行。下面是模型的训练代码,由于飞桨实现动转静的功能是在内部完成的,对使用者来说,动态图的训练代码和动转静模型的训练代码是完全一致的。训练代码如下:

import paddle import paddle.nn.functional as F # 确保从paddle.vision.datasets.MNIST中加
载的图像数据是np.ndarray类型 paddle.vision.set_image_backend('cv2') # 图像归一化函数,
将数据范围为[0, 255]的图像归一化到[-1, 1] def norm_img(img): batch_size = img.shape[0]
 # 归一化图像数据 img = img/127.5 - 1 # 将图像形式reshape为[batch_size, 784] img = paddle.
reshape(img, [batch_size, 784]) return img def train(model): model.train() # 加载训练集 
batch_size 设为 16 train_loader = paddle.io.DataLoader(paddle.vision.datasets.MNIST(mode='train'), 
                                        batch_size=16, 
                                        shuffle=True)
    opt = paddle.optimizer.SGD(learning_rate=0.001, parameters=model.parameters())
    EPOCH_NUM = 10 for epoch in range(EPOCH_NUM): for batch_id, data in enumerate(train_loader()):
            images = norm_img(data[0]).astype('float32')
            labels = data[1].astype('int64') #前向计算的过程 predicts = model(images) # 
计算损失 loss = F.cross_entropy(predicts, labels)
            avg_loss = paddle.mean(loss) #每训练了1000批次的数据,打印下当前Loss的情况
 if batch_id % 1000 == 0:
                print("epoch_id: {}, batch_id: {}, loss is: {}".format(epoch, batch_id,
 avg_loss.numpy())) #后向传播,更新参数的过程 avg_loss.backward()
            opt.step()
            opt.clear_grad()


model = MNIST() 

train(model)

paddle.save(model.state_dict(), './mnist.pdparams')
print("==>Trained model saved in ./mnist.pdparams")

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
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/layers/utils.
py:77: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from
 'collections.abc' is deprecated, and in 3.8 it will stop working
  return (isinstance(seq, collections.Sequence) and

epoch_id: 0, batch_id: 0, loss is: [3.0346446]
epoch_id: 0, batch_id: 1000, loss is: [1.1114309]
epoch_id: 0, batch_id: 2000, loss is: [0.56083727]
epoch_id: 0, batch_id: 3000, loss is: [0.56929463]
epoch_id: 1, batch_id: 0, loss is: [0.64646566]
epoch_id: 1, batch_id: 1000, loss is: [0.4265188]
epoch_id: 1, batch_id: 2000, loss is: [0.2182416]
epoch_id: 1, batch_id: 3000, loss is: [0.5384557]
epoch_id: 2, batch_id: 0, loss is: [0.22628105]

我们可以观察到,动转静的训练方式与动态图训练代码是完全相同的。因此,在动转静训练的时候,开发者只需要在动态图的组网前向计算函数上添加一个装饰器即可实现动转静训练。 在模型构建和训练中,飞桨更希望借用动态图的易用性优势,实际上,在加上@to_static装饰器运行的时候,飞桨内部是在静态图模式下执行OP的,但是展示给开发者的依然是动态图的使用方式。

动转静更能体现静态图的方面在于模型部署上。下面将介绍动态图转静态图的部署方式。

动态图转静态图模型保存

在推理&部署场景中,需要同时保存推理模型的结构和参数,但是动态图是即时执行即时得到结果,并不会记录模型的结构信息。动态图在保存推理模型时,需要先将动态图模型转换为静态图写法,编译得到对应的模型结构再保存,而飞桨框架2.0版本推出paddle.jit.save和paddle.jit.load接口,无需重新实现静态图网络结构,直接实现动态图模型转成静态图模型格式。paddle.jit.save接口会自动调用飞桨框架2.0推出的动态图转静态图功能,使得用户可以做到使用动态图编程调试,自动转成静态图训练部署。

这两个接口的基本关系如下图所示:



图1:动转静模型保存接口

当用户使用paddle.jit.save保存Layer对象时,飞桨会自动将用户编写的动态图Layer模型转换为静态图写法,并编译得到模型结构,同时将模型结构与参数保存。paddle.jit.save需要适配飞桨沿用已久的推理模型与参数格式,做到前向完全兼容,因此其保存格式与paddle.save有所区别,具体包括三种文件:保存模型结构的*.pdmodel文件;保存推理用参数的*.pdiparams文件和保存兼容变量信息的*.pdiparams.info文件,这几个文件后缀均为paddle.jit.save保存时默认使用的文件后缀。

比如,如果保存上述手写字符识别的inference模型用于部署,可以直接用下面代码实现:

# save inference model from paddle.static import InputSpec # 加载训练好的模型参数 state_dict
 = paddle.load("./mnist.pdparams") # 将训练好的参数读取到网络中 model.set_state_dict(state_dict)
 # 设置模型为评估模式 model.eval() # 保存inference模型 paddle.jit.save(
    layer=model,
    path="inference/mnist",
    input_spec=[InputSpec(shape=[None, 784], dtype='float32')])

print("==>Inference model saved in inference/mnist.")

其中,paddle.jit.save API 将输入的网络存储为 paddle.jit.TranslatedLayer 格式的模型,载入后可用于预测推理或者fine-tune训练。 该接口会将输入网络转写后的模型结构 Program 和所有必要的持久参数变量存储至输入路径 path 。

path 是存储目标的前缀,存储的模型结构 Program 文件的后缀为 .pdmodel ,存储的持久参数变量文件的后缀为 .pdiparams ,同时这里也会将一些变量描述信息存储至文件,文件后缀为 .pdiparams.info。

通过调用对应的paddle.jit.load接口,可以把存储的模型载入为 paddle.jit.TranslatedLayer格式,用于预测推理或者fine-tune训练。

import numpy as np import paddle import paddle.nn.functional as F # 确保从paddle.vision.datas
ets.MNIST中加载的图像数据是np.ndarray类型 paddle.vision.set_image_backend('cv2') # 读取mnist测试数据,
获取第一个数据 mnist_test = paddle.vision.datasets.MNIST(mode='test')
test_image, label = mnist_test[0] # 获取读取到的图像的数字标签 print("The label of readed image is
 : ", label) # 将测试图像数据转换为tensor,并reshape为[1, 784] test_image = paddle.reshape(paddle.
to_tensor(test_image), [1, 784]) # 然后执行图像归一化 test_image = norm_img(test_image) 
# 加载保存的模型 loaded_model = paddle.jit.load("./inference/mnist") # 利用加载的模型执行预测 
preds = loaded_model(test_image)
pred_label = paddle.argmax(preds) # 打印预测结果 print("The predicted label is : ", pred_label.numpy())
The label of readed image is :  [7]
The predicted label is :  [7]

paddle.jit.save API 可以把输入的网络结构和参数固化到一个文件中,所以通过加载保存的模型,可以不用重新构建网络结构而直接用于预测,易于模型部署。

总结:

本章节中,介绍了飞桨动转静的功能和基本原理,并以一个例子介绍了如何将一个动态图模型转换到静态图模式下训练,并将训练好的模型转换成更易于部署的inference模型,有关更多动转静的功能介绍,可以参考官方文档。

相似文档
  • 截止目前,诸位读者已经掌握了使用飞桨完成深度学习建模的方法,并且可以编写相当强大的模型。如果将每个模型部分均展开,整个模型实现有几百行代码,可以灵活的实现各种建模过程中的需求。
  • 计算机视觉作为一门让机器学会如何去“看”的学科,具体的说,就是让机器去识别摄像机拍摄的图片或视频中的物体,检测出物体所在的位置,并对目标物体进行跟踪,从而理解并描述出图片或视频里的场景和故事,以此来模拟人脑视觉系统。因此,计算机视觉也通常被叫做机器视觉,其目的是建立能够从图像或者视频中“感知”信息的人工系统。
  • 图像分类是根据图像的语义信息对不同类别图像进行区分,是计算机视觉的核心,是物体检测、图像分割、物体跟踪、行为分析、人脸识别等其他高层次视觉任务的基础。图像分类在许多领域都有着广泛的应用,如:安防领域的人脸识别和智能视频分析等,交通领域的交通场景识别,互联网领域基于内容的图像检索和相册自动归类,医学领域的图像识别等。
  • 对计算机而言,能够“看到”的是图像被编码之后的数字,但它很难理解高层语义概念,比如图像或者视频帧中出现的目标是人还是物体,更无法定位目标出现在图像中哪个区域。目标检测的主要目的是让计算机可以自动识别图片或者视频帧中所有目标的类别,并在该目标周围绘制边界框,标示出每个目标的位置,如 图1 所示。
  • 林业病虫害数据集和数据预处理方法介绍: 在本课程中,将使用百度与林业大学合作开发的林业病虫害防治项目中用到昆虫数据集。 读取AI识虫数据集标注信息: AI识虫数据集结构如下: 提供了2183张图片,其中训练集1693张,验证集245,测试集245张。 包含7种昆虫,分别是Boerner、Leconte、Linnaeus、acuminatus、armandi、coleoptera和linnaeus。
官方微信
联系客服
400-826-7010
7x24小时客服热线
分享
  • QQ好友
  • QQ空间
  • 微信
  • 微博
返回顶部