深度学习模型训练全过程


本文记录学习深度学习模型训练的流程,包括:数据准备、定义并调用模型、训练模型、评估模型、保存模型、测试模型等

1. 环境准备

首先引入一些深度学习模型所需要的库和包

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms

from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

2. 数据准备

定义数据变换;

下载并加载训练集和测试集;

创建数据加载器

def get_training_dataloader(mean, std, batch_size=16, num_workers=2, shuffle=True):
    """ return training dataloader
    Args:
        mean: mean of cifar100 training dataset
        std: std of cifar100 training dataset
        path: path to cifar100 training python dataset
        batch_size: dataloader batchsize
        num_workers: dataloader num_works
        shuffle: whether to shuffle
    Returns: train_data_loader:torch dataloader object
    """
#对数据进行随机变换生成新的训练样本,提高泛化能力,减少过拟合;将不同格式的数据转换为模型可以处理的统一格式;归一化
    transform_train = transforms.Compose([ #定义数据变换,一个数据预处理流水线
        #transforms.ToPILImage(),
        transforms.RandomCrop(32, padding=4), #随机裁剪图像到 32x32 大小,裁剪前在图像周围填充 4 个像素
        transforms.RandomHorizontalFlip(), #以一定的概率随机水平翻转图像
        transforms.RandomRotation(15), #随机旋转图像,旋转角度在 -15 度到 15 度之间
        transforms.ToTensor(), #将图像从 PIL 图像或 numpy 数组转换为 PyTorch 张量
        transforms.Normalize(mean, std) #使用给定的均值和标准差对图像进行归一化处理
    ])
    #cifar100_training = CIFAR100Train(path, transform=transform_train)
    cifar100_training = torchvision.datasets.CIFAR100(root='./data', train=True, download=True, transform=transform_train)
    cifar100_training_loader = DataLoader(
        cifar100_training, shuffle=shuffle, num_workers=num_workers, batch_size=batch_size)

    return cifar100_training_loader


def get_test_dataloader(mean, std, batch_size=16, num_workers=2, shuffle=True):
    """ return training dataloader
    Args:
        mean: mean of cifar100 test dataset
        std: std of cifar100 test dataset
        path: path to cifar100 test python dataset
        batch_size: dataloader batchsize
        num_workers: dataloader num_works
        shuffle: whether to shuffle
    Returns: cifar100_test_loader:torch dataloader object
    """

    transform_test = transforms.Compose([ #定义数据变换
        transforms.ToTensor(),
        transforms.Normalize(mean, std)
    ])
    #cifar100_test = CIFAR100Test(path, transform=transform_test)
    cifar100_test = torchvision.datasets.CIFAR100(root='./data', train=False, download=True, transform=transform_test)
    cifar100_test_loader = DataLoader(
        cifar100_test, shuffle=shuffle, num_workers=num_workers, batch_size=batch_size)

    return cifar100_test_loader

3. 模型定义/调用模型

自定义一个模型或调用一个模型;

使用之前需要先实例化模型(以vgg为例)

# 定义一个模型
class VGG(nn.Module):

    def __init__(self, features, num_class=100):
        super().__init__()
        self.features = features

        self.classifier = nn.Sequential(
            nn.Linear(512, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, num_class)
        )

    def forward(self, x):
        output = self.features(x)
        output = output.view(output.size()[0], -1)
        output = self.classifier(output)

        return output

def make_layers(cfg, batch_norm=False):
    layers = []

    input_channel = 3
    for l in cfg:
        if l == 'M':
            layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
            continue

        layers += [nn.Conv2d(input_channel, l, kernel_size=3, padding=1)]

        if batch_norm:
            layers += [nn.BatchNorm2d(l)]

        layers += [nn.ReLU(inplace=True)]
        input_channel = l

    return nn.Sequential(*layers)

def vgg11_bn():
    return VGG(make_layers(cfg['A'], batch_norm=True))
# 调用模型并实例化
from models.vgg import vgg11_bn
net = vgg11() # 实例化模型

4. 定义损失函数和优化器

需要定义一个损失函数来衡量模型的预测和真实值之间的差异,同时还需要一个优化器来更新模型的参数

loss_function = nn.CrossEntropyLoss() #交叉熵损失函数。衡量模型预测与真实值之间的差异
optimizer = optim.Adam(net.parameters(), lr=0.001)  #使用自适应矩估计(Adam)优化器
# 或
optimizer = optim.SGD(net.parameters(), lr=args.lr, momentum=0.9, weight_decay=5e-4) #使用随机梯度下降(SGD)优化器

优化器优劣比较:

SGD(随机梯度下降)
基本思想: SGD在每一步更新中仅使用一个(或一小批)样本来计算梯度,而不是使用整个数据集。这种方法可以显著减少计算量,使得训练大规模数据集变得可行。
学习率: SGD通常需要手动调整学习率,并且可能会使用如学习率衰减这样的技巧来帮助模型收敛。学习率的选择对SGD的性能影响很大。
收敛速度: SGD的收敛速度通常比较慢,尤其是在接近最小值的平坦区域。
泛化能力: 研究表明,由于SGD的噪声更大,它可能有助于模型找到泛化性能更好的解。

优点:对于大数据集,可以更快地进行优化;可以有效地跳出局部最优解。

缺点:1. 更新方向不稳定,可能导致优化过程震荡;2. 可能需要较长时间才能收敛;3. 对所有的参数使用相同的学习率。这可能导致优化过程在某些方向上过快,而在其他方向上过慢。

Momentum(动量优化)

动量优化在SGD的基础上引入了动量项,使得优化过程可以积累之前的梯度,从而更快地收敛

优点:可以加速SGD在相关方向的收敛速度,抑制震荡。

缺点:1. 需要人工设置动量参数;2. 可能会导致优化过程在某些方向上过快,从而跳过最优解。

Adam(自适应矩估计)
基本思想: Adam是一种自适应学习率的优化算法,它结合了动量(Momentum)和RMSprop的优点。Adam会为不同的参数计算不同的自适应学习率。
学习率: Adam自动调整学习率,通常不需要像SGD那样手动微调学习率,这使得Adam在很多情况下都能较快地收敛。
收敛速度: 由于自适应学习率的特性,Adam在初期训练阶段通常比SGD收敛得更快。
泛化能力: 尽管Adam在许多任务中都显示出了较快的收敛速度,但一些研究表明,对于某些问题,Adam可能导致过拟合,泛化能力不如SGD。
优点:既可以加速优化,又可以自动调整学习率,对于深度学习模型的优化效果较好。

缺点:1. 需要人工设置初始学习率和两个衰减系数;2. Adam优化器的收敛性并不是单调的,这可能会导致在训练过程中出现波动,从而影响模型的性能和稳定性;3. Adam优化器在训练初期阶段对梯度的估计存在偏差,但通过偏差纠正可以降低其影响,然而这种偏差纠正可能会带来额外的计算负担。

5. 模型训练

模型训练包括:前向传播、计算损失、反向传播、参数更新。

net.train() #设置神经网络为训练模式

for batch_index, (images, labels) in enumerate(cifar100_training_loader): #遍历训练数据加载器
    optimizer.zero_grad() #将优化器中所有参数的梯度清零,以便进行新一轮的梯度计算
    outputs = net(images) #将输入图像传递给神经网络进行前向传播
    loss = loss_function(outputs, labels) #计算输出和标签之间的损失
    loss.backward() #进行反向传播,计算损失对网络参数的梯度
    optimizer.step() #根据计算的梯度更新网络参数

6. 模型评估

在测试集上评估模型的性能,看它在未见过的数据集上的性能表现如何

net.eval() #将神经网络设置为评估模式,禁用 dropout 和 batch normalization 等训练相关的操作

test_loss = 0.0 # cost function error
correct = 0.0

for (images, labels) in cifar100_test_loader:

    if args.gpu:
        images = images.cuda()
        labels = labels.cuda()

    outputs = net(images)
    loss = loss_function(outputs, labels)

    test_loss += loss.item() #累加损失值
    _, preds = outputs.max(1) #获取输出中每行最大值的索引,即预测的类别
    correct += preds.eq(labels).sum() #将预测正确的样本数累加

7. 保存模型

将训练好的模型保存下来,方便后续使用或部署

#start to save best performance model after learning rate decay to 0.01
if epoch > settings.MILESTONES[1] and best_acc < acc:
    weights_path = checkpoint_path.format(net=args.net, epoch=epoch, type='best')
    print('saving weights file to {}'.format(weights_path))
    torch.save(net.state_dict(), weights_path)
    best_acc = acc
    continue
#定期保存模型。当前epoch是SAVE_EPOCH的倍数
if not epoch % settings.SAVE_EPOCH:
    weights_path = checkpoint_path.format(net=args.net, epoch=epoch, type='regular')
    print('saving weights file to {}'.format(weights_path))
    torch.save(net.state_dict(), weights_path)

8. 加载模型

在需要时,可以随时加载已保存的模型权重继续使用

net = vgg11()
net.load_state_dict(torch.load('model.pth'))
net.eval()

文章作者: Antonio
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Antonio !
  目录