【cs231n】图像分类 k-Nearest Neighbor Classifier(K最近邻分类器)【python3实现】

 【学习自CS231n课程】

 转载请注明出处:http://www.cnblogs.com/GraceSkyer/p/8763616.html

k-Nearest Neighbor(KNN)分类器

  与其只找最相近的那1个图片的标签,我们找最相似的k个图片的标签,然后让他们针对测试图片进行投票,最后把票数最高的标签作为对测试图片的预测。所以当k=1的时候,k-Nearest Neighbor分类器就是Nearest Neighbor分类器。从直观感受上就可以看到,更高的k值可以让分类的效果更平滑,使得分类器对于异常值更有抵抗力。

  【或者说,用这种方法来检索相邻数据时,会对噪音产生更大的鲁棒性,即噪音产生不确定的评价值对评价结果影响很小。】

 

  上面例子使用了训练集中包含的2维平面的点来表示,点的颜色代表不同的类别或不同的类标签,这里有五个类别。不同颜色区域代表分类器的决策边界。这里我们用同样的数据集,使用K=1的最近邻分类器,以及K=3、K=5。K=1时(即最近邻分类器),是根据相邻的点来切割空间并进行着色;K=3时,绿色点簇中的黄色噪点不再会导致周围的区域被划分为黄色,由于使用多数投票,中间的整个绿色区域,都将被分类为绿色;k=5时,蓝色和红色区域间的这些决策边界变得更加平滑好看,它针对测试数据的泛化能力更好。

【注:白色区域表示 这个区域中没有获得K-最近邻的投票,你或许可以假设将其归为一个类别,表示这些点在这个区域没有最近的其他点】

建议:去网站上尝试用KNN分类你自己的数据,改变K值,改变距离度量,来培养决策边界的直觉。

网址:http://vision.stanford.edu/teaching/cs231n-demos/knn/

超参数的选择

  在实际中,大多使用k-NN分类器,但是K值如何确定?距离度量如何选择?向K值和距离度量这样的选择,被称为超参数(hyperparameter)。问题在于,在时间中该如何设置这些超参数。

  × 你首先可能想到的是,选择能对你的训练集给出最高准确率,表现最佳的超参数,这是糟糕的做法!例如:在之前的k-最近邻分类算法中,假设k=1,我们总能完美分类训练集数据,我们便采用了这个策略...但之前实践中让K取更大的值,尽管在训练集中分错个别数据,但对于在训练集中未出现过的数据分类性能更佳,可见K=1并不合适。【在机器学习中,我们关心的不是要尽可能拟合训练集,而是要让我们的分类器以及方法,在训练集以外的未知数据上表现得更好。】

   × 你或许又会想,把所有的数据分成两部分:训练集和测试集。然后在训练集上用不同的超参数来训练算法,然后将训练好的分类器用在测试集上,再选择一组在测试集上表现最好的超参数。这也很糟糕!如果采用这种方法,那么很可能我们选择了一组超参,只是让我们的算法在这组测试集上表现良好,但是这组测试集的表现无法代表在全新的数据上的表现。所以,不要用测试集去调整参数,容易使得你的模型过拟合。【机器学习系统的目的,是让我们了解算法表现究竟如何,所以,测试集的目的是给我们一种预估方法,即在没遇到的数据上算法表现将会如何。】

   更常见的做法,就是将数据分为三组:训练集(大部分数据),验证集(从训练集中取出一小部分数据用来调优),测试集。我们在训练集上用不同超参来训练算法,在验证集上进行评估,然后用一组超参(选择在验证集上表现最好的)。然后,当完成了这些步骤以后,再把这组在验证集上表现最佳的分类器拿出来,在测试集上跑一跑。这个数据才是告诉你,你的算法在未见的新数据上表现如何。非常重要的一点是,必须分割验证集和测试集,所以当我们做研究报告时,往往只是在最后一刻才会接触到测试集。

  以CIFAR-10为例,我们可以用49000个图像作为训练集,用1000个图像作为验证集。验证集其实就是作为假的测试集来调优。

代码实现:

 1 import numpy as np
 2 import pickle
 3 import os
 4 
 5 
 6 class KNearestNeighbor(object):
 7     def __init__(self):
 8         pass
 9 
10     def train(self, X, y):
11         """ X is N x D where each row is an example. Y is 1-dimension of size N """
12         # the nearest neighbor classifier simply remembers all the training data
13         self.Xtr = X
14         self.ytr = y
15 
16     def predict(self, X, k=1):
17         """ X is N x D where each row is an example we wish to predict label for """
18         """ k is the number of nearest neighbors that vote for the predicted labels."""
19         num_test = X.shape[0]
20         # lets make sure that the output type matches the input type
21         Ypred = np.zeros(num_test, dtype=self.ytr.dtype)
22 
23         # loop over all test rows
24         for i in range(num_test):
25             # using the L1 distance (sum of absolute value differences)
26             distances = np.sum(np.abs(self.Xtr - X[i, :]), axis=1)
27             # L2 distance:
28             # distances = np.sqrt(np.sum(np.square(self.Xtr - X[i, :]), axis=1))
29             indexes = np.argsort(distances)
30             Yclosest = self.ytr[indexes[:k]]
31             cnt = np.bincount(Yclosest)
32             Ypred[i] = np.argmax(cnt)
33 
34         return Ypred
35 
36 
37 def load_CIFAR_batch(file):
38     """ load single batch of cifar """
39     with open(file, 'rb') as f:
40         datadict = pickle.load(f, encoding='latin1')
41         X = datadict['data']
42         Y = datadict['labels']
43         X = X.reshape(10000, 3, 32, 32).transpose(0, 2, 3, 1).astype("float")
44         Y = np.array(Y)
45     return X, Y
46 
47 
48 def load_CIFAR10(ROOT):
49     """ load all of cifar """
50     xs = []
51     ys = []
52     for b in range(1, 6):
53         f = os.path.join(ROOT, 'data_batch_%d' % (b, ))
54         X, Y = load_CIFAR_batch(f)
55         xs.append(X)
56         ys.append(Y)
57     Xtr = np.concatenate(xs)  # 使变成行向量
58     Ytr = np.concatenate(ys)
59     del X, Y
60     Xte, Yte = load_CIFAR_batch(os.path.join(ROOT, 'test_batch'))
61     return Xtr, Ytr, Xte, Yte
62 
63 
64 Xtr, Ytr, Xte, Yte = load_CIFAR10('data/cifar-10-batches-py/')  # a magic function we provide
65 # flatten out all images to be one-dimensional
66 Xtr_rows = Xtr.reshape(Xtr.shape[0], 32 * 32 * 3)  # Xtr_rows becomes 50000 x 3072
67 Xte_rows = Xte.reshape(Xte.shape[0], 32 * 32 * 3)  # Xte_rows becomes 10000 x 3072
68 
69 
70 # assume we have Xtr_rows, Ytr, Xte_rows, Yte as before
71 # recall Xtr_rows is 50,000 x 3072 matrix
72 Xval_rows = Xtr_rows[:1000, :]  # take first 1000 for validation
73 Yval = Ytr[:1000]
74 Xtr_rows = Xtr_rows[1000:, :]  # keep last 49,000 for train
75 Ytr = Ytr[1000:]
76 
77 # find hyperparameters that work best on the validation set
78 validation_accuracies = []
79 for k in [1, 3, 5, 10, 20, 50, 100]:
80     # use a particular value of k and evaluation on validation data
81     nn = KNearestNeighbor()
82     nn.train(Xtr_rows, Ytr)
83     # here we assume a modified NearestNeighbor class that can take a k as input
84     Yval_predict = nn.predict(Xval_rows, k=k)
85     acc = np.mean(Yval_predict == Yval)
86     print('accuracy: %f' % (acc,))
87 
88     # keep track of what works on the validation set
89     validation_accuracies.append((k, acc))
View Code

这代码我有空的话再完善吧......没空(

 

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

把训练集分成训练集和验证集。使用验证集来对所有超参数调优。最后只在测试集上跑一次并报告结果。

交叉验证

  设定超参数的另一个策略是交叉验证。这在小数据集中更常用一些,在深度学习中不那么常用。当我们训练大型模型时,训练本身非常消耗计算能力,因此这个方法实际上不常用。

  还是用刚才的例子,如果是交叉验证集,我们就不是取1000个图像,而是将训练集平均分成5份,其中4份用来训练,1份用来验证。然后我们循环着取其中4份来训练,其中1份来验证,最后取所有5次验证结果的平均值作为算法验证结果。

下图是5份交叉验证对k值调优的例子。针对每个k值,得到5个准确率结果,取其平均值,然后对不同k值的平均表现画线连接。本例中,当k=7的时算法表现最好(对应图中的准确率峰值)。如果我们将训练集分成更多份数,直线一般会更加平滑(噪音更少)。

实际应用:

  在实际情况下,人们不是很喜欢用交叉验证,主要是因为它会耗费较多的计算资源。一般直接把训练集按照50%-90%的比例分成训练集和验证集。但这也是根据具体情况来定的:如果超参数数量多,你可能就想用更大的验证集,而验证集的数量不够,那么最好还是用交叉验证吧。至于分成几份比较好,一般都是分成3、5和10份。

  常用的数据分割模式。给出训练集和测试集后,训练集一般会被均分。这里是分成5份。前面4份用来训练,黄色那份用作验证集调优。如果采取交叉验证,那就各份轮流作为验证集。最后模型训练完毕,超参数都定好了,让模型跑一次(而且只跑一次)测试集,以此测试结果评价算法。

 

KNN的劣势:

其实,KNN在图像分类中很少用到,原因如下:

  • 它在测试时运算时间很长,这和我们刚才提到的需求不符,
  • 像欧几里得距离或者L1距离这样的衡量标准用在比较图像上不太合适,这种向量化的距离函数,不太适合表示图像之间视觉的相似度。
  • 它并不能体现图像之间的语义差别,更多的是图像的背景,色彩的差异。
  • K-近邻算法还有另一个问题,我们称之为“维度灾难”。 K-近邻分类器,它有点像是用训练数据 把样本空间分成几块,这意味着,如果我们希望分类器有好的效果,我们需要训练数据能密集地分布在空间中,否则最近邻点的实际距离可能很远,也就是说,和待测样本的相似性没有那么高。而问题在于,想要密集地分布在空间中,我们需要指数倍地训练数据,这很糟糕,我们根本不可能拿到那么多的图片去密布这样的高维空间里的像素。

参考:

https://www.bilibili.com/video/av17204303/?from=search&seid=6625954842411789830

https://zhuanlan.zhihu.com/p/20900216?refer=intelligentunit

原文地址:https://www.cnblogs.com/GraceSkyer/p/8763616.html