paddelpaddel
模型训练、评估与推理
1、训练前准备
1.1 指定训练的硬件
模型训练时,需要用到 CPU、 GPU 等计算处理器资源,由于飞桨框架的安装包是区分处理器类型的,默认情况下飞桨框架会根据所安装的版本自动选择对应硬件,比如安装的 GPU 版本的飞桨,则自动使用 GPU 训练模型,无需手动指定。因此一般情况下,无需执行此步骤。
但是如果安装的 GPU 版本的飞桨框架,想切换到 CPU 上训练,则可通过 paddle.device.set_device 修改。如果本机有多个 GPU 卡,也可以通过该 API 选择指定的卡进行训练,不指定的情况下则默认使用 ‘gpu:0’。
import paddle
# 指定在 CPU 上训练
paddle.device.set_device('cpu')
# 指定在 GPU 第 0 号卡上训练
paddle.device.set_device('gpu:0')
1.2 准备训练用的数据集和模型
模型训练前,需要先完成数据集的加载和模型组网,以 MNIST 手写数字识别任务为例,代码示例如下:
from paddle.vision.transforms import Normalize
transform = Normalize(mean=[127.5], std=[127.5], data_format='CHW')
# 加载 MNIST 训练集和测试集
train_dataset = paddle.vision.datasets.MNIST(mode='train', transform=transform)
test_dataset = paddle.vision.datasets.MNIST(mode='test', transform=transform)
# 模型组网,构建并初始化一个模型 mnist
mnist = paddle.nn.Sequential(
paddle.nn.Flatten(1, -1),
paddle.nn.Linear(784, 512),
paddle.nn.ReLU(),
paddle.nn.Dropout(0.2),
paddle.nn.Linear(512, 10)
)
2、使用 paddle.Model 高层 API 训练、评估与推理
2.1 使用 paddle.Model 封装模型
# 封装模型为一个 model 实例,便于进行后续的训练、评估和推理
model = paddle.Model(mnist)
2.2 使用 Model.prepare 配置训练准备参数
用 paddle.Model 完成模型的封装后,需通过 Model.prepare 进行训练前的配置准备工作,包括设置优化算法、Loss 计算方法、评价指标计算方法:
- 优化器(optimizer):即寻找最优解的方法,可计算和更新梯度,并根据梯度更新模型参数。飞桨框架在 paddle.optimizer 下提供了优化器相关 API。并且需要为优化器设置合适的学习率,或者指定合适的学习率策略,飞桨框架在 paddle.optimizer.lr 下提供了学习率策略相关的 API。
- 损失函数(loss):用于评估模型的预测值和真实值的差距,模型训练过程即取得尽可能小的 loss 的过程。飞桨框架在 paddle.nn Loss层 提供了适用不同深度学习任务的损失函数相关 API。
- 评价指标(metrics):用于评估模型的好坏,不同的任务通常有不同的评价指标。飞桨框架在 paddle.metric 下提供了评价指标相关 API。
# 为模型训练做准备,设置优化器及其学习率,并将网络的参数传入优化器,设置损失函数和精度计算方式
model.prepare(optimizer = paddle.optimizer.Adam(learning_rate = 0.001,parameters = model.parameters()),
loss = paddle.nn.CrossEntropyLoss(),
metrics = paddle.metric.Accuracy())
2.3 使用 Model.fit 训练模型
- 训练数据集:传入之前定义好的训练数据集。
- 训练轮次(epoch):训练时遍历数据集的次数,即外循环轮次。
- 批次大小(batch_size):内循环中每个批次的训练样本数。
# 启动模型训练,指定训练数据集,设置训练轮次,设置每次数据集计算的批次大小,设置日志格式
model.fit(train_dataset,
epochs=5,
batch_size=64,
verbose=1)
2.4 使用 Model.evaluate 评估模型
使用 Model.evaluate 接口完成模型评估操作,结束后根据在 Model.prepare 中定义的 loss 和 metric 计算并返回相关评估结果。
返回格式是一个字典:
- 只包含loss,
{'loss': xxx} - 包含loss和一个评估指标,
{'loss': xxx, 'metric name': xxx} - 包含loss和多个评估指标,
{'loss': xxx, 'metric name1': xxx, 'metric name2': xxx}
# 用 evaluate 在测试集上对模型进行验证
eval_result = model.evaluate(test_dataset, verbose=1)
print(eval_result)
2.5 使用 Model.predict 执行推理
只需传入待执行推理验证的样本数据,即可计算并返回推理结果。
返回格式是一个列表:
- 模型是单一输出:
[(numpy_ndarray_1, numpy_ndarray_2, …, numpy_ndarray_n)] - 模型是多输出:
[(numpy_ndarray_1, numpy_ndarray_2, …, numpy_ndarray_n), (numpy_ndarray_1, numpy_ndarray_2, …, numpy_ndarray_n), …]
# 用 predict 在测试集上对模型进行推理
test_result = model.predict(test_dataset)
print(test_result[0][0])
# 打印推理结果,这里的argmax函数用于取出预测值中概率最高的一个的下标,作为预测标签
pred_label = test_result[0][0].argmax()
print('true label: {}, pred label: {}'.format(label[0], pred_label))
[[ -6.512169 -6.7076845 0.5048795 1.6733919 -9.670526 -1.6352568
-15.833721 13.87411 -8.215239 1.5966017]]
true label: 7, pred label: 7
经过模型的计算得到一个数组:
[[ -6.5593615 -6.4680595 -1.4708003 2.1043894 -11.743436 -4.4516582 -14.733968 12.036645 -6.582403 -1.8672216]]
取其中最大的值(12.036645)的下标(对应 label 7),即得到该样本数据的预测结果(pred label: 7),可视化该样本图像(true label: 7),与预测结果一致,说明模型准确预测了样本图像上的数字。
3、使用基础 API 训练、评估与推理
3.1 模型训练(拆解 Model.prepare、Model.fit)
飞桨框架通过基础 API 对模型进行训练,对应高层 API 的 Model.prepare 与 Model.fit ,一般包括如下几个步骤:
import paddle
import paddle.nn as nn
import paddle.optimizer as optim
from paddle.vision.datasets import DatasetFolder
from paddle.io import DataLoader
# 1. Load training dataset, declare model, and set model instance to `train` mode
class MyDataset(DatasetFolder):
def __init__(self, data_path, transform=None):
super(MyDataset, self).__init__(data_path, transform=transform)
dataset = MyDataset('path/to/dataset', transform=None)
data_loader = DataLoader(dataset, batch_size=32, shuffle=True)
class MyModel(nn.Layer):
def __init__(self):
super(MyModel, self).__init__()
# define model architecture here
model = MyModel()
model.train()
# 2. Set optimizer, loss function, and hyperparameters
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), learning_rate=0.001)
# 3. Set up two-layer nested loop for training
for epoch in range(10): # outer loop for epochs
for batch in data_loader: # inner loop for batches
# 3.1 Get a batch of training data from DataLoader
inputs, labels = batch
# 3.2 Execute a forward pass to get predicted values
outputs = model(inputs)
# 3.3 Calculate loss between predicted values and labels
loss = criterion(outputs, labels)
# 3.4 Calculate accuracy between predicted values and labels
accuracy = paddle.metric.accuracy(outputs, labels)
# 3.5 Backward pass to compute gradients
loss.backward()
# 3.6 Print model's epoch, batch, loss, and accuracy
print(f'Epoch: {epoch}, Batch: {batch}, Loss: {loss.item():.4f}, Accuracy: {accuracy.item():.4f}')
# 3.7 Update model parameters using optimizer
optimizer.step()
# 3.8 Clear gradients for next batch
optimizer.clear_grad()
3.2 模型评估(拆解 Model.evaluate)
飞桨框架通过基础 API 对训练好的模型进行评估,对应高层 API 的 Model.evaluate 。与模型训练相比,模型评估的流程有如下几点不同之处:
- 加载的数据从训练数据集改为测试数据集
- 模型实例从
train模式改为eval模式 - 不需要反向传播、优化器参数更新和优化器梯度清零
# 加载测试数据集
test_loader = paddle.io.DataLoader(test_dataset, batch_size=64, drop_last=True)
# 设置损失函数
loss_fn = paddle.nn.CrossEntropyLoss()
# 将该模型及其所有子层设置为预测模式。这只会影响某些模块,如Dropout和BatchNorm
mnist.eval()
# 禁用动态图梯度计算
for batch_id, data in enumerate(test_loader()):
x_data = data[0] # 测试数据
y_data = data[1] # 测试数据标签
predicts = mnist(x_data) # 预测结果
# 计算损失与精度
loss = loss_fn(predicts, y_data)
acc = paddle.metric.accuracy(predicts, y_data)
# 打印信息
if (batch_id+1) % 30 == 0:
print("batch_id: {}, loss is: {}, acc is: {}".format(batch_id+1, loss.numpy(), acc.numpy()))
3.3 模型推理(拆解 Model.predict)
飞桨框架通过基础 API 对训练好的模型执行推理,对应高层 API 的 Model.predict 。模型的推理过程相对独立,是在模型训练与评估之后单独进行的步骤。只需要执行如下步骤:
- 加载待执行推理的测试数据,并将模型设置为
eval模式 - 读取测试数据并获得预测结果
- 对预测结果进行后处理
# 加载测试数据集
test_loader = paddle.io.DataLoader(test_dataset, batch_size=64, drop_last=True)
# 将该模型及其所有子层设置为预测模式
mnist.eval()
for batch_id, data in enumerate(test_loader()):
# 取出测试数据
x_data = data[0]
# 获取预测结果
predicts = mnist(x_data)
print("predict finished")
# 从测试集中取出一组数据
img, label = test_loader().next()
# 执行推理并打印结果
pred_label = mnist(img)[0].argmax()
print('true label: {}, pred label: {}'.format(label[0].item(), pred_label[0].item()))
# 可视化图片
from matplotlib import pyplot as plt
plt.imshow(img[0][0])
模型保存练与加载
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)。
1.2 使用高层API
保存动态图模型
- 方式一:开启训练时调用的
paddle.Model.fit函数可自动保存模型,通过它的参数save_freq可以设置保存动态图模型的频率,即多少个 epoch 保存一次模型,默认值是 1。 - 方式二:调用
paddle.Model.saveAPI。只需要传入保存的模型文件的前缀,格式如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
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)
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)