斯坦福大学计算机视觉课程cs231-02-最近邻算法

最近邻分类器

作为我们的第一个方法——最近邻分类器。这个分类器与卷积神经网络无关,在实际中很少使用,但它可以让我们了解一个图像分类问题的基本方法。
示例图像分类数据集:CIFAR-10。一个流行的图像分类数据集是CIFAR-10数据集。由6万个微小的图像组成,这些图像的高度和宽度都是32个像素。每个图像都标有10个类别之一(例如“飞机,汽车,鸟”等)。这60000个图像被分割成50,000个图像的训练集和10000个图像的测试集。在下面的图片中,您可以看到来自10个类中的每一个的10个随机示例图像:


左图:来自CIFAR-10数据集的示例图像。右图:第一列显示了一些测试图像,并在每个旁边显示了训练集中根据像素差异排名前十的最近邻居。

                                                                                                                                                                                              
假设我们获得了50,000张图像的CIFAR-10训练集(每个标签有5000张图像),我们希望标注剩余的10,000张图像。最近的邻居分类器将选取一张测试图像,将其与每一个训练图像进行比较,并且预测为最接近的训练图像的标签。在上图的右图中,可以看到10个示例测试图像的一个示例结果。请注意,在10个例子中,只有三个例子检索到同一个类的图像,而在另外7个例子中则不是这样。例如,在第八排马头最近的训练图像是红色的车,可能是由于黑色的背景。因此,在这种情况下,这匹马的形象将被误认为是一辆汽车。
你可能已经注意到,我们没有详细说明如何比较两个图像的距离,在这种情况下,只是两个32 x 32 x 3的块。最简单的方法是逐个像素地比较图像,并将所有图像差异相加。换句话说,给定两个图像并将它们表示为向量

比较它们的合理选择可能是L1范式距离:


在所有像素上求和,可视化过程如下


L1距离对比像素差异来比较两张图像(示例是在一个色彩通道上),将两张图像的对应像素值相减后再将所有差值求和。如果两张图像完全一样则结果为0,两张图像差异很大时结果的数值也会很大。

                                                                                                                                                                                              

我们来看看如何在代码中实现分类器。首先将CIFAR-10数据加载到内存中作为4个数组:训练数据/标签和测试数据/标签。在下面的代码中,Xtr(大小为50,000 x 32 x 32 x 3)保存训练集中的所有图像,相应的一维数组Ytr(长度为50,000)保存训练标签(从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
现在我们把所有的图像都拉伸成行,下面是我们如何训练和评估一个分类器:

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) 传入训练数据X和对应标签y并从中学习的功能。在函数内,应该建立某种标签的模式,以及如何从数据中预测标签。然后是一个 predict(X) 函数,它接收新的数据并预测其标签当然,我们已经省略排除了分类器实际代码。下面是一个简单的最近邻分类器的实现,L1距离满足这个模板:
import numpy as np

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

  def 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 data
    self.Xtr = X
    self.ytr = y

  def 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 type
    Ypred = np.zeros(num_test, dtype = self.ytr.dtype)

    # loop over all test rows
    for 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 distance
      Ypred[i] = self.ytr[min_index] # predict the label of the nearest example

    return Ypred
如果你运行这个代码,你会发现这个分类器在CIFAR-10上只能达到 38.6% 这比随机猜测更令人印象深刻(由于有10个类别,这会给出10%的准确度),但远不及人类的表现( 估计约为94% )或接近最先进的卷积神经网络的准确度95%(参见CIFAR-10最近的Kaggle比赛 排行榜 )。
距离的选择. 计算矢量之间距离的方法还有很多。另一个常见的选择是使用L2范式距离,它的几何解释是计算两个向量之间的欧氏距离。距离采取的形式是:

换句话说,我们将像以前一样计算像素差异,但是这次是将所有这些差值平方累加求和,再取平方根。在numpy中,使用上面的代码我们只需要替换一行代码,计算距离的行:
distances = np.sqrt(np.sum(np.square(self.Xtr - X[i,:]), axis = 1))
请注意,上述代码包含了 np.sqrt 调用,但是在实际的最近邻居应用程序中,我们可以忽略平方根操作,因为平方根是一个 单调函数 也就是说,它缩放距离的绝对大小,但它保留了排序,所以有或没有它的最近邻是相同的。如果在CIFAR-10上运行最近邻分类器,则可以获得 35.4%的 准确度(略低于我们的L1距离结果)。
L1 VS L2.考虑这两个指标之间的差异是很有趣的。特别是,当涉及两个向量之间的差异时,L2距离比L1距离更不能容忍差异。也就是说,L2距离更喜欢扩大一个大的差异。L1和L2距离(或者等价于一对图像之间差异的L1 / L2范数)是p范数最常用的特例
                                                                                                                                                                                              

K-最近邻分类器

你可能已经注意到,当我们想要进行预测时,只使用最近图像的标签是很奇怪的。实际上,通过使用所谓的K-NN分类器,可以做得更好这个想法很简单:不是在训练集中找到最接近的单个图像,而是找到最近的K个图像,并让它们对测试图像的标签投票。特别的是当k = 1时,退化为最近邻分类器。直观上,较高的k具有平滑效果,使得分类器对异常值更具抵抗性:

                                                                                                                                                                                              


使用2维点和3个类(红色,蓝色,绿色)的最近邻和K=5最近邻分类器比较的一个例子。有色区域显示由L2距离的分类器生成的决策边界白色区域显示分类不清的点(即分类票数至少与两个类得票数并列)。请注意,在最近邻分类器的情况下,异常数据点(例如蓝点群中间的绿点)会创建可能预测不正确的小岛,而K=5最近邻分类器会平滑这些不规则性,可能会导致在测试数据上更好的泛化性(未示出)。还要注意,K=5最近邻分类器图像中的灰色区域是由最近邻居之间的投票关系引起的(例如,2个邻居是红色的,接下来的两个邻居是蓝色的,最后一个邻居是绿色的)。
                                                                                                                                                                                             
实际上,你几乎总是想用K-NN。但是,K该取多少呢?接下来我们转向这个问题。

调整超参数的验证集

K-NN需要对 k 进行设置但是,什么数字最适合?另外,我们可以使用许多不同的距离函数:L1范数,L2范数,还有很多我们没有考虑过的其他选择(例如点积)。这些参数的选择被称为 超参数 ,他们经常在许多从数据中学习的机器学习算法的设计中出现。人们应该选择什么样的值/设置通常不是很明显。(参数 vs 超参数:前者是通过训练数据和机器学习算法学习得的参数,如系数,权重,偏差等;后者是确定学习模型的参数值,如K-NN的K值,CNN的层数,学习率等);
你可能会建议我们应该尝试许多不同的K值,看看取多少效果最好。这是一个好主意,也确实是我们要做的事情,但一定要非常小心。特别的是,你可能会试图建议我们应该尝试许多不同的价值观,看看什么效果最好。这是一个好主意,这确实是我们要做的事情,但这一定要非常小心。特别是,我们不能使用测试集来调整超参数无论何时设计机器学习算法,都应该视测试集为一个非常宝贵的资源,理想情况下永远不会被触及,直到最后一次。否则,真正的危险是你可能将超参数调整到在测试集上工作得很好,但当你调用模型时,可能会看到性能显著下降。实践中,我们称之为在测试集上过拟合。Another way of looking at it is that if you tune your hyperparameters on the test set, you are effectively using the test set as the training set, and therefore the performance you achieve on it will be too optimistic with respect to what you might actually observe when you deploy your model. But if you only use the test set once at end, it remains a good proxy for measuring the generalization of your classifier (we will see much more discussion surrounding generalization later in the class).
另一方面,如果你在测试集上调整超参数,有效地将测试集用做训练集,因此你观察到性能会优于实际调用时的模型。但是如果你只是最后使用测试集,它仍是一个可以很好衡量分类器泛化性的指标( 我们将在后面看到关于泛化性的更多讨论)。


只在最后利用测试集进行一次评估

幸运的是,调整超参数有一个正确的方法,它根本不会触及测试集。我们的想法是将训练集分成两部分:取一个训练集的稍小部分,称之为验证集以CIFAR-10为例,我们可以使用49,000个训练图像进行训练,并留出1,000个用于验证。该验证集本质上用作假测试集来调整超参数。

以下是CIFAR-10的情况:

# 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 data
  nn = NearestNeighbor()
  nn.train(Xtr_rows, Ytr)
  # here we assume a modified NearestNeighbor class that can take a k as input
  Yval_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 set
  validation_accuracies.append((k, acc))

最后,我们可以绘制一个图表,显示哪个K 性能最好。然后,我们将选择这个值,并在实际的测试集上评估一次。

将你的训练集分成训练集和验证集,使用验证集来调整所有超参数。最后在测试集上运行一次并报告性能。

交叉验证 .如果训练数据的大小(也就是验证数据)可能很小,那么人们有时会使用更复杂的超参数调整技术(称为 交叉验证) 在之前的例子中,不是随意选取前1000个数据点为验证集,其余为训练集,通过迭代不同的验证集并且将性能取平均值,得到一个更优的低噪声K值估计。例如,在5倍交叉验证中,我们将训练数据分成5个相等的部分,使用其中4个用于训练,1个用于验证。然后,我们将迭代选择验证,评估性能,并最终对不同验证集的性能进行平均。

                                                                                                                                                                                              


对参数 K 进行5次交叉验证运行的示例对于 K的 每个值,我们在4个训练集上学习,并在第5次进行评估。因此,对于每个 k, 我们在验证折叠上获得5个精度(精度是y轴,每个结果是一个点)。趋势线是通过每个 k 的结果的平均值绘制的,误差线表示标准差。请注意,在这种特定情况下,交叉验证表明,对于此特定数据集(对应于图中的峰值), k  = 7 的值最为有效。如果我们使用超过5倍,我们可能会看到一个更平滑(即较少噪音)的曲线。

                                                                                                                                                                                              

实践中.人们倾向于避免交叉验证而偏向于进行单个验证分割,因为交叉验证可能在计算上代价更高。人们倾向于使用训练数据的50%-90%,其余用于验证。但是,这取决于多个因素:例如,如果超参数的数量很大,您可能更愿意使用更大的验证拆分。如果验证集中的示例数量很少(可能只有几百个),则使用交叉验证更安全。实践中可以看到的典型划分数量是3倍,5倍或10倍的交叉验证。


常见的数据分割。 给出了一个训练和测试集。 训练集划分为多个不相交子集合(例如5倍)。 子集1-4为训练集, 一个子集(例如,以黄色子集5)表示为验证子集并被用于调整超参数。 交叉验证更进一步,迭代选择哪一个是验证折叠,与1-5分开。 这称为5倍交叉验证。 最后,一旦模型被训练并确定了所有最好的超参数,模型将在测试数据(红色)上被单次评估。
                                                                                                                                                                                              

最近邻分类器的优缺点

最近邻分类器一些优点和缺点值得考虑。显然,其中一个优点是实施和理解起来非常简单。此外,分类器不需要时间来训练,因为所需要的是存储并且可能将训练数据索引(懒惰学习)。然而,我们在测试需要付出计算代价,因为对一个测试例子预测时需要同每个训练样例比较。这是落后之处,因为在实践中,我们关心测试时间的效率远远超过训练时的效率。事实上,我们稍后在课堂上介绍的深度神经网络将这种折衷转移到另一个极端:训练代价非常昂贵,但是一旦训练结束,对新的测试例子进行分类的代价非常低。这种操作模式在实践中更加可取。

另外,最近邻分类器的计算复杂度是一个活跃的研究领域,存在几个近似最近邻(ANN)算法和库,其可以加速数据集(例如FLANN)中的最近邻居查找这些算法允许在检索期间利用最近邻检索的正确性换取其空间/时间复杂度,并且通常依赖于涉及构建kdtree或运行k均值算法的预处理/索引阶段。

最近邻分类器在某些情况下可能是不错的选择(尤其是在数据维度较低的情况下),但在实际的图像分类情况中很少使用。一个问题是图像是高维对象(即它们通常包含许多像素),并且高维空间上的距离可能非常不直观。下面的图像说明了我们上面开发的基于像素的L2范式相似性与感知相似性的差异:


高维数据(尤其是图像)上基于像素的距离可能非常不直观。基于L2像素距离,原始图像(左)和其他三个旁边的图像都距离它们很远。显然,像素距离与感知或语义相似性完全不符。

                                                                                                                                                                                              

这里有一个可视化的方法更加能说服你,使用像素差异来比较图像是不够的。我们可以使用称为t-SNE的可视化技术来获取CIFAR-10图像,并将它们嵌入到两个维度中,以便最好地保留它们(局部)的成对距离。在这个可视化中,根据我们在上面开发的L2像素距离,附近显示的图像被认为是非常接近的:


CIFAR-10利用t-SNE技术将图像嵌入在二维中。根据L2像素距离,此图像附近的图像被认为是接近的。注意图像背景的影响远远大于图像语义。点击这里查看这个可视化的更大的版本。

                                                                                                                                                                                              

特别要注意的是,彼此相邻的图像更多的是因为图像的一般颜色分布或背景类型的函数决定,而不是其语义。例如,一只狗可以看到非常接近一只青蛙,因为它们都发生在白色背景上。理想情况下,我们希望所有10个类的图像形成自己的簇,使得同一类的图像彼此相邻,而与无关的特征和变化(例如背景)等无关。但是,要获得这个属性,我们将不得不超越原始像素。

概要

综上所述:

  • 我们介绍了图像分类的问题,在这个问题中,我们给出了一组图像,这些图像都被标记为一个类别。然后要求预测这些类别的一组新的测试图像,并测量预测的准确性。
  • 我们引入了一个简单的分类器,称为最近邻分类器我们看到有多个超参数(如k的值,或用于比较示例的距离类型)与此分类器相关联,并且没有明显的方法选择超参数的值。
  • 我们看到,设置这些超参数的正确方法是将训练数据分为两部分:训练集和假测试集,我们称之为验证集我们尝试不同的超参数值,并保留导致验证集上性能最佳的值。
  • 如果缺乏训练数据是一个问题,我们讨论了一个叫做交叉验证的程序,它可以帮助减少估计哪些超参数工作得最好的噪声。
  • 一旦找到最好的超参数,我们就修复它们,并对实际测试集进行单一评估
  • 我们看到“最近邻”在CIFAR-10上可以达到40%左右的精度。实现起来很简单,但要求我们存储整个训练集,并且在测试图像上评估是昂贵的。
  • 最后,我们看到在原始像素值上使用L1或L2距离是不够的,因为距离与图像的背景和颜色分布相比,与其语义内容相关更强烈。

在接下来的讲座中,我们将着手解决这些挑战,并最终达成90%准确度的解决方案,让我们在学习完成后完全丢弃训练集,并让我们在不到一毫秒的时间内评估测试图像。

总结:在实践中应用kNN

如果你想在实践中应用kNN(希望不是图像,或者也许只是一个基线),按如下步骤进行:

  1. 预处理数据:标准化数据中的特征(例如图像中的一个像素),使其具有零均值和单位方差(方差值为1)。我们将在后面的章节中更详细地介绍这一点,因为图像中的像素通常是均匀的,并且不会呈现广泛不同的分布,因此减少了对数据规范化的需求。
  2. 如果你的数据是非常高维的,可以考虑使用维技术,比如PCA(wiki refCS229refblog ref)甚至是Random Projections
  3. 将训练数据随机分成训练/验证分组。作为一个经验法则,70-90%的数据通常是训练数据划分大小。这个设置取决于你有多少超参数以及你期望他们有多少影响。如果有多个超参数需要估算,那么您应该在更大的验证集上进行更有效的估算。如果您担心验证数据的大小,最好将训练数据拆分为折叠并执行交叉验证。如果你能负担得起计算预算,那么交叉验证就越安全(子集划分越好,但代价越高)。
  4. 在验证数据上训练和评估kNN分类器(如果进行交叉验证,迭代所有可能验证集),对于k个选择(例如越多越好)和不同距离类型(L1和L2是好的候选)
  5. 如果您的kNN分类器运行时间过长,请考虑使用近似最近邻库(例如FLANN)来加速检索(以某种准确度为代价)。
  6. 记下给出最佳结果的超参数。有一个问题是,是否应该使用具有最佳超参数的完整训练集,因为如果将验证数据折叠到训练集中(因为数据的大小会更大),最优超参数可能会发生变化。实际上,在最终的分类器中不使用验证数据并且在估计超参数时认为它被烧毁是更清洁的。评估测试集上的最佳模型。报告测试集的准确度并将结果声明为数据上kNN分类器的性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值