cs231n---图像分类

知乎原文链接

目标

分类就是常规理解的分类,之后我们可以看到CV中很多看似不同的问题,比如物体检测和分割,都可以归结为图像分类问题。

例子

图像分类模型读取该图片,并生成该图片属于集合{cat,都给,hat,mug}中各个标签的概率。
图像:248 × 400 × 3 = 297600
这里写图片描述

困难和挑战

视角变化
大小变化
形变:形状并非一成不变
遮挡:部分被挡住,剩下一部分可见
光照条件:在像素层面上,光照影响非常大
背景干扰:物体可能混入背景之中,使之难以被辨认
类内差异:一类物体的个体之间的外形差异很大,比如椅子。这一类物体有许多不同的对象,每个都有自己的外形。
这里写图片描述

数据驱动方法

代码如何看出一个物体是怎样的,类似于教小孩看图识物,给计算机很多数据,然后实现学习算法,让计算机学习到每个类的外形,即为数据驱动方法。
这里写图片描述

图像分类流程

图像分类就是输入一个元素为像素值的数组,然后给它分配一个分类书签。
1)输入:输入是包含N个图像的集合,每个图像的标签是K种分类标签中的一种,这个集合称为训练集。
2)学习:这一步的任务是使用训练集来学习每个类到底长什么样。
3)评价:让分类器来预测它未曾见过的图像的分类标签,并以此来评价分类器的质量。我们会把分类器预测的标签和图像真正的分类标签对比。

Nearest Neighbor 分类器

数据集:一个非常流行的图像分类数据集是CIFAR-10
这里写图片描述
假设现在我们有CIFAR-10的50000张图片作为训练集,我们希望余下的10000作为测试集并给他们打上标签。NearestNeighbor 算法将会拿着测试图片和训练集中每一张图片去比较,然后将它认为最相似的那个训练集图片的标签赋给这张测试图片。上面右边的图片就展示了这样的结果。请注意上面10个分类中,只有3个是准确的。比如第8行中,马头被分类为一个红色跑车,原因在于红色跑车的黑色背景非常强烈,所以这匹马就被错误的分类为跑车了。

如何比较这两张图片呢?在本例中,就是比较32×32×3的像素块。最简单的方法就是逐个像素比较,最后将差异值全部加起来。换句话说,就是将两张图片先转化为两个向量 I1 I 1 I2 I 2 ,然后计算L1距离:
d1 d 1 ( I1 I 1 I2 I 2 ) = p|Ip1Ip2| ∑ p | I 1 p − I 2 p |
这里的求和是针对所有的像素。
下面是整个比较流程的图例:
这里写图片描述

代码实现:

1)将数据加载到内存中,并分成4个数组:训练数据和标签,测试数据和标签。
在下面的代码中,Xtr(大小是50000×32×32×3)存有训练集中所有的图像,Ytr是对应的长度为50000的一维数组,存有图像对应的分类标签(从0~9):

Xtr,Ytr,Xte,Yte = load_CIFAR10('data/cifar10/')
Xtr_rows = Xtr.reshape(Xtr.shape[0]32 × 32 × 3)
Xte_rows = Xte.reshape(Xte.shape[0]32 × 32 × 3)

现在我们得到所有的图像数据,并且把他们拉长成为行向量了。
如何训练并且评价一个分类器:

nn = NearesNeighbor()  #创建一个分类器
nn.train(Xtr_rows,Ytr) #训练
Yte_predict = nn.predict(Xte_rows) #使用test_images训练
print 'accuracy : %f'%(np.mean(Yte——predict == Yte))

作为评价标准,我们常常食用准确率,他描述了我们预测正确的得分。
注意:以后实现的所有分类器都需要这个API:train(X,y)函数。
该函数使用训练集的数据和标签来进行训练,从其内部来看,类应该实现一些关于标签和标签如何被预测的模型。
predict(X)函数:作用是预测输入的新数据的分类标签。
使用L1距离的Nearest Neighbor分类器的实现套路:

import numpy as np

class NearestNeighbor(object):
    def _init_(self):
        pass

    def train(self,X,y): #训练一部分做训练街,一部分做验证集
        self.Xtr = X
        self.ytr = y

    def predict(self,X):
        num_test = X.shape[0]
        Ypred = np.zeros(num_test,dtype = self.ytr.dtype)

        for i in xrange(num_test): #计算距离
            distances = np.sum(np.abs(self.Xtr = X[i,:]),axis = 1)

        min_index = np.argmin(distances) #找最小的
        Ypred[i] = self.ytr[min_index]
        return Ypred

准确率只有38.6%
距离选择
计算向量间的距离有很多种方法,另一个常用的L2距离,从几何学的角度,可以理解为它在计算两个向量间的欧式距离。L2距离公式如下:
d2 d 2 ( I1 I 1 I2 I 2 ) = p|Ip1Ip2| ∑ p | I 1 p − I 2 p |
依旧在计算像素间的差值,就是先求其平方,然后把这些平方全部加起来,最后对这个和开方。在Numpy中,我们只需要替换上面代码中1行代码就行:

distances = np.sqrt(distances = np.sum(np.abs(self.Xtr - X[i,:]),axis = 1))

L1和L2比较:
在面对两个向量间的差异时,L2比L1更加不能容忍这些差异,也就是说,相对于一个巨大的差异,L2距离更倾向于接受多个中等程度的差异。L1和L2都是在p-norm常用的特殊形式。(L1 norm就是绝对值相加,又称曼哈顿距离,L2 norm 距离就是欧几里得距离)

K-Nearest Neighbor分类器

为什么只用最相似的1张图片的标签作为测试图像的标签呢?这不是很奇怪吗?使用K-Nearest Neighbor分类器能够做的更好。

思想

与其只找最相近的那1个图片的标签,我们找最相似的K个图片的标签,然后让他们针对测试图片进行投票,最后把票数最高的标签作为对测试图片的预测。所以当K = 1 时,K-Nearest Neighbor的分类器就是NearestNeighbor分类器。
直观来看,更高的K值可以使分类效果更平滑,使得分类器对于异常值更有抵抗力。
这里写图片描述
注意:

1)异常的数据点(在NN中蓝色区域出现绿点)制造出一个不正确预测的孤岛。5-NN分类器将这些不规则都平滑了,使得它针对测试数据的泛化能力更好。
2)5-NN中的灰色区域是因为最高票数相同导致的(比如:红色票数为2,蓝色票数为2,绿色票数为1)不能解决吗?继续投票不可以吗?直到能决定到底是哪一个


那么K如何决定呢?

用于超参数调优的验证集

KNN分类器需要设定K值,那么选择哪个K值最合适呢?我们可以选择不同的距离函数,比如L1范数和L2范数,选哪个好?还有不少选择我们甚至连考虑都没有考虑到(比如:点积),被称为超参数(hyperparameter)

K如何选择呢?尝试不同的值,看哪个值表现好就选哪个。

注意:绝不能使用测试集来进行调优(当在设计机器学习算法的时候,应该把测试集看做非常珍贵的资源,不到最后一步,绝不使用它,因为可能会过拟合,性能在真正测试时并没期望的好)

测试数据集只使用一次,即在训练网后评价最终的模型使用
调优并不使用测试集

正确的思路:
从训练集找找那个取出一部分数据用来调优,称之为验证集(validation set),我们可以用49000个图像作为训练集,用1000个图像作为验证集。验证集其实就是作为假的测试集进行调优。
调优代码:(—————-不懂————————-)

Xval_rows = Xtr_rows[:1000,:]  #取1000个做验证集
Yval = Ytr[:1000]
Xtr_rows = Xtr_rows[1000:,:]   #剩下的49000 个做训练集
Ytr = Ytr[1000:]


validation_accuracies = []   #验证集的准确率
for k in [1,3,5,10,20,50,100]:

    nn = NearestNeighbor()
    nn.train(Xtr_rows,Ytr)

    # 这里我们假设该模型的K作为输入
    Yval_predict = nn.predict(Xval_rows,k = k)
    acc = np.mean(Yval_predict == Yval)
    print('accuracy:%f'%(acc,))

    validaiton_accuracies.append((k,acc))   

程序结束后,会作图分析出哪个K值表现最好,然后用这个K值来跑真正的测试集,并对算法做出评价。

交叉验证

比如:将训练集平均分成5大份,其中每一小份也分成5份,4份用来训练,1份用来验证,然后我们循环着取其中4份来训练,1份来验证,最后取所有5次验证结果的平均值作为算法验证结果。
这里写图片描述
这是5份交叉验证对K值调优(分别选取不同的K值,每一个K值都需要进行调优)的例子,针对每个K值,得到5个准确率结果,取平均值,然后对不同的K值的平均表现画线连接。本例中,k=7时算法表现最好,如果我们将训练集分成更多份数,直线会更加平滑(噪音更少)。
实际应用
在实际情况下,人们不是很喜欢用交叉验证,主要是因为它会耗费较多的计算资源。一般直接把训练集按照50%-90%的比例分成训练集和验证集。但这也是根据具体情况来定的:如果超参数数量多,你可能就想用更大的验证集,而验证集的数量不够,那么最好还是用交叉验证吧。至于分成几份比较好,一般都是分成3、5和10份。
这里写图片描述
常用的数据分割模式:
给出训练集和测试集后,训练集一般会被均分。这里是分成5份。前面4份用来训练,黄色那份用作验证集调优。如果采取交叉验证,那就各份轮流作为验证集。最后模型训练完毕,超参数都定好了,让模型跑一次(而且只跑一次)测试集,以此测试结果评价算法。


NN分类器的优劣

1)训练不需要花时间,因为其训练过程只是将训练集数据存储起来,然而测试要花费大量时间计算,因为每个测试图像需要和所有存储图像进行比较,这显然是一个缺点。在实际应用中,我们关注测试效率远远高于训练效率。
其实,我们后续要学习的卷积神经网络在这个权衡上走到了另一个极端:虽然训练花费很多时间,但是一旦训练完成,对新的测试数据进行分类非常快。这样的模式就符合实际使用需求。

NN分类器的计算复杂度研究度是一个活跃的研究领域,若干Approximate NN (ANN)算法和库的使用可以提升NN分类器在数据上的计算速度(比如:FLANN)这些算法可以在准确率和时空复杂度之间进行权衡,并通常依赖一个预处理/索引过程,这个过程中一般包括kd树的创建和k-means算法的运用。

Nearest Neighbor分类器在某些特定情况(比如数据维度较低)下,可能是不错的选择。但是在实际的图像分类工作中,很少使用。因为图像都是高维度数据(他们通常包含很多像素),而高维度向量之间的距离通常是反直觉的。下面的图片展示了基于像素的相似和基于感官的相似是有很大不同的:
这里写图片描述
在高维度数据上,基于像素的的距离和感官上的非常不同。上图中,右边3张图片和左边第1张原始图片的L2距离是一样的。很显然,基于像素比较的相似和感官上以及语义上的相似是不同的。

这里还有个视觉化证据,可以证明使用像素差异来比较图像是不够的。z这是一个叫做t-SNE的可视化技术,它将CIFAR-10中的图片按照二维方式排布,这样能很好展示图片之间的像素差异值。在这张图片中,排列相邻的图片L2距离就小。
这里写图片描述
上图使用t-SNE的可视化技术将CIFAR-10的图片进行了二维排列。排列相近的图片L2距离小。可以看出,图片的排列是被背景主导而不是图片语义内容本身主导。
具体说来,这些图片的排布更像是一种颜色分布函数,或者说是基于背景的,而不是图片的语义主体。比如,狗的图片可能和青蛙的图片非常接近,这是因为两张图片都是白色背景。从理想效果上来说,我们肯定是希望同类的图片能够聚集在一起,而不被背景或其他不相关因素干扰。为了达到这个目的,我们不能止步于原始像素比较,得继续前进。

小结

· 介绍了图像分类问题。在该问题中,给出一个由被标注了分类标签的图像组成的集合,要求算法能预测没有标签的图像的分类标签,并根据算法预测准确率进行评价。
· 介绍了一个简单的图像分类器:最近邻分类器(Nearest Neighbor classifier)。分类器中存在不同的超参数(比如k值或距离类型的选取),要想选取好的超参数不是一件轻而易举的事。
· 选取超参数的正确方法是:将原始训练集分为训练集和验证集,我们在验证集上尝试不同的超参数,最后保留表现最好那个。
如果训练数据量不够,使用交叉验证方法,它能帮助我们在选取最优超参数的时候减少噪音。
· 一旦找到最优的超参数,就让算法以该参数在测试集跑且只跑一次,并根据测试结果评价算法。
· 最近邻分类器能够在CIFAR-10上得到将近40%的准确率。该算法简单易实现,但需要存储所有训练数据,并且在测试的时候过于耗费计算能力。
· 最后,我们知道了仅仅使用L1和L2范数来进行像素比较是不够的,图像更多的是按照背景和颜色被分类,而不是语义主体分身。

小结:实际应用k-NN

如果你希望将k-NN分类器用到实处(最好别用到图像上,若是仅仅作为练手还可以接受),那么可以按照以下流程:

1.预处理你的数据:对你数据中的特征进行归一化(normalize),让其具有零平均值(zero mean)和单位方差(unit variance)。在后面的小节我们会讨论这些细节。本小节不讨论,是因为图像中的像素都是同质的,不会表现出较大的差异分布,也就不需要标准化处理了。
2.如果数据是高维数据,考虑使用降维方法,比如PCA(wiki ref, CS229ref, blog ref)或随机投影。
3.将数据随机分入训练集和验证集。按照一般规律,70%-90% 数据作为训练集。这个比例根据算法中有多少超参数,以及这些超参数对于算法的预期影响来决定。如果需要预测的超参数很多,那么就应该使用更大的验证集来有效地估计它们。如果担心验证集数量不够,那么就尝试交叉验证方法。如果计算资源足够,使用交叉验证总是更加安全的(份数越多,效果越好,也更耗费计算资源)。
4.在验证集上调优,尝试足够多的k值,尝试L1和L2两种范数计算方式。
5.如果分类器跑得太慢,尝试使用Approximate Nearest Neighbor库(比如FLANN)来加速这个过程,其代价是降低一些准确率。
6.对最优的超参数做记录。记录最优参数后,是否应该让使用最优参数的算法在完整的训练集上运行并再次训练呢?因为如果把验证集重新放回到训练集中(自然训练集的数据量就又变大了),有可能最优参数又会有所变化。在实践中,不要这样做。千万不要在最终的分类器中使用验证集数据,这样做会破坏对于最优参数的估计。直接使用测试集来测试用最优参数设置好的最优模型,得到测试集数据的分类准确率,并以此作为你的kNN分类器在该数据上的性能表现。

摘自知乎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值