65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Neon 进行迁移学习

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2017年4月12日

CPOL

10分钟阅读

viewsIcon

15159

在这篇博文中,我们将解释迁移学习及其一些应用,解释如何使用 Neon 进行迁移学习,逐步讲解使用 Neon 将预训练模型迁移到新数据集的示例代码,并讨论迁移学习的优点及一些结果。

请查看 Nervana 网站上的原始文章,以了解有关此主题以及 Intel Nervana 深度学习框架的更多信息。

引言

在过去的几年里,大量的深度神经网络 (DNN) 模型已经应用于各种应用,例如分类、图像识别和语音翻译。通常,这些模型中的每一个都是为非常特定的目的而设计的,但可以扩展到新的用例。例如,可以训练一个模型来识别图像中的数字和字符,然后重用该模型来读取更广泛的模型中的路标或自动驾驶中使用的数据集。

在这篇博文中,我们将

  1. 解释迁移学习及其一些应用
  2. 解释如何使用 Neon 进行迁移学习
  3. 逐步讲解使用 Neon* 将预训练模型迁移到新数据集的示例代码
  4. 讨论迁移学习的优点及一些结果

迁移学习

考虑视觉分类任务。卷积神经网络 (CNN) 组织成多个层,每个层学习不同尺度的特征。较低层识别低级特征,例如猫的皮毛或砖墙的纹理。较高层识别更高级的特征,例如步行行人的身体形状或汽车中窗户的配置。

在各种尺度上学习到的特征为各种分类任务提供了出色的特征向量。它们与人类操作员开发的基于核算法获得的特征向量根本不同,因为这些特征向量是在广泛的训练运行之后学习到的。这些训练运行旨在系统地完善模型参数,使预测输出 yp=f(xt)(其中 xt 是观察到的真实世界信号,f() 是模型)与实际值 yt 之间的典型误差尽可能小。

有几个例子可以重用经过良好训练的 CNN 学习到的特征。Oquab 等人

[1] 展示了如何使用在单个对象图像上训练的 AlexNet 模型的特征来识别现实世界中更复杂图像中的对象。Szegedy 等人 [2] 表明,给定一个非常深的神经网络,仅通过网络一半层学习到的特征也可以用于视觉分类。Bell 等人 [3] 表明,通过各种预训练 CNN(如 AlexNet 和 GoogLeNet)学习到的材质特征(如木材、玻璃等)可以用于其他相关任务,例如图像分割。预训练网络学习到的特征之所以效果如此好,是因为它们捕获了数据中的一般统计数据、空间相干性和分层元关系。

使用 Neon 进行迁移学习

Neon 不仅擅长 DNN 的训练和推理,还提供了一个丰富的生态系统,支持围绕 DNN 的各种需求。例如,您可以序列化学习到的模型,加载预训练或部分训练的模型,从行业专家构建的多个 DNN 中进行选择,并在云中运行它,无需任何自己的物理基础设施。您可以在此处获取 Neon API 的良好概述。

您可以加载模型的预训练权重,并通过以下两行代码在每层级别访问它们

from neon.util.persist import load_obj 
pre_trained_model = load_obj(filepath)
pre_trained_layers = pre_trained_model['model']['config']['layers']

然后,您可以使用以下一行代码将这些预学习层的权重迁移到您自己模型中兼容的层

[代码]layer_of_new_model.load_weights(pre_trained_layer, load_states=True)[/代码]

然后,将权重从预学习模型迁移到您模型的少数选定层的任务就变得简单明了

new_layers = [l for l in new_model.layers.layers]
for i, layer in enumerate(new_layers):
    if load_pre_trained_weight(i, layer):
        layer.load_weights(pre_trained_layers[i], load_states=True)

就是这样!您已选择性地将预训练模型迁移到 Neon 中。在本文的其余部分,我们将讨论

  1. 如何构建新模型
  2. 如何选择性地编写代码并最大限度地重用 Neon 框架以及
  3. 如何在 Neon 中快速训练新模型以达到非常高的精度,而无需进行大量的重新训练。我们将在实现 Oquab 等人 [1] 的工作时讨论这一点。

使用在单个对象上训练的权重进行通用场景分类

ImageNet 是一个非常流行的数据集,其中训练数据集的图像主要代表了 1000 个不同类别的单个对象。它是获取代表单个对象的特征向量的优秀数据库。然而,现实世界中拍摄的图片往往要复杂得多,在单个图像中以各种尺度捕获了许多对象实例。遮挡使这些场景进一步复杂化。如下图所示,您会发现许多不同尺度和遮挡程度的人和牛的实例。

在此类图像中进行分类通常使用两种技术:1) 使用滑动多尺度采样器,尝试对图像的小部分进行分类,2) 选择性地馈送由更复杂算法发现的区域提议,然后将其馈送到 DNN 进行分类。使用 Fast R-CNN[4] 实现后一种方法可以在此处找到。Fast R-CNN 也使用迁移学习来加速其训练速度。在本节中,我们将讨论更容易实现的前一种方法。我们的实现可以在此处找到。我们的实现使用在 ImageNet 数据集上预训练的 AlexNet 模型在 Pascal VOC 数据集上进行训练。

实现的核心结构很简单

def main():
 
    # Collect the user arguments and hyper parameters
    args, hyper_params = get_args_and_hyperparameters()
 
    # setup the CPU or GPU backend
    be = gen_backend(**extract_valid_args(args, gen_backend))
 
    # load the training dataset. This will download the dataset 
    # from the web and cache it locally for subsequent use.
    train_set = MultiscaleSampler('trainval', '2007', ...)
 
    # create the model by replacing the classification layer 
    # of AlexNet with new adaptation layers
    model, opt = create_model( args, hyper_params)
 
    # Seed the Alexnet conv layers with pre-trained weights
    if args.model_file is None and hyper_params.use_pre_trained_weights:
        load_imagenet_weights(model, args.data_dir)
 
    train( args, hyper_params, model, opt, train_set)
 
    # Load the test dataset. This will download the dataset
    # from the web and cache it locally for subsequent use.
    test_set = MultiscaleSampler('test', '2007', ...)
    test( args, hyper_params, model, test_set)
 
    return

创建模型

我们新的神经网络的结构与预训练的 AlexNet 相同,只是我们将其最终分类层替换为两个仿射层和一个 dropout 层,用于使训练到 ImageNet 标签的神经网络适应 Pascal VOC 数据集的新标签集。凭借 Neon 的简洁性,这相当于替换以下代码行(参见 create_model()

# train for the 1000 labels of ImageNet
Affine(nout=1000, init=Gaussian(scale=0.01), 
       bias=Constant(-7), activation=Softmax())

替换为这些

Affine(nout=4096, init=Gaussian(scale=0.005), 
       bias=Constant(.1), activation=Rectlin()),
Dropout(keep=0.5),
# train for the 21 labels of PascalVOC
Affine(nout=21, init=Gaussian(scale=0.01), 
       bias=Constant(0), activation=Softmax())

由于我们已经在使用预训练模型,所以我们只需要进行大约 6-8 个训练周期。因此,我们将使用 0.0001 的小学习率。此外,我们每隔几个周期就会积极地降低学习率,并使用较高的动量分量,因为预学习的权重已经接近局部最小值。这些都是作为超参数设置完成的

if hyper_params.use_pre_trained_weights:
    # This will typically train in 5-10 epochs. Use a small learning rate
    # and quickly reduce every few epochs. 
    s = 1e-4
    hyper_params.learning_rate_scale = s
    hyper_params.learning_rate_sched = Schedule(step_config=[15, 20], 
                                                change=[0.5*s, 0.1*s])
    hyper_params.momentum = 0.9
else: 
    # need to actively manage the learning rate if the 
    # model is not pre-trained
    s = 1e-2
    hyper_params.learning_rate_scale = 1e-2
    hyper_params.learning_rate_sched = Schedule(
                            step_config=[8, 14, 18, 20], 
                            change=[0.5*s, 0.1*s, 0.05*s, 0.01*s])
    hyper_params.momentum = 0.1

这些强大的超参数在 create_model() 中通过一行代码强制执行

opt = GradientDescentMomentum(hyper_params.learning_rate_scale, 
                              hyper_params.momentum, wdecay=0.0005,       
                              schedule=hyper_params.learning_rate_sched)

多尺度采样器

2007 年 Pascal VOC 数据集为每张图像提供了多个感兴趣区域 (ROI) 的矩形区域,并为每个 ROI 提供了标签。Neon 附带了 Pascal VOC 数据集的加载器。我们将通过创建一个派生自该数据集的 PASCALVOCTrain 类的类来创建数据集加载器。

我们将以 [1.、1.3、1.6、2.、2.3、2.6、3.0、3.3、3.6、4.、4.3、4.6、5.] 的逐级细化尺度对输入图像进行采样,并收集 448 个补丁。在给定尺度上的采样过程很简单(参见 compute_patches_at_scale())

size = (np.amin(shape)-1) / scale
num_samples = np.ceil( (shape-1) / size)

由于补丁是生成的而不是从真实数据中派生的,我们需要为其分配一个标签。补丁被分配给与其显著重叠的 ROI 的标签。我们选择的重叠标准是补丁面积至少有 20% 需要与 ROI 重叠,并且该 ROI 面积至少有 60% 需要被重叠区域覆盖。如果对于给定补丁,没有 ROI 或有多个 ROI 符合此标准,我们将该补丁标记为背景(参见 get_label_for_patch())。通常,背景补丁占主导地位。在训练期间,我们偏向于采样更多非背景补丁(参见 resample_patches())。所有采样都在 MultiscaleSampler 的 __iter__() 函数中动态完成。当 Neon 要求数据集提供下一个小批量数据时,会调用此函数。此过程的动机在 [1] 的图 4 中有所说明

我们将此补丁采样方法用于训练和推理。MultiscaleSampler 向 Neon 提供了一个小批量的输入和标签数据,而 Neon 甚至不知道正在进行元形式的多尺度学习。由于每张图像的补丁数量多于小批量大小,因此单个图像在训练和推理期间都会提供多个小批量。在训练期间,我们简单地使用 Neon 附带的 CrossEntropyMulti 成本函数。在推理期间,我们通过定义自己的成本函数来利用 Neon 的灵活性。

推理

我们在推理过程中进行多类分类,通过预测图像中特定对象标签的存在或不存在。我们通过将类别预测与指数进行偏斜,并将此偏斜值累积到图像上推断出的所有补丁中,从而按类别执行此操作。换句话说,图像 i 中类别 c 的分数 S(i,c) 是该类别 c 的单个补丁分数 P(j,c) 的总和,并提高到指数。

这是由 ImageScores 类实现的,分数计算可以用两行代码表示(参见 __call__()

exp = self.be.power(y, self.exponent)
self.scores_batch[:] = self.be.add(exp, self.scores_batch)

这种评分技术的直觉在 [1] 的图 5 和图 6 中有所说明。

结果

以下是测试数据集的结果。预测质量通过平均精度指标衡量。整体平均精度 (mAP) 为 74.67。对于一个相当简单的实现来说,这些都是不错的数字。它只需要 15 个训练周期,而预训练模型则需要 90 多个训练周期。此外,如果考虑到预训练模型中的超参数优化,我们节省了大量的计算。

飞机自行车瓶子公交车汽车cat椅子
AP81.1779.3281.2174.8452.8974.5787.7278.5463.0069.57
餐桌摩托车植物沙发火车电视
AP58.2874.0677.3879.9190.6969.0578.0259.5581.3282.26

正如预期的那样,使用预训练模型训练收敛速度更快,如下图所示。

以下是一些运行示例的有用提示

  1. 使用此命令开始全新的训练运行
    [代码]./transfer_learning.py -e10 -r13 -b gpu –save_path model.prm –serialize 1 –history 20 > train.log 2>&1 &[/代码]
  2. 使用此命令运行测试。确保此命令中 -e 选项指定的周期数为零。这确保 Neon 将跳过训练并直接跳转到测试。
    [代码]./transfer_learning.py -e0 -r13 -b gpu –model_file model.prm > infer.log 2>&1 &[/代码]
  3. 如果您在包含 5000 张图像的完整训练数据集上进行训练,每个训练周期可能需要 4-6 小时。如果您出于某种原因不得不终止训练任务,您始终可以使用此命令从上次保存的周期重新开始。
    [代码]./transfer_learning.py -e10 -r13 -b gpu –save_path train.prm –serialize 1 –history 20 –model_file train.prm > train.log 2>&1 &[/代码]

我们使用的预训练模型可以在此处找到。

经过迁移学习后获得的完全训练模型可以在此处找到。

您可以使用训练好的模型,使用 AlexNet 在 Pascal VOC 数据集上进行分类。

参考文献

[1] M. Oquab 等人,使用卷积神经网络学习和迁移中层图像表示,CVPR 2014。
[2] C. Szegedy 等人,重新思考计算机视觉的 Inception 架构。2015
[3] S. Bell, P. Upchurch, N. Snavely 和 K. Bala。使用上下文数据库中的材料识别野外材料。CVPR 2015。
[4] R. Girshick。快速 R-CNN。2015。

关于作者

Aravind Kalaiah 是一位技术负责人,在构建用于实时查询处理和高性能计算的可伸缩分布式系统方面经验丰富。他曾是 NVIDIA 的技术负责人,是构建世界上第一个大规模并行处理器调试器的团队的创始工程师。Aravind 之前曾创办过在机器学习和计算机视觉领域为企业和消费者市场创造收入的初创公司。

© . All rights reserved.