0
图片来源:https://habr.com/company/ods/blog/415571/
雷锋网 AI 科技评论按:大约一年前,Kaggle 举办了一个名为「相机型号识别」(https://www.kaggle.com/c/sp-society-camera-model-identification)的计算机视觉挑战赛,任务是确定出用来拍摄某张图像的照相机的型号。比赛结束后,Arthur Kuzin, Artur Fattakhov, Ilya Kibardin, Ruslan Dutov 和 Vladimir Iglovikov 决定写一篇科技报告,向大家介绍如何解决这个问题,并分享他们在此过程中获得的心得体会。这篇文章被在西雅图举办的第二届网络犯罪调查大数据分析国际研讨会(http://folk.ntnu.no/andriis/bdaccip2018/)接受,而 Vladimir Iglovikov 将于 2018 年 12 月 10 日在大会上对这篇论文进行报告。以下是他对报告内容(https://arxiv.org/abs/1810.02981)的扩展版。
本文的作者共有 5 人:
Arthur Fattakhov, Arthur Kuzin, 以及 Ilya Kibrdin 是本次比赛亚军团队,他们的最终得分为 0.987。Vladimir Iglovikov 是获得第 9 名的团队的成员,最终得分为 0.985。这两支团队的解决方案非常相似,因此他们决定写一份技术报告。Ruslan 是深圳大学大数据研究院的一名研究生,他帮助完成了文本方面的工作,这对于这篇论文的顺利发表至关重要。
在本文接下来的部分,作者会把亚军团队所使用的方法称为「我们」的解决方案。此外,也将跳过一些不重要的技术细节。亚军的解决方案的代码可以在 GitHub( https://github.com/ikibardin/kaggle-camera-model-identification)上获得。比赛结束后,他们还根据自己的认识进行了一组单独实验。本文主要是关于进一步的研究。
正文如下,雷锋网 AI 科技评论编译整理:
相机型号检测是图像处理的众多应用之一。例如,在司法鉴定领域,判断出一幅图片是使用谷歌 Pixel 还是 iPhone 拍摄或许会非常重要,这可以进一步确定出谁可能是非法图像的所有者,甚至确定谁是知识产权的合法所有者,这种系统甚至可以用于在「诽谤或传播假新闻」的案件中发现犯罪嫌疑人。
计算机将图像存储为数字矩阵和相应的元数据,在最简单的情况下,相机型号通常会被存储在图像元数据中,此时对型号的识别就成为一个非常简单的问题。但是图像元数据可能不可靠,而且很容易被不法用户操纵。
还有一种更复杂、但更可靠的方法。图像采集完成后,数码相机将在一系列后续处理步骤之后,创建一个针对图像的保真度和内存占用进行优化后的图像。这些算法是一些高度非线性、极其复杂的方法,例如去马赛克、噪声滤波、修正镜头畸变等。不同型号的相机使用不同的算法,这意味着每个算法在特定的型号上都会给出特定的输出,我们可以将这些数据作为机器学习工作流中使用的特征。
当然,目前已经有很多关于这个主题的文献,大多数现有的文献在此基础上提出了基于 SVM 或类似的分类算法的人工特征提取步骤。例如,当我碰到这个问题时,我脑海中出现的第一个想法就是取一张图像,用它减去自身经过平滑处理后的图像,然后计算出这种差异的不同统计量(比如均值、中值、标准差、不同的分位数),然后利用他们作为输入训练 xgboost 集成学习模型。关于如何进行相机模型检测的第一篇论文所使用的方法与我刚才描述的方法非常接近,其它后续的论文提出了一些更复杂的模型,但方法非常相似。方法无外乎:基于领域知识的人工特征提取,在此基础上加入逻辑回归、决策树或支持向量机等等。
将这种方法扩展到新的相机型号也是一件很困难的事。假设我们想为一款新发布的相机进行这种检测。那么,专家应该花多少时间来找出哪些特性将有助于将其与其他型号的相机区分开来?另一方面,深度学习方法可以同时解决这些问题。一般来说,深度学习就好比一种强大的野兽,如果你知道如何驯服它,它可能会帮助你创建高度精确的黑盒模型。
像往常一样,你几乎不可能通过翻阅文献试图找到「最好的方法」。几乎每篇论文都会告诉你,他们和其他人不一样,而他们的方法是最先进的。想要解决这个问题,你就需要在同一数据集上对不同方法进行评估。这样的比较不会告诉你哪种算法总体上更好,但是你可以看出在给定的数据集上用某种特定的度量标准去评价时哪种算法更好。这并不意味着算法在类似的数据集上的性能排名一定是相同的,比如在 CIFAR 数据集上最先进的架构可能在 ImageNet 上表现不佳,反之亦然。但是有这样一个标准一致的比较总比没有好。
在这次的挑战赛中,582 支参赛团队将在两个月的时间内在实时排行榜上比拼他们的算法。582 对于 kaggle 竞赛的参赛队伍数来说是一个相对较大的数字,这就保证了这个问题将由一些有着不同专业背景和技能的人来解决。一些参赛人员在工业界和学术界从事司法鉴定的相关工作,还有一些像我一样的参赛人员,则有着计算机视觉方面的经验,但我们对这样的相机识别问题并没有很深刻的认识,也不知道有人有兴趣解决它。
大赛组织者准备了一套由 10 个相机拍摄的 2750 张照片(每个相机拍摄了 275 张照片)组成的训练集。
我们需要区分的相机型号有:
1. Sony NEX-7
2. Motorola Moto X
3. Motorola Nexus 6
4. Motorola DROID MAXX
5. LG Nexus 5x
6. Apple iPhone 6
7. Apple iPhone 4s
8. HTC One M7
9. Samsung Galaxy S4
10. Samsung Galaxy Note 3
为了增加比赛难度,组织者共用了 20 部不同的手机来收集图像,其中有 10 部被用于建立训练集,另外 10 部被用于建立测试集。这意味着在训练过程中,你的模型可能学习到的并不是与后续处理算法相关的针对特定相机型号的特性,而是会过拟合特定手机产生的特定的输出。
测试集中的图像是用 10 个与训练集中对应相同型号的相机拍摄的,但使用的是相同型号的第二台设备。例如,如果在训练数据中使用 iPhone6 拍摄的图像是使用 Ben Hamner 的第一台 iPhone 6 拍的,那么在测试数据中使用 iPhone6 拍摄的图像则是使用 Ben Hamner 的第二台 iPhone 6 拍的,因为他在玩风筝时在海湾丢失了第一部手机。
此外。训练集中的图像是完整全尺寸的,而在测试中只使用了图像中央裁剪出的 512x512 像素的部分。之所以这样处理,是因为径向畸变在图像的两侧更加明显。一些论文仅仅基于径向畸变特征就取得了较好的结果。然而,目前还尚不清楚它们能带来多大的帮助,但组织者决定禁止参与者利用这些特征。
在许多情况下,通常存储在计算机上的图像会受到不同类型的图像变换的影响,如 jpeg 压缩、伽马变换、对比度、亮度、大小等方面的变换。
从实用的角度来看,模型最好能对这些变换具有很强的鲁棒性,组织者对一半的测试图像进行了类似的变换,如下所示:
1. JPEG 压缩质量因子 = 70
2. JPEG 压缩质量因子 = 90
3. 通过因子 = 0.5 的双三次插值进行缩放
4. 通过因子 = 0.8 的双三次插值进行缩放
5. 通过因子 = 1.5 的双三次插值进行缩放
6. 通过因子 = 2.0 的双三次插值进行缩放
7. 使用 gamma = 0.8 的伽马校正
8. 使用 gamma = 1.2 的伽马校正
如上文所述,不同型号的相机拍出来的图片的规模在千兆字节,我们可以利用这些数据提取出相应相机型号。Kaggle 上的不同竞赛对于使用外部数据有不同的规则,但在本项比赛中,Kaggle 允许我们使用外部的图像数据训练模型。一般来说,所有允许使用外部数据的竞赛都有一个规则,Kaggle 规定所有其他的参赛者也可以访问这些数据。在任何此类竞赛中,Kaggle 论坛上都会专门设有一个论坛,参与者可以在其中分享他们将使用的数据和预训练的模型。
这次比赛是个例外,管理员忘记在规则中添加关于共享数据的条款,这就改变了游戏规则。
深度学习模型的优势在于,它们可以从大量的训练数据中获取知识。此外,我们还应该特别注意到,许多训练标签可能是错误的,但这个问题也比较好解决,只要错误标签的百分比小于某个阈值(比如百分之十五),这个训练数据集就是一个好的数据集。使用的数据越多,你的模型就越好。
参与者可能并不是司法鉴定专家,但他们肯定知道更多的数据对深度学习来说是更好的。通常,在竞赛中,你试图从给定的数据集中获取最多的信息,在进行了一些实验之后,你会选择一个性能良好的模型,巧妙地选择数据增强的方式,探索相关领域知识,花时间开发一个智能训练计划,训练损失函数等等。如果你没有更好的选择,完成所有这些步骤都很重要。
我们在 Flickr、Yandex、Fotki、Wikipedia 上爬到了一些数据,而在这种情况下,我们的团队拥有的原始图像数据总量约为 500GB。我们可以使用所有这些数据,但是为了加速训练,并潜在地提高模型质量,我们进行对数据进行了过滤。
在训练过程中,我们需要的是未经处理的数据,即不受 Photoshop 、LightRoom 或类似图像编辑软件的影响,不进行缩放的高质量图像。
不同型号相机的数据集样本数。上表给出了包含外部和组织者提供的数据集的最终数据集。
首先,我们删除掉了元数据中包含 Photoshop 和 LightRoom 的图像。接着,我们删除了 JPEG 质量低于 95 的图像。第三,我们让不同的相机拍摄固定尺寸的照片。如果图像的大小与我们预期的不匹配,我们就会认为这些图像是经过了缩放的。我们删除了不满足这些条件的图像。
这并不意味着我们得到的所有图像都是未经处理的,比如有人可能使用质量为 10% 的 JPEG压缩,然后再应用质量为 99% 的 JPEG 压缩。实际上,压缩后的质量仍然是 10%,但是对于软件来说,很难分析出压缩因子是 10 而不是 99。我认为这是不可能做到的。但我在研究这个问题时,我看到一些论文也试图识别出这种「二次 JPEG 压缩」。再说一次,我甚至不知道存在这样的问题。
经过过滤,我们得到了 78,807 张爬取的图像,我们认为这些图像是原始的,未经处理的。这些图像的类别分布是不均匀的。正如你可以想到的那样,并非所有手机都同样受欢迎,或者说手机型号与用户拍照并上传至互联网的频率之间存在相关性。因此,对于某些类别的手机所得到的图像,我们所得到的样本就相对较少一些:
总的来说,这个机器学习工作流类似于利用 ImageNet 进行迁移学习,你会使用预训练的网络,去掉最后一个预测 1000 个类的层,用一个预测你所需要的类别的层替换它。在本例中,这个层的输出类别数是10。在此之后,你可以使用分类交叉熵作为损失函数并训练网络。有很多方法可以做到这一点,而第 1 名的解决方案和第 50 名的解决方案之间的区别通常不在他们使用的网络类型,而在于训练过程以及进行训练的人。从实践的角度来看,与其说深度学习是科学,不如说更像是炼金术。因此,当一个人从事不同的工作时,直觉是至关重要的。
Remi Cadene 在 GitHub 上的代码库(https://github.com/Cadene/pretrained-models.pytorch)提供了一个 PyTorch 框架下的带权重的网络列表,人们可以使用类似的 API 获得不同的网络和预训练的权重,从而使他们的实验能够快速进行。这个代码库被参赛人员广泛使用。各个参赛队伍用 Resnet、VGG、DPN 和所有其它类型的网络进行了实验。
对于这个问题,一个经验性的结论是:DenseNet(https://arxiv.org/abs/1608.06993) 的性能会稍好一些,但是团队之间的差异非常小,所以不清楚这样的说法是否为真。什么是 DenseNet 呢?DenseNet是一种架构,它进一步推动了在 Resnet 类型的网络中使用跳跃连接的想法。
DenseNet 论文的作者将卷积块中的所有层连接起来,跳跃连接简化了梯度流,使得训练深层网络成为可能。在跳跃连接成为主流之前,处理只有 19 层的 VGG19 网络是一件痛苦的事情,但是在引入它们之后,使用具有 100 多个层的网络处理高度抽象特性就不再是一个困难的问题了。
研究人员相信,这样做会使得损失表面更加平滑,防止基于梯度下降的训练过程陷入许多局部最小值。
图片来源:https://arxiv.org/abs/1712.09913
除此之外,网络是相对标准的,它使用了一组具有批量归一化和 ReLu 层的卷积块,卷积块之间使用了最大池化层,最后是全局平均池化和全连接层。需要注意的是,全局平均池化层允许我们使用不同大小的图像作为输入。
研究社区中的一些人认为 DenseNet 的性能总是优于 Resnet,因为它是相对较后提出的,并成为了 CVPR 2017 最佳论文。然而,事实并非如此!在原始论文中,DenseNet 在 CIFAR 上取得了良好的结果,但在数据更多样化的 ImageNet 数据集上,更深的DenseNet 才能得到与更浅的 ResNet 网络相当的准确率。
DenseNet比Resnet更好/更差吗?这个问题得视情况而定。在「Planet: Understanding the Amazon from Space challenge」竞赛(https://www.kaggle.com/c/planet-understanding-the-amazon-from-space)中,我们团队使用 DenseNet,从 900 多支团队中夺得第 7 名,这一网络在这次比赛中表现得更好,但它在 ImageNet 上的表现较差。
我和我的合作者对此进行了深入的讨论,有人认为跳跃连接不仅平滑了损失表面,而且还降低了模型容量。这也许可以解释为什么 DenseNet 在像 CIFAR 这样的非多样化问题中表现更好,但是需要在 ImageNet 这样的多样化的数据集上增加网络的深度,以补偿容量的损失。基于这种推测,我们可以认为当数据集不是非常多样化且不需要大容量的网络时,DenseNet 的性能会很好。但是当判别特性高度抽象时,我们就可能需要非常深的网络。
相机检测任务属于这一类问题吗?我不知道。从经验上说它确实是的,但我们并没有足够的证据证明这一点。
在训练过程中加入智能的正则化标准方法是使用数据增强。不同的问题可能受益于不同的数据增强方法。同样的,如果你拥有更好的直觉,你可能会选出更好的数据增强方法和参数。
对于这个问题,我们使用了:
1. Dihedral Group D4 变换:将图像旋转 90、180、270 度,以及进行翻转操作。
2. 伽马变换:我们在 [80,120] 的范围内均匀地选择了伽马参数。
3. 质量因子参数从 70 到 90 均匀采样的 JPEG 压缩。
4. 缩放因子在 [0.5,2] 的范围内的变换。
使用 albumentations 程序库(https://github.com/albu/albumentations)的代码如下:
import albumentations as albu
def train_transform(image, p=1):
aug = albu.Compose([albu.RandomRotate90(p=0.5),
albu.HorizontalFlip(p=0.5),
albu.RandomGamma(gamma_limit=(80, 120), p=0.5),
albu.JpegCompression(quality_lower=70, quality_upper=90, p=0.5),
albu.RandomScale(scale_limit=(0.5, 2), interpolation=cv2.INTER_CUBIC, p=1)
], p=p)
return aug(image=image)['image']
左图是我最近去 Bishop 攀岩时的原始照片;右图是翻转过来的结果,其颜色深一些。后者是进行了伽马变换得到的结果。在这里我们也应用了 JPEG 压缩,但由于这类图像的质量 [70:90] 相对较高,肉眼很难看出它的效果。
组织者告诉我们他们用三次插值来进行缩放,如果不知道使用三次插值,我们会交替使用不同的插值方式。一般来说,机器学习竞赛社区中经常会使用这种技巧,但是我还没在文献中看到对这样的方法的描述。
如果我们想要添加这些替代方法,代码将会更复杂一些,但思路仍然是相对直观的。
import albumentations as albu
def train_transform(image, p=1):
scale_limit = (0.5, 2) aug = albu.Compose([
albu.RandomRotate90(p=0.5),
albu.HorizontalFlip(p=0.5),
albu.RandomGamma(gamma_limit=(80, 120), p=0.5),
albu.JpegCompression(quality_lower=70, quality_upper=90, p=0.5),
albu.OneOf([albu.RandomScale(scale_limit=scale_limit, interpolation=cv2.INTER_NEAREST, p=0.5),
albu.RandomScale(scale_limit=scale_limit, interpolation=cv2.INTER_LINEAR, p=0.5),
albu.RandomScale(scale_limit=scale_limit, interpolation=cv2.INTER_CUBIC, p=0.5),
albu.RandomScale(scale_limit=scale_limit, interpolation=cv2.INTER_AREA, p=0.5),
albu.RandomScale(scale_limit=scale_limit, interpolation=cv2.INTER_LANCZOS4, p=0.5),
], p=0.5),
], p=p)
return aug(image=image)['image']
原图的分辨率很大,在全分辨率上对它们进行缩放是不明智的,因此我们进行了两次连续裁剪(缩放前后各一次)。
import albumentations as albu
def train_transform(image, p=1):
aug = albu.Compose([
albu.RandomCrop(height=960, width=960, p=1),
albu.RandomRotate90(p=0.5),
albu.HorizontalFlip(p=0.5),
albu.RandomGamma(gamma_limit=(80, 120), p=0.5),
albu.JpegCompression(quality_lower=70, quality_upper=90, p=0.5),
albu.RandomScale(scale_limit=(0.5, 2), interpolation=cv2.INTER_CUBIC, p=1)
albu.RandomCrop(height=480, width=480, p=1),
], p=p)
return aug(image=image)['image']
我们使用 Adam 优化器对网络进行了 100 个 epoch 的训练,初始学习率为 0.001。
为了获得更高的准确率,当学习速率在不同的值之间振荡时,我们还采用了循环学习速率方法进行改进。
你从这张图中可以猜到,学习速率在第 17 个 epoch 前后降低了。图中存在两个连续衰减的损失峰,一个大约在第 42 个 epoch 左右,另一个大约在第 53 个 epoch 左右。在训练过程中,网络容易陷入局部极小值,而这些振荡有助于跳出这些局部最小值。
在进行推断时,我们对测试图像进行增强,对在不同的 480x480 的裁剪图像上的预测结果进行平均。
我们还想评估不同的变换是如何降低模型准确率的。
我们通过绿色的部分显示了训练中使用的参数的范围。正如预期的那样,在特定的参数范围内,准确率没有显著下降。这表明,如果我们使用深度学习方法,并拥有足够的数据,加强数据增强可能会提升模型的鲁棒性。
我们想要回答的另一个问题是,如果我们在测试时减小输入到网络中的裁剪图像的大小,准确率会如何变化?
我认为网络正在学习的特性是局部的,所以不需要进行大的裁剪,但似乎事实并非如此。要想在 10 个类上获得 90% 以上的精度,就必须有至少 250x250 的裁剪图片作为输入,这可能意味着后续处理算法在网络捕获的像素之间创建了远距离的相关性联系。
我们还想证实:当训练数据量增加时,模型的准确率也会提高。正如你在上图中看到的,这样的情况并没有发生。我想 25k 图片对于一个高质量的模型来说已经足够了。如果我们需要检测的不是 10 个类,而是 1000 个类,那么模型质量的提升可能会更加显著。
1. 利用 ImageNet 预训练模型进行的迁移学习在相机检测等使用低级特征的任务中,可以取得很好的性能。
2. DenseNet 的性能更好,但我们相信,如果我们从 Cadene 提供的代码列表中任意选择预训练好的网络,它的表现也会类似。
3. 在训练中使用数据增强技术会帮助模型提升鲁棒性。
4. 如果你能获取到更多的带标签的数据,尽量去获取吧。在大多数情况下,尽管利用领域知识可以提供额外的重要帮助,「暴力」地处理大量数据可能比使用复杂的解决方案提高模型性能更容易。
1. 是否有必要添加额外的输出来显示出图像是否被修改过?
2. checkpoint averaging 等技巧。
3. 其他架构的性能如何?
4. 如何将集成学习技术应用到这个问题上。
5. 测试集中的类是平衡的,我们可以如何利用。
6. 如何将伪标记技术(一种半监督学习技术)应用于此任务。
7. 对那些我们尝试过、但是没有成功的方法进行讨论。
8. 解释网络学习到的特征是什么?
9. 评价指标的细微差别。
所有这些议题都很重要,但我不认为它们有太大的影响,或者我更愿意就每一个议题单独进行讨论。
注:本文作者将于 2018 年 12 月 10-13 日在美国西雅图举行的 IEEE 2018 大数据国际会议「第二届网络犯罪调查与预防大数据分析国际研讨会」上做与本文相关的报告。
(完)
雷锋网 AI 科技评论编译整理
雷峰网版权文章,未经授权禁止转载。详情见转载须知。