训练框架
文章目录
- 训练框架
- 1. 模型网络结构
- 2. 数据读取与数据加载
- 2.1Dataloater参数
- 2.2 collate_fn
- 3. 优化器与学习率调整
- 3.1 优化器
- 3.2 学习率调度
- 4迭代训练
- 4.1 train_epoch
- 4.2 train iteration
- 5.1 保存模型权重
本文内容以pytorch为例
1. 模型网络结构
自定义网络模型继承‘nn.Module’,实现模型的参数的初始化与前向传播;自定义网络模型可以添加权重初始化、网络模块组合等其他方法
import torch.nn as nnimport torch.nn.functional as Fclass Model(nn.Module):def __init__(self):super().__init__()self.conv1 = nn.Conv2d(1, 20, 5)self.conv2 = nn.Conv2d(20, 20, 5)def forward(self, x):x = F.relu(self.conv1(x))return F.relu(self.conv2(x))
2. 数据读取与数据加载
数据集类的基础方法
- dataset:
需要包含数据迭代方法
def __getitem__(self, index):image, target = self.list(index)return image, target
利用torch.utils.data.DataLoader封装后,用于迭代遍历数据元素;
数据长度方法
def __len__(self):return self.dataset_size
数据加载
- dataloader:
对数据集类(通常实现了 getitem 和 len 方法)时,
你可以使用 DataLoader 来轻松地进行批量加载、打乱数据、并行加载以及多进程数据加载。
collate_fn:将字典或数组数据流进行拆分,拆分为图像、label、边界框、文字编码等不同类型数据与模型的输入与输出相匹配
2.1Dataloater参数
参数:
- dataset (Dataset): 加载数据的数据集。
- batch_size (int, 可选): 每批加载的样本数量(默认:1)。
- shuffle (bool, 可选): 设置为 True 以在每个 epoch 重新洗牌数据(默认:False)。
- sampler (Sampler 或 Iterable, 可选): 定义从数据集中抽取样本的策略。可以是任何实现了 len 的 Iterable。如果指定了 sampler,则不能指定 :attr:shuffle。
- batch_sampler (Sampler 或 Iterable, 可选): 与 :attr:sampler 类似,但一次返回一批索引。与 :attr:batch_size, :attr:shuffle, :attr:sampler, 和 :attr:drop_last 互斥。
num_workers (int, 可选): 数据加载使用的子进程数量。0 表示数据将在主进程中加载(默认:0)。 - collate_fn (Callable, 可选): 将样本列表合并以形成 Tensor(s) 的 mini-batch。在使用 map-style 数据集的批量加载时使用。
- pin_memory (bool, 可选): 如果设置为 True,则数据加载器将在返回它们之前将 Tensors 复制到设备/CUDA 固定内存中。如果你的数据元素是自定义类型,或者你的 :attr:collate_fn 返回的批次是自定义类型,请参见下面的例子。
- drop_last (bool, 可选): 设置为 True 以丢弃最后一个不完整的批次,如果数据集大小不能被批量大小整除。如果设置为 False 并且数据集大小不能被批量大小整除,则最后一个批次会较小(默认:False)。
- timeout (numeric, 可选): 如果为正数,这是从工作进程收集一个批次的超时值。应始终为非负数(默认:0)。
- worker_init_fn (Callable, 可选): 如果不是 None,这将在每个工作进程子进程上调用,输入为工作进程 id(一个在 [0, num_workers - 1] 中的 int),在设置种子和数据加载之前。(默认:None)
- generator (torch.Generator, 可选): 如果不是 None,这个 RNG 将被 RandomSampler 用来生成随机索引,并被多进程用来为工作进程生成 base_seed。(默认:None)
- prefetch_factor (int, 可选,关键字参数): 每个工作进程预先加载的批次数量。2 意味着将有总共 2*num_workers 个批次被预先加载。(默认值取决于 num_workers 的设定值。如果 num_workers=0,默认是 None。否则如果 num_workers>0,默认是 2)。
- persistent_workers (bool, 可选): 如果设置为 True,则数据加载器在数据集被消费一次后不会关闭工作进程。这允许保持工作进程的 Dataset 实例存活。(默认:False)。
- pin_memory_device (str, 可选): 如果将 pin_memory 设置为 true,则数据加载器将在返回它们之前将 Tensors 复制到设备固定内存中。
2.2 collate_fn
class CollateFunc(object):def __call__(self, batch):targets = []images = []for sample in batch:image = sample[0]target = sample[1]images.append(image)targets.append(target)images = torch.stack(images, 0) # [B, C, H, W]return images, targets
3. 优化器与学习率调整
3.1 优化器
在训练过程中,根据梯度变化、损失函数、动量(momontum)、学习率来调整模型参数
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
scheduler = ExponentialLR(optimizer, gamma=0.9)for epoch in range(20):for input, target in dataset:optimizer.zero_grad()output = model(input)loss = loss_fn(output, target)loss.backward()optimizer.step()scheduler.step()
优化器调整的模型参数参数包含权重的偏置与归一化项;
优化器可以为不同的网络层设置学习率与权重衰减
import torch
import torch.nn as nn# 定义一个简单的神经网络模型
class SimpleModel(nn.Module):def __init__(self):super(SimpleModel, self).__init__()self.conv1 = nn.Conv2d(1, 10, kernel_size=5)self.conv2 = nn.Conv2d(10, 20, kernel_size=5)self.fc = nn.Linear(320, 50)def forward(self, x):x = torch.relu(torch.max_pool2d(self.conv1(x), 2))x = torch.relu(torch.max_pool2d(self.conv2(x), 2))x = x.view(-1, 320)x = self.fc(x)return x# 创建模型实例
model = SimpleModel()# 使用 named_parameters() 获取参数名称和参数
for name, param in model.named_parameters():print(name, param.size())'''
输出结果:
conv1.weight torch.Size([10, 1, 5, 5])
conv1.bias torch.Size([10])
conv2.weight torch.Size([20, 10, 5, 5])
conv2.bias torch.Size([20])
fc.weight torch.Size([320, 50])
fc.bias torch.Size([50])
'''
Optimizer.add_param_group 将参数组添加到Optimizerparam_groups中。
Optimizer.load_state_dict 加载优化器状态。
Optimizer.state_dict 以字典的形式返回优化器的状态dict。
Optimizer.step 参数更新
Optimizer.zero_grad 重置累计梯度梯度(梯度累计发生在反向传播之前)
优化器在模型训练中的作用是调整模型的参数,以最小化损失函数。训练过程通常遵循以下步骤:
- 重置梯度:在每次迭代开始时,需要将模型参数的梯度清零,以避免累积。
- 前向传播:模型接收输入数据,通过其参数进行计算,得到预测值。
- 计算损失:使用损失函数(如均方误差、交叉熵等)计算模型预测值与真实值之间的差异,这个差异被称为损失值。损失函数为模型提供了优化的方向。
- 反向传播:根据损失值对模型参数进行反向传播,计算每个参数的梯度,这些梯度指示了- 如何调整参数以减少损失。
- 梯度累计
梯度:表示模型参数发生微小变化,损失函数该如何变化
学习率:控制参数更新的步长,学习率在参数更新前进行更新
Momentum :考虑过去梯度的指数加权平均值来调整参数的更新规则,从而帮助模型更快地收敛,并在梯度很小时减少震荡
累加梯度:在每次迭代中(反向传播后),将计算得到的梯度累加到梯度累积器中,而不是立即更新模型参数。(梯度累计是一种灵活的技术,它使得在资源有限的情况下训练大型模型成为可能,并且可以帮助优化训练过程。在进行反向传播之前,如果没有直接进行模型的梯度更新,一般会进行梯度累计)
3.2 学习率调度
学习率控制着模型参数的更新变化率,在训练过程中采用不同的学习率衰减策略,能更帮助模型更好的拟合数据,提升模型的泛化能力,定义优化器时,会设置初始学习率,利用 torch.optim.lr_scheduler中的学习率函数对优化器与学习率调整策略进行封装,结果返回封装了optimizer,scheduler对象。更新optimizer的学习率
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)
- 训练完一个epoch进行更新
- 迭代一次进行一次更新
- 可以在训练过程中设置不同参数层的学习率
4迭代训练
train_eopch:训练完全部数据跟新一次学习或优化器参数,或者指定更新优化器参数的更新频率
iteration: 没迭代一次更新一次优化器参数;
两者的主要区别在于遍历数据的形式不同
4.1 train_epoch
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset# 定义一个简单的模型
class SimpleModel(nn.Module):def __init__(self):super(SimpleModel, self).__init__()self.linear = nn.Linear(10, 2) # 一个简单的线性层def forward(self, x):return self.linear(x)# 实例化模型、损失函数和优化器
model = SimpleModel()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)# 创建数据集和数据加载器
x_dummy = torch.randn(1000, 10)
y_dummy = torch.randint(0, 2, (1000,))
dataset = TensorDataset(x_dummy, y_dummy)
data_loader = DataLoader(dataset, batch_size=100, shuffle=True)# 假设我们想要模拟的批量大小是1000,但由于内存限制,我们只能实际使用批量大小为100
accumulation_steps = 10 # 需要累积10个steps的梯度
model.train()for epoch in range(2): # 训练2个epochfor i, (inputs, targets) in enumerate(data_loader):# 前向传播outputs = model(inputs)loss = criterion(outputs, targets)# 累加梯度而不是立即清零loss.backward()# 每累积一定步数后更新一次参数if (i + 1) % accumulation_steps == 0:# 更新模型参数之前,我们需要梯度optimizer.step()optimizer.zero_grad() # 清零梯度,准备下一次累积# 打印损失信息if (i + 1) % (accumulation_steps * 10) == 0: # 每100个iteration打印一次print(f'Epoch [{epoch+1}/{2}], Step [{i+1}/{len(data_loader)*accumulation_steps}], Loss: {loss.item():.4f}')print("Training complete.")
4.2 train iteration
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset# 定义一个简单的模型
class SimpleModel(nn.Module):def __init__(self):super(SimpleModel, self).__init__()self.linear = nn.Linear(10, 2) # 一个简单的线性层def forward(self, x):return self.linear(x)# 实例化模型、损失函数和优化器
model = SimpleModel()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)# 创建数据集和数据加载器
x_dummy = torch.randn(1000, 10)
y_dummy = torch.randint(0, 2, (1000,))
dataset = TensorDataset(x_dummy, y_dummy)
data_loader = DataLoader(dataset, batch_size=100, shuffle=True)# 假设我们想要模拟的批量大小是1000,但由于内存限制,我们只能实际使用批量大小为100
accumulation_steps = 10 # 需要累积10个steps的梯度
model.train()
max_iter = 100
# 设置最大迭代次数
iterator = iter(data_loader)
for iter in range(1,max_iter): # 训练max_iter个iter# 迭代数据,若完成数据一轮迭代,则重新初始化iterator = iter(train_loader)# 直至完成max_iter次迭代try:inputs, targets = next(iterator)except:iterator = iter(train_loader)inputs, targets = next(iterator)# 前向传播outputs = model(inputs)loss = criterion(outputs, targets)# 累加梯度而不是立即清零loss.backward()# 每累积一定步数后更新一次参数if (iter) % accumulation_steps == 0:# 更新模型参数之前,我们需要梯度optimizer.step()optimizer.zero_grad() # 清零梯度,准备下一次累积# 打印损失信息if (iter) % (accumulation_steps * 10) == 0: # 每100个iteration打印一次print(f'Epoch [{epoch+1}/{2}], Step [{i+1}/{len(data_loader)*accumulation_steps}], Loss: {loss.item():.4f}')print("Training complete.")
5.1 保存模型权重
以字典形式,保存权重与详细的参数
torch.save({'model': model_eval.state_dict(),'mAP': -1.,'optimizer': self.optimizer.state_dict(),'epoch': self.epoch,'args': self.args}, checkpoint_path)
只保存模型参数
torch.save(model.state_dict(), save_temp_weights+"_fg{}.pt".format(it))