本文为你介绍图像分类的5种技术,总结并归纳算法、实现方式,并进行实验验证。
图像分类问题就是从固定的一组分类中,给输入图像分配标签的任务。这是计算机视觉的核心问题之一,尽管它看似简单,却在实际生活中有着各种各样的应用。
传统方式:功能描述和检测。
也许这种方法对于一些样本任务来说是比较好用的,但实际情况却要复杂得多。
因此,我们将使用机器学习来为每个类别提供许多示例,然后开发学习算法来查看这些示例,并了解每个类的视觉外观,而不是试图直接在代码中指定每一个大家感兴趣的类别是什么样的。
然而,图像分类问题就是一个非常复杂的工作,它总是借用诸如卷积神经网络(CNN)这样的深度学习模型来完成。但我们也知道,通常我们在课堂中学习到的,诸如KNN(邻近算法)和SVM(支持向量机)这样的许多算法,在数据挖掘问题上做得非常好,但似乎它们有时也不是图像分类问题的最佳选择。
因此,我们想要比较一下我们在课堂中学到的算法与CNN和迁移学习算法的性能。
目标
我们的目标是:
将KNN、SVM和BP神经网络,与通常用于工业中图像分类问题的算法进行比较,例如CNN和迁移学习。
获得深度学习的经验。
通过Google的TensorFlow来探索机器学习框架。
算法和工具
我们在这个项目中使用的5种方法分别是KNN、SVM、BP神经网络、CNN,以及迁移学习。
整个项目主要分为3种方法。
第一种方法:使用KNN、SVM和BP神经网络,这是我们在课堂上学到的算法,功能强大而且易于实施。我们主要使用sklearn来实现这些算法。
第二种方法:虽然传统的多层感知器(MLP)模型成功地应用于图像识别,但由于节点之间的完全连通性受到维度灾难的影响,因此不能很好地扩展到更高分辨率的图像。所以在这一部分我们使用Google的TensorFlow深度学习框架来构建一个CNN。
第三种方法:重新训练预先训练的深层神经网络的最后一层(称为Inception V3),同样也是由TensorFlow来实现。Inception V3是为ImageNet大型视觉识别挑战而进行的训练,数据从2012年开始采集。这是计算机视觉中的标准任务,其中模型尝试将整个图像分为1000个类别,如“斑马”、“斑点狗”和“洗碗机”。为了重新训练这个预先训练网络,我们需要确保我们自己的数据集尚未被预先训练。
如何实现
第一种方法:
预处理数据集,并用sklearn来运行KNN、SVM和BP神经网络。
首先,我们使用openCV包定义了两种不同的预处理函数:第一个称为图像特征向量,调整图像大小,然后将图像平坦化为行像素列表。第二个称为提取颜色直方图,使用cv2.normalize从HSV颜色间距中提取3D颜色直方图,然后平坦化结果。
然后,我们构造需要解析的几个参数,因为我们要测试这个部分的准确性,不仅是针对整个数据集的,还要测试具有不同数量标签的子数据集,我们将数据集构造为解析到我们程序中的参数。与此同时,我们还构造了用于k-NN方法的相邻数作为解析参数。
做好这些之后,我们开始提取数据集中的每个图像特征并将其放入数组中。我们使用cv2.imread来读取每个图像,通过从图像名称中提取字符串来拆分标签。在我们的数据集中,我们使用相同的格式设置名称:“类标签”.“图像号”.jpg,因此我们可以轻松地提取每个图像的类标签。然后我们使用之前定义的2个函数来提取2种特征,并附加到数组rawImages和特征中,而我们之前提取的标签则附加到数组标签。
下一步是使用从sklearn包导入的函数train_test_split拆分数据集。具有后缀RI、RL的集合是rawImages和标签对的拆分结果,另一个是特征和标签对的拆分结果。我们使用数据集的85%作为训练集,15%作为测试集。
最后,我们运用KNN、SVM和BP神经网络函数来评估数据。对于KNN,我们使用KNeighborsClassifier;对于SVM,我们使用SVC;对于BP神经网络,我们使用MLPClassifier。
第二种方法:
使用TensorFlow构建CNN。TensorFlow的目的是让你构建一个计算图(使用任何类似Python的语言),然后用C ++来执行图形操作,这比直接用Python来执行相同的计算要高效得多。
TensorFlow还可以自动计算优化图形变量所需的梯度,以便使模型更好地运行。这是因为图形是简单数学表达式的组合,因此可以使用导数的链式规则来计算整个图形的梯度。
TensorFlow图由以下部分组成:
用于将数据输入图表的占位符变量。
要进行优化的变量,以便使卷积网络更好地得以运行。
卷积网络的数学公式。
可用于指导变量优化的成本衡量标准。
一种更新变量的优化方法。
CNN架构由不同层的堆叠形成,其通过可微函数将输入量转换成输出量(例如类别分数)。
所以在我们的实现操作中,第一层是保存图像,然后我们构建了3个具有2×2最大池和修正线性单元(ReLU)的卷积层。
输入是一个具有以下尺寸的四维张量:
图像编号。
每个图像的Y轴。
每个图像的X轴。
每个图像的通道。
输出是另一个四维张量,具有以下尺寸:
图像号,与输入相同。
每个图像的Y轴。如果使用的是2×2池,则输入图像的高度和宽度除以2。
每个图像的X轴。同上。
由卷积滤波器产生的通道。
然后我们在网络末端构建了2个完全连接的层。输入是形状为[num_images,num_inputs]的2维张量。输出是形状为[num_images,num_outputs]的2维张量。
然而,为了连接卷积层和完全连接层,我们需要一个平坦层,将4维张量减小到2维,从而可以用作完全连接层的输入。
CNN的最后端始终是一个softmax层,它将来自全连接层的输出归一化,使得每个元素被限制在0和1之间,而所有元素总和为1。
为了优化训练结果,我们需要一个成本衡量标准,并尽量减少每次迭代。我们在这里使用的成本函数是交叉熵(从tf.nn.oftmax_cross_entropy_with_logits()调用),并对所有图像分类采用交叉熵的平均值。优化方法是tf.train.AdamOptimizer(),它是Gradient Descent的高级形式。这是一个调整的参数学习率。
第三种方法:
Retrain Inception V3物体识别模型有数百万个参数,可能需要几周才能完全训练。迁移学习是一种技术,可以通过为一组类别(如ImageNet)采用训练有素的模型来快速完成此项工作,并从新类别的现有权重中进行训练。虽然它不如全训练运行得那么好,但对于许多应用来说,这是非常有效的,并且可以在笔记本电脑上运行,只要运行三十分钟即可,无需GPU。对于这部分的实现,我们可以按照下边的说明进行操作:
https://www.tensorflow.org/tutorials/image_retraining
首先,我们需要获得预先训练的模型,删除旧的顶层,并在我们拥有的数据集上训练一个新的模型。在一个没有猫品种的原始ImageNet类中,要对完整的网络进行训练。迁移学习的神奇之处在于,经过训练以区分某些对象的较低层可以重用于许多识别任务,而无需任何更改。然后我们分析磁盘上的所有图像,并计算其中每个图像的瓶颈值(bottleneck values)。点击这里查看bottleneck的详细信息(https://www.tensorflow.org/tutorials/image_retraining)。每个图像在训练过程中被重复使用多次,所以计算每个瓶颈值都需要花费大量的时间,因此可以加快缓存这些瓶颈值,从而不必重复计算。
该脚本将运行4000个训练步骤。每个步骤从训练集中随机选择十个图像,从缓存中发现其瓶颈,并将它们馈送到最后一层以获得预测。然后将这些预测与实际标签进行比较,从而通过反向传播过程更新最终层的权重。
开始实验
数据集
牛津IIIT宠物数据集(http://www.robots.ox.ac.uk/~vgg/data/pets/)
有25个品种的狗和12个品种的猫。每个品种有200张图像。
我们在项目中只使用了10个猫品种。
我们在这里使用的类型是['Sphynx'(加拿大无毛猫,也称斯芬克斯猫),'Siamese'(暹罗猫),'Ragdoll'(布偶猫),'Persian'(波斯猫),'Maine-Coon'(缅因猫),'British-shorthair'(英国短毛猫),'Bombay'(孟买猫),'Birman'(缅甸猫),'Bengal'(孟加拉猫)]。
所以我们在数据集中共有2000张图像,彼此的尺寸各不同。但是我可以将它们调整为固定大小,如64 x 64或128 x 128。
预处理
在这个项目中,我们主要使用OpenCV进行图像数据的处理,比如将图像读入数组,并重新形成我们需要的尺寸。
改进图像训练结果的一个常见方法是以随机方式变形,裁剪或增亮训练输入,这具有扩展训练数据的有效大小的优点,而这归功于相同图像的所有可能的变化,并且倾向于帮助网络学习应对在分类器的现实使用中将发生的所有失真问题。
详情请参阅链接:https://github.com/aleju/imgaug
评估
第一种方法:
第一部分:预处理数据集,并用sklearn应用KNN、SVM和BP神经网络。
在程序中有很多参数可以调整:在image_to_feature_vector函数中,我们设置的尺寸是128x128,我们之前也尝试过像8x8,64x64,256x256这样的大小。从而我们发现图像尺寸越大,精度越好。但是,大的图像尺寸也会增加执行时间和内存消耗。所以我们终于决定图像尺寸为128x128,因为它不是太大,但同时也可以保证精度。
在extract_color_histogram函数中,我们将每个通道的bin数设置为32,32,32。像上一个函数一样,我们也尝试了8,8,8和64,64,64,并且更高的数字可以产生更高的结果,但同时也伴随着更高的执行时间。所以我们认为32,32,32是最合适的。
而至于数据集,我们尝试了3种数据集。第一个是具有400个图像,2个标签的子数据集。第二个是具有1000个图像,5个标签的子数据集。最后一个是具有1997个图像,10个标签的整个数据集。并且我们将不同的数据集解析为程序中的参数。
在KNeighborsClassifier中,我们只更改了邻居数,并将结果存储为每个数据集的最佳K。然后将我们设置的所有其他参数初始为默认值。
在MLPClassifier中,我们设置了一个含有50个神经元的隐藏层。我们测试了多个隐藏层,但最终结果似乎没有什么明显的变化。最大迭代时间为1000,容差为1e-4,以确保其收敛。并将L2惩罚参数α设置为默认值,随机状态为1,求解器为“sgd”,学习速率为0.1。
在SVC中,最大迭代时间为1000,类的权重值为“平衡”。
我们的程序的运行时间不是很长,从2个标签数据集到10个标签数据集需要大约3到5分钟。
第二种方法:
用TensorFlow构建CNN。
计算模型的梯度是需要很长时间的,因为这个模型使用的是大型数据集的整体。因此,我们在优化器的每次迭代中仅仅使用少量的图像。批量大小通常为32或64。数据集分为包含1600张图像的训练集,包含400张图像的验证集和包含300张图像的测试集。
有很多参数是可以进行调整的。
首先是学习率。只要它足够小,可以收敛和足够大得不会使程序太慢,一个好的学习率还是很容易找到的。我们选择了1×10 ^ -4。
第二个是我们向网络提供的图像的大小。我们尝试了64 * 64和128 * 128。事实证明,图像越大,我们得到的准确性越高,但代价是运行时间也相应地增加。
然后是层和它们的形状。但实际上有太多的参数可以调整,所以想要找到这些参数的最佳值是一件非常困难的工作。
根据网上的许多资源,我们了解到,建立网络的参数的选择几乎都取决于经验。
起初我们试图建立一个相对复杂的网络,其参数如下所示:
我们使用3个卷积层和2个完全连接的层,而这些都是相对复杂的。
但是,结果是——过度拟合。只有经过一千次迭代,我们的程序才能获得100%的训练精度,而只有30%的测试精度。起初我很困惑为什么我们会得到一个过度拟合的结果,并且我试图随机调整参数,但是结果却始终没有变好。几天后,我碰巧看到一篇文章,谈到中国研究人员进行的一个深入学习项目(https://medium.com/@blaisea/physiognomys-new-clothes-f2d4b59fdd6a)。他们指出,他们进行的研究是有问题的。“一个技术性的问题是,想要训练和测试像AlexNet这样的CNN,而结果不会过度拟合,仅仅使用不到2000个例子是不足以做到的。”所以,这个时候我才意识到,首先我们的数据集实际上是很小的,其次就是我们的网络太复杂了。
要记得我们的数据集是刚好包含2000张图像。
然后我尝试减少内核的数量层和大小。我尝试了很多参数,下图就是我们使用的最终结构。
我们只使用2个小形状的卷积层和2个完全连接的层。可结果并不是很理想,4000次迭代后得到的结果仍然是过度拟合,但是测试结果比以前好了10%。
我们仍然在寻找一种处理方法,但是显而易见的原因是我们的数据集不足,而我们没有足够的时间进行更好的改进。
最终结果就是,经过5000次迭代之后,我们大概达到了43%的精度,而运行时间却超过半个小时。
PS:实际上,由于这个结果,我们感到有些不安。 所以我们发现了另一个标准的数据集—CIFAR-10(https://www.cs.toronto.edu/~kriz/cifar.html)。
CIFAR-10数据集由10个类别的60000个32x32彩色图像组成,每个类别有6000张图像。里面含有50000个训练图像和10000个测试图像。
我们使用上面构造的相同网络,经过10小时的训练,我们在测试集上得到了78%的准确度。
第三种方法:
重新训练Inception V3,与此相同,我们随机选择几个图像进行训练,并选择另一批图像进行验证。
有很多参数是可以进行调整的。
首先是训练步骤,默认值是4000,如果我们可以早些得到一个合理的结果,我们尝试更多或尝试一个较小的。
学习率是控制训练过程中最后一层更新的大小的。直观地说,如果越小,那么学习将需要更长的时间,但最终可以帮助提高整体精度。**train batch**size会在一个训练步骤中控制检查了的图像的数量,并且由于学习率是应用到每个批次中的,所以如果你想要让更大的批次来获得相同的整体效果的话,我们将需要减少它们的数量。
因为深入学习任务繁重,运行时间通常相对较长,所以我们不希望经过数小时的训练之后得知,我们的模式实际上是很糟糕的。因此我们经常检验验证的准确性。这样我们也可以避免过度拟合。通过分割可以将80%的图像放入主要训练集中,保持10%作为训练期间的验证,频繁运行,然后将最终10%的图像用作测试集,以预测分类器在现实世界的表现。
结果
第一种方法:预处理数据集,并用sklearn来运行KNN、SVM和BP神经网络。
结果如下图所示。因为SVM的结果非常差,甚至低于随机猜测,所以我们没有提供其运行结果。
从结果我们可以看出:
在k-NN中,原始像素精度和直方图精度相对相同。在含有5个标签的子数据集中,直方图精度比原始像素高出那么一点,但是在所有原始像素中,原始像素显示出更好的结果。
在神经网络MLP分类器中,原始像素精度远低于直方图精度。而对于整个数据集(含有10个标签)来说,原始像素精度甚至低于随机猜测。
所有这两种sklearn方法都没有给出非常好的性能,在整个数据集(含有10个标签的数据集)中识别正确类别的准确度只有约24%。这些结果表明,使用sklearn方法进行图像识别的效果不够好。他们在具有多种类别的复杂图像的分类中并不具备良好的性能。但是,与随机猜测相比,他们确实做了一些改进,但这还远远不够。
基于此结果,我们发现为了提高准确性,必须采用一些深度学习的方法。
第二种方法:使用TensorFlow构建如上所述的CNN,由于过度拟合,我们无法获得良好的效果。
训练通常需要半小时的时间来进行,但是由于结果过度拟合,我们认为这个运行时间并不重要。与方法1进行比较,我们可以看到:虽然CNN的结果过度拟合,但我们仍然会得到一个比方法1更好的结果。
第三种方法:重新训练 Inception V3。
整个训练进度不超过10分钟。而我们可以取得非常好的成绩。基于此,我们实际上可以看到深度学习和迁移学习的巨大能量。
演示:
目标
基于上述的比较,我们可以得出这样的结论:
KNN、SVM和BP神经网络是不能够很好地完成诸如图像分类这样的特定任务的。
虽然我们在CNN部分得到的结果过度拟合,但仍然比在课堂中学到的处理图像分类问题的其他方法要好得多。
迁移学习在图像分类问题上具有非常高的效率。无需GPU即可在短时间内准确快捷地完成训练。即使你有一个小的数据集,它也可以很好地防止过度拟合。
我们学到了一些非常重要的图像分类任务经验。这样的任务与我们上课时所做的其他任务完全不同。数据集相对较大而不稀疏,网络复杂,因此如果不使用GPU,运行时间会相当长。
裁剪或调整图像大小使其更小。
随机选择一个小批量进行每次迭代训练。
在验证集中随机选择一个小批量进行验证,在训练过程中经常报告验证的得分情况。
尝试使用图像增强将一组输入图像转换为一组新的,更大的,略有更改的图像。
对于图像分类任务,我们需要一个比200 x 10的更大的数据集,CIFAR10数据集包含6万张图像。
更复杂的网络需要更多的数据集来进行训练。
注意过度拟合。