人工智能看“手相”?这回不是玄学,而是Fast AI的经典案例!
点击上方关注,All in AI中国
作者——Zahar Chikishev
利用较小的训练数据集检测图像的关键点位置。
要训练一个可以准确预测手指和手掌线条位置的网络,需要多少张贴有标签的图像?我的灵感来自于这篇博客(https://towardsdatascience.com/fun-with-small-image-data-sets-part-2-54d683ca8c96)。在这篇博客中,作者谈及,从每次训练的135张图片中区分出某一人是否配带眼镜的准确率高达97.5%。那么回到正文,从15个不同人的60幅贴有标签的图像中,我的任务能得到什么样的准确性?
12个检测点
其目的是准确估计一个手部图像12个点的x和y坐标。4个点在指尖,4个点在指根,另外4个点沿着掌心线等距,第一个点和最后一个点正好在这条线的起点和终点。
这些训练所需的照片是我朋友的照片。每人6张,左手3张,右手3张。这些照片是用不同的智能手机在不同白色背景和不同灯光条件下拍摄的。60张图片都是我手工标注的。
对于神经网络的处理,我使用基于PyTorch的fastai library v1.0.42(https://www.fast.ai/2018/10/02/fastai-ai/)。作为IDE的Jupyter笔记本,和我笔记本的NVidia GTX 960M 4Gb VRAM进行训练。我所得结果的总训练时间是25小时,考虑到这个GPU远远不是目前市场上最好的硬件,所以25个小时也还不错。
这个项目的主题是数据扩充,幸运的是Fast AI提供了高效的图像转换算法,并提供了干净的API来定义它们。让我们深入讨论细节。
数据和模型
将标记后的数据分为51幅训练图像和9幅验证图像。验证图像包括3张人的照片,以及6张既不出现在训练集上也不与训练中上的任何人共用背景或相机的照片。所有右手的图像在预处理中我们都进行了水平翻转(如下图所示)。
所有进行过标记的训练图片
在如此小的数据集上进行数据增强是必要的,我对进入神经网络的每个图像采取了随机缩放、旋转、亮度和对比度转换等等。Fast AI库允许我们容易地定义它。在底层,同样的仿射变换也应用于标签点。图像按照样本均值和序列集的方差进行归一化,其中每个RGB通道单独进行归一化,并按4:3的比例调整大小,更具体地说,是384×288像素。听起来有很多东西需要我们定义。但令人惊讶的是,整个数据定义可以归结为以下代码片段:
这个模型是标准的resnet34。从resnet34中删除最后两个分类层,然后加上1x1卷积来减少通道数,并与两个完全连接的层相连。第二个全连通层输出24个激活,经过双曲正切激活函数后表示为12个关键点的x、y坐标。
但有时用代码说话更好:
代码头部的定义使用了常规的PyTorch语法和模块,除了我写的重塑模块之外,它还重构了张量。这个重塑是必要的,因为我的标签坐标在内部由Fast AI表示为12×2张量,它需要匹配。另外,标签被Fast AI重定位到[-1;1]范围,所以这里使用双曲正切激活函数比较合适。
优化目标是使训练中的L1损失最小化。
有两个额外的准确性指标来判断网络的性能和进度。第一个和第二个指标分别为0.1和0.01。即,在验证集上计算实际标签与预测坐标误差在0.1和0.01之间的比例。这里标签的范围也是[-1;1],给定图像大小为384×288像素。我们可以很容易地计算出第二个度量允许高度和宽度的最大误差分别为1.92和1.44像素。
神经网络训练
神经网络训练是通过运行这行代码40次来完成的:
除了使用Adam optimizer定期进行100个迭代次数的训练外,Fast AI方法还具较好的学习率和动量策略,这是Fast AI在广泛的应用中使用的一种更快收敛的方法。更多细节见Sylvain Gugger的博客(https://sgugger.github.io/the-1cycle-policy.html)。我发现它很适合我的模型。对于每100个训练周期而言,在50个训练周期之后,错误率比开始时要高,但是在周期结束时,错误总是会得到改善。如果你还是不明白,请看下面这张图片。
学习速率(左)和动量(右)在100个epoch变化。
迭代次数损失为2500到2600,每回迭代次数有8批。我们在迭代次数 2500中添加了更多的数据。
这个学习速率和动量的过程称为1cycle策略。据说,它还有助于对抗过度拟合,而且它似乎比我尝试过的其他选项收敛得更快。
我将训练分为5个步骤,以帮助大家理解不同变化的影响:
- 1500个迭代次数, resnet34个骨干层"冻结"在ImageNet的预训练值上,只训练自定义头层,并且仅使用35个训练图像集。
- 300个迭代次数后,"解冻"骨干层。
- 700个迭代次数后,增加了更多的数据扩充。具体来说,max_zoom 5%到10%,max_warp 5%到20%,max_rotate 5到10度。
- 500个迭代次数后,从另外4个人那里向训练集添加了16个图像。使训练集的总大小达到51。
- 1000个迭代次数后,每个cycle减少20%的学习率,最后一个cycle达到1e-5左右的学习率。记住,每个周期是100个迭代次数。
以下图表总结了进展情况:
在4000个迭代次数后的训练中损失和准确性度量。
这5个步骤中的每一个都对模型进行了额外的改进。数据扩充中的转换尤其重要,它对验证集错误的改进有重要贡献。"解冻"和更多的数据也为验证集提供了很好的改善。另一方面,虽然降低学习率显著改善了训练集的错误,但并没有较大的突破。过度拟合在这里是一个真正的问题,使用较小的学习率会使情况变得更糟。
总的来说,在训练过程中,网络共看到14.7万张不同的变换图像,训练时间为25.5时。
讨论的结果
训练集的最终平均L1误差为0.46像素,而验证集的平均L1误差为1.90像素。此外,训练集的这个分数是针对转换后的图像,而验证图像则没有转换。这是明显的过拟合。
尽管如此,结果还是相当不错的,验证集推断如下图所示。注意,绿色的点是实际的标签,红色的点是最终模型的预测。从结果上看,该模型似乎使其预测更具有全局性和相互依赖性,而不是给局部边缘更多的权重。
最终结果:图1、图2和图3是训练集中不同人的手。图4到6和图7到9是没有出现在训练集合中两个人的手。绿色的点是实际的标签,红色的点是预测的结果。
模型改进的明确方向是更多的数据和更智能的数据扩充。平均误差仍然需要小4-5倍才可以。
是哪里出现了问题?
在不同的地方添加dropout层等是没有用的。这可能是因为使用1cycle策略的高学习率本身就是一种正则化的形式,不需要更多的正则化。为不同的层组选择不同的学习率也是一个死胡同。
如果一开始就删除BatchNorm层,模型就会停止训练,即使学习率更低(1e-5)。
我尝试了另外两种骨干模型架构,Darknet(https://docs.fast.ai/vision.models.html)和U-Net(https://docs.fast.ai/vision.models.unet.html#vision.models.unet),它们具有不同的自定义头部,但是在我的实验中,它不如简单的resnet34工作得好。
最后,Fast AI库在这一点上只有仿射变换(平行线映射到平行线)和透视变换(直线映射到直线)。考虑到数据增强在这个项目中的重要性,我进行了一个额外的转换,如图所示。然而,由于某些原因,它并没有带来改善。
结论
该模型仅用51幅训练图像就达到了较好的预测精度。越来越多的数据被证明可以提高精度,并在一定程度上成功地对抗过度拟合。
Fast AI库是这个项目的一个合适工具,它有以下优越性:
- 简洁但灵活的数据和模型定义
- 用于数据扩充的一系列内置仿射和透视图转换,它们可以自动转换标签点
- 智能学习率和动量策略似乎能更快地收敛并减少过拟合