这是一个介绍性课程, 旨在向人们介绍从计算机视觉到图像识别问题,以及数据驱动的方法。目录如下:
-
介绍图像分类、数据驱动方法、管线
-
最近邻分类器
2.1 k-最近邻(k-Nearest Neighbor) -
验证集, 交叉验证集,超参数调整(hyperparameter tuning)
-
最近邻分类器的优缺点
-
总结
-
总结:kNN实战运用
-
延伸阅读
图像分类
动机在本节中,我们将介绍图像分类问题,该问题的任务是从一组固定的类别中为输入图像分配一个标签。这是计算机视觉中的核心问题之一,尽管其简单,但具有各种各样的实际应用。此外,正如我们将在本课程后面看到的那样,许多其他看似不同的计算机视觉任务(例如目标检测,分割)可以简化为图像分类。
例子例如,在下面的图像中,图像分类模型采用单个图像并将概率分配给4个标签{cat,dog,hat,mug}。如图所示,请记住,对于计算机,图像表示为一个大的三维数字数组。在该示例中,猫图像是248像素宽,400像素高,并且具有三个颜色通道红色,绿色,蓝色(或简称RGB)。因此,图像由248 x 400 x 3个数字组成,或总共297,600个数字。每个数字都是一个整数,范围从0(黑色)到255(白色)。我们的任务是将这个25万个数字转换为单个标签,例如“cat”。
图像分类中的任务是预测给定图像的单个标签(或标签上的分布,如此处所示,以表示我们的信心)。图像是从0到255的整数的三维数组,大小宽度x高度x3.3代表三个颜色通道红色,绿色,蓝色。
挑战。由于识别视觉概念(例如猫)的这项任务对于人来说是相对微不足道的,因此从计算机视觉算法的角度考虑所涉及的挑战是值得的。正如我们在下面提出(一个无穷无尽的)挑战列表,请记住图像的原始表示作为亮度值的三维数组:
- 视角多样性一个单一实例的物体可以相对于相机以多种方式定向。
- 规模变化视觉类通常表现出其大小的变化(现实世界中的大小,不仅仅是它们在图像中的范围。
- 形变许多感兴趣的物体不是刚体,并且可以以极端方式变形。
- 闭塞感兴趣的对象可以被遮挡。有时只能看到一小部分物体(少至几个像素。
- 照明条件照明的影响在像素级别上是激烈的。
- 背景杂乱感兴趣的物体可能融入其环境中,使其难以识别。
- 类内变化感兴趣的类别通常可以比较宽泛,例如椅子。这些对象有许多不同类型,每个对象都有自己的外观。
良好的图像分类模型必须对所有这些变化的交叉积不变,同时保持对类间变化的敏感性。
数据驱动的方法我们如何编写可以将图像分类为不同类别的算法?与编写算法(例如,对数字列表进行排序)不同,人们如何编写用于识别图像中的猫的算法并不明显。因此,我们不会尝试直接在代码中指定每个感兴趣类别的内容,而是采用与孩子一样的方法:我们将为计算机提供许多示例每个类,然后开发学习算法,查看这些示例,并了解每个类的视觉外观。这种方法被称为数据驱动方法,因为它依赖于首先累积标记图像的训练数据集。以下是此类数据集的示例:
四个视觉类别的示例训练集。在实践中,我们可能为每个类别分别拥有数千个类别和数十万个图像。
图像分类管道。我们已经看到图像分类中的任务是采用表示单个图像的像素数组并为其指定标签。我们的完整管道可以正式化如下:
输入:我们的输入由一组N个图像组成,每个图像用K个不同的类中的一个标记。我们将此数据称为训练集。
学习:我们的任务是使用训练集来了解每个类的外观。我们将此步骤称为训练分类器或学习模型。
评估:最后,我们通过要求分类器预测从未见过的一组新图像的标签来评估分类器的质量。然后,我们将比较这些图像的真实标签与分类器预测的标签。直观地说,我们希望很多预测与真正的答案(我们称之为基本事实)相匹配。
作为我们的第一种方法,我们将开发我们称之为最近邻分类器的方法。该分类器与卷积神经网络无关,在实践中很少使用,但它可以让我们了解图像分类问题的基本方法。
示例图像分类数据集:CIFAR-10。一种流行的玩具图像分类数据集是CIFAR-10数据集。该数据集由60,000个高32像素的宽图像组成。每个图像都标有10个类别中的一个(例如“飞机,汽车,鸟等”)。这些60,000个图像被划分为50,000个图像的训练集和10,000个图像的测试集。在下图中,您可以看到10个类中每个类的10个随机示例图像:
左:来自CIFAR-10数据集的示例图像。右:第一列显示一些测试图像,然后在每个测试图像旁边根据像素差异显示训练集中的前10个最近邻居。
现在假设我们获得了50,000张图像的CIFAR-10训练集(每个标签有5,000张图像),我们希望标记剩下的10,000张图像。最近邻分类器将拍摄测试图像,将其与每个训练图像进行比较,并预测最接近的训练图像的标签。在上图和右图中,您可以看到10个示例测试图像的此类过程的示例结果。请注意,在10个示例中,只有大约3个检索到同一类的图像,而在其他7个示例中,情况并非如此。例如,在第8行中,最靠近马头的训练图像是红色汽车,可能是由于强烈的黑色背景。结果,在这种情况下,这种马的图像被错误标记为汽车。
你可能已经注意到我们没有详细说明我们如何比较两个图像的细节,在这种情况下只是两个32 x 32 x 3的块。最简单的可能性之一是逐个像素地比较图像并将所有图像相加差异。换句话说,给定两个图像并将它们表示为矢量I1,I2,比较它们的合理选择可能是L1距离:d1(I1,I2)=∑p|Ip1−Ip2|
使用像素方差来比较具有L1距离的两个图像(在该示例中为一个颜色通道)的示例。从元素中减去两个图像,然后将所有差异加到一个数字上。如果两个图像相同,则结果为零。但如果图像非常不同,结果会很大。
我们还看看如何在代码中实现分类器。首先,让我们将CIFAR-10数据作为4个数组加载到内存中:训练数据/标签和测试数据/标签。在下面的代码中,Xtr(大小为50,000 x 32 x 32 x 3)保存训练集中的所有图像,相应的1维数组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)函数,它接收新数据并预测标签。当然,我们遗漏了事情的本质 - 实际的分类器本身。以下是一个简单的最近邻分类器的实现,其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距离,其具有计算两个向量之间的欧氏距离的几何解释。距离采取以下形式:
d2(I1,I2)=sqrt(∑p(Ip1−Ip2)^2)
换句话说,我们将像以前一样计算像素差异,但这次我们将所有这些差异化,将它们相加并最终取平方根。在numpy中,使用上面的代码,我们只需要替换一行代码。计算距离的线:
distances = np.sqrt(np.sum(np.square(self.Xtr - X[i,:]), axis = 1))
请注意,我在上面包含了np.sqrt调用,但是在实际的最近邻居应用程序中,我们可以省略平方根操作,因为平方根是单调函数。也就是说,它缩放距离的绝对大小,但它保留了排序,因此最近的邻居有或没有它是相同的。如果您使用此距离在CIFAR-10上运行最近邻分类器,您将获得35.4%的准确度(略低于我们的L1距离结果)。
L1与L2。考虑两个指标之间的差异很有意思。特别地,当涉及两个矢量之间的差异时,L2距离比L1距离更加不可原谅。也就是说,L2距离更喜欢许多中等分歧与一个大分歧。 L1和L2距离(或等效于一对图像之间差异的L1 / L2范数)是p范数中最常用的特殊情况。
k - 最近邻分类器
您可能已经注意到,当我们希望进行预测时,仅使用最近图像的标签是很奇怪的。实际上,通过使用所谓的k-最近邻分类器,人们可以做得更好。这个想法非常简单:我们不是在训练集中找到最近的单个图像,而是找到最前面的k个最近的图像,并让它们在测试图像的标签上投票。特别是,当k = 1时,我们恢复最近邻分类器。直观地说,较高的k值具有平滑效应,使分类器对异常值更具抵抗力:
最近邻和最近邻分类器之间差异的一个示例,使用2维点和3个类(红色,蓝色,绿色)。有色区域显示由具有L2距离的分类器引起的决策边界。白色区域显示模糊分类的点(即,类别投票与至少两个类别相关联)。请注意,在NN分类器的情况下,异常数据点(例如蓝点云中间的绿点)会产生可能不正确预测的小岛,而5-NN分类器会平滑这些不规则性,可能会导致更好的泛化在测试数据上(未显示)。还要注意,5-NN图像中的灰色区域是由最近邻居之间的投票关系引起的(例如,2个邻居是红色,接下来的两个邻居是蓝色,最后一个邻居是绿色)
在实践中,您几乎总是希望使用k-Nearest Neighbor。但是你应该使用什么价值?接下来我们转向这个问题。
超参数调整的验证集
k近邻分类器需要k的设置
但是哪个数字效果最好?另外,我们看到我们可以使用许多不同的距离函数:L1范数,L2范数,还有许多我们甚至没有考虑的其他选择(例如点积)。这些选择被称为超参数,它们经常出现在许多从数据中学习的机器学习算法的设计中。人们应该选择哪些值/设置通常并不明显。
您可能会建议我们尝试使用许多不同的值,看看哪种方法效果最好。这是一个好主意,这确实是我们将要做的,但必须非常谨慎地完成。特别是,我们不能使用测试集来调整超参数。无论何时设计机器学习算法,您都应该将测试集视为一种非常宝贵的资源,理想情况下,直到最后一次才能触及。否则,真正的危险在于您可以调整超参数以在测试集上正常工作,但如果您要部署模型,则可能会发现性能显着降低。在实践中,我们会说你适应测试集。另一种看待它的方法是,如果你在测试集上调整超参数,你就可以有效地使用测试集作为训练集,因此你在其上实现的性能对于你实际观察到的内容会过于乐观部署模型时。但是如果你最后只使用一次测试集,那么它仍然是衡量分类器泛化的一个很好的代理(我们将在后面的课程中看到更多关于泛化的讨论)。
在最后评估测试集只有一次。
幸运的是,有一种调整超参数的正确方法,它根本不会触及测试集。我们的想法是将我们的训练集分成两部分:一个稍小的训练集,以及我们称之为验证集的训练集。以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个用于验证。然后我们将迭代哪个折叠是验证折叠,评估性能,最后平均不同折叠的性能。
Example of a 5-fold cross-validation run for the parameter k.
对于k的每个值,我们训练4次并在第五轮进行评估。因此,对于每个k,我们在验证折叠上获得5个精度(精度是y轴,每个结果是一个点)。趋势线通过每个k的结果的平均值绘制,误差条表示标准偏差。请注意,在此特定情况下,交叉验证表明约k<= 7的值最适合此特定数据集(对应于图中的峰值)。如果我们使用超过5折,我们可能会看到更平滑(即噪声较小)的曲线。
在实践中。在实践中,人们更喜欢避免交叉验证而支持单个验证拆分,因为交叉验证在计算上可能是昂贵的。人们倾向于使用的分组是训练数据的50%-90%之间用于训练和休息以进行验证。但是,这取决于多个因素:例如,如果超参数的数量很大,您可能更喜欢使用更大的验证拆分。如果验证集中的示例数量很少(可能只有几百个左右),则使用交叉验证会更安全。您在实践中可以看到的典型折叠数量是交叉验证的3-fold,5-flod或10-fold。
常见数据拆分。给出了训练和测试集。训练集分为折叠(例如,这里为5折)。折叠1-4成为训练集。一次折叠(例如折叠5,此处为黄色)表示为验证折叠并用于调整超参数。交叉验证更进一步,重复选择哪个折叠是验证折叠,与1-5分开。这将被称为5倍交叉验证。最后,一旦训练模型并确定了所有最好的超参数,就会在测试数据上单次评估模型(红色)。
最近邻分类器的优缺点。
值得考虑最近邻分类器的一些优点和缺点。显然,一个优点是实现和理解起来非常简单。另外,分类器没有时间训练,因为所需要的只是存储并可能索引训练数据。但是,我们在测试时支付计算成本,因为对测试示例进行分类需要与每个训练示例进行比较。这是倒退的,因为在实践中我们经常关心测试时间效率远远超过训练时的效率。事实上,我们将在本课程后期开发的深度神经网络将这种权衡转移到另一个极端:训练非常昂贵,但是一旦完成训练,对新的测试示例进行分类是非常便宜的。这种操作模式在实践中更加令人满意。
另外,最近邻分类器的计算复杂度是研究的活跃领域,并且存在可以加速数据集(例如FLANN)中的最近邻居查找的若干近似最近邻(ANN)算法和库。这些算法允许人们在检索期间利用其空间/时间复杂度来权衡最近邻检索的正确性,并且通常依赖于涉及构建kdtree或运行k均值算法的预处理/索引阶段。
在某些设置中,最近邻分类器有时可能是一个不错的选择(特别是如果数据是低维的),但它很少适用于实际的图像分类设置。一个问题是图像是高维物体(即它们通常包含许多像素),并且高维空间上的距离可能非常违反直觉。下图说明了我们上面开发的基于像素的L2相似度与感知相似度非常不同的观点:
高维数据(尤其是图像)上的基于像素的距离可能非常不直观。原始图像(左)和其旁边的三个其他图像,基于L2像素距离,它们都离它很远。显然,像素距离根本不对应于感知或语义相似性。
这是另一个可视化,以说服您使用像素差异来比较图像是不够的。我们可以使用一种名为t-SNE的可视化技术来获取CIFAR-10图像并将它们嵌入到二维中,以便最好地保留它们的(局部)成对距离。在此可视化中,根据我们上面开发的L2像素距离,附近显示的图像被认为非常接近:
使用t-SNE以二维嵌入的CIFAR-10图像。基于L2像素距离,认为该图像附近的图像接近。注意背景的强烈影响而不是语义类差异。
特别要注意的是,彼此相邻的图像更多地是图像的一般颜色分布,或背景的类型而不是它们的语义标识的函数。例如,可以看到一只狗非常靠近青蛙,因为两者都碰巧在白色背景上。理想情况下,我们希望所有10个类的图像形成自己的聚类,因此相同类的图像彼此相邻,而不管无关的特征和变化(例如背景)。但是,要获得此属性,我们必须超越原始像素。
摘要
综上所述:
我们介绍了图像分类的问题,其中我们给出了一组图像,这些图像都用单个类别标记。然后,我们要求为一组新的测试图像预测这些类别,并测量预测的准确性。
我们引入了一个名为Nearest Neighbor分类器的简单分类器。我们看到有多个超参数(例如k的值,或用于比较示例的距离类型)与此分类器相关联,并且没有明显的选择方法。
我们看到设置这些超参数的正确方法是将训练数据分成两部分:训练集和假测试集,我们称之为验证集。我们尝试不同的超参数值,并保留在验证集上获得最佳性能的值。
如果缺乏训练数据,我们讨论了一个称为交叉验证的程序,它可以帮助减少噪声,估计哪些超参数最有效。
找到最佳超参数后,我们会修复它们并对实际测试集执行单一评估。
我们看到最近邻可以让我们在CIFAR-10上获得大约40%的准确率。它实现起来很简单,但要求我们存储整个训练集,并且在测试图像上进行评估是很昂贵的。
最后,我们看到在原始像素值上使用L1或L2距离是不够的,因为距离与图像的背景和颜色分布的相关性比与其语义内容的相关性更强。
在接下来的讲座中,我们将着手解决这些挑战并最终达到提供90%准确度的解决方案,让我们在学习完成后完全丢弃训练集,并且它们将允许我们在不到一毫秒的时间内评估测试图像。
总结:在实践中应用kNN
如果您希望在实践中应用kNN(希望不在图像上,或者可能仅作为基线),请按以下步骤操作:
1、预处理数据:规范化数据中的要素(例如图像中的一个像素),使其均值和单位方差为零。我们将在后面的章节中更详细地介绍这一点,并选择不在本节中介绍数据规范化,因为图像中的像素通常是同构的,并且没有表现出大不相同的分布,从而减少了对数据规范化的需求。
2、如果您的数据非常高维,请考虑使用降维技术,如PCA(维基参考,CS229ref,博客参考)或甚至随机投影。
3、将训练数据随机分成训练/分组。根据经验,70-90%的数据通常用于火车拆分。此设置取决于您拥有多少超参数以及您希望它们具有多大影响力。如果有很多超参数需要估算,那么你应该在设置更大的验证集以便有效地估算它们时犯错误。如果您担心验证数据的大小,最好将训练数据拆分为折叠并执行交叉验证。如果你能负担得起计算预算,那么使用交叉验证总是更安全(越多越好,但更昂贵)。
4、针对k的许多选择(例如越多越好)和跨越不同距离类型(L1和L2是良好的候选者),在验证数据上训练和评估kNN分类器(对于所有折叠,如果进行交叉验证)
5、如果您的kNN分类器运行时间过长,请考虑使用近似最近邻库(例如FLANN)来加速检索(以一定精度为代价)。
6、记下产生最佳结果的超参数。有一个问题是你是否应该使用具有最佳超参数的完整训练集,因为如果你要将验证数据折叠到训练集中,最佳超参数可能会改变(因为数据的大小会更大)。在实践中,不在最终分类器中使用验证数据并且考虑在估计超参数时将其烧毁是更清洁的。评估测试集上的最佳模型。报告测试集准确度,并将结果声明为数据上kNN分类器的性能。
Further Reading
Here are some (optional) links you may find interesting for further reading:
A Few Useful Things to Know about Machine Learning, where especially section 6 is related but the whole paper is a warmly recommended reading.
Recognizing and Learning Object Categories, a short course of object categorization at ICCV 2005.