论智
首发于论智
从fast.ai学到的十大技巧:如何在几周内上手顶级算法

从fast.ai学到的十大技巧:如何在几周内上手顶级算法

作者:Samuel Lynn-Evans

编译:Bot

编者按:此前,论智曾写过一篇Kaggle竞赛方案分享:如何分辨杂草和植物幼苗,介绍了当时排名第五的开发者Kumar Shridhar的实战思路。同样是这个竞赛,自参赛起,fast.ai联合创始人Jeremy Howard的名次却经历连连暴跌,最后止步第105名。那么这位明星数据科学家究竟遭遇了何方狙击?没错,就是他自己的学生,而这些新手最后也成功霸榜Kaggle。
当我离开你时,我只是求学者,但现在,我才是王者

随着互联网和知识传播的深度结合,现在在线课程对许多人来说已经不是新鲜事物,在深度学习领域,最受学生欢迎的MOOC课程平台有三个:Fast.ai、deeplearning.ai /Coursera和Udacity。其中,因为Jeremy Howard化繁为简、实战为上的独特授课风格,Fast.ai给人的印象一直很“接地气”,而植物幼苗分类赛的结果也证实了课程的教学效果。

那么这些新手在短短几周内就学会使用顶级算法的秘诀是什么?是什么让他们能在竞赛中击败拥有大量GPU的资深深度学习专家?下文是你想知道的所有答案。

如果你已经上手深度学习,并希望快速了解Fast.ai课程中使用的强大技术,请继续阅读。 如果你已经完成Fast.ai课程,并希望回顾所学内容,请继续阅读。 如果你正准备入门深度学习,并希望了解Fast.ai对初学者的帮助和这个行业的未来发展,请继续阅读。

首先,在正式开始前,我们都应该知道,如果要有效学习Fast.ai的课程内容,云GPU必不可少。这里我们先介绍一个好用的工具——FloydHub,对于初学者来说,这是训练深度学习模型最好的、也是最简单的方法。法国的Ecole 42非常喜爱这个工具,我们也可以借此为接触更多有趣实现做准备,如:

下面,让我们正式开始!

1. 使用Fast.ai库

from fast.ai import *

Fast.ai库不仅是让新手快速构建深度学习实现的工具包,它也是提供最佳实践的一个强大而便捷的资源。每当Fast.ai团队(包括AI研究人员和合作者网络)发现一篇特别有趣的论文,他们就会在各种数据集上测试,然后找出调整优化方法。如果这些成果确实是有效的,它们会陆续出现在库中,以便用户快速接触新技术。

这样做的结果是Fast.ai库现在已经成为一个功能强大的工具箱,比如去年学界公认的深度学习年度进展:SGDR、循环学习,现在所有Fast.ai用户都可以快速访问,并把它们用于自己的实现。

这个库基于PyTorch构建,使用流畅,用户体验很好。

2. 使用多个学习率,而不是一个

使用不同的学习率意味着在训练期间,神经网络前几层的变化比后几层更多。在计算机视觉任务中,现在通行的一种做法是直接在现有架构上构建深度学习模型,实践证明这样做模型的性能更好。

而大多数架构,如Resnet、VGG、inception等都是在ImageNet上经过预训练的,如果要使用它们,我们必须考量手头数据集和ImageNet图像的相似程度,并以此对权重做或多或少的调整。在修改权重时,模型的最后几层调整幅度更大,而用于检测基础特征的层(比如边缘和轮廓)则只需极少调整。

下面是一些代码示例,首先,我们从Fast.ai库里获得预训练模型:

from fastai.conv_learner import *

# 导入用于创建卷积学习对象的库 #选择VVG16

# 将模型分配给resnet、vgg,甚至是你自己的自定义模型
PATH = './folder_containing_images' 
data = ImageClassifierData.from_paths(PATH)

# 创建fast ai数据对象,这里我们用from_paths
# 其中PATH指向每个图像类所属的不同文件夹

learn = ConvLearner.pretrained(model, data, precompute=True)

# 创建一个学习对象,以便快速调用Fast.ai库里的先进算法

通过创建好的学习对象,我们可以冻结最后一层以前的所有层,单独调整最后一层的参数:

learn.freeze()

# 冻结最后一层之前的所有层,保持它们的参数不变

learning_rate = 0.1
learn.fit(learning_rate, epochs=3)

# 只训练最后一层几个epoch

如果调参结果不错,我们就可以在不同层使用不同的学习率,比如中间几层参数变化幅度没最后几层那么大,所以它的学习率可以是后者的1/10。

learn.unfreeze()

# 将所有图层的requires_grads设置为True,以便进行更新

learning_rate = [0.001, 0.01, 0.1]
# first layer的学习率是0.001,middle layer的是0.01,final layer则是0.1.

learn.fit(learning_rate, epochs=3)
# 用不同学习率训练模型三个epoch

3. 如何找到合适的学习率

学习率是训练神经网络最重要的一个超参数,但直到最近,许多人才发现以前设置学习率的方式非常不当。去年,Leslie N. Smith在arXiv上提交了一个预印本:Cyclical Learning Rates for Training Neural Networks。他在文中提出一种确定学习率的新方法:循环学习率,即不使用固定值,而是用一个在合理阈值内循环变化的数值,实验证明它可以减少迭代次数,提高模型分类准确率。文章一经发布,Fast.ai就立马推广了这种方法。

对于这种方法,我们可以从一个较低的学习率开始训练神经网络,然后随着迭代进行,逐渐对学习率做指数增加。以下是示例代码:

learn.lr_find()
# 随着学习率呈指数增长,训练学习对象

learn.sched.plot_lr()
# 绘制学习率和迭代的进展图
学习率随迭代呈指数上升

同时,记录不同学习率时每个值的损失,并绘制相关图像:

learn.sched.plot()
# 学习率和损失的关系图 
损失减少

在上图情况下,我们可以确定的最佳学习率是0.01。

4. 余弦退火

随着每个batch随机梯度下降(SGD)的进行,神经网络的损失会逐渐接近全局最小值,相应的,学习率也应该变得更小,防止算法超调。余弦退火是一种将学习率设置为随模型迭代轮数不断改变的方法,因为更新学习率用的是cos(),所以称余弦。如下图所示:

随着x增加,cos(x)会不断缩小

当我们不断增加x时,cos(x)的变化是一个先缓慢后急剧再缓慢的过程,这种下降趋势十分符合学习率,因此也可以提高模型性能。

learn.fit(0.1, 1)
# 在Fast.ai库中调用learn.fit函数可以直接使用余弦退火

5. SGDR

在训练期间,梯度下降可能会陷入局部最小值而不是全局最小值。

梯度下降可能会陷入局部最小值

这时,通过突然提高学习率,梯度下降可以“跳出”局部最小值,重新回归寻找全局最小值的正轨。这种方法被称为热重启随机梯度下降(SGDR),它首次出现在德国弗莱堡大学的论文SGDR: Stochastic Grandient Descent with warm Restarted中,这也是ICLR 2017的重磅成果。

现在,SGDR已经加入Fast.ai库,当用户调用learn.fit(learning_rate, epochs)时,每个epoch的学习率会被重新设置成原始超参数,然后在用余弦退火逐渐缩小。

每个epoch的学习率回归原始值

每次学习率下降到最低点,我们就称之为一个循环。

cycle_len = 1
# 决定学习率降到最低要几个epoch
# 在这种情况下,1个epoch

cycle_mult=2
# 在每个循环结束时,将cycle_len值乘以2

learn.fit(0.1, 3, cycle_len=2, cycle_mult=2)
# 在这种情况下,将重启3次
# 第一次的cycle_len为1,所以我们用1个epoch完成循环
# cycle_mult=2,所以下个循环是2个epoch
# 然后是4个epoch,以此类推
每个循环所需epoch是上个循环的两倍

使用这种方法可以帮助开发者在图像分类问题中占据先机。

6. 把激活函数想象成人

Softmax是一个专一的家伙,只喜欢挑选一个目标;Sigmoid只想知道你在-1和1之间的位置,无论你增加/减少得多厉害,他完全不care;Relu是一名称职的夜店门卫,如果颜值为负,你就别想过这道门。

以上述方式看待激活函数纵然有点蠢,但至少它区分了三种函数的不同,可以有效防止误用。Jeremy Howard曾表示,他在许多学术论文中都看到过把Softmax用于多元分类,而文章、博客中的激活函数滥用更不鲜见。

7. 迁移学习对NLP任务非常有用

众所周知,迁移学习在计算机视觉中的效果非常出色,而随着研究人员的不断探索,如今越来越多线索开始指向另一个现实:自然语言处理(NLP)模型同样能从迁移学习中收益颇多。

在fast.ai的第4课中,Jeremy Howard构建了一个用于分类IMDB电影评论消极与否的模型,他把迁移学习思想引入模型,发现模型的准确率远超Bradbury等人的最先进成果,效果立竿见影。

他加入了一个预训练模型

而这个做法的成功秘诀是先训练一个模型,让它对语言产生基础理解,然后再把这个预训练模型作为模型的一部分用于情感分析。为了构建这第一个模型,我们需要让RNN学会预测文本序列中的下一个词,也就是语言建模。一旦模型训练完毕,性能很好,第二个新模型就能利用它对每个次的编码分析影评是积极的还是消极的。

虽然课程示例是个情感分析模型,但我们也可以把它用到其他NLP和计算机视觉任务中。

8. 用深度学习处理结构化数据

在介绍机器学习和深度学习优势时,我们一般会夸它们可以处理非结构化数据,认为这是统计学无法做到的,但Fast.ai反其道而行之,他们用深度学习实现了在结构化数据上快速生成出色结果,而无需借助特征工程和应用领域的特定知识。

他们的库充分利用了PyTorch的嵌入功能,允许将分类变量快速转换为嵌入矩阵。当然,课程中展示的技术相对较简单,只是将分类变量转换为数字,然后为每个值分配嵌入向量:

为一周中的每一天嵌入4个值

与创建虚拟变量(one-hot编码)的传统方法相比,这样做的好处是对于每一天,我们可以用4个值代替一个值,从而创建维度更高、更丰富的矩阵。

9. 竞赛致胜关键:扩大图像尺寸、Dropout和TTA

4月30日,在斯坦福大学举办的DAWNBench中,fast.ai团队一举赢得Imagenet和CIFAR10分类竞赛,之后Jeremy Howard写了一篇获胜感言,他把竞赛结果归功于fast.ai库中的一些独特工具。

两年前Geoffrey Hinton提出的Dropout是其中之一。尽管论文发布之初,学界对这个概念十分追捧,但它在计算机视觉领域却一直不受重视。幸好,现在有了PyTorch,如果我们用PyTorch实现Dropout,它会变得异常简单,而如果用了fast.ai库,整个过程就更简单了。

空格表示Dropout函数激活的区域

Dropout可以很好地防止模型过拟合,这对于在CIFAR10这样的小型数据集上构建分类器是非常重要的。在创建学习对象时,fast.ai会自动执行Dropout,但它可以自定义修改:

learn = ConvLearner.pretrained(model, data, ps=0.5, precompute=True)
# 在测试集上创建0.5的Dropout(激活的一半)
# 验证集会自动关闭此功能 

除此之外,他们采用的另一种方法是先在较小的图像上训练,然后扩大图像尺寸,在用相同的模型在上面训练。这样做可以有效防止过拟合,同时提高模型性能。

# 基于某尺寸图像创建数据对象 
def get_data(sz): 
    tmfs = tfms_from_model(model, sz)
    # 指明图像像素尺寸
    # 这里可以轻松进行数据增强,如翻转、缩放

    data = ImageClassifierData.from_paths(PATH, tfms=tfms)
    # 创建和上面尺寸一样大小的fastai数据对象

    return data

learn.set_data(get_data(299))
# 把学习对象的图像尺寸改成299
# 记住不要改动模型

learn.fit(0.1, 3)
# 在这些大尺寸图像上训练几个epoch,防止过拟合

最后一种方法则是测试时数据增强(TTA),也就是把测试集里的原始图像做裁剪、缩放,转换成一系列不同的图像,然后用于图像测试。这之后,我们计算不同版本的平均输出,并将其作为图像的最终分数,这可以通过调用learn.TTA()直接实现:

preds, target = learn.TTA()

10. 创造力是关键

fast.ai团队不仅在DAWNBench竞赛中赢得了训练速度最快奖(3小时),也把成本压缩到25美元,堪称奇迹。这里我们可以学到的经验是,创建一个成功的深度学习模型并不意味着投入更多GPU,创造力、想法和创新可以为我们打开另一扇窗。

本文提到的大多数学术突破也是创造力的一个佐证,当别人用千篇一律的做法解决问题时,这些学者想到了不同的方法,而且这些创新确实有效。虽然硅谷的大公司拥有海量GPU,这是常人不敢奢望的,但我们要勇于发起挑战,创造出属于自己的特殊的、新的东西。

有时候,现实的挤压也是一种机遇,毕竟必要性是成功之母。

编辑于 2018-08-07

文章被以下专栏收录

    专注于人工智能新技术、新应用 【公众号:论智 (jqr_AI) 】