首页 Paddle框架 帖子详情
飞桨框架2.0RC新增模型保存、加载方案,更全面
收藏
快速回复
Paddle框架 其他深度学习 4207 17
飞桨框架2.0RC新增模型保存、加载方案,更全面
收藏
快速回复
Paddle框架 其他深度学习 4207 17

通过一段时间系统的课程学习,算法攻城狮张同学对于飞桨框架的使用越来越顺手,于是他打算在企业内尝试使用飞桨进行AI产业落地。


但是AI产业落地并不是分秒钟的事情,除了专业技能过硬,熟悉飞桨的使用外,在落地过程中还会遇到很多细节的问题。这不,他就想到了两个棘手的小问题:
· 企业的数据集都比较大,使用这种大规模数据集进行模型训练的耗时会很长,往往需要持续数天甚至更长时间。这种情况下,就需要多次保存模型训练的参数,避免由于训练意外中断而前功尽弃。

 · 模型训练至收敛后,需要将模型及参数保存下来,用于后续在服务器或者移动端环境中部署,在推理场景中发挥作用。

那么,如何高效地解决张同学提出的这两个问题呢?飞桨框架2.0RC为开发者提供了全新的动态图模式下的模型保存与加载体系,其中包含两个模型保存与加载的方案,分别适用于上述两个场景。(友情提示:飞桨框架2.0RC版本开始主推动态图模式,仍兼容保留对静态图模式的支持,但不再推荐使用。)

场景一:训练场景模型保存与加载(只需保存和加载模型参数即可)

在训练阶段,开发者仅需要保存和加载模型参数即可。飞桨提供了paddle.save和paddle.load接口用于实现该功能。当保存和加载模型参数时,可使用 paddle.save/load 结合Layer和Optimizer的state_dict()方法实现,这两个接口的关系入下图所示:

· state_dict是保存Layer或者Optimizer参数的键值对,state_dict的key为参数名,value为参数真实的numpy array数值;

· pdparams为Layer参数文件名的后缀;

· pdopt为Optimizer参数文件名的后缀。

相关文档获取地址:
https://www.paddlepaddle.org.cn/documentation/docs/zh/2.0-rc1/api/paddle/framework/io/save_cn.html

https://www.paddlepaddle.org.cn/documentation/docs/zh/2.0-rc1/api/paddle/framework/io/load_cn.html

下面举一个简单的线性回归模型示例。

import numpy as np

import paddle

import paddle.nn as nn

import paddle.optimizer as opt

 

BATCH_SIZE = 16

BATCH_NUM = 4

EPOCH_NUM = 10

 

IMAGE_SIZE = 784

CLASS_NUM = 10

 

# define a random dataset

class RandomDataset(paddle.io.Dataset):

    def __init__(self, num_samples):

        self.num_samples = num_samples

 

    def __getitem__(self, idx):

        image = np.random.random([IMAGE_SIZE]).astype('float32')

        label = np.random.randint(0, CLASS_NUM - 1, (1, )).astype('int64')

        return image, label

 

    def __len__(self):

        return self.num_samples

 

class LinearNet(nn.Layer):

    def __init__(self):

        super(LinearNet, self).__init__()

        self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)

 

    def forward(self, x):

        return self._linear(x)

 

# create network

layer = LinearNet()

loss_fn = nn.CrossEntropyLoss()

adam = opt.Adam(learning_rate=0.001, parameters=layer.parameters())

 

# create data loader

dataset = RandomDataset(BATCH_NUM * BATCH_SIZE)

loader = paddle.io.DataLoader(dataset,

    batch_size=BATCH_SIZE,

    shuffle=True,

    drop_last=True,

num_workers=2)

 

# train

for epoch_id in range(EPOCH_NUM):

    for batch_id, (image, label) in enumerate(loader()):

        out = layer(image)

        loss = loss_fn(out, label)

        loss.backward()

        adam.step()

        adam.clear_grad()

        print("Epoch {} batch {}: loss = {}".format(

            epoch_id, batch_id, np.mean(loss.numpy())))

如果想每训练一个epoch就保存一次模型参数,要如何实现呢?此时只需要在每个epoch训练结束后,保存一次Layer和Optimizer的参数即可。
# train

for epoch_id in range(EPOCH_NUM):

    for batch_id, (image, label) in enumerate(loader()):

        out = layer(image)

        loss = loss_fn(out, label)

        loss.backward()

        adam.step()

        adam.clear_grad()

        print("Epoch {} batch {}: loss = {}".format(

            epoch_id, batch_id, np.mean(loss.numpy())))

    # save state_dict

    paddle.save(layer.state_dict(), "{}/epoch_{}.pdparams".format(

      'checkpoints', epoch_id))

    paddle.save(adam.state_dict(),"{}/epoch_{}.pdopt".format(

      'checkpoints', epoch_id))

执行该训练示例后,保存的结果如下,每个epoch执行完都保存了相应的训练参数。

λ ls checkpoints/

epoch_0.pdopt     epoch_1.pdparams  epoch_3.pdopt     epoch_4.pdparams  epoch_6.pdopt     epoch_7.pdparams  epoch_9.pdopt

epoch_0.pdparams  epoch_2.pdopt     epoch_3.pdparams  epoch_5.pdopt     epoch_6.pdparams  epoch_8.pdopt     epoch_9.pdparams

epoch_1.pdopt     epoch_2.pdparams  epoch_4.pdopt     epoch_5.pdparams  epoch_7.pdopt     epoch_8.pdparams

如果训练意外中断,想要从某个epoch继续训练,或者想要加载某个推理效果更好的epoch的保存结果,可以通过paddle.load接口加载,然后通过set_state_dict接口配置。这里以加载第8个epoch的训练参数为例,只需要在创建网络之后,训练之前,将相应文件加载配置到的Layer和Optimizer中即可。
# create network

layer = LinearNet()

loss_fn = nn.CrossEntropyLoss()

adam = opt.Adam(learning_rate=0.001, parameters=layer.parameters())

 

# load

layer_state_dict = paddle.load("checkpoints/epoch_8.pdparams")

opt_state_dict = paddle.load("checkpoints/epoch_8.pdopt")

 

layer.set_state_dict(layer_state_dict)

adam.set_state_dict(opt_state_dict)

 

# create data loader

...

# train

...

场景二:推理&部署场景的模型保存与加载(需要同时保存推理模型的结构和参数)

在推理&部署场景中,需要同时保存推理模型的结构和参数,此时需要使用 paddle.jit.save和paddle.jit.load 接口实现。paddle.jit.save接口会自动调用飞桨框架2.0RC推出的动态图转静态图功能,使得用户可以做到使用动态图编程调试,自动转成静态图训练部署。

(小伙伴们应该了解,动态图是即时执行即时得到结果,并不会记录模型的结构信息。动态图在保存推理模型时,需要先将动态图模型转换为静态图写法,编译得到对应的模型结构再保存,飞桨框架2.0RC版本推出的动静转换体系,用于解决这个难题。)

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

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

仍然接着前面的模型示例追加说明,直接在train实现后调用paddle.jit.save保存推理模型即可。

# save inference model

from paddle.static import InputSpec

paddle.jit.save(

    layer=layer,

    path="inference/linear",

    input_spec=[InputSpec(shape=[None, 784], dtype='float32')])

此时,inference目录下的保存结果为:

λ ls inference/

linear.pdiparams  linear.pdiparams.info  linear.pdmodel

这里InputSpec是用于描述推理模型输入特性的对象,包括输入Tensor的shape和dtype。paddle.jit.save会根据input_spec传入的输入描述信息,推理得到整个模型的结构,该场景中input_spec是必须指定的。

另外,在使用paddle.jit.save保存需要注意:确保Layer.forward方法中仅实现推理相关的功能,避免将训练所需的loss计算逻辑写入forward方法。Layer更准确的语义是描述一个具有推理功能的模型对象,输出推理的结果,而loss计算是仅属于模型训练中的概念。将loss计算的实现放到Layer.forward方法中,会使Layer在不同场景下概念有所差别,并且增大Layer使用的复杂性,因此建议保持Layer实现的简洁性。举个例子:

下面代码是不推荐的Layer写法:
class LinearNet(nn.Layer):

    def __init__(self):

        super(LinearNet, self).__init__()

        self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)

 

    def forward(self, x, label=None):

        out = self._linear(x)

        if label:

            loss = nn.functional.cross_entropy(out, label)

            avg_loss = nn.functional.mean(loss)

            return out, avg_loss

        else:

            return out

这才是推荐的Layer写法:

class LinearNet(nn.Layer):

    def __init__(self):

        super(LinearNet, self).__init__()

        self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)

    def forward(self, x):

        return self._linear(x)

在模型加载方面,一般由paddle.jit.save保存的推理模型,会通过Paddle inference或者Paddle Lite等高效的推理工具加载并进行线上部署,不会再通过飞桨基础框架加载使用。但出于接口设计一致性的考虑,飞桨框架2.0RC新增了paddle.jit.load接口,也支持了通过飞桨基础框架的接口加载paddle.jit.save保存的推理模型,且加载后可以用于推理,也可以用于继续进行增量训练。

· 加载后进行推理:直接以保存之前的方式使用加载的对象即可,但要注意加载对象的输入需要和保存时指定的input_spec保持一致,示例如下:

# load inference model

loaded_layer = paddle.jit.load("inference/linear")

 

# inference

loaded_layer.eval()

x = paddle.randn([1, IMAGE_SIZE], 'float32')

pred = loaded_layer(x)

· 加载后进行增量训练:由于保存的模型是用于inference的模型,所以需要重新为网络添加loss function和optimizer,示例如下:

# load inference model

loaded_layer = paddle.jit.load("inference/linear")

 

# fine-tune

loaded_layer.train()

dataset = RandomDataset(BATCH_NUM * BATCH_SIZE)

loader = paddle.io.DataLoader(dataset,

    batch_size=BATCH_SIZE,

    shuffle=True,

    drop_last=True,

    num_workers=2)

loss_fn = nn.CrossEntropyLoss()

adam = opt.Adam(learning_rate=0.001, parameters=loaded_layer.parameters())

for epoch_id in range(EPOCH_NUM):

    for batch_id, (image, label) in enumerate(loader()):

        out = loaded_layer(image)

        loss = loss_fn(out, label)

        loss.backward()

        adam.step()

        adam.clear_grad()

        print("Epoch {} batch {}: loss = {}".format(

            epoch_id, batch_id, np.mean(loss.numpy())))


相关文档获取地址:
https://www.paddlepaddle.org.cn/documentation/docs/zh/2.0-rc1/api/paddle/fluid/dygraph/jit/save_cn.html
https://www.paddlepaddle.org.cn/documentation/docs/zh/2.0-rc1/api/paddle/fluid/dygraph/jit/load_cn.html

模型保存和加载是深度学习框架的基础I/O模块,是模型训练与部署的必用接口,整体关系如下图所示:

在最新版本中,相应的模型保存加载体系也有重大更新,在接口功能和易用性方面均有显著提升。除上述功能外,模型保存与加载模块还包含其他诸多易用的功能:
· 以上接口均兼容支持了从飞桨框架1.x的
paddle.fluid.io.save_inference_model、 paddle.fluid.save 、 paddle.fluid.io.save_params/save_persistables 等接口保存的结果中加载模型或者参数;

相关文档获取地址:
https://www.paddlepaddle.org.cn/documentation/docs/zh/2.0
rc1/guides/02_paddle2.0_develop/08_model_save_load_cn.html#id15
· 仍然支持直接保存静态图模型,并保留了相关接口;
· 推出了动静一体的高层API,也有相应的保存/加载接口。更多内容,可以查阅飞桨官网→API文档(paddle.Modell.save/load)。
模型保存和加载体系是深度学习框架必不可少的部分,飞桨团队仍然在优化相关接口的质量和易用性,希望能给文中的张同学及广大开发者带来更好的产品体验。如果大家发现模型保存与加载相关接口有BUG、出现覆盖不到的场景等问题,欢迎通过Issue反馈给我们。对于工匠精神的追求,飞桨一直在努力,并非常期待与广大开发者携手并行,共同构建功能强大且易用的开源深度学习框架。

如在使用过程中有问题,可加入官方QQ群进行交流:1108045677。

如果您想详细了解更多飞桨的相关内容,请参阅以下文档。

·飞桨官网地址·

https://www.paddlepaddle.org.cn/

·飞桨开源框架项目地址·
GitHub: https://github.com/PaddlePaddle/Paddle
Gitee: https://gitee.com/paddlepaddle/Paddle

3
收藏
回复
全部评论(17)
时间顺序
w
wangwei8638
#2 回复于2021-01

收藏学习

0
回复
test6618
#3 回复于2021-01

test

0
回复
周小鱼whoyou
#4 回复于2021-01

参考来练习一下

0
回复
w
wangwei8638
#5 回复于2021-01

中断再训练功能很赞

0
回复
AIStudio810258
#10 回复于2021-01

讲得很清楚唉

0
回复
AIStudio810260
#11 回复于2021-01

越来越完善了啊

0
回复
AIStudio810260
#12 回复于2021-01

以后在AI社区贴代码还是转成图片好了……没有高亮啊

0
回复
AIStudio810258
#13 回复于2021-01
以后在AI社区贴代码还是转成图片好了……没有高亮啊

先用插件改善下吧~

0
回复
AIStudio810258
#14 回复于2021-01
以后在AI社区贴代码还是转成图片好了……没有高亮啊

说不定哪天就又悄悄的更新了

0
回复
AIStudio810260
#15 回复于2021-01
先用插件改善下吧~

没吧,有插件可以改善么

0
回复
AIStudio810260
#16 回复于2021-01
说不定哪天就又悄悄的更新了

社区的话估计难,框架一看就挺旧哈哈哈,维护起来吃力不讨好

0
回复
l
lihejun02891
#17 回复于2021-02

PaddlePaddle2.0,CUDA 版本11.0,使用预训练模型deeplabv3p_xception65_humanseg进行抠图操作,有CPU内存泄漏,扣一张图看不出来,扣多图(例如视频的帧)时,占用内存一直在增加,直到本崩溃。

原因应该是paddlepaddle-gpu==2.0.0.post110,的某一模块没有释放内存。

CUDA10.2等低版本都是好的。

麻烦开发团队解决。如想复现问题可以联系我。

0
回复
w
wangwei8638
#18 回复于2021-02
说不定哪天就又悄悄的更新了

更新速度着实很快,攻城狮冲啊

0
回复
AIStudio810258
#19 回复于2021-02
没吧,有插件可以改善么

现在社区代码不“吃”缩进了~

0
回复
pika
#20 回复于2021-02

save load数据

 

0
回复
T
TigerLogician
#21 回复于2022-07

设计师哪去了

0
回复
李长安
#22 回复于2022-07

优秀,但是是不是需要更新一版了?

0
回复
需求/bug反馈?一键提issue告诉我们
发现bug?如果您知道修复办法,欢迎提pr直接参与建设飞桨~
在@后输入用户全名并按空格结束,可艾特全站任一用户