CS231n的全称是CS231n: Convolutional Neural Networks for Visual> Recognition,即面向视觉识别的卷积神经网络。今天所来了解的是图像的分类。仅供自己记录学习。
图像分类
一.Nearest Neighbor Classifier(最近邻分类器)
二.k-Nearest Neighbor Classifier(k-最近邻分类器)
三.验证集、交叉验证集和超参数调优
四.Nearest Neighbor的优劣
前言
什么是图像分类?
图像分类问题,就是已有固定的分类标签集合,然后对于输入的图像,从分类标签集合中找出一个分类标签,最后把分类标签分配给该输入图像。虽然看起来挺简单的,但这可是计算机视觉领域的核心问题之一,并且有着各种各样的实际应用。比如物体检测和分割,都可以被归结为图像分类问题。
图像分类面临问题?
但同时,在图像识别中,会因为视角变化(Viewpoint variation)、大小变化(Scale variation)、形变(Deformation)、遮挡(Occlusion)、光照条件(Illumination conditions)、背景干扰(Background clutter)以及类内差异(Intra-class variation)等问题影响图像的识别
数据驱动方法?
就我而言,数据驱动即在数据的支撑下或者指导下进行科学的行动。如何写一个图像分类的算法呢?怎么写一个从图像中认出猫的算法?不太清楚。因此,与其在代码中直接写明各类物体到底看起来是什么样的,倒不如说我们采取的方法和教小孩儿看图识物类似:
给计算机很多数据,然后实现学习算法,让计算机学习到每个类的外形。这种方法,就是数据驱动方法(train训练机器)。
如图为四个视觉分类的训练集
图像分类流程?
输入一个元素为像素值的数组,然后给它分配一个分类标签。
流程:输入----->学习----->评价
输入:输入是包含N个图像的集合,每个图像的标签是K种分类标签中的一种。这个集合称为训练集。
学习:这一步的任务是使用训练集来学习每个类到底长什么样。一般该步骤叫做训练分类器或者学习一个模型。
评价:让分类器来预测它未曾见过的图像的分类标签,并以此来评价分类器的质量。我们会把分类器预测的标签和图像真正的分类标签对比。毫无疑问,分类器预测的分类标签和图像真正的分类标签如果一致,那就是好事,这样的情况越多越好。
一.Nearest Neighbor Classifier(最近邻分类器)
虽然这个分类器和卷积神经网络没有任何关系,实际中也极少使用,但通过实现它,可以在解决图像分类问题的方法有个基本的认识。
该分类器的实现与运用:
第一点------图像分类数据集:CIFAR-10
该数据集包含了60000张32X32的小图像。每张图像都有10种分类标签中的一种。这60000张图像被分为包含50000张图像的训练集和包含10000张图像的测试集。
在下左图中你可以看见10个类的10张随机图片。即从数据集CIFAR-10里来的样本图像。
右边图像:
第一列是测试图像,然后第一列的每个测试图像右边是使用Nearest Neighbor算法,根据像素差异,从训练集中选出的10张最类似的图片。
大致讲解:
假设现在我们有CIFAR-10的50000张图片(每种分类5000张)作为训练集,我们希望将余下的10000作为测试集并给他们打上标签。Nearest Neighbor算法将会拿着测试图片和训练集中每一张图片去比较,然后将它认为最相似的那个训练集图片的标签赋给这张测试图片。上面右边的图片就展示了这样的结果。请注意上面10个分类中,只有3个是准确的。比如第8行中,马头被分类为一个红色的跑车,原因在于红色跑车的黑色背景非常强烈,所以这匹马就被错误分类为跑车了。
第二点------针对以上的讲解,那么具体是如何比较的呢?
首先这里需要稍微看看L1距离或L2距离的含义、用法
在本例中,就是比较32x32x3的像素块。最简单的方法:逐个像素比较,最后将差异值全部加起来。换句话说,就是将两张图片先转化为两个向量Ι_1和Ι_2,然后计算他们的L1距离:
(这里的求和是针对所有的像素)
即比较流程图例如下:
以图片中的一个颜色通道为例来进行说明:两张图片使用L1距离来进行比较------>逐个像素求差值------>将所有差值加起来得到一个数值。如果两张图片一模一样,那么L1距离为0,但是如果两张图片很是不同,那L1值将会非常大。
第三点------用代码实现该分类器
1.将CIFAR-10的数据加载到内存中,并分成4个数组:训练数据和标签,测试数据和标签;
2.在Xtr(大小是50000x32x32x3)存有训练集中所有的图像,Ytr是对应的长度为50000的1维数组,存有图像对应的分类标签(从0到9);
该部分代码如下
Xtr, Ytr, Xte, Yte = load_CIFAR10('data/cifar10/') # a magic function we provide
# flatten out all images to be one-dimensional
Xtr_rows = Xtr.reshape(Xtr.shape[0], 32 * 32 * 3) # Xtr_rows becomes 50000 x 3072
Xte_rows = Xte.reshape(Xte.shape[0], 32 * 32 * 3) # Xte_rows becomes 10000 x 3072
3.把得到的所有图像数据拉长成为行向量,展示如何训练并(打印出准确率)评价一个分类器;
该部分代码如下
nn = NearestNeighbor() # create a Nearest Neighbor classifier class
nn.train(Xtr_rows, Ytr) # train the classifier on the training images and labels
Yte_predict = nn.predict(Xte_rows) # predict labels on the test images
# and now print the classification accuracy, which is the average number
# of examples that are correctly predicted (i.e. label matches)
print 'accuracy: %f' % ( np.mean(Yte_predict == Yte) )
注:作为评价标准,我们常常使用准确率,它描述了我们预测正确的得分。以后实现的所有分类器都需要有这个API:train(X, Y)函数,该函数使用训练集的数据和标签来进行训练。从其内部来看,类应该实现一些关于标签和标签如何被预测的模型。这里还有个predict(X)函数,它的作用是预测输入的新数据的分类标签。
下面就是使用L1距离的Nearest Neighbor分类器的实现套路:
import numpy as npclass NearestNeighbor(object):def __init__(self):passdef train(self, X, y):""" X is N x D where each row is an example. Y is 1-dimension of size N """# the nearest neighbor classifier simply remembers all the training dataself.Xtr = Xself.ytr = ydef predict(self, X):""" X is N x D where each row is an example we wish to predict label for """num_test = X.shape[0]# lets make sure that the output type matches the input typeYpred = np.zeros(num_test, dtype = self.ytr.dtype)# loop over all test rowsfor i in xrange(num_test):# find the nearest training image to the i'th test image# using the L1 distance (sum of absolute value differences)distances = np.sum(np.abs(self.Xtr - X[i,:]), axis = 1)min_index = np.argmin(distances) # get the index with smallest distanceYpred[i] = self.ytr[min_index] # predict the label of the nearest examplereturn Ypred
如果你用这段代码跑CIFAR-10,你会发现准确率能达到38.6%。这比随机猜测的10%要好,但是比人类识别的水平(据研究推测是94%)和卷积神经网络能达到的95%还是差多了。点击查看基于CIFAR-10数据的Kaggle算法竞赛排行榜。
第三点------向量间的距离选择
以上是L1距离,而另一个常用的方法是L2距离,从几何学的角度,可以理解为它在计算两个向量间的欧式距离。L2距离的公式如下:
从以上公式可以看出,我们依旧是在计算像素间的差值,只是先求其平方,然后把这些平方全部加起来,最后对这个和开方。
在Numpy中,我们只需要替换上面代码中的1行代码就行即:
distances = np.sqrt(np.sum(np.square(self.Xtr - X[i,:]), axis = 1))
注:此处使用了np.sqrt,但是在实际中可能不用。因为求平方根函数是一个单调函数,它对不同距离的绝对值求平方根虽然改变了数值大小,但依然保持了不同距离大小的顺序,所以用不用它,都能够对像素差异的大小进行正确比较。如果你在CIFAR-10上面跑这个模型,正确率是35.4%,比刚才低了一点。
第四点------L1距离和L2距离两个度量方式的比较
在面对两个向量之间的差异时,L2比L1更加不能容忍这些差异。也就是说,相对于一个巨大的差异,L2距离更倾向于接受多个中等程度的差异。(L1和L2都是在p-norm常用的特殊形式)
二.k-Nearest Neighbor Classifier(k-最近邻分类器)
由上面的分类法,为什么只用最相似的1张图片的标签来作为测试图像的标签呢?这不是很奇怪吗!是的,使用k-Nearest Neighbor分类器就能做得更好。它的思想很简单:与其只找最相近的那一张图片的标签,我们找最相似的k个图片的标签,然后让他们针对测试图片进行投票,最后把票数最高的标签作为对测试图片的预测。所以当k=1的时候,k-Nearest Neighbor分类器就是Nearest Neighbor分类器。从直观感受上就可以看到,更高的k值可以让分类的效果更平滑,使得分类器对于异常值更有抵抗力。
k值不同时:
上面示例展示了Nearest Neighbor分类器和5-Nearest Neighbor分类器的区别。例子使用了二维的点来表示,分成3类(红、蓝和绿)。不同颜色区域代表的是使用L2距离的分类器的决策边界。白色的区域是分类模糊的例子(即图像与两个以上的分类标签绑定)。需要注意的是,在NN分类器中,异常的数据点(比如:在蓝色区域中的绿点)制造出一个不正确预测的孤岛。5-NN分类器将这些不规则都平滑了,使得它针对测试数据的泛化(generalization)能力更好(例子中未展示)。
注:5-NN中也存在一些灰色区域,这些区域是因为近邻标签的最高票数相同导致的(比如:2个邻居是红色,2个邻居是蓝色,还有1个是绿色)。额。。。。这里没看懂 2个邻居。。。
三.验证集、交叉验证集和超参数调优
用于超参数调优的验证集
k-NN分类器需要设定k值,那么选择哪个k值最合适的呢?我们可以选择不同的距离函数,比如L1范数和L2范数等,那么选哪个好?还有不少选择我们甚至连考虑都没有考虑到(比如:点积)。所有这些选择,被称为超参数(hyperparameter)。在基于数据进行学习的机器学习算法设计中,超参数是很常见的。一般说来,这些超参数具体怎么设置或取值并不是显而易见的。
你可能会建议尝试不同的值,看哪个值表现最好就选哪个。(就是试!)好主意!我们就是这么做的,但这样做的时候要非常细心。特别注意:决不能使用测试集来进行调优。当你在设计机器学习算法的时候,应该把测试集看做非常珍贵的资源,不到最后一步,绝不使用它。如果你使用测试集来调优,而且算法看起来效果不错,那么真正的危险在于:算法实际部署后,性能可能会远低于预期。这种情况,称之为算法对测试集过拟合。从另一个角度来说,如果使用测试集来调优,实际上就是把测试集当做训练集,由测试集训练出来的算法再跑测试集,自然性能看起来会很好。这其实是过于乐观了,实际部署起来效果就会差很多。(也就是说,对新的数据不太适用???)所以,最终测试的时候再使用测试集,可以很好地近似度量你所设计的分类器的泛化性能(指学习到的模型对未知数据的预测能力)。
注:也就是说,测试数据集只使用一次,即在训练完成后评价最终的模型时使用。
所以我们不用测试数据集来进行调优,其思路是:从训练集中取出一部分数据用来调优,我们称之为验证集(validation set)。以CIFAR-10为例,我们可以用49000个图像作为训练集,用1000个图像作为验证集。(验证集其实就是作为假的测试集来调优)
该部分代码如下:
# assume we have Xtr_rows, Ytr, Xte_rows, Yte as before
# recall Xtr_rows is 50,000 x 3072 matrix
Xval_rows = Xtr_rows[:1000, :] # take first 1000 for validation
Yval = Ytr[:1000]
Xtr_rows = Xtr_rows[1000:, :] # keep last 49,000 for train
Ytr = Ytr[1000:]# find hyperparameters that work best on the validation set
validation_accuracies = []
for k in [1, 3, 5, 10, 20, 50, 100]:# use a particular value of k and evaluation on validation datann = NearestNeighbor()nn.train(Xtr_rows, Ytr)# here we assume a modified NearestNeighbor class that can take a k as inputYval_predict = nn.predict(Xval_rows, k = k)acc = np.mean(Yval_predict == Yval)print 'accuracy: %f' % (acc,)# keep track of what works on the validation setvalidation_accuracies.append((k, acc))
在程序结束以后,我们会作图分析出哪个k值表现最好,然后用这个k值来跑真正的测试集,并作出对算法的评价。(换句话总结就是:把训练集分成训练集和验证集。使用验证集来对所有超参数调优。最后只在测试集上跑一次并报告结果。)
交叉验证
有时候,训练集数量较小(因此验证集的数量会更小),这时会使用一种被称为交叉验证的方法,这种方法更加复杂些。还是用刚才的例子,如果是交叉验证集,我们就不是取1000个图像,而是将训练集平均分成5份,其中4份用来训练,1份用来验证。然后我们循环着取其中4份来训练,其中1份来验证,最后取所有5次验证结果的平均值作为算法验证结果。
以下是交叉验证图例:
这就是5份交叉验证对k值调优的例子。针对每个k值,得到5个准确率结果,取其平均值,然后对不同k值的平均表现画线连接。本例中,当k=7时算法表现最好(对应图中的准确率峰值)。如果我们将训练集分成更多份数,直线一般会更加平滑(噪音更少)。
实际生活中的超参数调优
在实际情况下,人们不是很喜欢用交叉验证,主要是因为它会耗费较多的计算资源。一般直接把训练集按照50%-90%的比例分成训练集和验证集。但这也是根据具体情况来定的:如果超参数数量多,你可能就想用更大的验证集,而验证集的数量不够,那么最好还是用交叉验证吧。至于分成几份比较好,一般都是分成3、5和10份。
如下图划分方式:
常用的数据分割模式。给出训练集和测试集后,训练集一般会被均分。这里是分成5份。前面4份用来训练,黄色那份用作验证集调优。如果采取交叉验证,那就各份轮流作为验证集。最后模型训练完毕,超参数都定好了,让模型跑一次(而且只跑一次)测试集,以此测试结果评价算法。
四.Nearest Neighbor的优劣
优点:易于理解,实现简单。其次,算法的训练不需要花时间,因为其训练过程只是将训练集数据存储起来。
缺点:测试要花费大量时间计算,因为每个测试图像需要和所有存储的训练图像进行比较,在实际应用中,我们关注测试效率远远高于训练效率。
其实,后续要学习的卷积神经网络在这个权衡上走到了另一个极端:虽然训练花费很多时间,但是一旦训练完成,对新的测试数据进行分类非常快。这样的模式就符合实际使用需求。
Nearest Neighbor分类器的计算复杂度研究是一个活跃的研究领域,若干Approximate Nearest Neighbor (ANN)算法和库的使用可以提升Nearest Neighbor分类器在数据上的计算速度(比如:FLANN)。这些算法可以在准确率和时空复杂度之间进行权衡,并通常依赖一个预处理/索引过程,这个过程中一般包含kd树的创建和k-means算法的运用。
Nearest Neighbor分类器在某些特定情况(比如数据维度较低)下,可能是不错的选择。但是在实际的图像分类工作中,很少使用。因为图像都是高维度数据(他们通常包含很多像素),而高维度向量之间的距离通常是反直觉的。下面的图片展示了基于像素的相似和基于感官的相似是有很大不同的:
在高维度数据上,基于像素的的距离和感官上的非常不同。上图中,右边3张图片和左边第1张原始图片的L2距离是一样的。很显然,基于像素比较的相似和感官上以及语义上的相似是不同的。
这里还有个视觉化证据,可以证明使用像素差异来比较图像是不够的。z这是一个叫做t-SNE的可视化技术,它将CIFAR-10中的图片按照二维方式排布,这样能很好展示图片之间的像素差异值。在这张图片中,排列相邻的图片L2距离就小。
使用t-SNE的可视化技术将CIFAR-10的图片进行了二维排列图如下:
排列相近的图片L2距离小。可以看出,图片的排列是被背景主导而不是图片语义内容本身主导。具体说来,这些图片的排布更像是一种颜色分布函数,或者说是基于背景的,而不是图片的语义主体。比如,狗的图片可能和青蛙的图片非常接近,这是因为两张图片都是白色背景。从理想效果上来说,我们肯定是希望同类的图片能够聚集在一起,而不被背景或其他不相关因素干扰。为了达到这个目的,我们不能止步于原始像素比较,得继续前进。
资料来源:
1.斯坦福CS231n李飞飞计算机视觉视频课程:https://study.163.com/course/courseMain.htm?courseId=1003223001
2.CS231n官方笔记授权翻译总集篇:https://zhuanlan.zhihu.com/p/21930884