模型存储与载入

一、存储载入体系简介

1.1 接口体系

飞桨框架2.x对模型与参数的存储与载入相关接口进行了梳理,根据接口使用的场景与模式,分为三套体系,分别是:

1.1.1 动态图存储载入体系

为提升框架使用体验,飞桨框架2.0将主推动态图模式,动态图模式下的存储载入接口包括:

  • paddle.save

  • paddle.load

  • paddle.jit.save

  • paddle.jit.load

本文主要介绍飞桨框架2.0动态图存储载入体系,各接口关系如下图所示:

https://githubraw.cdn.bcebos.com/PaddlePaddle/FluidDoc/develop/doc/paddle/guides/images/main_save_load_2.0.png?raw=true

1.1.2 静态图存储载入体系

静态图存储载入相关接口为飞桨框架1.x版本的主要使用接口,出于兼容性的目的,这些接口仍然可以在飞桨框架2.x使用,但不再推荐。相关接口包括:

  • paddle.static.save

  • paddle.static.load

  • paddle.static.save_inference_model

  • paddle.static.load_inference_model

  • paddle.static.load_program_state

  • paddle.static.set_program_state

由于飞桨框架2.0不再主推静态图模式,故本文不对以上主要用于飞桨框架1.x的相关接口展开介绍,如有需要,可以阅读对应API文档。

1.1.3 高阶API存储载入体系

  • paddle.Model.fit (训练接口,同时带有参数存储的功能)

  • paddle.Model.save

  • paddle.Model.load

飞桨框架2.0高阶API仅有一套Save/Load接口,表意直观,体系清晰,若有需要,建议直接阅读相关API文档,此处不再赘述。

注解

本教程着重介绍飞桨框架2.x的各个存储载入接口的关系及各种使用场景,不对接口参数进行详细介绍,如果需要了解具体接口参数的含义,请直接阅读对应API文档。

1.2 接口存储结果组织形式

飞桨2.0统一了各存储接口对于同一种存储行为的处理方式,并且统一推荐或自动为存储的文件添加飞桨标准的文件后缀,详见下图:

https://githubraw.cdn.bcebos.com/PaddlePaddle/FluidDoc/develop/doc/paddle/guides/images/save_result_format_2.0.png?raw=true

二、参数存储载入(训练调优)

若仅需要存储/载入模型的参数,可以使用 paddle.save/load 结合Layer和Optimizer的state_dict达成目的,此处state_dict是对象的持久参数的载体,dict的key为参数名,value为参数真实的numpy array值。

结合以下简单示例,介绍参数存储和载入的方法,以下示例完成了一个简单网络的训练过程:

  1. import numpy as np
  2. import paddle
  3. import paddle.nn as nn
  4. import paddle.optimizer as opt
  5. BATCH_SIZE = 16
  6. BATCH_NUM = 4
  7. EPOCH_NUM = 4
  8. IMAGE_SIZE = 784
  9. CLASS_NUM = 10
  10. # define a random dataset
  11. class RandomDataset(paddle.io.Dataset):
  12. def __init__(self, num_samples):
  13. self.num_samples = num_samples
  14. def __getitem__(self, idx):
  15. image = np.random.random([IMAGE_SIZE]).astype('float32')
  16. label = np.random.randint(0, CLASS_NUM - 1, (1, )).astype('int64')
  17. return image, label
  18. def __len__(self):
  19. return self.num_samples
  20. class LinearNet(nn.Layer):
  21. def __init__(self):
  22. super(LinearNet, self).__init__()
  23. self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)
  24. def forward(self, x):
  25. return self._linear(x)
  26. def train(layer, loader, loss_fn, opt):
  27. for epoch_id in range(EPOCH_NUM):
  28. for batch_id, (image, label) in enumerate(loader()):
  29. out = layer(image)
  30. loss = loss_fn(out, label)
  31. loss.backward()
  32. opt.step()
  33. opt.clear_grad()
  34. print("Epoch {} batch {}: loss = {}".format(
  35. epoch_id, batch_id, np.mean(loss.numpy())))
  36. # create network
  37. layer = LinearNet()
  38. loss_fn = nn.CrossEntropyLoss()
  39. adam = opt.Adam(learning_rate=0.001, parameters=layer.parameters())
  40. # create data loader
  41. dataset = RandomDataset(BATCH_NUM * BATCH_SIZE)
  42. loader = paddle.io.DataLoader(dataset,
  43. batch_size=BATCH_SIZE,
  44. shuffle=True,
  45. drop_last=True,
  46. num_workers=2)
  47. # train
  48. train(layer, loader, loss_fn, adam)

2.1 参数存储

参数存储时,先获取目标对象(Layer或者Optimzier)的state_dict,然后将state_dict存储至磁盘,示例如下(接前述示例):

  1. # save
  2. paddle.save(layer.state_dict(), "linear_net.pdparams")
  3. paddle.save(adam.state_dict(), "adam.pdopt")

2.2 参数载入

参数载入时,先从磁盘载入保存的state_dict,然后通过set_state_dict方法配置到目标对象中,示例如下(接前述示例):

  1. # load
  2. layer_state_dict = paddle.load("linear_net.pdparams")
  3. opt_state_dict = paddle.load("adam.pdopt")
  4. layer.set_state_dict(layer_state_dict)
  5. adam.set_state_dict(opt_state_dict)

三、模型&参数存储载入(训练部署)

若要同时存储/载入模型结构和参数,可以使用 paddle.jit.save/load 实现。

3.1 模型&参数存储

模型&参数存储根据训练模式不同,有两种使用情况:

  1. 动转静训练 + 模型&参数存储

  2. 动态图训练 + 模型&参数存储

3.1.1 动转静训练 + 模型&参数存储

动转静训练相比直接使用动态图训练具有更好的执行性能,训练完成后,直接将目标Layer传入 paddle.jit.save 存储即可。:

一个简单的网络训练示例如下:

  1. import numpy as np
  2. import paddle
  3. import paddle.nn as nn
  4. import paddle.optimizer as opt
  5. BATCH_SIZE = 16
  6. BATCH_NUM = 4
  7. EPOCH_NUM = 4
  8. IMAGE_SIZE = 784
  9. CLASS_NUM = 10
  10. # define a random dataset
  11. class RandomDataset(paddle.io.Dataset):
  12. def __init__(self, num_samples):
  13. self.num_samples = num_samples
  14. def __getitem__(self, idx):
  15. image = np.random.random([IMAGE_SIZE]).astype('float32')
  16. label = np.random.randint(0, CLASS_NUM - 1, (1, )).astype('int64')
  17. return image, label
  18. def __len__(self):
  19. return self.num_samples
  20. class LinearNet(nn.Layer):
  21. def __init__(self):
  22. super(LinearNet, self).__init__()
  23. self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)
  24. @paddle.jit.to_static
  25. def forward(self, x):
  26. return self._linear(x)
  27. def train(layer, loader, loss_fn, opt):
  28. for epoch_id in range(EPOCH_NUM):
  29. for batch_id, (image, label) in enumerate(loader()):
  30. out = layer(image)
  31. loss = loss_fn(out, label)
  32. loss.backward()
  33. opt.step()
  34. opt.clear_grad()
  35. print("Epoch {} batch {}: loss = {}".format(
  36. epoch_id, batch_id, np.mean(loss.numpy())))
  37. # create network
  38. layer = LinearNet()
  39. loss_fn = nn.CrossEntropyLoss()
  40. adam = opt.Adam(learning_rate=0.001, parameters=layer.parameters())
  41. # create data loader
  42. dataset = RandomDataset(BATCH_NUM * BATCH_SIZE)
  43. loader = paddle.io.DataLoader(dataset,
  44. batch_size=BATCH_SIZE,
  45. shuffle=True,
  46. drop_last=True,
  47. num_workers=2)
  48. # train
  49. train(layer, loader, loss_fn, adam)

随后使用 paddle.jit.save 对模型和参数进行存储(接前述示例):

  1. # save
  2. path = "example.model/linear"
  3. paddle.jit.save(layer, path)

通过动转静训练后保存模型&参数,有以下三项注意点:

  1. Layer对象的forward方法需要经由 paddle.jit.to_static 装饰

经过 paddle.jit.to_static 装饰forward方法后,相应Layer在执行时,会先生成描述模型的Program,然后通过执行Program获取计算结果,示例如下:

  1. import paddle
  2. import paddle.nn as nn
  3. IMAGE_SIZE = 784
  4. CLASS_NUM = 10
  5. class LinearNet(nn.Layer):
  6. def __init__(self):
  7. super(LinearNet, self).__init__()
  8. self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)
  9. @paddle.jit.to_static
  10. def forward(self, x):
  11. return self._linear(x)

若最终需要生成的描述模型的Program支持动态输入,可以同时指明模型的 InputSepc ,示例如下:

  1. import paddle
  2. import paddle.nn as nn
  3. from paddle.static import InputSpec
  4. IMAGE_SIZE = 784
  5. CLASS_NUM = 10
  6. class LinearNet(nn.Layer):
  7. def __init__(self):
  8. super(LinearNet, self).__init__()
  9. self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)
  10. @paddle.jit.to_static(input_spec=[InputSpec(shape=[None, 784], dtype='float32')])
  11. def forward(self, x):
  12. return self._linear(x)
  1. 请确保Layer.forward方法中仅实现预测功能,避免将训练所需的loss计算逻辑写入forward方法

Layer更准确的语义是描述一个具有预测功能的模型对象,接收输入的样本数据,输出预测的结果,而loss计算是仅属于模型训练中的概念。将loss计算的实现放到Layer.forward方法中,会使Layer在不同场景下概念有所差别,并且增大Layer使用的复杂性,这不是良好的编码行为,同时也会在最终保存预测模型时引入剪枝的复杂性,因此建议保持Layer实现的简洁性,下面通过两个示例对比说明:

错误示例如下:

  1. import paddle
  2. import paddle.nn as nn
  3. IMAGE_SIZE = 784
  4. CLASS_NUM = 10
  5. class LinearNet(nn.Layer):
  6. def __init__(self):
  7. super(LinearNet, self).__init__()
  8. self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)
  9. @paddle.jit.to_static
  10. def forward(self, x, label=None):
  11. out = self._linear(x)
  12. if label:
  13. loss = nn.functional.cross_entropy(out, label)
  14. avg_loss = nn.functional.mean(loss)
  15. return out, avg_loss
  16. else:
  17. return out

正确示例如下:

  1. import paddle
  2. import paddle.nn as nn
  3. IMAGE_SIZE = 784
  4. CLASS_NUM = 10
  5. class LinearNet(nn.Layer):
  6. def __init__(self):
  7. super(LinearNet, self).__init__()
  8. self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)
  9. @paddle.jit.to_static
  10. def forward(self, x):
  11. return self._linear(x)
  1. 如果你需要存储多个方法,需要用 paddle.jit.to_static 装饰每一个需要被存储的方法。

注解

只有在forward之外还需要存储其他方法时才用这个特性,如果仅装饰非forward的方法,而forward没有被装饰,是不符合规范的。此时 paddle.jit.saveinput_spec 参数必须为None。

示例代码如下:

  1. import paddle
  2. import paddle.nn as nn
  3. from paddle.static import InputSpec
  4. IMAGE_SIZE = 784
  5. CLASS_NUM = 10
  6. class LinearNet(nn.Layer):
  7. def __init__(self):
  8. super(LinearNet, self).__init__()
  9. self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)
  10. self._linear_2 = nn.Linear(IMAGE_SIZE, CLASS_NUM)
  11. @paddle.jit.to_static(input_spec=[InputSpec(shape=[None, IMAGE_SIZE], dtype='float32')])
  12. def forward(self, x):
  13. return self._linear(x)
  14. @paddle.jit.to_static(input_spec=[InputSpec(shape=[None, IMAGE_SIZE], dtype='float32')])
  15. def another_forward(self, x):
  16. return self._linear_2(x)
  17. inps = paddle.randn([1, IMAGE_SIZE])
  18. layer = LinearNet()
  19. before_0 = layer.another_forward(inps)
  20. before_1 = layer(inps)
  21. # save and load
  22. path = "example.model/linear"
  23. paddle.jit.save(layer, path)

存储的模型命名规则:forward的模型名字为:模型名+后缀,其他函数的模型名字为:模型名+函数名+后缀。每个函数有各自的pdmodel和pdiparams的文件,所有函数共用pdiparams.info。上述代码将在 example.model 文件夹下产生5个文件: linear.another_forward.pdiparams、 linear.pdiparams、 linear.pdmodel、 linear.another_forward.pdmodel、 linear.pdiparams.info

3.1.2 动态图训练 + 模型&参数存储

动态图模式相比动转静模式更加便于调试,如果你仍需要使用动态图直接训练,也可以在动态图训练完成后调用 paddle.jit.save 直接存储模型和参数。

同样是一个简单的网络训练示例:

  1. import numpy as np
  2. import paddle
  3. import paddle.nn as nn
  4. import paddle.optimizer as opt
  5. from paddle.static import InputSpec
  6. BATCH_SIZE = 16
  7. BATCH_NUM = 4
  8. EPOCH_NUM = 4
  9. IMAGE_SIZE = 784
  10. CLASS_NUM = 10
  11. # define a random dataset
  12. class RandomDataset(paddle.io.Dataset):
  13. def __init__(self, num_samples):
  14. self.num_samples = num_samples
  15. def __getitem__(self, idx):
  16. image = np.random.random([IMAGE_SIZE]).astype('float32')
  17. label = np.random.randint(0, CLASS_NUM - 1, (1, )).astype('int64')
  18. return image, label
  19. def __len__(self):
  20. return self.num_samples
  21. class LinearNet(nn.Layer):
  22. def __init__(self):
  23. super(LinearNet, self).__init__()
  24. self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)
  25. def forward(self, x):
  26. return self._linear(x)
  27. def train(layer, loader, loss_fn, opt):
  28. for epoch_id in range(EPOCH_NUM):
  29. for batch_id, (image, label) in enumerate(loader()):
  30. out = layer(image)
  31. loss = loss_fn(out, label)
  32. loss.backward()
  33. opt.step()
  34. opt.clear_grad()
  35. print("Epoch {} batch {}: loss = {}".format(
  36. epoch_id, batch_id, np.mean(loss.numpy())))
  37. # create network
  38. layer = LinearNet()
  39. loss_fn = nn.CrossEntropyLoss()
  40. adam = opt.Adam(learning_rate=0.001, parameters=layer.parameters())
  41. # create data loader
  42. dataset = RandomDataset(BATCH_NUM * BATCH_SIZE)
  43. loader = paddle.io.DataLoader(dataset,
  44. batch_size=BATCH_SIZE,
  45. shuffle=True,
  46. drop_last=True,
  47. num_workers=2)
  48. # train
  49. train(layer, loader, loss_fn, adam)

训练完成后使用 paddle.jit.save 对模型和参数进行存储:

  1. # save
  2. path = "example.dy_model/linear"
  3. paddle.jit.save(
  4. layer=layer,
  5. path=path,
  6. input_spec=[InputSpec(shape=[None, 784], dtype='float32')])

动态图训练后使用 paddle.jit.save 存储模型和参数注意点如下:

  1. 相比动转静训练,Layer对象的forward方法不需要额外装饰,保持原实现即可

  2. 与动转静训练相同,请确保Layer.forward方法中仅实现预测功能,避免将训练所需的loss计算逻辑写入forward方法

  3. 在最后使用 paddle.jit.save 时,需要指定Layer的 InputSpec ,Layer对象forward方法的每一个参数均需要对应的 InputSpec 进行描述,不能省略。这里的 input_spec 参数支持两种类型的输入:

  • InputSpec 列表

使用InputSpec描述forward输入参数的shape,dtype和name,如前述示例(此处示例中name省略,name省略的情况下会使用forward的对应参数名作为name,所以这里的name为 x ):

  1. paddle.jit.save(
  2. layer=layer,
  3. path=path,
  4. input_spec=[InputSpec(shape=[None, 784], dtype='float32')])
  • Example Tensor 列表

除使用InputSpec之外,也可以直接使用forward训练时的示例输入,此处可以使用前述示例中迭代DataLoader得到的 image ,示例如下:

  1. paddle.jit.save(
  2. layer=layer,
  3. path=path,
  4. input_spec=[image])

3.2 模型&参数载入

载入模型参数,使用 paddle.jit.load 载入即可,载入后得到的是一个Layer的派生类对象 TranslatedLayerTranslatedLayer 具有Layer具有的通用特征,支持切换 train 或者 eval 模式,可以进行模型调优或者预测。

注解

为了规避变量名字冲突,载入之后会重命名变量。

载入模型及参数,示例如下:

  1. import numpy as np
  2. import paddle
  3. import paddle.nn as nn
  4. import paddle.optimizer as opt
  5. BATCH_SIZE = 16
  6. BATCH_NUM = 4
  7. EPOCH_NUM = 4
  8. IMAGE_SIZE = 784
  9. CLASS_NUM = 10
  10. # load
  11. path = "example.model/linear"
  12. loaded_layer = paddle.jit.load(path)

载入模型及参数后进行预测,示例如下(接前述示例):

  1. # inference
  2. loaded_layer.eval()
  3. x = paddle.randn([1, IMAGE_SIZE], 'float32')
  4. pred = loaded_layer(x)

载入模型及参数后进行调优,示例如下(接前述示例):

  1. # define a random dataset
  2. class RandomDataset(paddle.io.Dataset):
  3. def __init__(self, num_samples):
  4. self.num_samples = num_samples
  5. def __getitem__(self, idx):
  6. image = np.random.random([IMAGE_SIZE]).astype('float32')
  7. label = np.random.randint(0, CLASS_NUM - 1, (1, )).astype('int64')
  8. return image, label
  9. def __len__(self):
  10. return self.num_samples
  11. def train(layer, loader, loss_fn, opt):
  12. for epoch_id in range(EPOCH_NUM):
  13. for batch_id, (image, label) in enumerate(loader()):
  14. out = layer(image)
  15. loss = loss_fn(out, label)
  16. loss.backward()
  17. opt.step()
  18. opt.clear_grad()
  19. print("Epoch {} batch {}: loss = {}".format(
  20. epoch_id, batch_id, np.mean(loss.numpy())))
  21. # fine-tune
  22. loaded_layer.train()
  23. dataset = RandomDataset(BATCH_NUM * BATCH_SIZE)
  24. loader = paddle.io.DataLoader(dataset,
  25. batch_size=BATCH_SIZE,
  26. shuffle=True,
  27. drop_last=True,
  28. num_workers=2)
  29. loss_fn = nn.CrossEntropyLoss()
  30. adam = opt.Adam(learning_rate=0.001, parameters=loaded_layer.parameters())
  31. train(loaded_layer, loader, loss_fn, adam)
  32. # save after fine-tuning
  33. paddle.jit.save(loaded_layer, "fine-tune.model/linear", input_spec=[x])

此外, paddle.jit.save 同时保存了模型和参数,如果你只需要从存储结果中载入模型的参数,可以使用 paddle.load 接口载入,返回所存储模型的state_dict,示例如下:

  1. import paddle
  2. import paddle.nn as nn
  3. IMAGE_SIZE = 784
  4. CLASS_NUM = 10
  5. class LinearNet(nn.Layer):
  6. def __init__(self):
  7. super(LinearNet, self).__init__()
  8. self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)
  9. @paddle.jit.to_static
  10. def forward(self, x):
  11. return self._linear(x)
  12. # create network
  13. layer = LinearNet()
  14. # load
  15. path = "example.model/linear"
  16. state_dict = paddle.load(path)
  17. # inference
  18. layer.set_state_dict(state_dict, use_structured_name=False)
  19. layer.eval()
  20. x = paddle.randn([1, IMAGE_SIZE], 'float32')
  21. pred = layer(x)

四、旧存储格式兼容载入

如果你是从飞桨框架1.x切换到2.x,曾经使用飞桨框架1.x的fluid相关接口存储模型或者参数,飞桨框架2.x也对这种情况进行了兼容性支持,包括以下几种情况。

飞桨1.x模型准备及训练示例,该示例为后续所有示例的前序逻辑:

  1. import numpy as np
  2. import paddle
  3. import paddle.fluid as fluid
  4. import paddle.nn as nn
  5. import paddle.optimizer as opt
  6. BATCH_SIZE = 16
  7. BATCH_NUM = 4
  8. EPOCH_NUM = 4
  9. IMAGE_SIZE = 784
  10. CLASS_NUM = 10
  11. # enable static mode
  12. paddle.enable_static()
  13. # define a random dataset
  14. class RandomDataset(paddle.io.Dataset):
  15. def __init__(self, num_samples):
  16. self.num_samples = num_samples
  17. def __getitem__(self, idx):
  18. image = np.random.random([IMAGE_SIZE]).astype('float32')
  19. label = np.random.randint(0, CLASS_NUM - 1, (1, )).astype('int64')
  20. return image, label
  21. def __len__(self):
  22. return self.num_samples
  23. image = fluid.data(name='image', shape=[None, 784], dtype='float32')
  24. label = fluid.data(name='label', shape=[None, 1], dtype='int64')
  25. pred = fluid.layers.fc(input=image, size=10, act='softmax')
  26. loss = fluid.layers.cross_entropy(input=pred, label=label)
  27. avg_loss = fluid.layers.mean(loss)
  28. optimizer = fluid.optimizer.SGD(learning_rate=0.001)
  29. optimizer.minimize(avg_loss)
  30. place = fluid.CPUPlace()
  31. exe = fluid.Executor(place)
  32. exe.run(fluid.default_startup_program())
  33. # create data loader
  34. dataset = RandomDataset(BATCH_NUM * BATCH_SIZE)
  35. loader = paddle.io.DataLoader(dataset,
  36. feed_list=[image, label],
  37. places=place,
  38. batch_size=BATCH_SIZE,
  39. shuffle=True,
  40. drop_last=True,
  41. num_workers=2)
  42. # train model
  43. for data in loader():
  44. exe.run(
  45. fluid.default_main_program(),
  46. feed=data,
  47. fetch_list=[avg_loss])

4.1 从 paddle.fluid.io.save_inference_model 存储结果中载入模型&参数

  1. 同时载入模型和参数

使用 paddle.jit.load 配合 **configs 载入模型和参数。

如果你是按照 paddle.fluid.io.save_inference_model 的默认格式存储的,可以按照如下方式载入(接前述示例):

  1. # save default
  2. model_path = "fc.example.model"
  3. fluid.io.save_inference_model(
  4. model_path, ["image"], [pred], exe)
  5. # enable dynamic mode
  6. paddle.disable_static(place)
  7. # load
  8. fc = paddle.jit.load(model_path)
  9. # inference
  10. fc.eval()
  11. x = paddle.randn([1, IMAGE_SIZE], 'float32')
  12. pred = fc(x)

如果你指定了存储的模型文件名,可以按照以下方式载入(接前述示例):

  1. # save with model_filename
  2. model_path = "fc.example.model.with_model_filename"
  3. fluid.io.save_inference_model(
  4. model_path, ["image"], [pred], exe, model_filename="__simplenet__")
  5. # enable dynamic mode
  6. paddle.disable_static(place)
  7. # load
  8. fc = paddle.jit.load(model_path, model_filename="__simplenet__")
  9. # inference
  10. fc.eval()
  11. x = paddle.randn([1, IMAGE_SIZE], 'float32')
  12. pred = fc(x)

如果你指定了存储的参数文件名,可以按照以下方式载入(接前述示例):

  1. # save with params_filename
  2. model_path = "fc.example.model.with_params_filename"
  3. fluid.io.save_inference_model(
  4. model_path, ["image"], [pred], exe, params_filename="__params__")
  5. # enable dynamic mode
  6. paddle.disable_static(place)
  7. # load
  8. fc = paddle.jit.load(model_path, params_filename="__params__")
  9. # inference
  10. fc.eval()
  11. x = paddle.randn([1, IMAGE_SIZE], 'float32')
  12. pred = fc(x)
  1. 仅载入参数

如果你仅需要从 paddle.fluid.io.save_inference_model 的存储结果中载入参数,以state_dict的形式配置到已有代码的模型中,可以使用 paddle.load 配合 **configs 载入。

如果你是按照 paddle.fluid.io.save_inference_model 的默认格式存储的,可以按照如下方式载入(接前述示例):

  1. model_path = "fc.example.model"
  2. load_param_dict = paddle.load(model_path)

如果你指定了存储的模型文件名,可以按照以下方式载入(接前述示例):

  1. model_path = "fc.example.model.with_model_filename"
  2. load_param_dict = paddle.load(model_path, model_filename="__simplenet__")

如果你指定了存储的参数文件名,可以按照以下方式载入(接前述示例):

  1. model_path = "fc.example.model.with_params_filename"
  2. load_param_dict = paddle.load(model_path, params_filename="__params__")

注解

一般预测模型不会存储优化器Optimizer的参数,因此此处载入的仅包括模型本身的参数。

注解

由于 structured_name 是动态图下独有的变量命名方式,因此从静态图存储结果载入的state_dict在配置到动态图的Layer中时,需要配置 Layer.set_state_dict(use_structured_name=False)

4.2 从 paddle.fluid.save 存储结果中载入参数

paddle.fluid.save 的存储格式与2.x动态图接口 paddle.save 存储格式是类似的,同样存储了dict格式的参数,因此可以直接使用 paddle.load 载入state_dict,但需要注意不能仅传入保存的路径,而要传入保存参数的文件名,示例如下(接前述示例):

  1. # save by fluid.save
  2. model_path = "fc.example.model.save"
  3. program = fluid.default_main_program()
  4. fluid.save(program, model_path)
  5. # enable dynamic mode
  6. paddle.disable_static(place)
  7. load_param_dict = paddle.load("fc.example.model.save.pdparams")

注解

由于 paddle.fluid.save 接口原先在静态图模式下的定位是存储训练时参数,或者说存储Checkpoint,故尽管其同时存储了模型结构,目前也暂不支持从 paddle.fluid.save 的存储结果中同时载入模型和参数,后续如有需求再考虑支持。

4.3 从 paddle.fluid.io.save_params/save_persistables 存储结果中载入参数

这两个接口在飞桨1.x版本时,已经不再推荐作为存储模型参数的接口使用,故并未继承至飞桨2.x,之后也不会再推荐使用这两个接口存储参数。

对于使用这两个接口存储参数兼容载入的支持,分为两种情况,下面以 paddle.fluid.io.save_params 接口为例介绍相关使用方法:

  1. 使用默认方式存储,各参数分散存储为单独的文件,文件名为参数名

这种存储方式仍然可以使用 paddle.load 接口兼容载入,使用示例如下(接前述示例):

  1. # save by fluid.io.save_params
  2. model_path = "fc.example.model.save_params"
  3. fluid.io.save_params(exe, model_path)
  4. # load
  5. state_dict = paddle.load(model_path)
  6. print(state_dict)
  1. 指定了参数存储的文件,将所有参数存储至单个文件中

将所有参数存储至单个文件中会导致存储结果中丢失Tensor名和Tensor数据之间的映射关系,因此这部分丢失的信息需要用户传入进行补足。为了确保正确性,这里不仅要传入Tensor的name列表,同时要传入Tensor的shape和dtype等描述信息,通过检查和存储数据的匹配性确保严格的正确性,这导致载入数据的恢复过程变得比较复杂,仍然需要一些飞桨1.x的概念支持。后续如果此项需求较为普遍,飞桨将会考虑将该项功能兼容支持到 paddle.load 中,但由于信息丢失而导致的使用复杂性仍然是存在的,因此建议你避免仅使用这两个接口存储参数。

目前暂时推荐你使用 paddle.static.load_program_state 接口解决此处的载入问题,需要获取原Program中的参数列表传入该方法,使用示例如下(接前述示例):

  1. # save by fluid.io.save_params
  2. model_path = "fc.example.model.save_params_with_filename"
  3. fluid.io.save_params(exe, model_path, filename="__params__")
  4. # load
  5. import os
  6. params_file_path = os.path.join(model_path, "__params__")
  7. var_list = fluid.default_main_program().all_parameters()
  8. state_dict = paddle.io.load_program_state(params_file_path, var_list)