模型保存常见问题

问题:静态图的save接口与save_inference_model接口存储的结果有什么区别?

  • 答复:主要差别在于保存结果的应用场景:

    1. save接口(2.0的paddle.static.save或者1.8的fluid.io.save

      该接口用于保存训练过程中的模型和参数,一般包括*.pdmodel*.pdparams*.pdopt三个文件。其中*.pdmodel是训练使用的完整模型program描述,区别于推理模型,训练模型program包含完整的网络,包括前向网络,反向网络和优化器,而推理模型program仅包含前向网络,*.pdparams是训练网络的参数dict,key为变量名,value为Tensor array数值,*.pdopt是训练优化器的参数,结构与*.pdparams一致。

    2. save_inference_model接口(2.0的paddle.static.save_inference_model或者1.8的fluid.io.save_inference_model

      该接口用于保存推理模型和参数,2.0的paddle.static.save_inference_model保存结果为*.pdmodel*.pdiparams两个文件,其中*.pdmodel为推理使用的模型program描述,*.pdiparams为推理用的参数,这里存储格式与*.pdparams不同(注意两者后缀差个i),*.pdiparams为二进制Tensor存储格式,不含变量名。1.8的fluid.io.save_inference_model默认保存结果为__model__文件,和以参数名为文件名的多个分散参数文件,格式与2.0一致。

    3. 关于更多2.0动态图模型保存和加载的介绍可以参考教程:模型存储与载入


问题:增量训练中,如何保存模型和恢复训练?

  • 答复:在增量训练过程中,不仅需要保存模型的参数,也需要保存优化器的参数。

具体地,在2.0版本中需要使用Layer和Optimizer的state_dictset_state_dict方法配合paddle.save/load使用。简要示例如下:

import paddle

emb = paddle.nn.Embedding(10, 10)
layer_state_dict = emb.state_dict()
paddle.save(layer_state_dict, "emb.pdparams")

scheduler = paddle.optimizer.lr.NoamDecay(
    d_model=0.01, warmup_steps=100, verbose=True)
adam = paddle.optimizer.Adam(
    learning_rate=scheduler,
    parameters=emb.parameters())
opt_state_dict = adam.state_dict()
paddle.save(opt_state_dict, "adam.pdopt")

load_layer_state_dict = paddle.load("emb.pdparams")
load_opt_state_dict = paddle.load("adam.pdopt")

emb.set_state_dict(para_state_dict)
adam.set_state_dict(opti_state_dict)

问题:paddle.load可以加载哪些API产生的结果呢?

  • 答复:

    为了更高效地使用paddle存储的模型参数,paddle.load支持从除paddle.save之外的其他save相关API的存储结果中载入state_dict,但是在不同场景中,参数path的形式有所不同:

    1. paddle.static.save或者paddle.Model().save(training=True)的保存结果载入:path需要是完整的文件名,例如model.pdparams或者model.opt

    2. paddle.jit.save或者paddle.static.save_inference_model或者paddle.Model().save(training=False)的保存结果载入:path需要是路径前缀, 例如model/mnistpaddle.load会从mnist.pdmodelmnist.pdiparams中解析state_dict的信息并返回。

    3. 从paddle 1.x APIpaddle.fluid.io.save_inference_model或者paddle.fluid.io.save_params/save_persistables的保存结果载入:path需要是目录,例如model,此处model是一个文件夹路径。

    需要注意的是,如果从paddle.static.save或者paddle.static.save_inference_model等静态图API的存储结果中载入state_dict,动态图模式下参数的结构性变量名将无法被恢复。在将载入的state_dict配置到当前Layer中时,需要配置Layer.set_state_dict的参数use_structured_name=False

问题:paddle.save 是如何保存state_dict,Layer对象,Tensor以及包含Tensor的嵌套list、tuple、dict的呢?

  • 答复:

    1. 对于state_dict保存方式与paddle2.0完全相同,我们将Tensor转化为numpy.ndarray保存。

    2. 对于其他形式的包含Tensor的对象(Layer对象,单个Tensor以及包含Tensor的嵌套listtupledict),在动态图中,将Tensor转化为tuple(Tensor.name, Tensor.numpy());在静态图中,将Tensor直接转化为numpy.ndarray。之所以这样做,是因为当在静态图中使用动态保存的模型时,有时需要Tensor的名字因此将名字保存下来,同时,在load时区分这个numpy.ndarray是由Tenosr转化而来还是本来就是numpy.ndarray;保存静态图的Tensor时,通常通过Variable.get_value得到Tensor再使用paddle.save保存Tensor,此时,Variable是有名字的,这个Tensor是没有名字的,因此将静态图Tensor直接转化为numpy.ndarray保存。

    此处动态图Tensor和静态图Tensor是不相同的,动态图Tensor有name、stop_gradient等属性;而静态图的Tensor是比动态图Tensor轻量级的,只包含place等基本信息,不包含名字等。

问题:将Tensor转换为numpy.ndarray或者tuple(Tensor.name, Tensor.numpy())不是惟一可译编码,为什么还要做这样的转换呢?

  • 答复:

    1. 我们希望paddle.save保存的模型能够不依赖paddle框架就能够被用户解析(pickle格式模型),这样用户可以方便的做调试,轻松的看到保存的参数的数值。其他框架的模型与paddle模型做转化也会容易很多。

    2. 我们希望保存的模型尽量小,只保留了能够满足大多场景的信息(动态图保存名字和数值,静态图只保存数值),如果需要Tensor的其他信息(例如stop_gradient),可以向被保存的对象中添加这些信息,load之后再还原这些信息。这样的转换方式可以覆盖绝大多数场景,一些特殊场景也是可以通过一些方法解决的,如下面的问题。

问题:什么情况下save与load的结果不一致呢,应该如何避免这种情况发生呢?

  • 答复:

    以下情况会造成save与load的结果不一致:

    1. 被保存的对象包含动态图Tensor同时包含tuple(string, numpy.ndarray)

    2. 被保存的对象包含静态图Tensor,同时包含numpy.ndarray或者tuple(string, numpy.ndarray)

    3. 被保存的对象只包含numpy.ndarray,但是包含tuple(string, numpy.ndarray)

    针对这些情况我们有以下建议:

    1. 被保存的对象(包括Layer对象中的ParamBase),避免包含形如tuple(string, numpy.ndarray)的对象;

    2. 如果被保存的对象包含numpy.ndarray,尽量在load时设置return_numpy = True

    3. 对于Layer对象,只保存参数的值和名字,如果需要其他信息(例如stop_gradient),请将手将这些信息打包成dict等,一并保存。

问题:paddle 2.x 如何保存模型文件?如何保存paddle 1.x 中的 model 文件?

  • 答复:

    1. 在paddle2.x可使用paddle.jit.save接口以及paddle.static.save_inference_model,通过指定path来保存成为path.pdmodelpath.pdiparams,可对应paddle1.x中使用save_inference_model指定dirname和params_filename生成dirname/__model__dirname/params文件。paddle2.x保存模型文件详情可参考:

    1. 如果想要在paddle2.x中读取paddle 1.x中的model文件,可参考:

问题:paddle如何单独load存下来所有模型变量中某一个变量,然后修改变量中的值?

  • 答复:

    1. 如果目的是修改存储变量的值,可以使用paddle.save保存下来所有变量,然后再使用paddle.load将所有变量载入后,查找目标变量进行修改,示例代码如下:

import paddle

layer = paddle.nn.Linear(3, 4)
path = 'example/model.pdparams'
paddle.save(layer.state_dict(), path)
layer_param = paddle.load(path)
# 修改fc_0.b_0的值
layer_param["fc_0.b_0"] = 10
2. 如果目的是单独访问某个变量,需要单独存储然后再单独读取,示例代码如下:
import paddle

layer = paddle.nn.Linear(3, 4)
path_w = 'example/weight.tensor'
path_b = 'example/bias.tensor'
paddle.save(layer.weight, path_w)
paddle.save(layer.bias, path_b)
tensor_bias = paddle.load(path_b)
tensor_bias[0] = 10

更多介绍请参考以下API文档: