高效的图像分类训练技巧

释放双眼,带上耳机,听听看~!
本文介绍了图像分类训练中的高效技巧,包括大Batch训练和训练优化方法。通过实验不同batch大小来探讨最佳值,以提高模型性能和收敛速度。同时,还介绍了图像分类中的训练优化方法。

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第17天,www.kaggle.com/dansbecker/…

  • 解压缩数据
  • 使用split_food-101.py将Food-101分割为训练/测试文件夹。这个脚本会解析train.txttest.txt并复制图像到相应的子文件夹。注意,我们硬编码了将要使用的类。
  • 基线

    我们使用ResNet-18架构作为基线。为了提升结果,我们使用了一个预训练过的ImageNet模型,该模型使用Adam优化器和交叉熵损失函数。默认LR为1e-4,在epochs 15和30之后,每次乘以0.1。总的来说,模型在1个Nvidia 1080Ti GPU上训练了40个epoch,batch size大小为32。我们使用PyTorch-Lightning框架来组织我们的代码。

    :为了使我们的结果更可靠,我们在每个实验中使用不同的种子启动3次,并提供平均结果。

    由于我们的数据集很大而且很多样,所以我们使用一个简单的增强策略。在训练期间,我们使用:

    • RandomResizedCrop
    • HorizontalFlip
    • Normalization
    def get_training_augmentation():
        augmentations_train = A.Compose(
            [
                A.RandomResizedCrop(224224, scale=(0.81.0)),
                A.HorizontalFlip(),
                A.Normalize(mean=[0.4850.4560.406], std=[0.2290.2240.225]),
                ToTensorV2(),
            ],
        )
        return lambda img: augmentations_train(image=np.array(img))
    

    在验证过程中,我们遵循作者的策略,将图像的短边调整为256,保持宽高比不变。然后使用中心裁剪,得到224×224的方形区域:

    def get_test_augmentation():
        augmentations_val = A.Compose(
            [
                A.SmallestMaxSize(256),
                A.CenterCrop(224224),
                A.Normalize(mean=[0.4850.4560.406], std=[0.2290.2240.225]),
                ToTensorV2(),
            ],
        )
        return lambda img: augmentations_val(image=np.array(img))
    

    图像分类的技巧

    首先,让我们把技巧分成两类:

    1. 高效的训练技巧 — 硬件和模型相结合的技巧,可能提高性能
    2. 训练优化 — 进一步提高质量的几个有趣的方法。

    让我们详细讨论每一个技巧。

    高效的训练技巧

    Trick #1: 大Batch训练

    Batch大小是一个至关重要的训练参数,尽管Batch越大收敛速度越快,效果越好,但对于其最优值却不一定。这是一个有争议的,同时也是一个被广泛研究的话题。下面是一些处理这个问题的启发式方法。由于资源有限,我们试验了batch大小16、32、64、96。

    当我们增加batch大小时,我们不改变随机梯度的期望,但减少噪音,因此,减少了方差。这意味着,batch越大,我们的学习效率就越高。一种流行的方法是在训练过程中线性缩放学习率。例如,假设我们选择1e-4作为batch大小为32的初始学习率。然后,通过改变batch大小,我们增加学习率为1e-4*b/32。然而,在我们的案例中,我们发现,Adam优化器1e-4的收敛性和稳定性更好,所以我们没有进行太多的线性缩放实验。

    高效的图像分类训练技巧

    表1:使用不同的batch size训练的结果

    batch越大,训练时间越短,训练精度越低。

    Trick #2: LR Warm-up

    遵循这一启发式,我们使用最初的几个epochs来“热身”学习率。在训练开始时(当所有的参数都远离最优参数时)使用较高的学习率可能会导致数值不稳定性的而导致质量下降。假设我们想要在前m个epochs上热身,使用初始学习率,在第i个epoch中,1≤i≤m,学习率为:

    def optimizer_step(self, epoch, batch_idx, optimizer, *args, **kwargs):
        # Learning Rate warm-up
        if self.args.warmup != -1 and epoch < self.args.warmup:
            lr = self.args.lr * (epoch + 1) / self.args.warmup
            for pg in optimizer.param_groups:
                pg["lr"] = lr
    

    在那之后,我们可以使用任何策略(multi-step衰减,plateau衰减)。在我们的实验中,我们使用6 epochs 热身,直到学习率变为1e-4,然后在15和30个epochs上衰减到得1e-5,1e-6。

    高效的图像分类训练技巧

    图3:学习率策略

    总的来说,这个技巧提高了0.08%的准确率,不是很显著。

    高效的图像分类训练技巧

    表2:学习率热身的结果

    Trick #3: 混合精度

    在常用框架(PyTorch、TensorFlow)中,我们用32位浮点精度格式(FP32)训练我们的模型。换句话说,所有的参数,梯度,算术运算的结果都以这种格式存储。然而,由于优化的逻辑单元,现代硬件在精度较低的数据类型上可能表现出更好的性能。文章的作者表示,他们的Nvidia V100在FP32上具有14个TFLOPS,而在FP16中具有100个TFLOPS。不幸的是,我们的GPU (Nvidia 1080Ti)FP16时速度较低,所以我们不会看到FP32和FP16性能的任何显著差异。

    高效的图像分类训练技巧

    混合精度训练的结果

    如你所见,FP16提高了所有batch大小(BS)设置的训练速度,而且也提高了准确度。我们使用了Nvidia apex库,其中有FP32的O0优化级别和FP16的O1优化级别。在 PyTorch-Lightning 中,可以通过在命令行参数中添加--amp_level [Opt_level]在FP32和FP16之间切换。

    训练优化

    Trick #4: 余弦学习率衰减

    除了多步衰减学习率策略外,还有一些我们可以使用的策略。例如,我们可以应用一个余弦函数来将学习率从初始值降低到0。假设有T个epoch(忽略热身阶段),初始学习率为l,那么在epoch T时,学习率lT的计算为:

    高效的图像分类训练技巧

    这样做的目的是为了平稳地降低学习率,与步进衰减策略相比,可以获得更好的训练效果。在余弦衰减过程中,我们在开始和结束时慢慢降低学习速率,而在中间,下降速率几乎是线性的。

    高效的图像分类训练技巧

    图4:余弦学习率衰减

    可以注意到,在我们的案例中,这种方法提高了准确率。此外,使用余弦策略的实验时间更短。

    高效的图像分类训练技巧

    表4:使用余弦学习率策略的结果

    Trick #5: 标签平滑

    在图像分类中,我们通常使用交叉熵损失函数:

    高效的图像分类训练技巧

    通过标签平滑,我们将二元指标yi替换为:

    高效的图像分类训练技巧

    代码实现:

    # Based on https://github.com/pytorch/pytorch/issues/7455
    class LabelSmoothingLoss(nn.Module):
        def __init__(self, n_classes, smoothing=0.0, dim=-1):
            super(LabelSmoothingLoss, self).__init__()
            self.confidence = 1.0 - smoothing
            self.smoothing = smoothing
            self.cls = n_classes
            self.dim = dim
    
        def forward(self, output, target, *args):
            output = output.log_softmax(dim=self.dim)
            with torch.no_grad():
                # Create matrix with shapes batch_size x n_classes
                true_dist = torch.zeros_like(output)
                # Initialize all elements with epsilon / N - 1
                true_dist.fill_(self.smoothing / (self.cls - 1))
                # Fill correct class for each sample in the batch with 1 - epsilon
                true_dist.scatter_(1, target.data.unsqueeze(1), self.confidence)
            return torch.mean(torch.sum(-true_dist * output, dim=self.dim))
    

    对于独热编码,模型通常对其预测过于自信,因为这种方法迫使模型做出最大可能的logit差距。这意味着正确的类的logit和其他类别的logit之间的训练结果会有巨大的差异,同时也可能导致错误的类的logit彼此之间有很大的差异。

    标签平滑的使用鼓励模型从全连接层产生有限的输出,这可能导致更好的泛化。它迫使模型将正确类的logit与其他类的logit之间的差异设置为依赖于ε的常数。

    高效的图像分类训练技巧

    表5:使用标签平滑训练的结果

    总的来说,标签平滑使我们的结果提高了0.9%,我们还减少了6分钟的训练时间。

    Trick #6: 知识蒸馏

    知识蒸馏,就是先训练一个复杂而重的模型(我们使用ResNet-50),即教师模型,然后在教师的帮助下训练一个较轻的模型(学生模型)。我们假设一个更复杂的模型应该具有更高的准确率,因此,理论上,它可以提高学生模型的结果,同时保持其简单性。学生试图复制老师的结果。

    为了进行蒸馏,我们修改了损失函数。我们根据老师和学生的得分的差别来进行惩罚。我们的损失函数从交叉熵损失,变成:

    高效的图像分类训练技巧

    代码实现:

    # Based on https://github.com/peterliht/knowledge-distillation-pytorch/blob/master/model/net.py
    class KnowledgeDistillationLoss(nn.Module):
        def __init__(self, alpha, T, criterion=nn.CrossEntropyLoss()):
            super().__init__()
            self.criterion = criterion
            self.KLDivLoss = nn.KLDivLoss(reduction="batchmean")
            self.alpha = alpha
            self.T = T
    
        def forward(self, input, target, teacher_target):
            loss = self.KLDivLoss(
                F.log_softmax(input / self.T, dim=1),
                F.softmax(teacher_target / self.T, dim=1),
            ) * (self.alpha * self.T * self.T) + self.criterion(input, target) * (
                1.0 - self.alpha
            )
            return loss
    

    我们使用ResNet-50作为教师模型。对模型进行了标签平滑、余弦退火LR和线性预热的训练,获得了92.18%的Top-1准确率。

    高效的图像分类训练技巧

    表6:使用知识蒸馏的训练结果

    我们在准确率上取得了显著的增长,但增加了训练时间,因为我们需要从老师那里得到预测。

    Trick #7: Mix-up 增强

    Mix-up是一种增强技术,构造一个新的图像作为两个其他的线性组合。假设我们有两个batch的样本(我们取当前的batch和早期迭代中的batch),我们所做的是随机洗牌第二个batch,并从这两个batch中创建一个线性组合图像:

    高效的图像分类训练技巧

    作为目标,我们从这两个batch中取标签。我们计算每个标签的损失,并返还加权总和作为总损失:

    高效的图像分类训练技巧

    λ是一个来自β分布的随机数。

    此外,还可以为这个新样本创建一个增强目标,作为原始目标的线性组合(如果目标是one-ho编码或平滑的)。

    这一技巧有助于减少高置信度预测的数量,并可以提高准确率,但对人类来说,可能很难判断增强的图片是什么。

    高效的图像分类训练技巧

    图5:mix-up增强的例子

    代码实现:

        def mixup_batch(self, x, y, x_previous, y_previous):
            lmbd = (
                np.random.beta(self.args.mixup_alpha, self.args.mixup_alpha)
                if self.args.mixup_alpha > 0
                else 1
            )
            if x_previous is None:
                x_previous = torch.empty_like(x).copy_(x)
                y_previous = torch.empty_like(y).copy_(y)
            batch_size = x.size(0)
            index = torch.randperm(batch_size)
            # If current batch size != previous batch size, we take only a part of the previous batch
            x_previous = x_previous[:batch_size, ...]
            y_previous = y_previous[:batch_size, ...]
            x_mixed = lmbd * x + (1 - lmbd) * x_previous[index, ...]
            y_a, y_b = y, y_previous[index]
            return x_mixed, y_a, y_b, lmbd
    
    class MixUpAugmentationLoss(nn.Module):
        def __init__(self, criterion):
            super().__init__()
            self.criterion = criterion
    
        def forward(self, input, target, *args):
            # Validation step
            if isinstance(target, torch.Tensor):
                return self.criterion(input, target, *args)
            target_a, target_b, lmbd = target
            return lmbd * self.criterion(input, target_a, *args) + (
                1 - lmbd
            ) * self.criterion(input, target_b, *args)
    

    应用该技术的结果如下表所示:

    高效的图像分类训练技巧

    表7:使用mix-up增强的训练结果

    通过平滑标签来进行两个batch之间的Mix-up增强,可以提高准确率,但需要更多的时间。

    福利: 技巧组合

    最后,我们将这些技巧结合在一起,重新进行了实验。总体来说,我们使用了:

    • 线性学习率热身
    • 余弦学习率策略
    • 标签平滑
    • 知识蒸馏

    可以预料,这些技巧的组合会给我们带来强大的改进,因为我们结合了最好的技巧。这种设置可以得到性能的提升。总的来说,我们将基线准确率提高了1%。你可以看到汇总表如下:

    高效的图像分类训练技巧

    表8:结果汇总

    总结

    如上所示,以不同的方式改变训练过程可以帮助你提高准确率,但它是依赖于任务和数据的。这就是为什么,在我们的案例中,改进并不是那么显著,因为基线模型已经能够达到很高的结果了。

    英文原文:www.learnopencv.com/bag-of-tric…

    本网站的内容主要来自互联网上的各种资源,仅供参考和信息分享之用,不代表本网站拥有相关版权或知识产权。如您认为内容侵犯您的权益,请联系我们,我们将尽快采取行动,包括删除或更正。
    AI教程

    多伦多大学研究人员开发机器学习模型加速长效注射剂研发

    2023-11-30 21:18:14

    AI教程

    半监督广义学习系统的新方法——S2-BLS

    2023-11-30 21:22:14

    个人中心
    购物车
    优惠劵
    今日签到
    有新私信 私信列表
    搜索