2.9 手写数字识别之恢复训练

简介: 这篇文章介绍了如何在飞桨(PaddlePaddle)框架中实现手写数字识别模型的恢复训练,包括保存和加载模型参数以及优化器状态,确保训练过程在中断后能够从上次保存的状态继续进行。

2.9 手写数字识别之恢复训练

在上一节中,我们已经介绍了将训练好的模型保存到磁盘文件的方法。应用程序可以随时加载模型,完成预测任务。但是在日常训练工作中我们会遇到一些突发情况,导致训练过程主动或被动的中断。如果训练一个模型需要花费几天的训练时间,中断后从初始状态重新训练是不可接受的。万幸的是,飞桨支持从上一次保存状态开始训练,只要我们随时保存训练过程中的模型状态,就不用从初始状态重新训练。

2.9.1 构建并训练网络

下面介绍恢复训练的实现方法,依然使用手写数字识别的案例,网络定义的部分保持不变,直接调用封装好的代码。

In [1]

import paddle
import os
from data_process import get_MNIST_dataloader
from MNIST_network import MNIST
import paddle.nn.functional as F
train_loader, test_loader = get_MNIST_dataloader()

定义训练Trainer,包含训练过程和模型保存

In [2]

class Trainer(object):
    def __init__(self, model_path, model, optimizer):
        self.model_path = model_path   # 模型存放路径 
        self.model = model             # 定义的模型
        self.optimizer = optimizer     # 优化器
    def save(self):
        # 保存模型
        paddle.save(self.model.state_dict(), self.model_path)
    def train_step(self, data):
        images, labels = data
        # 前向计算的过程
        predicts = self.model(images)
        # 计算损失
        loss = F.cross_entropy(predicts, labels)
        avg_loss = paddle.mean(loss)
        # 后向传播,更新参数的过程
        avg_loss.backward()
        self.optimizer.step()
        self.optimizer.clear_grad()
        return avg_loss
    def train_epoch(self, datasets, epoch):
        self.model.train()
        for batch_id, data in enumerate(datasets()):
            loss = self.train_step(data)
            # 每训练了1000批次的数据,打印下当前Loss的情况
            if batch_id % 500 == 0:
                print("epoch_id: {}, batch_id: {}, loss is: {}".format(epoch, batch_id, loss.numpy()))
    def train(self, train_datasets, start_epoch, end_epoch, save_path):
        if not os.path.exists(save_path):
            os.makedirs(save_path)
        for i in range(start_epoch, end_epoch):
            self.train_epoch(train_datasets, i)
            paddle.save(self.optimizer.state_dict(), './{}/mnist_epoch{}'.format(save_path,i)+'.pdopt')
            paddle.save(self.model.state_dict(), './{}/mnist_epoch{}'.format(save_path,i)+'.pdparams')
        self.save()

在开始介绍使用飞桨恢复训练前,先正常训练一个模型,优化器使用Adam,使用动态变化的学习率,学习率从0.01衰减到0.001。每训练一轮后保存一次模型,之后将采用其中某一轮的模型参数进行恢复训练,验证一次性训练和中断再恢复训练的模型表现是否相差不多(训练loss的变化)。

注意进行恢复训练的程序不仅要保存模型参数,还要保存优化器参数。这是因为某些优化器含有一些随着训练过程变换的参数,例如Adam、Adagrad等优化器采用可变学习率的策略,随着训练进行会逐渐减少学习率。这些优化器的参数对于恢复训练至关重要

为了演示这个特性,训练程序使用Adam优化器,PolynomialDecay的变化策略将学习率从0.01衰减到0.001。

classpaddle.optimizer.lr.PolynomialDecay (learningrate, decaysteps, endlr=0.0001, power=1.0, cycle=False, lastepoch=- 1, verbose=False)

参数说明如下:

  • learning_rate (float):初始学习率,数据类型为Python float。
  • decay_steps (int):进行衰减的步长,这个决定了衰减周期。
  • end_lr (float,可选):最小的最终学习率,默认值为0.0001。
  • power (float,可选):多项式的幂,默认值为1.0。
  • last_epoch (int,可选):上一轮的轮数,重启训练时设置为上一轮的epoch数。默认值为-1,则为初始学习率。
  • verbose (bool,可选):如果是 True,则在每一轮更新时在标准输出stdout输出一条信息,默认值为False。
  • cycle (bool,可选):学习率下降后是否重新上升。若为True,则学习率衰减到最低学习率值时会重新上升。若为False,则学习率单调递减。默认值为False。

PolynomialDecay的变化曲线下图所示:

In [ ]

#在使用GPU机器时,可以将use_gpu变量设置成True
use_gpu = True
paddle.set_device('gpu:0') if use_gpu else paddle.set_device('cpu')
paddle.seed(1024)
epochs = 3
BATCH_SIZE = 32
model_path = './mnist.pdparams'
model = MNIST()
total_steps = (int(50000//BATCH_SIZE) + 1) * epochs
lr = paddle.optimizer.lr.PolynomialDecay(learning_rate=0.01, decay_steps=total_steps, end_lr=0.001)
optimizer = paddle.optimizer.Adam(learning_rate=lr, parameters=model.parameters())
trainer = Trainer(
    model_path=model_path,
    model=model,
    optimizer=optimizer
)
trainer.train(train_datasets=train_loader, start_epoch = 0, end_epoch = epochs, save_path='checkpoint')

W0901 17:29:43.145830 192 device_context.cc:447] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 10.1

W0901 17:29:43.152843 192 device_context.cc:465] device: 0, cuDNN Version: 7.6.

epoch_id: 0, batch_id: 0, loss is: [3.900196]

epoch_id: 0, batch_id: 500, loss is: [0.1180672]

epoch_id: 1, batch_id: 0, loss is: [0.05891193]

epoch_id: 1, batch_id: 500, loss is: [0.16080402]

epoch_id: 2, batch_id: 0, loss is: [0.05444674]

epoch_id: 2, batch_id: 500, loss is: [0.02639692]

2.9.2 恢复训练

模型恢复训练,需要重新组网,所以我们需要重启AI Studio,运行数据处理和 MNIST网络定义、 Trainer部分代码,再执行模型恢复代码。

在上述训练代码中,我们训练了3轮(epoch)。在每轮结束时,我们均保存了模型参数和优化器相关的参数。

  • 使用 model.state_dict() 获取模型参数。
  • 使用 opt.state_dict 获取优化器和学习率相关的参数。
  • 调用 paddle.save 将参数保存到本地。

比如第一轮训练保存的文件是mnist_epoch0.pdparams,mnist_epoch0.pdopt,分别存储了模型参数和优化器参数。

使用paddle.load分别加载模型参数和优化器参数,如下代码所示。

paddle.load(params_path+'.pdparams')
paddle.load(params_path+'.pdopt')

如何判断模型是否准确的恢复训练呢?

理想的恢复训练是模型状态回到训练中断的时刻,恢复训练之后的梯度更新走向和恢复训练前的梯度走向完全相同的。基于此,我们可以通过恢复训练后的损失变化,判断上述方法是否能准确的恢复训练。即从epoch 0结束时保存的模型参数和优化器状态恢复训练,校验其后训练的损失变化(epoch 1)是否和不中断时的训练相差不多。


说明:

恢复训练有如下两个要点:

  • 保存模型时分别保存模型参数和优化器参数。
  • 恢复参数时分别恢复模型参数和优化器参数。

下面的代码将展示恢复训练的过程,并验证恢复训练是否成功。加载模型参数并从第一个epoch开始训练,以便读者可以校验恢复训练后的损失变化。

In [3]

# MLP继续训练
paddle.seed(1024)
epochs = 3
BATCH_SIZE = 32
model_path = './mnist_retrain.pdparams'
model = MNIST()
# lr = 0.01
total_steps = (int(50000//BATCH_SIZE) + 1) * epochs
lr = paddle.optimizer.lr.PolynomialDecay(learning_rate=0.01, decay_steps=total_steps, end_lr=0.001)
optimizer = paddle.optimizer.Adam(learning_rate=lr, parameters=model.parameters())
params_dict = paddle.load('./checkpoint/mnist_epoch0.pdparams')
opt_dict = paddle.load('./checkpoint/mnist_epoch0.pdopt')
# 加载参数到模型
model.set_state_dict(params_dict)
optimizer.set_state_dict(opt_dict)
trainer = Trainer(
    model_path=model_path,
    model=model,
    optimizer=optimizer
)
# 前面训练模型都保存了,这里save_path设置为新路径,实际训练中保存在同一目录就可以
trainer.train(train_datasets=train_loader,start_epoch = 1, end_epoch = epochs, save_path='checkpoint_con')

W0901 17:31:41.696164 509 device_context.cc:447] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 10.1

W0901 17:31:41.700848 509 device_context.cc:465] device: 0, cuDNN Version: 7.6.

epoch_id: 1, batch_id: 0, loss is: [0.03602091]

epoch_id: 1, batch_id: 500, loss is: [0.4263561]

epoch_id: 2, batch_id: 0, loss is: [0.0733113]

epoch_id: 2, batch_id: 500, loss is: [0.13029066]

我们可以将模型参数和优化器参数分别保存,也可以同时保存,具体见下面的例子:

In [4]

import paddle
from paddle import nn
from paddle.optimizer import Adam
layer = paddle.nn.Linear(3, 4)
adam = Adam(learning_rate=0.001, parameters=layer.parameters())
obj = {'model': layer.state_dict(), 'opt': adam.state_dict(), 'epoch': 100}
path = 'example/model.pdparams'
paddle.save(obj, path)

从恢复训练的损失变化来看,加载模型参数继续训练的损失函数值和正常训练损失函数值是一致,可见使用飞桨实现恢复训练是极其简单的。 总结一下:

  • 保存模型时同时保存模型参数和优化器参数;
paddle.save(opt.state_dict(), 'model.pdopt')
paddle.save(model.state_dict(), 'model.pdparams')
  • 恢复参数时同时恢复模型参数和优化器参数。
model_dict = paddle.load("model.pdparams")
opt_dict = paddle.load("model.pdopt")
model.set_state_dict(model_dict)
opt.set_state_dict(opt_dict)
相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
9天前
|
机器学习/深度学习 PyTorch 算法框架/工具
目标检测实战(一):CIFAR10结合神经网络加载、训练、测试完整步骤
这篇文章介绍了如何使用PyTorch框架,结合CIFAR-10数据集,通过定义神经网络、损失函数和优化器,进行模型的训练和测试。
29 2
目标检测实战(一):CIFAR10结合神经网络加载、训练、测试完整步骤
|
5月前
|
机器学习/深度学习 PyTorch 算法框架/工具
【PyTorch实战演练】使用Cifar10数据集训练LeNet5网络并实现图像分类(附代码)
【PyTorch实战演练】使用Cifar10数据集训练LeNet5网络并实现图像分类(附代码)
377 0
|
8天前
|
数据可视化 计算机视觉
训练数据集(一):真实场景下采集的煤矸石目标检测数据集,可直接用于YOLOv5/v6/v7/v8训练
本文介绍了一个用于煤炭与矸石分类的煤矸石目标检测数据集,包含891张训练图片和404张验证图片,分为煤炭、矸石和混合物三类。数据集已标注并划分为训练和验证集,适用于YOLOv5/v6/v7/v8训练。数据集可通过提供的链接下载。
18 1
训练数据集(一):真实场景下采集的煤矸石目标检测数据集,可直接用于YOLOv5/v6/v7/v8训练
|
2月前
|
数据采集 人工智能 小程序
如何制作数据集并基于yolov5训练成模型并部署
这篇文章介绍了如何为YOLOv5制作数据集、训练模型、进行模型部署的整个流程,包括搜集和标注图片、创建数据集文件夹结构、编写配置文件、训练和评估模型,以及将训练好的模型部署到不同平台如ROS机器人、微信小程序和移动应用等。
如何制作数据集并基于yolov5训练成模型并部署
|
1月前
|
人工智能 自动驾驶 数据库
领域大模型的训练需要什么数据?
领域大模型的训练需要什么数据?
76 0
|
5月前
|
机器学习/深度学习 算法 Serverless
YoLo_V4模型训练过程
YoLo_V4模型训练过程
80 0
|
机器学习/深度学习 数据采集 监控
五、神经网络过拟合处理方法(二):手写数字识别(Mnist)数据集
五、神经网络过拟合处理方法(二):手写数字识别(Mnist)数据集
156 0
|
机器学习/深度学习 监控 算法
基于深度学习的跌倒检测系统(UI界面+YOLOv5+训练数据集)
基于深度学习的跌倒检测系统(UI界面+YOLOv5+训练数据集)
1461 0