CS231n Spring 2017 Module1 翻译学习

来源:互联网 发布:域名设置教程 编辑:程序博客网 时间:2024/06/13 10:13

CS231n Spring 2017 Module1 翻译学习

假期无事,准备入手CS231n。为了每天能学有所成,因此决定自己翻译一下课程的讲义。说实话这算是一种懒人方法了,勤能补拙吧!翻译水平有限,欢迎指正。原文链接为[http://cs231n.github.io/classification/]
这是这一课是节入门课,为了非计算视觉专业的人介绍基本的图像分类问题和数据驱动方法。内容如下所示:
- 介绍图像分类、数据驱动方法以及步骤
- 最邻近分类器
- k-最邻近分类器
- 验证集、交叉验证以及超参数调试
- 最邻近算法的利与弊
- 总结
- 总结:kNN的实际应用
- 延伸阅读


图像分类

动机:图像分类就是利用一个已知的类别数据集,给输入的图片分类。在计算视觉中,这是核心问题之一。虽然简单,但是应用广泛。另外,我们后面还会了解到其他看上去和图像分类完全不相关的计算视觉任务(比如,目标检测、图像分割),实际上也是图像分类问题。
举例:比如,对于一个图像分类模型,对一张图片可能会分成四类{猫,狗,帽子,马克杯}。如下图所示,对于计算机而言,一张图片会被表示成一个超大的三维数字矩阵。这里是一张宽248个像素,高400个像素的猫的照片,并且有三个颜色通道红、蓝、绿(简写成RGB)。因此,这张图片由248*400*3个数字构成,总共297600个数。每个数字的范围都是0-255(0对应这黑色,255对应着白色)。我们的任务就是去将这个三维矩阵转化成一个简单的类别标示,如“猫”


猫
图像分类器的目的就是为给定的照片预测类别(或者说是一个对类别的分配去表明了我们对分类的把握)。图片是由0-255的三维整数矩阵,大小就是宽*高*3。这里3表示为红蓝绿3个通道。


难点:对于人类来说,去识别分类一个物体的种类并不困难。但是让计算视觉算法的看法也参与到分类当中也是很有帮助的。下面我们列出了如下问题,注意图像是被表示成一个亮度值的三维矩阵:
- 视角的多样性。简单的例子就是,一个物体能够被相机从不同角度拍摄
- 缩放的多样性。观察的远近会让物体的大小发生变化(真实世界中的大小,而不是根据他们在图片中的伸缩)
- 形变。许多物体并不是硬物,在特殊情况下会发生形变
- 遮挡。目标物体会被其他物体所遮挡,有时只能看见物体的一小部分
- 光线的情况。光线会对图片产生严重影响
- 背景的混入。物体可能会混入背景之中,从而很难分辨出来。
- 子类的多样性。类别可能会相对宽泛,比如椅子中还有许多其他的子类,如沙发、板凳、长椅等等
这里不同种类的物体都有着独特的外表。一个好的图像分类器必须能够处理上述这些图片变化,同时还能保留对子类多样性的敏感度。


难点


数据驱动方法:我们如何写一个能够将图片分成不同类的算法呢?不像写一个排序算法,如何写一个分类猫的算法并没那么直接。实际上,不用在代码中去确定不同的类别具体长什么样,我们采取的方法就像是我们教小孩子辨识物体一样,我们会给计算机提供每个类别的大量实例,并由此形成一个算法。这个算法通过看这些例子,然后学习到类别的视觉外观特点。这个方法就被称为是数据驱动方法,因为它首先需要分析带有标签的训练集样本。下图为数据集的示例。


数据集
一个带有四个类别的训练集示例。实际上我们可能有上千个类别,每个类别有数十万个样本


图像分类的步骤。我们已经知道了,图像分类本质上就是处理一个像素矩阵,并且给它贴上类别标签。我们的整个分类步骤可以分为如下几步:
- 输入:输入包括一个包含N张图片的数据集,每张图片都标有k种类别中的一种。我们管这个数据集叫做训练集
- 学习:我们的任务就是利用训练集去学习每个类别长什么样子
- 评估:最后,我们验证分类器的质量。通过引入分类器从未见过的新数据集,让分类器来预测它们的类别。然后,我们对分类器预测的类别和真正标记的类别进行比较。事实上,我们总是希望和真实答案(称作ground true)匹配的数量越多越好。

最邻近分类器

这里介绍的第一种方法是最邻近分类器。这种分类器和卷积神经网络没有什么关系,并且在实际中很少应用,但是它展示了解决图像分类问题的基本方法。
CIFAR-10图像分类数据集CIFAR-10是一个十分流行的图像分类集。这个数据集包含了6万张32*32的图片。每张图片会被标记上10个类别中的一种。这6万张图片被分为5万张训练集和1万张测试集。在下面的图片中,你会看到从每个类别中随机挑选出的10张照片。


CIFAR-10
左:样例来自于CIFAR-10数据集。右:第一列是挑选出的10张测试图片,它们的右侧是从训练集中挑选出来的,和测试照片在像素级上最相似的前十张照片


假设现在我们有5万张CIFAR-10训练集的图片(每个类别5千张),然后我们希望对剩下的1万张图像进行分类。最邻近分类器会将测试样本同训练样本的每一张图片进行对比,并且预测该测试样本的类别成最靠近它的训练样本的类别。在上述右边的图片中,你会看到对10个测试样本进行分类的示例结果。注意只有3/10的测试样本被正确的分类,另外7个则被错误分类。例如,在第八行中最近似马头的训练集图片是一辆红色的车,可能是因为强烈的黑色背景。结果,马被错误分类成了车。
你可能已经注意到了上面分类是如何比较的两张32*32*3大小的照片。最简单的方法就是比较每个相应位置的像素值,然后将它们的差值的绝对值求和。换句话说,对于两张图片,将它们表示为向量I1,I2,那么计算它们之间的L1距离为:

d1(I1,I2)=pIp1Ip2

这个求和是对所有的像素的,这个过程如下图所示:


L1
基于像素级的L1距离度量来比较两张图片的相似度的示例(这里只展示了一个颜色通道)。两张图片的对应像素相减,然后取绝对值进行求和。如果两张图片一模一样,那么结果为0。但是如果,两张图片差异过大,则结果会得到一个非常大的数值。


接下来,我们用代码来实现该分类器。首先,我们将CIFAR-10数据集转化成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) # Xtr_rows 大小变为50000 x 3072Xte_rows = Xte.reshape(Xte.shape[0], 32 * 32 * 3) # Xte_rows 大小变为10000 x 3072

现在我们已经将所有的图片展开成一行(即一行包含32*32*3个数字),接下来我们开始训练数据和评估分类器:

nn = NearestNeighbor() # 创建一个最邻近分类器类nn.train(Xtr_rows, Ytr) # 用训练数据集训练出分类器Yte_predict = nn.predict(Xte_rows) # 预测测试图片的类别# 打印出最终预测的精度print 'accuracy: %f' % ( np.mean(Yte_predict == Yte) )

我们通常使用精度作为评估的准则,以此来测量我们预测对的概率。我们建立所有分类器都会满足这个API,通过输入数据和类别得到train(X,y)的函数。在函数中会为每个类别以及如何预测建立某种模型。然后得到predict(X)方程,向它输入图片数据就能得到相应的图片类别。下面是一个满足该模版并基于L1距离简易的最邻近分类器的代码实现:

import numpy as npclass 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%大没多少,然而当前最先进的卷积神经网络(CNN)的正确率达到了95%,和人的表现94%近似(参见最近在Kaggle竞赛上,基于CIFAR数据集分类的积分榜)
距离度量的选取。计算两个向量之间距离的方式十分多。除了L1距离,另外一个常用的距离度量方式是L2距离。L2距离就是两个向量之间的欧几里得距离,也就是向量的空间几何距离。L2距离的计算公式如下:

d2(I1,I2)=p(Ip1Ip2)2

换句话说,我们和之前一样也会计算对应像素上的差值,只不过这次是对差值进行平方后,再求和,最后却平方根。在numpy中,只需要简单的一段代码就可以完成。计算距离的代码为:

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

注意我们在上述代码中调用了np.sqrt方法,但实际上,我们会去掉平方根,因为平方根是一个单调函数。那就是,它只是缩放了距离的绝对大小,但是仍保留了大小的顺序。因此有没有平方根,找到的最邻近项都是同一个。如果基于此距离构造最邻近分类器,你会得到35.4%的精度(略低于L1距离的结果)。

L1vs.L2。 去考虑这两种度量方式之间的不同十分有趣。在衡量两个向量之间的差异时,特别是L2距离会更让人印象深刻。L1L2距离(换句话说,一对图片之间差值的L1/L2范数)是p-范数中最常见的特例。

k-最邻近分类器

在我们做预测的时候,你可能已经注意到了一个很奇怪的情况,那就是为什么只用到了距离最近的图片。事实上,使用k-临近分类器总能够达到更好的效果。这个方法的心路历程很简单,不是要找到训练集中距离待测试图像最近的一个照片,而是最近的前k张图像,然后进行投票表决确定出测试图像的类别。当k=1时,就是最邻近分类器。直观上,分类器的k值越大,那么平滑效果就会也好,也就更能抵抗住异类图像造成的影响。


这里写图片描述
这里我们通过分析示例数据集,包含二维样本点,并被分为三种类别(红蓝绿表示),来比较5-最邻近分类器和最邻近分类器之间的差别。彩色的区域展现了由基于L2距离的分类器得到的决策边界。白色区域为无法分类的点(即投票时有两个以上的类别获得了最多的票数,从而无法进行区分)。我们注意到,在最邻近分类器中,异类数据点创造了一些是可能错误的小岛(比如,在有蓝色点组成的块中间出现了个别绿色点)。然而在5-最邻近分类器中则平滑过滤掉了一部分点,因此5-最邻近分类器可能会在测试集数据上表现的更好。另外,因为在投票过程中出现了平票的情况(比如,在5个最靠近测试图像的训练集图像中,有两个是红色,两个是蓝色,最后一个是绿色),所以在5-最邻近分类器中出现了灰色区域。


实际上,你总是会想用k-最邻近分类器。但是你该如何选取k值呢?接下来我们来讨论这个问题。

对超参数进行验证集调试

k-最邻近分类器需要设定k值。但是k值为多少时,分类效果最好呢?除了已经用过的L1,L2之外,还有许多距离度量方式供我们选择,比如点积。这些选择被称作超参数。在基于数据进行学习的机器学习算法中,超参数十分常见。如何去选定超参数往往很困难。

你可能会觉得,直接每个值都拿来试试,然后选出表现最好的不就行了。这个主意很不错,我们也确实在这么做,但是这个必须要小心谨慎的完成。特别是,我们不能使用测试集数据去进行超参数的调试。无论何时你在设计机器学习算法,都要记住测试集是个十分宝贵的资源。因此,理想情况下,只有到了最后才可以使用测试集。否则,虽然你通过调试超参数让模型在测试集数据上表现的特别好,但是在具体应用时,性能会出现了显著的下降。这种情况是十分危险的。实际上,我们将这称为对测试数据的过拟合。另外,如果在测试集上调试超参数,那么你实际上就是将测试数据作为了训练数据。因此,在实际部署你的模型时,它的性能可能会显得过于乐观。但是如果,你只在最后才使用测试集数据,那么这会保留下对分类器泛化性能进行度量的价值(在后续课程中,你会接触到更多围绕泛化性能的讨论)。

到最后才在测试集上对模型进行评估,并且只能评估一次。

幸运的是,已经有了一种正确的方法来对超参数进行调试,并且不会涉及到测试集。那就是将训练数据集一分为二:一个略小的训练集和一个验证集。以CIFAR-10为例,我们取4万9个训练集中的数据进行训练,剩下的1000个作为验证集。这个验证集被作为一个伪测试集来调试超参数。

下面是对CIFAR-10数据集进行超参数调试的例子:

# assume we have Xtr_rows, Ytr, Xte_rows, Yte as before# recall Xtr_rows is 50,000 x 3072 matrixXval_rows = Xtr_rows[:1000, :] # take first 1000 for validationYval = Ytr[:1000]Xtr_rows = Xtr_rows[1000:, :] # keep last 49,000 for trainYtr = Ytr[1000:]# find hyperparameters that work best on the validation setvalidation_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折作为训练,另外一折作为验证。接下来在每次都选取不同的那一折作为验证集,并评估性能。这样反复五次,最后将每次的性能评估结果平均,得到最终结果。


交叉验证

5折交叉验证参数k的实例。对于每个k值,我们取其中的4折进行训练,在第五折上进行评估。因此,对与每个k值,我们会得到5个在验证集上的精度值(精度值是y轴,每个点代表一个结果)。趋势线是由每个k值所得的5个精度值的平均值来画出的,误差条则是表示了标准差。可以看到,交叉验证集结果表明在k=7时(对应着趋势线的顶点),分类效果最好。如果我们分的超过5折(减少了噪声),那么会看到一个更加平滑的曲线。


实际应用。事实上,人们通常会避免只切分出一份验证集的交叉验证。因为这样交叉验证的计算成本会很高。常用的切法是从训练集中取50%到90%作为训练,其余作为验证。但是这取决于诸多要素:比如,如果超参数很多,你会扩大你的验证集。如果验证集中样本的数量很少,那么就使用交叉验证。实际中常见的交叉验证为3折、5折或10折。

常见的数据切分。训练集和测试集已经给出了。训练集被划分成5折。1-4折为训练集,第5折为验证集用于调试超参数。交叉验证交叉验证下一步就是分别选取不用的折作为验证集折。最后模型训练完成并决定好超参数就在测试集上对模型进行单次的评估。

最邻近分类器的优缺点

接下来,我们要探讨最邻近分类器的优劣性。很显然,第一个优点就是简单易懂,实现起来很容易。另外,因为分类器将所有的训练数据存储起来了,所以也不用花时间训练。但是,由于在对一个测试样本分类时,需要同每个训练样本进行比对,我们付出了惨痛的计算代价。考虑到在实际中,比起训练效率,我们会更重视测试效率,因此这个缺点严重影响了它的应用。然而,我们后续介绍的深度神经网络的表现恰恰相反:训练时间长,但是测试时间短。后者的表现在实际中更受到人们的认可。

另外,对最邻近分类器计算复杂度的研究是个很活跃的领域,并由此出现了许多加快数据库检索速度的算法,如估计最邻近算法(ANN)、FLANN。这些算法需要在预处理阶段构建一棵kd树或者运行k-means,让我们可以在检索的时空复杂度和正确率上进行权衡。

最邻近分类器在处理低维数据时表现的比较好,但是不适用于图像分类。一个问题就是图像都是高维数据,然后高维空间中的距离都超出了人们的理解范围。下图就说明了基于L2度量的相似度同人们的直观感受之间的不同:


这里写图片描述

在高维数据中,基于像素点的距离难以理解。最左边的原图和右边三张图之间的L2距离都相同。但是,这样算出来的距离和直观看到的并不相符。


下面这幅图会让你更加确信用像素的不同去比较图像之间的差异是很欠缺的。我们用一种叫做t-SNE的可视化技术将CIFAR-10图像嵌入二维空间中,以此来将它们之间的距离关系最好的表现出来。在下图中,相邻两张图片之间的L2距离十分相近:


这里写图片描述

CIFAR-10图像库用t-SNE被嵌入二维空间中。相邻图像之间的L2距离十分相近,值得注意的是图像背景的巨大影响。点开查看大图


特别地,我们注意到这些相邻图像像是一种颜色分布的函数,或者说是背景类型的分布,而不是语义上的类别。比如,因为都是白色的背景,所以狗和青蛙之间的距离挨得很近。我们希望的是,十种类别各自的图片形成各自的聚类,每种类别的图案不论发生何种变化或是不同的背景色,都能相邻得很近。但是要实现这个目标我们就必须脱离计算这种像素之间的距离

总结

  • 我们介绍了图像分类问题,即给定一个标记上了类别的图片库,然后要求去预测一个新的测试图像集的类别,并计算精度。我们还介绍了一种简单的分类器叫作最邻近分类器。我们了解到了多个和分类器有关超参数(比如k值的选取和比较距离的选择)。而且这些超参数的选取并不能够直接观察得出。
  • 我们学习了一种合理的确定超参数的方法,那就是将你的训练集数据一分为二:一个训练集和一个称作验证集的伪测试集。我们尝试不同的超参数,并最后选取在验证集上表现最好的那个值
  • 如果训练数据比较少,那么就需要进行交叉验证。这样在估计超参数时能尽量避免噪声的影响。
  • 一旦超参数确定下来,我们就在测试集上对模型进行评估,注意在测试集上只能进行一次评估。
  • 我们看到最邻近分类在CIFAR-10数据集上的正确率只有40%。最邻近分类器很容易实现,但是需要我们存储整个训练集,而且在测试图像上会花费大量时间。
  • 最后,我们观察到用L1和L2距离来度量图片的相似度并不合理,因为这种度量的结果和图像的背景以及颜色分布有很大的关系,而不是它们所表示的语义内容。
    在下一节课,我们会着手解决这些问题,并最终达到90%的精确度。一旦训练完成我们会完全抛弃训练集,而且能在百万分之一秒内处理完一个测试样本。

总结:实践kNN

如果你想在实际中应用kNN,步骤如下:

  1. 处理你的数据:通过归一化将特征化为均值为0,方差为1的分布。我们会在后续再谈论细节,但是不要在图像分类中对数据进行归一化,因为图像中的像素都是同一类型的数,即有着同样范围的分布。这省去了数据归一化的必要
  2. 如果你的数据是高维的,考虑先使用降维的方法比如PCA或者随机映射等方法。
  3. 将你的训练数据随机的分成训练集和验证集。一般来说,将数据集的70%到90%作为训练集。这取决于你有多少超参数和你觉得它们对模型的影响如何。如果有许多超参数,那么你应该有更大的验证集来更有效的估计它们。如果你对验证集的大小十分在意,那么你最好将训练集分为几折,用交叉验证的方法。如果你有足够的计算容量,推荐使用交叉验证(越多折越好,但也越耗费计算成本)。
  4. 在验证集上训练和评估kNN分类器,来选定k值和不同的距离度量方法
  5. 如果你的kNN分类器运行时间过长,那么考虑使用估计最邻近库来加速整个检索过程。
  6. 确定下来最优的超参数。你是否应该使用带有最优超参数设定的整个数据集,因为如果你把验证集也加入到你的训练集中,那么最优超参数可能会发生改变(因为数据集变大了)。实际中,在最后的分类器中不加上验证集会更好,考虑到加上的话会破坏了最优超参数的选择。在测试集上评估最优的模型。得到在测试集上的精度,也就是kNN分类器在数据上表现出的性能。

延伸阅读

下面有几个延伸阅读的链接供你参考:
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.

原创粉丝点击