小猪观察家 2017-07-03
本文长度为5624字,建议阅读10分钟
本文为你分享《露脊鲸识别大赛》冠军的竞赛经验。
《露脊鲸识别大赛》是由NOAA渔场和Kaggle数据科学平台组织的计算机视觉竞赛。deepsense.io的机器学习团队在竞赛中获得了第一名。本文翻译自他们的经验分享帖。
比赛目标
本次竞赛的目标是在航拍的照片中识别单个露脊鲸。露脊鲸长达18米,重达91吨。数据集中含有447头不同的露脊鲸(基本上是现存露脊鲸的所有数量了)。虽然这个数字对于一个物种来说太少了,但是识别鲸鱼对人来说还是一个巨大的挑战。识别过程的自动化(至少部分自动化)对拯救露脊鲸是非常有益的。
研究者正致力于解救被渔具意外捕获的鲸鱼,而实时识别鲸鱼能够让研究者获得相关健康和渔网缠绕的历史记录,有助于研究者拯救鲸鱼。——摘录竞赛的描述页。
这些航拍的照片是在不同时间,使用不同设备拍摄的,质量参差不齐:有的非常清晰,鲸鱼正好居中;有的拍摄距离很远,聚焦很差。
鲸鱼图片示例
竞赛要求参赛团队建立一个模型,以识别照片中捕获的鲸鱼是447只鲸鱼中的哪一只。说的更专业一点,对于每张照片,我们需要提供所有447只鲸鱼的概率分布。模型的优劣通过多类对数损失(交叉熵)来评估。
值得注意的是,训练数据集并不平衡。不同鲸鱼之间的照片数量差别很大:有一些“名鲸”有40张左右,大多数有十几张,还有二十多只鲸鱼只有一张照片。另一个挑战是不同类别的图像(即不同的鲸鱼)彼此非常相似。这与我们区分狗、猫、袋熊和飞机的情况有些不同。这对我们训练的神经网络造成了一些困难:区别不同的鲸鱼的特征只占了图像的一小部分,并不是很明显。帮助我们的分类器专注于正确的特征,即鲸鱼的头部和皮肤斑纹(callosity pattern),被证明是至关重要的。
算法构架
卷积神经网络(CNN)已经被证明在图像识别领域中表现非常出色,所以我们自然而然地采用CNN作为基础。事实上,据我们所知,几乎所有顶级选手都在使用它们。即使有些人说CNN需要大量的数据(我们只有4,544个训练数据图像可用,一些鲸鱼在整个训练集中只出现一次),但我们还是能够训练出一个不错的模型,这证明即使是有限的数据,CNN也是一个强大的工具。
我们的算法包括以下几个步骤:
头部定位器(使用CNN)
头部校准器(使用CNN)
·在鲸鱼护照照片(经处理的标准照)上训练多个CNN网络
·做平均,调整预测(不用CNN)
算法的一个技巧是为网络提供一些额外的目标,即使这些额外的目标不一定在后期使用。如果附加目标依赖于我们特别感兴趣的部分图像(即在这种情况下的头部和皮肤斑纹),这个技巧将强制网络集中在该区域上。此外,网络有更多的刺激进行学习,因此必须开发更鲁棒的特征来限制过拟合。
软硬件
我们使用Python,Numpy和Theano来实现我们的算法。为了创建人工标注(并且保证在长时间里不失去理智),我们使用了Sloth(一个通用的标签工具)以及一个专用的Julia脚本。
为了训练我们的模型,我们使用了两种Nvidia的显卡:Tesla K80和GRID K520。
相关领域知识
尽管人类识别鲸鱼似乎比其他人更难,但神经网络不会因为明显的原因而遭受这种问题。即使这样,维基百科对鲸鱼的研究显示,“露脊鲸最大的特征是由于鲸鱼虱寄生而导致的头皮上的白色,粗糙的皮肤”,这不仅是露脊鲸区别于其他种类鲸鱼的特征,也是区别不同露脊鲸的特征。我们的算法也主要用了这个特征。除了这个微不足道的提示(由组织者提供)告诉我们应该关注什么,我们对露脊鲸并没有其他知识。
准备工作
在进一步了解细节之前,我们需要一个免责声明。在比赛中,人们会(或应该)倾向于测试新的方法,而不是微调和清理现有的方法。因此,在一个idea被证明工作得很好之后不久,我们通常就放下它。这样的一个副作用就是,算法的某些过于复杂,有缺陷的部分可能(并且应该)被解决,但我们在比赛中并没有着手解决。本文中也不会涉及这些问题。
人们不需要从数据集中看许多图像,以便意识到鲸鱼的姿势不是非常好(或者在这种特定情况下至少不愿意这样做)。
不太配合的鲸鱼
因此,在训练最终分类器之前,我们花了一些时间(和精力)来解释这个事实。这种方法的可以理解为从随机的图片中(被摄对象处于任意位置)获取护照照片(标准照片)。这也是头部定位器和头部校准器的目的。头部定位器对输入的照片中的头部产生一个边框,但此时头部还处于一个随机的方向,而且不一定在照片中间。头部校准器将前面圈出的头部区域进行旋转和缩放,使得喷水孔(blowhead)和帽尖(bonnet-tip)总是处于固定的位置,距离也不变。这两个步骤都是通过在人工标注的训练集上训练神经网络完成的。
帽尖(红点)和喷水孔(蓝点)
鲸鱼定位
这是为了获得优质的护照照片而迈出的第一步。 为了获得训练数据,我们在训练数据中手动标注了所有的鲸鱼,并用方框圈出了它们的头部(特别感谢我们的人力资源部门帮助!)。
头部定位器得到的头部边框
这些标注为训练集中的每个图像提供四个数字:矩形的左下角和右上角的坐标。然后我们训练一个收到原始图像(调整为256×256)的CNN并输出边界框的两个坐标。虽然这显然是一个回归的任务,但我们并未使用L2损失函数,而是将输出量化分组并使用Softmax以及交叉熵损失函数,并取得了更大的成功。我们还尝试了几种不同的方法,包括训练CNN来区分头部照片和非头部照片,甚至是一些非监督方法。然而,他们的结果却逊色不少。
此外,头部定位网络还必须预测喷水孔和帽尖的坐标(以相同的量化方式),但是在这个任务中不太成功,所以我们忽略了这个输出。
我们训练了5个不同的网络,架构基本相同。
头部定位器的构架
这5个网络不同之处在于用于量化坐标的buckets的数量。我们尝试了20,40,60,128,还有另外一个较小的网络用了20个buckets。
在将图像输入网络之前,我们进行了数据增强(调整到256 x 256以后)。
旋转:最大到10度(注意,如果使用更大的角度,就不能仅仅旋转这些点,你必须重新计算边界,我们太懒没有这么做)
缩放:1/1.2到1.2之间的随机比率
颜色扰动,比例为0.01。
虽然我们没有采用测试时增强技术,但我们将所有5个网络的输出相结合,即每次裁剪后的图片传到下一步(头部校准)时,我们会在5个网络的输出结果中随机选择一个。如上所述,这些网络的裁剪结果非常令人满意。实际上,我们并没有真正“物理地”裁剪图像(即产生一堆更小的图像),我们所做的(事实证明也是非常方便的)只是生成一个含有边框信息的json文件。这可能是无关紧要的,但是这样让我们能够轻松进行试验。
鲸鱼的“护照照片”
分类器的最后一步就是调整照片,使他们都符合相同的标准。基本的想法是训练一个CNN来估计喷水孔和帽尖的坐标。有了这些坐标,就可以很容易构造变换,使得原始图像变换为这两个点总是处于相同的位置(即头部校准)。由于Anil Thomas的标注,我们有了训练集的坐标。所以,我们再次训练CNN来预测量化坐标。虽然使用整个图像来确定这些点也是可能的(即跳过头部定位的步骤),但通过前面的操作我们可以更容易得实现头部校准。
另外,网络还有一些额外的任务要解决。首先,它需要预测哪只鲸鱼在图像上(即解决原始任务),此外还需要知道鲸鱼头部的皮肤斑纹是否连续(又一次需要在人工标注的训练集进行训练,虽然这时工作量少多了,每个鲸鱼2-3张图片就够了)。
不连续(左)和连续(右)的皮肤斑纹
我们使用如下的架构。
头部校准器的构架
从头部定位器中我们获得了头部的大概位置,来作为头部校准器的输入。这次我们敢于使用更大胆的数据增强:
平移,随机平移,最大4个像素
旋转,最大360度
缩放,1到1.5之间的随机比率
颜色扰动,比例为0.01
此外,我们使用了测试时的数据增强技术-对5次随机数据增强的结果进行平均。这个网络效果相当惊人。准确来说,在人工检查期间,几乎所有图像的喷水孔和帽尖的位置几乎都被完美地预测。此外,作为副产品,我们开始看到一些对识别鲸鱼有前景的结果。悲观的是在验证集上的损失在2.2左右。
把所有模块组装到一起
当链接多台机器学习算法时,需要相当谨慎。 如果我们在训练集上训练头部定位器和校准器,并使用它们为训练集和测试集制作“护照照片”,我们得到的照片可能会质量差异很大(可能还有一些其他属性)。 在这些裁剪的图片上训练分类器时,这可能是一个严重的困难。而在测试时,它接受的输入与其习惯的输入很不相似,导致算法无法正常工作。
虽然我们知道这一事实,但在检查裁剪结果时,我们发现训练集和测试集之间的质量没有太大差异。 此外,我们已经看到对数损失(log loss)的巨大进步。 按照“more idea, less dwelling”的思想,我们决定不再优化这个算法。
鲸鱼的“护照照片”
最终的分类器
网络架构
我们几乎所有的模型都在256×256图像上工作,使用相同的架构。所有卷积层都使用3×3的滤波器,图像大小没有变化,所有池化层均为3×3,stride为2,图像大小减半。 此外,所有卷积层之后都跟一个batch normalization和ReLU nonlinearity。
主网架构
在最终的混合模型中,我们还使用了一些具有类似架构的网络,但是在512×512图像上工作。有一个具有这种架构的“双”版本的网络(具有堆叠卷积层的独立副本,在全连接层前合并)。
再次,我们通过添加额外的目标 - 确定鲸鱼皮肤斑纹的连续性(与头对齐器情况相同),违反了网络的舒适区域(comfort zone)。 我们还尝试添加更多来自其他人工标注的目标,其中一个是“脸的对称性”。 不幸的是,它没有进一步改善结果。
数据增强
现阶段的数据增加比平常有点棘手。 原因在于,你必须在充分丰富数据集,和不破坏从上一步获得的对齐和归一化两者之间进行平衡。 我们最终使用相当温和的数据增强。 虽然所有模型中确切的参数不是固定的,但最常见的是:
平移:随机移动,最多4像素,
旋转:最大8度,
缩放:1.0和1.3之间的随机比率,
随机翻转
颜色扰动,比例为0.01
我们还使用了测试时的数据增强,对20多个随机增强数据进行平均。
初始化
我们使用了一个非常简单的初始化规则,零中心正态分布,卷积层的std为0.01,全连接层的std为0.001。 这已经很好了。
正则化
对于所有模型,我们仅使用L2正则化。不过卷积层和全连接层使用了单独的超参数。对于卷积层,我们使用较小的正则化,约0.0005;而对于全连接层,我们优选较高的值,通常在0.01 - 0.05左右。
前文提过,我们对网络增加额外的目标。这对权重造成了额外的限制,导致网络更多地关注鲸鱼的头部(而不是说水上的一些随机图案),这种方法很好地抑制了过拟合。
训练
基本上我们所有的模型都使用随机梯度下降(SGD)以0.9的momentum进行训练的。通常我们在约500-1000个epochs之后停下来(确切的时间并不重要,因为不会有过拟合)。在训练过程中,我们对学习率使用了相当缓慢的指数衰减(0.9955),并且还经常手动调整学习率。比如突然手动提高学习率(然后继续缓慢指数衰减),这时网络的错误(在训练集和验证集上)会先上升很多,然后在几个epoch之后,它又降到了更低水平。我们最好的模型还使用了另一个idea - 它首先用Nesterov momentum训练了超过一百个epoch,然后我们切换到Adam(自适应矩估计)。如果从一开始就使用Adam,我们无法实现类似的损失。初始学习率可能不重要,但是我们使用0.0005左右的初值。
验证
我们使用随机的10%的训练数据进行验证。多亏了我们最喜欢的7300号种子,我们所有模型都使用这个种子。虽然我们知道这种方法造成一些鲸鱼在训练集中不存在,但它已经很好了。验证集上效果相当差,并与排行榜很相关。
放弃10%的相对较小的数据集不是没有任何犹豫的决定。因此,在我们确定一个模型已经足够好之后,我们继续重新使用验证集进行训练(这很容易理解,因为我们没有任何过拟合的问题)。这是通过在整个训练集上从头开始重新运行所有计算(很少这么做),或者(更经常地)将验证集添加到训练集中并额外运行50-100个epoch完成的。这也是为了解决仅在我们的验证集中出现的单张鲸鱼照片的问题。
预测组合
最后我们得到了一系列在验证集上得分在0.97到1.3之间的模型(实际测试成绩更好)。由于保持了一致的验证集,我们能够测试一些混合技术以及基本变换。最后我们得出结论,使用更复杂的组合方法是不太合理的,因为我们的验证集不包括所有不同的鲸鱼,实际上相当小。我们并没有时间去做一些花哨的交叉验证,只做了一些简单的验证。
尽管对预测结果进行简单加权平均没有提供比最佳模型更好的分数(除非我们明显地提高了最佳模型的权重)。但是当将预测提升到中等功率(我们在最终解决方案中使用1.45)后,我们可以得到大约〜0.1的对数损失的改进。我们还没有仔细检查,但是可以假定这个技巧是有效的,因为我们全连接层被强制规范化了,并不容易产生更极端的概率。
我们还使用了一些“安全”的变换,比如对所有预测增加一个小量,以及将它们稍微偏斜到训练集中发现的鲸鱼分布。不过这些措施没怎么影响最后的分数。
加载图像
在我们算法的最后和中间阶段,我们从磁盘加载的图像是原始图像。这些文件很大,为了有效地使用gpu,我们必须并行加载它们。我们认为主要的时间开销是从磁盘加载图像,但后来发现这并非如此。相反,将JPEG文件解码为numpy数组才是最花时间的。我们做了一个快餐式的基准测试,来自数据集的111个随机原始图像总共为85Mb。当它们没有缓存在RAM中时读取他们大概需要420 ms,而读取和解码到numpy数组需要大约10秒,这是一个巨大的差别。可能的解决方案是使用其他图像格式,以提供更快的编码速度(会牺牲图像大小为代价),GPU解码等。
一些技巧
尽管如此,很难说一个技术能够超过其他一切其他技术(除非进行仔细的分析),我们这里只提供关键技巧。
获取高质量的裁剪图像,以及校准头部
我们已经通过两个阶段实现了这一点,结果远远超过了我们在一个阶段观察到的结果。
人工标注,为网络提供额外的目标
这是基于向原始图像添加附加标注。它需要人工检查数千个鲸鱼的图片,我们在各个阶段(头部定位,头部校准和分类)中都用到了这些人工的标注结果。
踢(加速)学习率
虽然这可能是一个粗心的初始化,差的学习率衰减策略,以及不够好的SGD导致的。但事实证明,踢(加速)学习率做得很好。
踢(加速)学习率后的损失函数
校正概率
将预测提升到[1.1 - 1.6]范围内的中等功率对几乎所有模型或者混合模型都有帮助。这个技巧是在比赛结束时发现的,而且将得分提高了0.1。
重用验证集
合并验证和训练集,并运行额外的50-100个epoch,我们在得分上得到了稳步的提升。
结论
总的来说,这个问题和比赛很吸引人。我们在这个过程中学到了很多东西,并且惊叹于深度学习的威力!
特别感谢Christin Khan和NOAA为数据科学界提供了这个特殊的挑战。我们希望这将激励其他人。当然,如果没有Kaggle.com(他们的平台做得很好)所有这一切都会更加困难。我们还要感谢Nvidia给我们使用Nvidia K80 GPU - 它们太棒了。
更多精彩干货内容,敬请搜索关注清华-青岛数据科学研究院官方公众平台“数据派THU”