小白将 2017-01-26
最近我在 Nexar 交通信号灯识别挑战赛上获得了第一名,这是一项由 Nexar 组织的计算机视觉比赛,该公司正在开发一款叫做 AI Dashcam 的软件。
本文中,我将对我所使用的方案进行相关叙述。同时,本文也涉及改善模型过程中使用的方法,不管其有用还是没用。
别担心,即使你不是人工智能方面的专家,也能读懂本文。在本文中。我会集中讲述我曾经的想法和用过的方法,而不是比赛过程中涉及的技术。
本项比赛中的挑战目标是,识别出司机使用 Nexar 软件所拍摄的照片中交通信号灯的状态。在给定的所有照片中,分类器需要识别出场景中是否存在交通信号灯,如果有,则需判断出是红灯还是绿灯。更确切的说,分类器应该仅识别出的,是车辆行进方向的交通信号灯。
为了便于理解,显得更加直观,请看下面的实例图:
上图中的例子是我们需要预测出的可能出现的三种情况:没有交通信号灯(左图)、交通信号灯为红色(中间图)、交通信号灯为绿色(右图)。
本项挑战要求解决方案必须基于卷积神经网络。卷积神经网络是一种非常流行的用于图像识别方法,其原理基于深度神经网络。对提交内容的打分是基于其模型的准确程度和模型的大小(兆字节)。模型越小,分数越高。另外,想要获胜,其模型的最低准确度应为95%。
Nexar 提供了18,659张有标签的图片作为训练数据。每张有标签的图片都属于上面提过的三种类型中的一种(没有交通信号灯/红灯/绿灯)。
我用Caffe来训练模型。我选择 Caffe 的主要原因是其有各种各样的预训练模型。
Python、Numpy 和 Jupyter 等都可用来做结果分析、数据探索和点对点的脚本语言。
我使用的是亚马逊的 GPU 实例(g2.2xlarge)来训练我的模型。最终,我总共为其花费了263美金,不便宜啊。
用来训练和运行模型的代码、文件,我都存放在 GitHub 上了。
用 Nexar 的测试集对最终的分类器进行测试,分类器的准确程度达到了94.955%,此时模型的大小为7.84MB。相比之下,GoogLeNet 模型的大小是41MB,VGG-16模型的大小是528MB。
Nexar 很友好,愿意接受94.955%的准确率,将其同95%的准确率同样视作达到了最低准确度的要求。
提高准确度的过程包含大量的实验和错误,远远超乎想象。其中,有些通过逻辑推断是有用的,而有些仅仅是“可能有用”。接下来。我会描述我为提高模型准确度而做的努力,有些是有帮助的,有些是没帮助的。之后,我会详细描述最终分类器的情况。
迁移学习
我开始试着微调模型,该模型是基于 GooleNet 架构的 Imagenet 数据库来进行预训练的。这让我的准确率非常快的就达到了90%以上。
Nexar在它的挑战页面中提到,使用 GoogLeNet 框架来微调,准确率可能达到93%。我不能完全确定我哪里做错了,我应该再查一下。
Squeeze Net 模型
Squeeze Net 模型:该模型的准确度与 AlexNet 模型相同,但是其参数却少了50x,并且在大小上小了0.5MB。
由于比赛的评奖方案中有使用小模型的要求,初期我决定寻找一种精简网络,即用尽可能少的参数得出相对更好的结果。近期发布的网络,大多数都比较有深度,参数也非常多。SqueezeNet 模型似乎是一个非常不错的选择,它有一个用 ImageNet 数据库进行训练的预先训练模型,而且 ImageNet 数据库可以很方便地从 Caffe 的 Model Zoo 中获取。
要想使网络紧凑简洁,你可以:
尽可能多的使用 1*1 卷积过滤器;
另外可以减少使用 3*3 的卷积过滤器的数量。
更多的细节,我建议你阅读一下由 Lab41 发布的博客或者原作文章(https://arxiv.org/abs/1602.07360)。
在反复调整学习速率之后,我能微调我的预训练模型,而且使其达到了准确度为92%的结果。非常酷!
旋转图片
大多数的图片和上面这张一样是水平的,但是有大概2.4%的图片是垂直的,此外还有各种各样的倾斜方向,如下图所示:
尽管这部分在整个数据集中并不占很大的比例,我们仍希望我们的模型能对这些垂直的图片进行正确的分类。
不幸的是,在 jpeg 格式图像的指定方向上,没有 EXIF(可交换的图像文件格式)数据。起初我认为可以探索性的通过识别到的天空来旋转图像,但这似乎并不简单。
于是,在模型不变的情况下,我尽量将图片进行旋转。我首先尝试训练网络随机旋转0°、90°、180°、 270°,这都没有任何实质性的帮助。但是,这样做使得图片的平均预测水平有了一定的提高。
92% → 92.6%
声明一下:这里的“平均预测”是指,图片经过上述四种旋转之后,模型的平均预测能力。
对剪切的图片进行多重采样
在训练 AqueezeNet 网络的过程中,首先需要根据默认的情况对输入的图片进行随机裁剪,我没有修改这个默认设定。这种类型数据的增加通常会使网络变得更好。
类似的,当生成预测的时候,我提取了些许输入图片的裁剪数据和平均的结果。我用了5个裁剪部位:四个角落和一个中间位置。此时,运用已经存在的 Caffe 代码是完全免费的。
92% → 92.46%
将旋转图片的方法和多次采样的方法结合在一起。准确率只提高了一点点。
低学习速率附加训练
所有的模型都会在某一个特定的点之后开始拟合。通过观察到在某些点上验证集损失开始上升的现象,我注意到了这个事情。
在那个点上,我停止了训练,因为模型自此可能不能再继续使用,这意味着学习速率没有时间衰减到零。我尝试用低于原来10倍的学习速率去恢复那个开始过度拟合点上的训练过程。这通常可以将准确率提高0-0.5%.
更多的训练数据
起初,我将数据分成3个集合:训练集(64%)、验证集(16%)和测试集(20%)。几天后,考虑到放弃36%的数据可能是太多了,我将训练集和验证集并到了一起,并且使用测试集来检验我的结果。
我采用“图像旋转”和“更低等级附加训练”重新训练了一个模型,准确率有所改进:
92.6% → 93.5%
重新标记训练数据中的错误
当在验证集里分析分类器的错误时,我注意到有些错误有着非常高的可信度。换句话说,模型是一回事(例如:绿灯),而测试数据则是另一回事(例如:红灯)。
注意在上面的图中,最右侧的柱状值相当高。这意味着很多错误的可信度大于95%。当对这些情况的检查接近尾声时,我注意到,这些错误大都存在于训练集中,而不是训练好的模型中。
我决定修复训练集里的这些问题,因为这些错误会使模型出错,使模型的普及变得困难。即使最终的测试集在实际情况中仍存在错误,但是相对更加一般化、更加普及的模型来说,预测的精确度仍有望获得提高。
我将709个图像手动贴上了标签,但是其中一个模型却出错了。这样做改变了709个图像中的337个输出的真实值。虽然在 python 的帮助下,效率有所提高,但这项手动工作仍然花了大约一个小时。
上图是重新贴签并重新训练模型后画出的图。看起来好了很多。
通过重新贴标签,模型的准确率提高情况如下:
93.5% → 94.1%
模型集
如果同时使用若干个模型并且取其平均结果,也可以提升精确度。在训练过程中,我对模型集里不同模型的变型做了些实验。重新训练出来的模型,尽管单独使用时精度较低,但当它与其他预先训练的细调过的模型组合在一起时,精度获得了极大的提升。可能是因为,跟那些预先训练过细调过的模型相比,该模型学习到了更多不同的特征。
模型集使用的3个模型精度分布为94.1%,94.2%和92.9%,总体的精度为94.8%。
很多东西!希望在其他情况下,这些想法会是有用的。
解决过拟合
在努力处理过拟合的过程中,我试了若干种方法,没有一个能产生重大的改进:
增加网络中的丢失率;
增加更多的数据(随机变化, zoom, 倾斜);
训练更多数据:使用 90/10 分割替代 80/20。
平衡数据集
数据集不是十分的平衡:
19%的图像被贴上没有交通灯的标签;
53% 红灯;
28% 绿灯。
我尝试过尽量多的采集不同种类的数据,来平衡数据集,但是并没有发现任何改善。
将白天和黑夜分开
直觉告诉我,白天和夜晚对交通灯的识别是完全不同的。我认为,把模型分成白天和黑夜两个更加简单的问题,或许是有用的。
通过图像的平均像素强度把图片分成白天和夜晚是一件相对容易的事情:
你可以看到低平均值图像有一个非常自然的图像划分,也就是说,在夜晚拍摄的图像比较暗,而在白天拍摄的图像比较明亮。
我尝试了两种方法,但作用都非常一般:
为白天的图像和夜晚的图像训练两个不同的模型;
因为模型也要预测是白天还是晚上,所以训练网络需要预测6种结果,而不是3种。
使用更好的 SqueezeNet 变量
我用两个改进好的 squeezeNet 变量做了一些实验。第一个使用剩余连接进行训练,第二个用从稠密到稀疏再到稠密的方式训练(文章里有更多细节,https://github.com/songhan/SqueezeNet-DSD-Training),效果都不乐观。
交通信号灯的局域化
在 deepsense.io 上阅读了很多识别鲸鱼的文章之后,我努力训练出一个定位器,首先识别图片里交通灯的位置,然后再在这个相对小区域内辨别交通灯的状态。
我花了几个小时使用 sloth 为大约2000张图片做注释。当训练模型时,它们很容易也很迅速地实现了过拟合,这可能是因为带标签的数据不够多造成的。如果我能注释了更多更大量的图片的话,也许是能起作用的。
运用不易识别的图片来训练分类器
我选择出了30%的最难识别的图片,其可信度均小于97%。然后,我用这些图片来训练分类器。但最终并没有任何改进作用。
不同的优化算法
我用 Caffe 的 Adam solver 取代 SGD 的线性下降的学习率做了简单的实验,但仍然没有看到任何改进。
增加模型集中的模型数量
由于模型集的方法能提高模型的识别效率,我在模型集方面又做了一次尝试。我尝试改变不同的参数以此生成不同的模型并且将他们添加到模型集里。但最终的结果却是,初始的种子、丢失率、不同的训练数据(不同的划分)以及训练过程中不同的检查点,均未获得明显的改进。
分类器运用的是同一模型中的3个不同的训练网络。分类器输出的,是每种类型的概率的加权平均数。虽然三个网络全都运用的是 SqueezeNet 网络,但它们接受的训练是不同的。
模型#1 —— 运用重复采样的方法,预先训练网络
在重新贴签的训练集里训练(在修复了真实值的错误之后)。模型基于 SqueezeNet 在 ImageNet 训练后的预先训练的模型进行了细调。
训练过程中数据放大:
随机水平镜像;
在网络读取数据之前随机裁剪成227x227大小的块。
在测试时间段,取每个图像10个变量的平均值,而后预测最终的结果。这10个变量组成如下:
大小为227x227的5个裁剪块:图像边角处各1个,中心处一个
每个裁剪块使用一个水平镜像的版本
模型在验证集里的准确度:94.21%
模型大小: ~2.6MB
模型#2 —— 增加旋转不变性
与模型#1非常近似,增加了图像的旋转。在训练过程中,图像被随机地旋转90°、180°、 270°或者根本不旋转。在测试过程中,模型#1中描述的10个变量,每一个都会在旋转90°、180°或270°的过程中新生成3个以上的变量。我们的模型总共会产生40个变量,需要将这40个变量放在一起,共同取其平均值。
模型在验证集里的的准确度:94.1%
模型大小: ~2.6MB
模型#3 —— 从头开始训练
这个模型没有经过细调,但却是从头开始训练的。其背后的基本原理是:即使该模型的准确度降低了,但相比于前面两个模型,其在训练集里学习了各种不同的特征,对模型集来说是有用的。
训练过程中的数据放大和测试跟模型#1一样:镜像和裁剪。
模型在验证集里的准确度:92.92%
模型大小: ~2.6MB
模型组合
每个模型输出3个值,分别代表图像在三种分类中的概率。我们用下面这些权重把输出结果做个平均:
模型#1: 0.28
模型#2: 0.49
模型#3: 0.23
这些权重值是通过对可能的值做网格搜索获得的,并且在验证集上进行了测试。对于验证集来说可能会有些过拟合,但是由于这是一个非常简单的操作,所以可能也不会过拟合太多。
模型在验证集里的准确度:94.83%
模型大小:~7.84MB
模型在Nexar测试集里的准确度:94.955%
模型错误的例子
眩光产生的棕榈树上的绿点可能使得模型预测错误,认为该处有一个绿灯。
模型预测结果为红灯而不是绿灯。如果场景中出现多于一个交通灯的状况就会比较难办了。
模型显示没有交通灯,但实际上有一个绿灯。
这是我第一次将深度学习应用到实际的问题中!我很高兴能看到自己的应用获得成功。在这个过程中,我学习了 LOT 并且很有可能写出另外一篇文章,尽量帮助新手避免重复我所犯的错误,在技术挑战中少浪费一些时间。
我想感谢 Nexar,它为大家提供了一次了不起的挑战机会。同时,我也希望 Nexar 能在未来为大家提供更多的挑战机会。
本文作者 David Brailovsky 是一名经验丰富的软件工程师,善于分析复杂的问题并且能找到简单的解决方案,能够深入理解计算机网络、操作系统等,目前是一位机器学习爱好者,致力于研究深度学习。
本文由 AI100 编译,转载请联系本公众号获得授权
编译:AI100
原文链接:https://medium.freecodecamp.com/recognizing-traffic-lights-with-deep-learning-23dae23287cc