2.10 动静转换

简介: 这篇文章介绍了飞桨框架的动静转换功能,允许开发者使用动态图的易用性编写代码,并通过添加装饰器 @paddle.jit.to_static 转换为静态图模式运行,以提高模型训练和部署的效率和性能。

2.10 动静转换

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

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

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

2.10.1 动态图转静态图训练

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

In [1]

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,要求模型在静态图模式下运行。下面是模型的训练代码,由于飞桨实现动转静的功能是在内部完成的,对使用者来说,动态图的训练代码和动转静模型的训练代码是完全一致的。训练代码如下:

In [2]

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")

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

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

2.10.2 动态图转静态图模型保存

在推理&部署场景中,需要同时保存推理模型的结构和参数,但是动态图是即时执行即时得到结果,并不会记录模型的结构信息。动态图在保存推理模型时,需要先将动态图模型转换为静态图写法,编译得到对应的模型结构再保存,而飞桨框架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模型用于部署,可以直接用下面代码实现:

In [3]

# 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.")

==>Inference model saved in inference/mnist.

/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/ dygraph/jit.py:434: UserWarning: The InputSpec(shape=(-1, 784), dtype=paddle.float32, name=None)'s name is None. When using jit.save, please set InputSepc's name in to_static(input_spec=[]) and jit.save(input_spec=[]) and make sure they are consistent.

warnings.warn(name_none_error % spec)

其中,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训练。

In [4]

import numpy as np
import paddle
import paddle.nn.functional as F
# 确保从paddle.vision.datasets.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())

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

小结

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

截止目前,诸位读者已经掌握了使用飞桨完成深度学习建模的方法,并且可以编写相当强大的模型。如果将每个模型部分均展开,整个模型实现有几百行代码,可以灵活的实现各种建模过程中的需求。

“横纵式”教学法编写相当强大的模型

本章内容覆盖了使用飞桨建模各方面的基础知识,但仅以手写数字识别为案例,还难以覆盖各个领域的建模经验。从下一章开始,我们正式进入本教程的下一部分:以推荐、计算机视觉和自然语言处理等多个领域的任务为例,讲述各行各业最常用的模型实现,并介绍更多使用飞桨的知识。

作业 2-5

正确运行模型的极简版代码,分析训练过程中可能出现的问题或值得优化的地方,通过以下几点优化:

(1)样本:数据增强的方法

(2)假设:改进网络模型

(3)损失:尝试各种Loss

(4)优化:尝试各种优化算法和学习率

目标:尽可能使模型在mnist测试集上的分类准确率最高。

相关文章
|
Cloud Native 容器
ldif 数据转成正确的组织结构再探
ldif 数据转成正确的组织结构再探
|
3月前
|
存储 安全 Java
"Java编码魔法:揭秘图片与文件的Base64神秘转换术,让数据在指尖跳跃!"
【8月更文挑战第16天】Base64编码在Java开发中常用于将二进制数据如图片转换为ASCII字符串以便传输。编码使用64个字符及等号填充,每3字节数据编码为4个字符。Java利用`java.util.Base64`类实现此功能:读取图片或文件为字节数组后进行编码。解码时将Base64字符串还原为字节数组并写入文件。需注意编码效率降低、不提供安全性及特殊字符兼容性等问题。掌握这些技巧有助于解决Web开发中的数据传输需求。
95 4
|
4月前
|
Serverless 网络安全 API
函数计算产品使用问题之遇到无法处理艺术字请求,该怎么办
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
5月前
|
传感器 存储 编解码
数码相机背后的像素秘密:静态与动态的真相
这篇文章探讨了数码摄影中“动态像素”和“静态像素”的概念。像素是图像质量的关键因素,而CCD或CMOS传感器负责将光信号转化为数字图像。RGB差值补偿算法用于恢复色彩,但牺牲了部分分辨率。所谓“动态像素”更多是营销术语,而非技术标准,它反映了拍照和录像时因硬件限制和处理需求不同而产生的差异。随着技术进步,硬件编码器的引入已显著提升视频处理能力,使得高清摄影和视频录制变得更加普遍。理解这些原理有助于消费者做出更明智的设备选择。
|
运维 前端开发 安全
万字长文搞懂产品模式和项目模式
万字长文搞懂产品模式和项目模式
177 0
|
传感器 存储 编解码
即时通讯音视频开发(二十):一文读懂视频的颜色模型转换和色域转换
本文将以通俗易懂的文字,引导你理解视频是如何从采集开始,历经各种步骤,最终通过颜色模型转换和不同的色域转换,让你看到赏心悦目的视频结果的。
80 0
|
存储 C#
【c#入门杂选】-显性转换,和隐形转换
【c#入门杂选】-显性转换,和隐形转换
130 0
【c#入门杂选】-显性转换,和隐形转换
|
存储 资源调度 算法
打通任督二脉:4000字,一文,从代码拆到计算机底层。
一个业务场景 假设我们要编写一个购买商品的程序,程序的内容很简单: 商品的库存数量存放在数据库中,每次完成库存数量-1。我们很快可以写出伪代码
70 0
|
前端开发
前端工作总结124-数组转换为对象项目案例
前端工作总结124-数组转换为对象项目案例
118 0
前端工作总结124-数组转换为对象项目案例
|
Java 编译器
七千字带你了解封装等机制下
七千字带你了解封装等机制下
92 0
七千字带你了解封装等机制下