模型训练后,训练好的模型参数保存在内存中,通常需要使用模型保存(save)功能将其持久化保存到磁盘文件中,并在后续需要训练调优或推理部署时,再加载(load)到内存中运行。本章详细介绍不同场景下模型保存与加载的方法。
1、训练调优场景
1.1 使用基础API
-
paddle.save:使用
paddle.save
保存模型,实际是通过 Python pickle 模块来实现的,传入要保存的数据对象后,会在指定路径下生成一个 pickle 格式的磁盘文件。 -
paddle.load:加载时还需要之前的模型组网代码,并使用
paddle.load
传入保存的文件路径,即可重新将之前保存的数据从磁盘文件中载入。
另外,paddle.save
还支持直接保存 Tensor 数据,或者含 Tensor 的 list/dict 嵌套结构。所以动态图模式下,可支持保存和加载的内容包括:
-
网络层参数:
Layer.state_dict()
-
优化器参数:
Optimizer.state_dict()
-
Tensor 数据 :(如创建的 Tensor 数据、网络层的 weight 数据等)
-
含 Tensor 的 list/dict 嵌套结构对象 (如保存 state_dict() 的嵌套结构对象:
obj = {'model': layer.state_dict(), 'opt': adam.state_dict(), 'epoch': 100}
)
保存动态图模型
# 保存Layer参数
paddle.save(layer.state_dict(), "linear_net.pdparams")
# 保存优化器参数
paddle.save(adam.state_dict(), "adam.pdopt")
# 保存检查点checkpoint信息
paddle.save(final_checkpoint, "final_checkpoint.pkl")
对于
Layer.state_dict()
(模型参数),推荐使用后缀.pdparams
;对于
Optimizer.state_dict()
(优化器参数),推荐使用后缀.pdopt
。
加载动态图模型
# 载入模型参数、优化器参数和最后一个epoch保存的检查点
layer_state_dict = paddle.load("linear_net.pdparams")
opt_state_dict = paddle.load("adam.pdopt")
final_checkpoint_dict = paddle.load("final_checkpoint.pkl")
# 将load后的参数与模型关联起来
layer.set_state_dict(layer_state_dict)
adam.set_state_dict(opt_state_dict)
# 打印出来之前保存的 checkpoint 信息
print("Loaded Final Checkpoint. Epoch : {}, Loss : {}".format(final_checkpoint_dict["epoch"], final_checkpoint_dict["loss"].numpy()))
加载以后就可以继续对动态图模型进行训练调优(fine-tune),或者验证预测效果(predict)。
完整代码
注:在参数保存(三)完成后,可以直接将其注释掉,进行参数加载(四)
import numpy as np
import paddle
import paddle.nn as nn
import paddle.optimizer as opt
BATCH_SIZE = 16
BATCH_NUM = 4
EPOCH_NUM = 4
IMAGE_SIZE = 784
CLASS_NUM = 10
final_checkpoint = dict()
# 定义一个随机数据集
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().__init__()
self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)
def forward(self, x):
return self._linear(x)
def train(layer, loader, loss_fn, opt):
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()
opt.step()
opt.clear_grad()
print("Epoch {} batch {}: loss = {}".format(
epoch_id, batch_id, np.mean(loss.numpy())))
# 最后一个epoch保存检查点checkpoint
if epoch_id == EPOCH_NUM - 1:
final_checkpoint["epoch"] = epoch_id
final_checkpoint["loss"] = loss
if __name__ == '__main__':
####################!!一!!##############################
#####################数据准备##############################
# 创建网络、loss和优化器
layer = LinearNet()
loss_fn = nn.CrossEntropyLoss()
adam = opt.Adam(learning_rate=0.001, parameters=layer.parameters())
# 创建用于载入数据的DataLoader
dataset = RandomDataset(BATCH_NUM * BATCH_SIZE)
loader = paddle.io.DataLoader(dataset,
batch_size=BATCH_SIZE,
shuffle=True,
drop_last=True,
num_workers=2)
####################!!二!!##############################
#####################模型训练##############################
# 开始训练
train(layer, loader, loss_fn, adam)
####################!!三!!##############################
#####################参数保存##############################
# 保存Layer参数
paddle.save(layer.state_dict(), "linear_net.pdparams")
# 保存优化器参数
paddle.save(adam.state_dict(), "adam.pdopt")
# 保存检查点checkpoint信息
paddle.save(final_checkpoint, "final_checkpoint.pkl")
####################!!四!!##############################
#####################参数加载##############################
# 载入模型参数、优化器参数和最后一个epoch保存的检查点
layer_state_dict = paddle.load("linear_net.pdparams")
opt_state_dict = paddle.load("adam.pdopt")
final_checkpoint_dict = paddle.load("final_checkpoint.pkl")
# 将load后的参数与模型关联起来
layer.set_state_dict(layer_state_dict)
adam.set_state_dict(opt_state_dict)
###########################################################
###########################################################
1.2 使用高层API
保存动态图模型
-
方式一:开启训练时调用的
paddle.Model.fit
函数可自动保存模型,通过它的参数save_freq
可以设置保存动态图模型的频率,即多少个 epoch 保存一次模型,默认值是 1。 -
方式二:调用
paddle.Model.save
API。只需要传入保存的模型文件的前缀,格式如dirname/file_prefix
或者file_prefix
,即可保存训练后的模型参数和优化器参数,保存后的文件后缀名固定为.pdparams
和.pdopt
。
#方式一:设置训练过程中保存模型
model.fit(data, epochs=1, batch_size=32, save_freq=1)
#方式二:设置训练后保存模型
model.save('checkpoint/test') # save for training
加载动态图模型
高层 API 加载动态图模型所需要调用的 API 是 paddle.Model.load
,从指定的文件中载入模型参数和优化器参数(可选)以继续训练。paddle.Model.load
需要传入的核心的参数是待加载的模型参数或者优化器参数文件(可选)的前缀(需要保证后缀符合 .pdparams
和.pdopt
)。
# 加载模型参数和优化器参数
model.load('checkpoint/test')
model.fit(data, epochs=1, batch_size=32, save_freq=1)
model.save('checkpoint/test_1') # save for training
完整代码
同上,Model.save或Model.fit后可以将其注释掉,直接
Model.load
import paddle
from paddle.vision.transforms import Normalize
##################!!一!!#################################
##################数据准备##################################
transform = Normalize(mean=[127.5], std=[127.5], data_format='CHW')
# 下载数据集并初始化 DataSet
train_dataset = paddle.vision.datasets.MNIST(mode='train', transform=transform)
test_dataset = paddle.vision.datasets.MNIST(mode='test', transform=transform)
# 模型组网并初始化网络
lenet = paddle.vision.models.LeNet(num_classes=10)
model = paddle.Model(lenet)
##################!!二!!#################################
##################模型训练##################################
# 模型训练的配置准备,准备损失函数,优化器和评价指标
model.prepare(paddle.optimizer.Adam(parameters=model.parameters()),
paddle.nn.CrossEntropyLoss(),
paddle.metric.Accuracy())
# 模型训练
model.fit(train_dataset, epochs=5, batch_size=64, verbose=1)
# 模型评估
model.evaluate(test_dataset, batch_size=64, verbose=1)
##################!!三!!#################################
##################保存模型##################################
# 保存模型
model.save('./output/mnist')
##################!!四!!#################################
##################加载模型##################################
#加载模型,调优参数
model.load('output/mnist')
model.fit(test_dataset, epochs=1, batch_size=32, save_freq=1)
model.save('checkpoint/test_1') # save for training
2、 推理部署场景
2.1 使用基础API
paddle.jit.save
保存模型paddle.jit.load
加载模型
使用 paddle.jit.save
保存模型,通常是在后台执行了两个步骤:
-
先执行了动转静。当然如果前面已经执行了动转静训练,则跳过这一步。在处理逻辑上,主要包含两个主要模块:
-
模型结构层面:将动态图模型中被
@paddle.jit.to_static
装饰的函数转化为完整的静态图 Program。 -
模型参数层面:将动态图模型中的参数(Parameters 和 Buffers )转为
Persistable=True
的静态图模型参数 Variable。
-
-
再将静态图模型和参数导出为磁盘文件。Program 和 Variable 都可以直接序列化导出为磁盘文件,与前端代码完全解耦,导出的文件包括:
-
后缀为
.pdmodel
的模型结构文件; -
后缀为
.pdiparams
的模型参数文件; -
后缀为
.pdiparams.info
的和参数状态有关的额外信息文件。
-
动转静
方式一:使用 @paddle.jit.to_static
装饰器
class LinearNet(nn.Layer):
def __init__(self):
super().__init__()
self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)
@paddle.jit.to_static # <----在前向计算 forward 函数前添加一个装饰器
def forward(self, x):
return self._linear(x)
方式二:使用 @paddle.jit.to_static
函数
# create network
layer = LinearNet()
layer = paddle.jit.to_static(layer) # <----通过函数式调用 paddle.jit.to_static(layer) 一键实现动转静
模型保存样例
动转静训练完成后,使用 paddle.jit.save
对模型和参数进行存储:
# 如果保存模型用于推理部署,则需切换 eval()模式
layer.eval()
# 使用 paddle.jit.save 保存训练好的静态图模型
path = "example.model/linear"
paddle.jit.save(layer, path)
模型加载样例
动转静训练保存模型后,如果需要再加载用于训练调优或验证推理效果,可以选择使用 paddle.jit.load 或 paddle.load API。
-
使用
paddle.jit.load
加载:该方式可以载入模型结构和参数,传入数据即可训练或推理。 -
使用
paddle.load
加载:如果已有组网代码,则只传入模型参数也可再训练,因此也可以选择该方式加载。
# 载入 paddle.jit.save 保存的模型
path = "example.model/linear"
loaded_layer = paddle.jit.load(path)
# 执行预测
loaded_layer.eval()
x = paddle.randn([1, IMAGE_SIZE], 'float32')
pred = loaded_layer(x)
完整代码
注意同上。
import numpy as np
import paddle
import paddle.nn as nn
import paddle.optimizer as opt
BATCH_SIZE = 16
BATCH_NUM = 4
EPOCH_NUM = 4
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, )).astype('int64')
return image, label
def __len__(self):
return self.num_samples
class LinearNet(nn.Layer):
def __init__(self):
super().__init__()
self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)
def forward(self, x):
return self._linear(x)
def train(layer, loader, loss_fn, opt):
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()
opt.step()
opt.clear_grad()
print("Epoch {} batch {}: loss = {}".format(
epoch_id, batch_id, np.mean(loss.numpy())))
if __name__ == '__main__':
#####################!!一!!#######################################
#####################定义模型#######################################
# 定义模型
layer = LinearNet()
layer = paddle.jit.to_static(layer) # <----通过函数式调用 paddle.jit.to_static(layer) 一键实现动转静
loss_fn = nn.CrossEntropyLoss()
adam = opt.Adam(learning_rate=0.001, parameters=layer.parameters())
#####################!!二!!#######################################
#####################数据加载#######################################
# 创建数据集
dataset = RandomDataset(BATCH_NUM * BATCH_SIZE)
loader = paddle.io.DataLoader(dataset,
batch_size=BATCH_SIZE,
shuffle=True,
drop_last=True,
num_workers=2)
#####################!!三!!#######################################
#####################模型训练#######################################
# 模型训练
train(layer, loader, loss_fn, adam)
#####################!!四!!#######################################
#####################参数保存#######################################
# 如果保存模型用于推理部署,则需切换 eval()模式
layer.eval()
# 使用 paddle.jit.save 保存训练好的静态图模型
path = "example.model/linear"
paddle.jit.save(layer, path)
#####################!!五!!#######################################
#####################参数加载#######################################
# 载入 paddle.jit.save 保存的模型
path = "example.model/linear"
loaded_layer = paddle.jit.load(path)
#####################!!六!!#######################################
#####################执行推理#######################################
# 执行预测
loaded_layer.eval()
x = paddle.randn([1, IMAGE_SIZE], 'float32')
pred = loaded_layer(x)
print(pred)
2.2 使用高层API
paddle.Model.save
的第一个参数需要设置为待保存的模型和参数等文件的前缀名,第二个参数 training
表示是否保存动态图模型以继续训练,默认是 True,这里需要设为 False,即保存推理部署所需的参数与文件。接前文高层 API 训练的示例代码,保存推理模型代码示例如下:
model.save('inference_model', False) # save for inference
其他同训练调优场景
# 加载模型参数和优化器参数
model.load('checkpoint/test')
test_result = model.predict(test_dataset)