本教程改编自2019年的Fastai DL第1课,并添加了我的许多补充和说明。
在本教程之后,您将能够在您选择的任何图像数据集上构建和训练图像识别器,同时充分了解底层模型架构和训练过程。
本教程包括:
- 数据提取
- 数据可视化
- 模范训练:CNN、ResNets、迁移学习
- 结果解释
- 模型层的冻结和解冻
- 微调:学习速率查找器,一周期策略
本教程主要针对的是一些深度学习实践者,任何想要使用CNN和ResNets“刷”图像分类基础知识的人,或任何没有使用过fastai库并希望试用它的人。
本教程的笔记本也可以在这里找到。https://github.com/SalChem/Fastai-iNotes-iTutorials/blob/master/Image_Recognition_Basics.ipynb
要运行笔记本,您只需使用Google Colab打开它即可。
笔记本电脑都是独立的,没有bug,所以你可以按原样运行它。
进入Colab后,请务必更改以下内容以启用GPU后端,
运行时 - >更改运行时类型 - >硬件加速器 - > GPU
本教程中的代码简要说明。有关任何类、方法等的进一步文档可以在fastai docs| fastai上找到。
让我们开始吧…
设置IPython内核并初始化
导入必要的库,
我们做一些初始化,
bs是我们的批量大小,即一次输入模型的训练图像的数量。模型参数会在每批迭代之后更新。
例如,如果我们有640个图像,我们的批量大小为64;参数将在1个历元的过程中更新10次。
如果您在本教程中的某个时刻遇到内存不足的情况,则较小的批处理大小可以提供帮助,批次大小通常是2的倍数。
使用特定的值初始化上面的伪随机数生成器可使系统稳定,从而产生可重现的结果。
1.数据提取
我们将使用的数据集是Oxford-IIIT Pet Dataset,它可以使用fastai数据集模块检索。
URLs.PETS是数据集的URL,它有12个猫品种和25个狗品种,untar_data解压并将数据文件下载到我们的路径中。
get_image_files获取images目录中包含的所有文件的路径,并将它们存储到fnames中。来自fnames的实例如下所示,
PosixPath('/home/jupyter/.fastai/data/oxford-iiit-pet/images/scottish_terrier_119.jpg')
由于每个图像的标签都包含在图像文件名中,我们将使用正则表达式来提取它。正则表达式(通常缩写为正则表达式)是描述一定量文本的模式。我们提取图像标模式如下,
后一步是特定于此数据集的。例如,如果属于同一类的图像位于同一文件夹中,我们不必担心它。
现在让我们创建我们的训练和验证数据集,
ImageDataBunch根据路径path_img中的图像创建训练数据集train_ds和验证数据集valid_ds。
from_name_re使用在编译表达式模式pat后获得的正则表达式从文件名称列表中获取标签。
df_tfms是动态应用于图像的转换。在这里,图像将调整为224x224,居中,裁剪和缩放。这种转换是数据增强的实例,已经证明在计算机视觉中很有前景。此类转换不会更改图像内部的内容,但会更改其像素值以获得更好的模型泛化。
normalize使用ImageNet图像的标准偏差和平均值对数据进行标准化。
2.数据可视化
训练数据样本表示为
(Image (3, 224, 224), Category scottish_terrier)
其中个元素表示图像3RGB通道、行和列。第二个元素是图像标签。
这个实例的相应图像是,
len(data.train_ds)和len(data.valid_ds)分别输出训练和验证样本的数量5912和1478。
data.c和data.classes分别输出类及其标签的数量。有37个类别,以下标签,
['Abyssinian', 'Bengal', 'Birman', 'Bombay', 'British_Shorthair', 'Egyptian_Mau', 'Maine_Coon', 'Persian', 'Ragdoll', 'Russian_Blue', 'Siamese', 'Sphynx', 'american_bulldog', 'american_pit_bull_terrier', 'basset_hound', 'beagle','boxer', 'chihuahua', 'english_cocker_spaniel', 'english_setter', 'german_shorthaired', 'great_pyrenees', 'havanese', 'japanese_chin', 'keeshond', 'leonberger', 'miniature_pinscher', 'newfoundland', 'pomeranian', 'pug', 'saint_bernard', 'samoyed', 'scottish_terrier', 'shiba_inu', 'staffordshire_bull_terrier', 'wheaten_terrier', 'yorkshire_terrier']
show_batch在批处理中显示少量的图像。
3.模型训练
cnn_learner使用来自给定架构的预训练模型构建CNN学习器。来自预训练模型的学习参数用于初始化我们的模型,允许更快的收敛和高精度。
这里使用的CNN架构是ResNet34,它在过去几年中取得了巨大成功,仍然被认为是先进的。
讨论CNN和ResNets非常有价值,因为这将有助于我们更好地理解我们的训练流程。我们可以?
CNNs简而言之:
首先,什么是卷积神经网络(CNN或convNet)?我们可以将ConvNet视为将图像卷转换为输出卷的图层列表,就像本教程中的情况一样,它可以是一个类得分。这些层由连接到前一层的其他神经元的神经元组成。为了你更好的进行阅读,我这里强烈推荐斯坦福大学CS231课程的卷积神经网络。
典型的CNN架构
该图展示了一个典型的convNet架构。我们可以将所有CNN架构视为不同可微函数(卷积、下采样和仿射变换)的各种组合。上图只有很少的层,但深层网络有几十到几百层。
ResNets简而言之:
深度网络中一个非常普遍的问题是降级问题,其中模型精度达到饱,然后迅速降级。这是违反直觉的,因为我们期望附加层应该能够实现更详细和抽象的表示。这个问题正是ResNets旨在解决的问题,因为它们可以安全地优化训练更深层次的网络,而不必担心降级问题。
ResNets解决降级问题的方法是引入“身份快捷连接”,跳过一个或多个层。跳过连接的输出被添加到堆叠层的输出中,如下图所示。跳过连接有效地跳过某些层上的学习过程,使深层网络在某种程度上也充当浅层网络。
Skip函数创建所谓的残差块,图中的F(x),这就是残差网这个名称的由来。传统网络的目标旨在直接学习输出H(x),而ResNets旨在学习残差F(x)。使F(x)= 0允许网络跳过该子网,如H(x)= x。
已经表明,添加这些身份映射允许模型更深入而不降低性能,并且这种网络比普通堆叠层更容易优化。 ResNets有几种变体,例如ResNet50、ResNet101、ResNet152; ResNet编号表示ResNet网络的层数(深度)。
在本教程中,我们使用的是ResNet34,如下所示,
ResNet34的体系结构和卷积内核
在图中,底部数字表示输入或特征地图大小(高度x宽度),上面的数字表示通道数(过滤器数量)。例如,个左块表示输入图像(224 x 224 x 3)。图中的每个“层”都包含很少的残余块,这些残余块又包含具有不同可微函数的堆叠层,从而导致34层端到端。下面是ResNet34架构的完整底层布局,与类似的普通架构相比;侧箭头表示身份连接。
平面34层CNN(左)和34层ResNet(右)
您可以随意尝试任何其他resnet,只需替换模型即可。resnet34by模型。resnet50或任何其他所需的架构。请记住,增加层数将需要更多的GPU内存。
我们上面描述的使用预训练模型并使其适应我们的数据集的内容称为迁移学习。但为什么要使用迁移学习呢?
迁移学习:
深度神经网络具有大量参数,通常在数百万的范围内。在小型数据集(一个小于参数数量的数据集)上训练此类网络会极大地影响网络的推广能力,从而导致过度拟合。因此在实践中,通过随机权重初始化从头开始训练网络是很少见的。
预训练模型通常在非常大的数据集上训练,例如ImageNet,其包含具有1000个类别的120万个图像。因此,预训练模型已经学会捕获其早期层中的曲线、颜色梯度和边缘等通用特征,这对于大多数其他计算机视觉分类问题是相关且有用的。迁移学习也被证明在其他领域也是有效的,例如NLP和语音识别。
现在,通过迁移学习,我们的模型已经在ImageNet上进行了预训练,我们只需要使其更具体地掌握我们数据集的细节。我们有两个选项可以做到这一点,我们只能更新后一层的参数,或者我们可以更新所有模型的图层。个选项通常称为特征提取,而第二个选项称为微调。在这两种方法中,由于ImageNet预训练模型的输出层的大小为1000,因此,重要的是首先对终层进行重新塑造,使数据集中的类数量相同。
到目前为止,我们已涵盖了许多核心概念
我们继续......
现在让我们在数据集上训练模型,
epochs数字表示模型查看整个图像集的次数。但是,在每个epoch,我们的数据增加后,相同的图像略有不同。
通常,度量误差将随着每个epoch而下降。只要验证集的精度不断提高,增加epoch数就是个好主意。然而,大量的epoch可能导致学习特定的图像而不是普通的类,这是我们想要避免的。
我们刚刚在这里进行的训练就是我们所说的特征提取,所以只更新了我们模型的头部(后一层)的参数。接下来我们将尝试微调所有层。
恭喜!该模型已成功训练,以识别狗和猫品种。
我们达到的准确率是≈93.5%
我们能做得更好吗?微调后我们会看到。
让我们保存当前的模型参数,以防我们稍后想要重新加载。
4.结果解释
现在让我们看看如何正确解释当前的模型结果。
Classification Interpretation提供错误分类图像的可视化。
plot_top_losses显示带有top loss的图像及其:
预测标签/实际标签/损失/实际图像类别的概率
高损失意味着对错误答案的高度信任。绘制高损失是可视化和解释分类结果的好方法。
具有高损失的错误分类图像
分类混淆矩阵
在混淆矩阵中,对角线元素表示预测标签等于真实标签的图像的数量,而非对角线元素是由分类器错误标记的元素。
most_confused简单地抓住预测和实际类别中混乱的组合;换句话说,经常出错的那些。我们可以看到它经常将斯塔福郡斗牛犬错误分类为美国斗牛梗,它们实际上看起来是有点相似的。
[('Siamese', 'Birman', 6),
('american_pit_bull_terrier', 'staffordshire_bull_terrier', 5),
('staffordshire_bull_terrier', 'american_pit_bull_terrier', 5),
('Maine_Coon', 'Ragdoll', 4),
('beagle', 'basset_hound', 4),
('chihuahua', 'miniature_pinscher', 3),
('staffordshire_bull_terrier', 'american_bulldog', 3),
('Birman', 'Ragdoll', 2),
('British_Shorthair', 'Russian_Blue', 2),
('Egyptian_Mau', 'Abyssinian', 2),
('Ragdoll', 'Birman', 2),
('american_bulldog', 'staffordshire_bull_terrier', 2),
('boxer', 'american_pit_bull_terrier', 2),
('chihuahua', 'shiba_inu', 2),
('miniature_pinscher', 'american_pit_bull_terrier', 2),
('yorkshire_terrier', 'havanese', 2)]
5.冷冻和解冻
默认情况下,在fastai中,使用预先训练的模型会冻结较早的层,以便网络只能更改后一层的参数,如上所述。冻结层并仅训练较深层可以显著减少大量计算。
我们总是可以通过调用unfreeze函数来训练所有网络层,然后是fit或fit_one_cycle。这就是我们所谓的微调,因为我们正在调整整个网络的参数。我们开始做吧,
现在的准确度比以前差一点。这是为什么?
这是因为我们以相同的速度更新所有层的参数,这不是我们想要的,因为层不需要像后一层那样需要做太多改变。控制权重更新量的超参数称为学习率,也称为步长。它根据损失的梯度调整权重,目的是减少损失。例如,在常见的梯度下降优化器中,权重和学习率之间的关系如下,
顺便说一下,梯度只是一个向量,它是导数的多变量泛化。
因此,对模型进行微调的更好方法是对较低层和较高层使用不同的学习速率,通常称为差异或判别学习速率。
顺便说一句,我在本教程中可以互换使用参数和权重。更准确地说,参数是权重和偏差,但我们不需要担心这里的细微之处。但请注意,超参数和参数不同;超参数无法在训练中估计。
6.微调
为了找到适合微调模型的学习率,我们使用学习速率查找器,其中学习速率逐渐增加并且在每批之后记录相应的损失。 fastai库在lr_find中实现了这一点。如需进一步阅读,请查看@GuggerSylvain如何找到良好的学习率。
让我们加载之前保存的模型并运行lr_find,
recorder.plot方法可用于绘制损失与学习率的关系。当损失开始分歧时,情节会停止。
从得到的图中,我们一致认为适当的学习率约为1e-4或更低,在损失开始增加并且失去控制之前。我们将1e-4分配给后的层,将1e-6分配给较早的层。同样,这是因为早期的层已经训练有素,可以捕获通用功能,并且不需要那么多的更新。
如果您想知道我们之前的实验中使用的学习率,因为我们没有明确声明它,它是0.003,这是库中默认设置的。
在我们使用这些判别性学习率训练我们的模型之前,让我们揭开fit_one_cycle和fitmethods之间的差异,因为两者都是训练模型的合理选择。这个讨论对于理解训练过程非常有价值,但可以直接跳到结果。
fit_one_cycle vs fit:
简而言之,不同之处在于fit_one_cycle实现了Leslie Smith 1循环策略,它不是使用固定或降低的学习速率来更新网络的参数,而是在两个合理的较低和较高学习速率范围之间振荡。让我们再深入了解一下这对我们的训练有何帮助。
训练中的学习率超参数
在调整我们的深度神经网络时,良好的学习率超参数是至关重要的。高学习速率允许网络更快地学习,但是太高的学习速率可能使模型无法收敛。另一方面,较小的学习率会使训练进度非常缓慢。
各种学习率对收敛的影响
在我们的案例中,我们通过查看不同学习率下记录的损失来估算适当的学习率(lr)。在更新网络参数时,可以将此学习速率用作固定值;换句话说,将通过所有训练迭代应用相同的学习率。这就是learn.fit(lr)所做的。一种更好的方法是随着训练的进行改变学习率。有两种方法可以做到这一点,即学习速率计划(基于时间的衰减、逐步衰减、指数衰减等)或自适应学习速率方法(Adagrad,RMSprop,Adam等)。有关此内容的更多信息,请查看有关参数更新的CS230 Stanford类注释。另一个很好的资源是@Sebastian Ruder对梯度下降优化算法的概述。
简而言之,一个周期策略
一个周期策略是一种学习速率调度器,其允许学习速率在合理的小和大边界之间振荡。这两个界限的价值是什么?上限是我们从学习速率查找器获得的,而小界限可以小10倍。这种方法的优点是它可以克服局部极小点和鞍点,鞍点是平面上具有典型小梯度的点。事实证明,1cycle策略比其他调度或自适应学习方法更快、更准确。 Fastai在fit_one_cycle中实现了1cycle策略,该策略在内部调用fit方法和1cycle Scheduler回调。
在fastai实现中对1cycle策略的一个小修改包括在从lr_max到0的第二阶段中的余弦退火。
1cycle策略发现
莱斯利史密斯首先发现了一种他称为循环学习率(CLR)的方法,他表明CLR在计算上并不昂贵,并且它们不需要找到佳学习速率值,因为佳学习速率将介于小和大界限之间。然后,他使用另一种规范的神经网络超参数方法来跟踪该论文:第1部分 - 学习率、批量大小、动量和权重衰减,他强调了各种评论和建议,以便加快网络训练以产生佳结果。其中一个建议是使用仅一个周期的CLR来实现佳和快速的结果。作者将方法命名为1cycle策略。
下图展示了如何使用56层残差网络架构,在Cifar-10更少的迭代中,超收敛方法比典型的(分段常数)训练机制达到更高的精度。
在Cifar-10上,超收敛精度测试与具有相同架构的典型训练机制的比较[Source]
如果你选择跳过阅读莱斯利史密斯的论文,我仍然会建议阅读这篇文章@GuggerSylvain的1cycle策略。
关键时刻
现在,我们为层选择了有区别的学习率,我们可以解冻模型并相应地进行训练。
切片函数将1e-4赋值给后一层,将1e-6赋值给层;中间的层在此范围内以相等的增量获得学习率。
我们看到准确度有所改善,但并不多,所以我们想知道是否需要对模型进行微调?
在微调任何模型之前始终要考虑的两个关键因素,数据集的大小及其与预训练模型的数据集的相似性。查看斯坦福大学关于何时以及如何微调的CS231笔记?在我们的例子中,我们的Pet数据集类似于ImageNet中的图像,并且它相对较小,这就是为什么我们从一开始就实现了高分类精度而没有对整个网络进行微调。
尽管如此,我们仍然能够提高我们的结果并学到很多东西,所以非常棒的工作
下图说明了使用和微调预训练模型的三种合理方法。在本教程中,我们尝试了个和第三个策略。策略2在数据集较小但与预训练模型的数据集不同或者数据集较大但与预训练模型的数据集相似的情况下也很常见。
在预先训练的模型上微调策略
恭喜,我们已经成功地使用先进的CNN覆盖了图像分类,并具有基础结构和训练流程的坚实基础。
您已准备好在自己的数据集上构建图像识别器。如果您还没有,可以从Google图像中删除图像并组成数据集。我为此做了一个非常简短的教程。https://towardsdatascience.com/https-medium-com-drchemlal-deep-learning-tutorial-1-f94156d79802