高阶训练技术


高阶训练技术

Imagenette

Fast.ai的团队在创建Imagenette数据集时,是基于一个核心理念:迭代速度对于机器学习模型的开发至关重要。他们注意到,虽然ImageNet、MNIST和CIFAR10是当时常用的数据集,但它们在规模和复杂性上存在差异,这影响了模型的泛化能力和开发者的迭代速度。

  • ImageNet 数据集包含约130万张不同大小的图片,分布在1000个类别中。训练一个模型通常需要几天的时间。
  • MNIST 数据集包含50000张28×28像素的灰度手写数字图片。
  • CIFAR10 数据集包含60000张32×32像素的彩色图片,分为10个类别。

小型数据集(如MNIST和CIFAR10)在ImageNet这样的大型数据集上的表现并不理想。有效的方法往往需要直接在ImageNet上开发和训练,这导致许多人认为只有拥有大量计算资源的研究人员才能有效地贡献图像分类算法的发展。

Fast.ai的团队质疑这一观点,并认为没有证据表明ImageNet就是唯一合适的数据集大小。因此,他们决定尝试创建一个新的数据集——Imagenette。他们从ImageNet中选择了10个外观差异很大的类别,以便快速、低成本地创建能够识别这些类别的分类器。通过在Imagenette上测试算法调整,他们发现了一些有效的方法,并将这些方法应用到ImageNet上,结果发现这些调整在ImageNet上也表现良好。

这个案例强调了一个重要的信息:给定的数据集不一定是你想要的数据集,尤其不太可能是你进行开发和原型设计时想要的数据集。你应该追求的是迭代速度不超过几分钟——也就是说,当你想尝试一个新想法时,你应该能够在几分钟内训练一个模型并看到结果。如果实验花费的时间更长,你应该考虑如何缩小数据集或简化模型来提高实验速度。你能做的实验越多,结果越好!

from fastai.vision.all import *
path = untar_data(URLs.IMAGENETTE)

dblock = DataBlock(blocks=(ImageBlock(), CategoryBlock()),
                   get_items=get_image_files,
                   get_y=parent_label,
                   item_tfms=Resize(460),
                   batch_tfms=aug_transforms(size=224, min_scale=0.75))
dls = dblock.dataloaders(path, bs=64)

# 进行一次训练作为基准
model = xresnet50(n_out=dls.c)
learn = Learner(dls, model, loss_func=CrossEntropyLossFlat(), metrics=accuracy)
learn.fit_one_cycle(5, 3e-3)

训练基准

n_out=dls.c 指定了模型输出层的神经元数量,应与数据集中的类别数量相匹配。这里,dls.cDataLoaders 对象 dls 中获取类别数。

这是一个不错的基准,因为没有使用预训练模型,也可以表现很好。

使用从头开始训练的模型,或对预训练模型进行微调,使其能够很好地泛化到一个差异很大的数据集时,一些额外的技术非常重要。

数据标准化

训练模型时,如果输入的数据是标准化的——即均值为0、标准差为1——会对之后的训练有很大帮助。但大多数图像和计算机视觉的像素值在0-255或0-1,在这两种情况下,数据都不是均值为0、标准差为1的。

x,y = dls.one_batch()
x.mean(dim=[0,2,3]),x.std(dim=[0,2,3])
# 对所有维度做均值化处理(除了通道数这一维度,索引1)
# (TensorImage([0.4715, 0.4795, 0.4579], device='cuda:0'),
# TensorImage([0.2759, 0.2758, 0.3021], device='cuda:0'))

可以在数据块的数据增强部分添加Normalize转换。

def get_dls(bs, size):
    dblock = DataBlock(blocks=(ImageBlock, CategoryBlock),
                   get_items=get_image_files,
                   get_y=parent_label,
                   item_tfms=Resize(460),
                   batch_tfms=[*aug_transforms(size=size, min_scale=0.75), Normalize.from_stats(*imagenet_stats)])
    return dblock.dataloaders(path, bs=bs)

dls = get_dls(64, 224)
x,y = dls.one_batch()
x.mean(dim=[0,2,3]),x.std(dim=[0,2,3])
# 查看标准化处理后的均值和标准差
# (TensorImage([-0.0150,  0.0287,  0.1145], device='cuda:0'),
# TensorImage([1.3090, 1.3218, 1.3806], device='cuda:0'))

imagenet_stats 是预定义的 ImageNet 数据集的均值和标准差,用于将图像数据标准化。

标准化后对训练模型的影响

model = xresnet50(n_out=dls.c)
learn = Learner(dls, model, loss_func=CrossEntropyLossFlat(), metrics=accuracy)
learn.fit_one_cycle(5, 3e-3)

标准化后的训练结果

😅,似乎看不出什么明显的效果。但在处理预训练的模型时,标准化会变得尤为重要,因为预训练模型只知道如何处理它以前见过的数据类型。如果预训练数据的平均像素值为0,但所用数据像素可能是最小值为0,那么模型和预期的结果会有很大差异!

这意味着当构建模型时,需要设定用于标准化的规则,因为用于推理和迁移学习的人都要使用相同的规则。

在使用预训练模型通过vision_learner进行训练时,fastai库会自动添加适当的Normalize变换。这是因为预训练模型是用特定的统计数据(通常来自ImageNet数据集)进行训练的,所以库能够为你填充这些统计数据。这就是为什么在使用预训练模型时,我们不需要手动处理标准化(normalization)。但是,当我们从头开始训练模型时,我们需要手动添加这些标准化信息。这是因为模型尚未学习到任何数据的分布,所以我们需要指定如何对输入数据进行标准化以匹配模型的预期输入。

渐进式调整尺寸(progressive resizing),这是一种训练技巧,其中我们首先以较小的图像尺寸开始训练,然后逐步增加图像尺寸。这样做的好处是可以加快训练的初期阶段,因为小尺寸的图像计算量更小。一旦模型在小尺寸上学习到了有用的特征,我们再逐步增加图像尺寸,以提高模型的准确性和泛化能力。这种方法可以看作是一种计算效率和性能之间的折中。

这种方法不仅可以提高训练速度,还可以帮助模型学习到从低分辨率到高分辨率的特征,这对于最终模型的性能是有益的。总的来说,渐进式调整尺寸是一种有效的策略,可以在保持较快迭代速度的同时,逐步提升模型的性能。

渐进式调整尺寸

开始训练时使用小图像,结束训练时使用大图像。花费大部分时间用小图像进行训练,有助于更快地完成训练。使用大图像完成训练,这让最终的准确率更高。

卷积神经网络(CNN)学习的特征与图像大小无关。确实,CNN的早期层次会学习到边缘和梯度等基本特征,而后期层次则可能识别出鼻子、日落等更复杂的特征。这意味着,即使在训练过程中改变图像大小,我们也不需要为模型找到完全不同的参数。

当我们从小尺寸图像转向大尺寸图像时,模型确实需要一些调整,因为大图像包含更多的细节和可能的特征。这与迁移学习(transfer learning)有些相似,我们利用已经学习到的知识来帮助模型学习新的任务。在这种情况下,我们可以使用fine_tune方法来调整模型参数,使其适应新的图像大小。

渐进式调整尺寸(progressive resizing)不仅可以提高训练效率,还是一种数据增强的形式。通过这种方式,模型在不同尺寸的图像上训练,可以学习到更多样化的特征,从而提高模型对新数据的泛化能力。因此,使用渐进式调整尺寸训练的模型通常会有更好的泛化表现。

构建一个小尺寸的DataLoaders进行训练

dls = get_dls(128, 128) #128批次大小,128*128像素
learn = Learner(dls, xresnet50(n_out=dls.c), loss_func=CrossEntropyLossFlat(), 
                metrics=accuracy)
learn.fit_one_cycle(4, 3e-3)

标准化后小尺寸的训练结果

image/image-20240620194230901.png

然后可以换掉Learner内部的DataLoaders,并进行微调:

learn.dls = get_dls(64, 224)
learn.fine_tune(5, 1e-3)

渐进放大尺寸的训练结果

可以反复增加图像大小并训练更多周期,但是不要不原始图像大。

对于迁移学习,如果预训练模型与迁移学习任务非常相似,并且使用了类似大小的图像,那么使用较小的图像进行训练可能会损害预训练好的权重,因为这些权重已经针对特定尺寸的图像进行了优化。但如果迁移学习任务使用的图像与预训练任务中的图像在大小、形状或风格上有所不同,那么渐进式调整图像大小可能会有所帮助,因为它允许模型适应新的图像特征。

测试期的数据增强

随机裁剪是一种有效的数据增强方法,它通过从图像中裁剪出不同的部分来增加模型训练的多样性,从而提高模型的泛化能力。然而,使用中心裁剪作为验证集的处理方式可能会导致一些问题,尤其是当图像边缘有重要特征时,这些特征可能会被裁剪掉,从而影响模型的准确性。

为了解决这个问题,您可以考虑以下几种方法:

  1. 避免随机裁剪:直接使用原始图像的比例,但这样会失去数据增强的好处。
  2. 调整图像比例:将矩形图像压缩或拉伸以适应正方形空间,但这可能会使模型难以识别因变形而失去原有比例的图像。
  3. 测试时增强(TTA):不仅仅使用中心裁剪,而是从原始矩形图像中选择多个区域进行裁剪,将每个裁剪区域通过模型进行预测,并取最大值或平均值作为最终结果。这种方法不仅可以用于不同的裁剪,还可以用于测试时增强的所有参数,从而提高模型在实际应用中的表现。

总的来说,测试时增强(TTA)是一个强大的技术,它通过在测试时应用多种数据增强策略来提高模型的性能。这种方法可以通过多次预测并综合结果来减少单次预测误差的影响。

“测试时增强(TTA)”的概念是指在推理或验证阶段,通过数据增强创建每张图像的多个版本,然后对每个增强版本的图像的预测结果取平均值或最大值。这是一种提高模型性能的技术,特别是在图像识别任务中。它可以帮助模型更好地泛化到未见过的数据,从而提高其准确性。

测试时增强(TTA)可以根据数据集的不同,显著提高模型的准确性。这种方法不会改变训练所需的时间,但会根据请求的测试时增强图像的数量增加验证或推理所需的时间。默认情况下,fastai 会使用未增强的中心裁剪图像加上四个随机增强的图像。

preds,targs = learn.tta()
accuracy(preds, targs).item()
# 0.8539955019950867

使用测试时增强(TTA)确实可以在不需要额外训练的情况下提高模型的性能。但是,这确实会使推理过程变慢。例如,如果您在 TTA 中平均使用五张图像,那么推理速度将会是原来的五倍慢。

这是一个权衡的问题,您需要根据实际应用场景来决定是否使用 TTA。如果模型的准确性是首要考虑的因素,而推理时间较长是可以接受的,那么 TTA 是一个很好的选择。但如果您需要快速的推理速度,那么可能需要寻找其他方法来提高性能,或者接受不使用 TTA 的准确性水平。

Mixup

Mixup 是一种数据增强技术,由 Hongyi Zhang 等人在 2017 年的论文 “mixup: Beyond Empirical Risk Minimization” 中介绍。这种技术可以显著提高准确性,特别是当您没有大量数据,且没有在与您的数据集相似的数据上训练过的预训练模型时。论文解释说:“虽然数据增强始终能够改善泛化,但该过程依赖于数据集,因此需要使用专家知识。”例如,翻转图像是数据增强的常见部分,但您应该只水平翻转,还是也垂直翻转?答案取决于您的数据集。此外,如果翻转(例如)没有为您提供足够的数据增强,您不能“更多地翻转”。拥有可以“增大”或“减小”变化量的数据增强技术很有帮助,以便查看哪种最适合您。

对于每张图像,Mixup 的工作方式如下:

  1. 从数据集中随机选择另一张图像。
  2. 随机选择一个权重。
  3. 使用步骤 2 中的权重对选定的图像与自己的图像进行加权平均;这将是自变量。
  4. 使用相同的权重对这张图像的标签与自己的图像的标签进行加权平均;这将是因变量。

在伪代码中,我们正在做这个(其中 t 是我们加权平均的权重):

image2, target2 = dataset[randint(0, len(dataset))]
t = random_float(0.5, 1.0)
new_image = t * image1 + (1-t) * image2
new_target = t * target1 + (1-t) * target2

为了使这个起作用,我们的目标需要是独热编码的。


Mixup论文摘录

维基百科数学符号表:https://en.wikipedia.org/wiki/Glossary_of_mathematical_symbols

使用Mixup对图像进行线性组合时的效果。

church = PILImage.create(get_image_files_sorted(path/'train'/'n03028079')[0])
gas = PILImage.create(get_image_files_sorted(path/'train'/'n03425413')[0])
church = church.resize((256,256))
gas = gas.resize((256,256))
tchurch = tensor(church).float() / 255.
tgas = tensor(gas).float() / 255.

_,axs = plt.subplots(1, 3, figsize=(12,4))
show_image(tchurch, ax=axs[0]);
show_image(tgas, ax=axs[1]);
show_image((0.3*tchurch + 0.7*tgas), ax=axs[2]);

混合一座教堂和加油站

如果第三张图像是通过将第一张图像的0.3倍与第二张图像的0.7倍相加构建的,模型应该预测“教堂”还是“加油站”?正确的答案是30%的教堂和70%的加油站,因为如果我们取独热编码目标的线性组合,就会得到这个结果。例如,假设我们有10个类别,“教堂”由索引2表示,“加油站”由索引7表示,独热编码的表示分别是:

[0,0,1,0,0,0,0,0,0,0]和[0,0,0,0,0,0,0,1,0,0]

所以我们的最终目标是:[0,0,0.3,0,0,0,0,0.7,0,0]

这意味着模型应该预测图像是30%的教堂和70%的加油站。这种方法允许模型学习从图像的混合中预测多个类别的概率,这是Mixup数据增强技术的核心思想。

通过给Learner添加一个回调函数,它时fastai内用于在训练循环中添加自定义行为的操作。

model = xresnet50(n_out=dls.c)
learn = Learner(dls, model, loss_func=CrossEntropyLossFlat(), 
                metrics=accuracy, cbs=MixUp())
learn.fit_one_cycle(5, 3e-3)

当我们以这种方式混合数据训练模型时,确实会更难训练,因为很难看清每张图片中的内容。模型必须预测每张图片的两个标签,而不仅仅是一个,同时还要弄清楚每个标签的权重。然而,过拟合似乎不太可能成为问题,因为我们在每个周期展示的不是同一张图片,而是两张图片的随机组合。

与我们见过的其他增强方法相比,Mixup 需要更多的周期来训练以获得更好的准确性。

对于超过 80 个周期的训练,所有领先结果都使用了 Mixup,而对于更少的周期,则没有使用 Mixup。这也符合我们使用 Mixup 的经验。

  1. Mixup的应用广泛性:Mixup不仅可以用于图像数据,还可以用于其他类型的数据,如自然语言处理(NLP)。这是因为Mixup的核心思想是将两个数据点混合在一起,这个过程不限于任何特定类型的数据。

  2. 完美损失的问题:在传统模型中,我们追求的是使损失函数尽可能接近完美,即标签值为1或0。但是,由于softmax和sigmoid函数的输出永远不会达到1或0,这导致在训练过程中,模型的激活值会越来越极端,以接近这些理想值。

  3. Mixup解决的问题:使用Mixup时,我们不再追求完美的1或0标签。除非两个混合的图像属于同一类别,否则我们得到的标签将是两个类别标签的线性组合,例如0.7和0.3。这减少了模型激活值变得极端的情况。

  4. Mixup的一个副作用:Mixup在混合标签时可能会不小心使标签值大于0或小于1。这意味着我们没有直接告诉模型以这种方式改变标签。如果我们想要控制标签值更接近或远离0和1,我们需要调整Mixup的比例,但这也会影响数据增强的程度,可能会带来我们不想要的结果。

标签平滑的解决方案:为了更直接地处理标签值的问题,我们可以使用标签平滑技术。标签平滑是一种正则化技术,它通过为标签引入噪声来使模型更加健壮,从而更好地泛化。它通过替换硬分类目标(0和1)为柔性目标,来解决数据集中可能存在的错误标签问题。

标签平滑

在分类问题中,理论上我们的目标是进行独热编码,即对于每个实例,我们有一个长度等于类别数的数组,其中一个类别对应的位置是1,其余都是0。这种编码方式意味着模型被训练为对除了正确类别之外的所有类别返回0,对正确类别返回1。然而,即使是0.999这样接近1的值也不被视为“足够好”,因为模型会得到梯度并学习预测更高置信度的激活值。这会鼓励过拟合,并且在推理时给出一个不会提供有意义概率的模型:它总是对预测类别说1,即使它不太确定,只是因为它是这样训练的。

如果数据标签不完美,这种情况会变得非常有害。例如,在我们研究的熊分类器中,一些图像被错误标记,或者包含两种不同类型的熊。通常情况下,你的数据永远不会是完美的。即使标签是人工制作的,人们也可能会犯错误,或者对难以标记的图像有不同的看法。

为了解决这个问题,我们可以将所有的1替换为略小于1的数字,将所有的0替换为略大于0的数字,然后进行训练。这就是所谓的标签平滑。通过鼓励模型不要过于自信,标签平滑将使训练更加稳健,即使存在标记错误的数据。结果将是一个泛化能力更强的模型。

标签平滑在实践中是这样工作的:我们从独热编码标签开始,然后将所有的0替换为 𝜖/𝑁 ,其中N是类别的数量,ε是一个参数(通常是0.1,这意味着我们对我们的标签有10%的不确定性)。由于我们希望标签总和为1,所以将1替换为 1−𝜖+𝜖/𝑁。这样,我们就不会鼓励模型过度自信地预测。在我们有10个类别的Imagenette示例中,目标可能变成如下(这里是对应于索引3的目标):

[0.01, 0.01, 0.01, 0.91, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]

在实践中,我们不希望对标签进行独热编码,幸运的是我们也不需要这样做(独热编码只是用来解释标签平滑是什么并将其可视化的一个好方法)。

只需在调用Learner时修改损失函数:

model = xresnet50(n_out=dls.c)
learn = Learner(dls, model, loss_func=LabelSmoothingCrossEntropy(), 
                metrics=accuracy)
learn.fit_one_cycle(5, 3e-3)

标签平滑

和Mixup一样,通常看不到标签平滑带来的显著改进,除非训练很多周期,那该训练多少个周期呢?

结论

通过Mixup和/或标签平滑进行更长时间的训练是否可以避免过拟合并获得更好的结果。尝试渐进式调整尺寸和测试期的数据增强。

最重要的是,如果数据集很大,没有必要对整个数据集进行建模。找到一个能代表整体的小数据子集,就像这里使用Imagenette,并在其上进行实验。


文章作者: nusqx
文章链接: https://nusqx.top
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 nusqx !
评论
  目录