cs231n 用卷积神经网络来进行图像识别

作者:imxtg

博主是大二学生,英语6级水平,翻译有不对的地方请从下面留言

转载请注明出处:blog,csdn.net/imxtg

原文链接:http://cs231n.github.io/classification/

下面开始:


这是一个给人们介绍计算机图像分类问题的课程,还有数据驱动的实现方法,下面是目录。

  • 介绍图像分类(Image Classification),数据驱动的实现方法(data-driven approach),图像分类流水线(pipeline)
  • 最近邻分类器(Nearest Neighbor Classifier)
  • K阶最近邻(k-Nearest Neighbor)分类算法
  • 验证集(Validation sets),交叉验证(Cross-validation),超参数调优(hyperparameter tuning)
  • 最近邻分类器(Nearest Neighbor Classifier)的优缺点
  • 总结
  • 总结:应用KNN
  • 进一步阅读

图像分类(Image Classification)


动力.在这个部分我们要介绍图像分类问题,那就是从设定好的一组分类标签中选定一个标签分配给输入的图像。这就是计算机视觉的核心问题之一,尽管看似很简单,但是还是有许多各种各样的实际应用程序。此外,正如我们在一会的课程中看到的那样,其他许多看起来似乎独特的计算机视觉工作(例如目标检测,对象分割)都可以简化为图像分类。


例子.举例来说,如下面的图片所示,一个图像分类模型处理一个图片,并分给它4中可能的标签(猫,狗,帽子,杯子)。如这个图片所示,将自己的大脑想象成计算机,如此的话一个图片就会被展示成一个大的由数字构成的三维数组。在这个例子中,猫的图片有248像素宽,400像素高,3个色彩通道:红色,绿色,蓝色(简称RGB)。因此,这个图片包含248*400*3个数字,总共297,600个数字。每个数字都是从0(表示黑市)到255(表示白色)的整数。我们的任务是把这一组几百万个数字打一个标签,叫“猫”。



图像处理的任务就是对给定的图片预测一个标签。图片是一个三维整数(p.s.不同于编程中的三维数组,而是一种由RGB值构成的矩阵)数组,从0到255,图片大小是宽*高*3。3代表的就是3个色彩通道:红色,绿色,蓝色。


挑战.从视觉观念上认识物体(如猫)对人类来说是相对简单的,但是从计算机视觉算法来说这是很有挑战的。我们列一下挑战清单(不详尽),记住图片的原始表示作为一个三维数组的亮度值(p.s.这句话的意思就是注意RGB值所表示的亮度,下面会有讲解):
拍摄角度的变化:一个物体可以被照相机拍出许多不同的角度。
比例的变化:图像种类常常表现出他们的大小变化(不仅是图片展现的大小,还有在真实世界中的大小)。
变形:很多物体的形状可能变形。
遮挡:许多物体可能被遮挡。有时候可能只有一小部分(可能是几个像素)是可见的。
光照条件:光照的影响也到像素级别。
背景杂波:物体可能融入到背景之中,使他们很难被发现。
类内变异:一种物体有很多种形态,例如椅子。同类物体有着不同的外观。



数据驱动的方法.我们如何写一个算法,可以将图片分成不同的类别?不像写那种比较明显的算法,例如排序算法,写一个识别猫的图片的算法好像不容易写出。因此,我们写的算法不是那种直接指定物体属于某种类别(例如指定第一张图属于猫,第二张图属于狗之类的),我们的算法就像教一个孩子:我们为计算机提供了大量的例子,然后开发那种通过观看大量图片来提取特征进行分类的学习算法。这种算法被称为数据驱动的方法,因为它依赖于第一个积累标记的训练数据集的图像。这有一个这种数据集的例子:



这是一个四种视觉类别的训练集实例。在实际中我们可能会有成千上万个训练集,每个训练集中有成百上千张图片。


图像分类流水线。我们已经看到,在图像分类任务的像素数组代表一个单一的图像并分配一个标签。我们完整的图像分类流水线可以被定为下面的格式:

输入:我们的输入包含一组N张图片,每张图片从K种不同的类别中选择正确的类别并打上标记。我们把这些数据称为训练组。

学习:我们的任务是用这些训练数据来学习每一组分类的图片长什么样子。我们把这一步称为训练一个分类器或是学习一个模型。

评估:最后,我们评价分类器的质量是通过预测一组之前它没有见过的数据。之后我们比较真正的分类标签和分类器得出的分类标签。理想上,我们希望分类器得出了正确的答案(这叫做参考标准)。


最近邻分类器(Nearest Neighbor Classifier)


作为我们尝试的第一种方法,我们把这种方法叫做最近邻分类器(Nearest Neighbor Classifier),这种方法与卷积神经网络无关,实际中也很少使用,但它将使我们了解一个图像分类的基本方法。

一个图像分类数据集的例子:CIFAR-10.一个很流行的小图像分类器的数据集就是CIFAR-10,这个数据集包含60,000张长宽为32像素的图片。每张图片都从10种分类选择正确的分类并打上该分类的标签(例如“飞机、汽车、鸟”等)。这60,000张图片被分为50,000张的训练集合10,000的测试集。下面的图片你可以看到从10中分类中随机挑选出的实例图片:



左边:CIFAR-10(链接:http://www.cs.toronto.edu/~kriz/cifar.html)数据集中选出的实例图片。右边:第一列是测试集图片,旁边的是根据像素宽度计算出的在训练集中最接近的10张图片。


假设我们现在已经有了一组50,000张图片的CIFAR-10训练集(每组5000张图片,10组),我们想把剩下的10,000张图片打上标签,最近邻分类器会拿1张测试图片把它与训练集的图片全部比较一遍,并预测出最接近的图片。上面的图片的右边你可以看到这个过程的一个例子,注意,只有3张图片选对了分类,其余7张图片没有选对分类。例如,第8排的图片是一个马但是右边第一个是一辆红色的汽车,原因大概是因为黑色的背景。因此,马被认成了红色的汽车。

你可能已经注意到我们脱离开了两张图片的细节比较,只是把它们当成32*32*3的2块。最简单的方法就是比较图片的像素差异并把这些差异加起来。换句话说,把这2张图片当成2个向量I1,I2 ,一个比较合理的方法就是比较L1的距离:


总和是所有像素的总和。下面是这个过程的可视化:




这是一个使用像素宽的差异来比较这两个图片和计算L1距离(本例为一个色彩通道)。这两张图片依次相减然后把这些数字加起来得到一个数字。如果两张图片是相同的话数字会为0.但是如果两张图片相差很大,最后得出的数字也可能会变得很大。


让我们看一下实现分类器的代码。首先,我们把CIFAR-10加载到内存中并变成4个数组:训练集数据/标签,测试集数据/标签。在下面的代码中,XTr(大小为50,000*32*32*3)包括所有训练集图片数据,相应的一维数组YTr(长度是50,000)包括训练集标签(从0到9):


(p.s.下面的代码博主也没运行过,博主认为这些代码应该是类似于伪代码)


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 3072Xte_rows = Xte.reshape(Xte.shape[0], 32 * 32 * 3) # Xte_rows becomes 10000 x 3072


现在,我们把所有的图片数据变成了一行,下面是我们如何训练和评估分类器:

nn = NearestNeighbor() # create a Nearest Neighbor classifier classnn.train(Xtr_rows, Ytr) # train the classifier on the training images and labelsYte_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) )


请注意,作为评判标准,通常使用精确度(accuracy)来衡量预测分数的正确性。注意,所有满足这一分类器的,我们将构建一个通用的API:它们有一个train(X,y)函数来接受数据和标签进行学习。在分类器的内部应该建立某种模型来学习预测数据和数据集的标签。之后是predict(X)函数,用来接受新数据并打上标签。当然,我们遗漏了某些重要的东西--分类器的本身。这有一个简单的实现最近邻分类器(Nearest Neighbor Classifier)方法,通过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%,链接:http://karpathy.github.io/2011/04/27/manually-classifying-cifar10/),也远不及最先进的卷积神经网络的正确率(95%),最新排行:https://www.kaggle.com/c/cifar-10/leaderboard

距离的选择。这还有许多计算向量之间的距离的方法。另一种距离是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.考虑2个指标之间的差异是很有意思的。在实际中,计算2个向量的差异时L2没有L1的好用。因为L2比较实用于相差不是很大的情况。想要看L1和L2之间的差距可以用这个例子(链接:http://planetmath.org/vectorpnorm)


K最近邻(k-Nearest Neighbor)分类


你可能已经注意到只用紧邻的图片进行预测很奇怪。事实上,如果用K最近邻(k-Nearest Neighbor)分类器会得到更好的结果。方法很简单:不用在训练集中最近的两张图片,我们用最近的K张图片,通过这些来给测试集图片打标签。特别的,当K=1时,我么哪有得到了最近邻分类器。直观地,更高的K值有平滑的作用,对未分类的图片有更强的作用。




这是一个最近邻分类器和5阶最近邻分类器区别的例子,用2维的点和3个类别(红绿蓝)来标识一下。有颜色的区域显示了分类器运用L2得出的决策边界(decision boundaries)。白色的区域显示的是分不清的类别。注意,在神经网络分类器的情况下,异常值数据点(如绿色的点再蓝色的区域中)是错误的分类,但是5-NN分类器对这些错误有更平滑的处理,得出更好的泛化测试数据(没有显示)还要注意,5-NN图像中灰色区域最近的邻居之间的关系造成的例如2邻居是红色的,两个邻居是蓝色的,最后的邻居是绿色.


在实际运用中,你肯定想用K阶最近邻分类器。但是K是多少?我们下面来说这个问题。


用验证集(Validation sets)进行超参数调优(hyperparameter tuning)


K阶最近邻分类器需要设定K的值。但是设定哪个值得效果最好?此外,我们看到有很多不同的距离函数我们可以使用:L1,L2范数,还有许多其他的选择我们甚至不考虑(如点积)。这还有一个选择叫超参数(hyperparameters),超参数经常用于机器学习算法来从数据中方学习。通常选择一个值或者设定是不明显的。
你可能会认为,我们应该尝试许多不同的值,看看效果最好。这确实是一个好主意,我们接下来也确实要这么做,但是做这个工作的时候要很小心。特别的,我们不能用测试集来调超参数。你在任何时候设计机器学习的算法的时候,应该把测试集当作非常珍贵的资源,不到最后不碰测试集。否则,你可能调整超参数在测试集上工作得很好,但是当你构建模型的时候会发现性能显著降低。在实践中,我们会说你过拟合(overfit)于测试集。另一种看这个问题的角度是当你用测试集来调整超参数时,你用测试集当作训练集,你就会对你的模型过于乐观。但是如果你在最后使用测试集,你的模型的泛化(普遍化)能力还是不错(我们之后会看到更多的围绕着泛化后讨论的类)。

最后测试你的测试集。

幸运的是这有一个正确的方法来调优超参数,而且它不接触测试集。这个方法是将训练集分为两个部分:一个是比以前稍小一点的训练集,另一些我们叫验证集(Validation sets)。拿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 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为何值时效果最好。之后我们会用这个值再在测试集上试一次。

把你的训练集分成训练集和验证集。用验证集调优超参数。最后一次运行测试集并得出性能。

交叉验证(Cross-validation).万一训练集(同时有验证集)样本很小,人们想出了一种好的用于超参数调优的方法叫做交叉验证(Cross-validation)。用我们之前的例子,不是任意选1,000张图片作为验证集剩下的作为训练集,通过迭代多次验证集并计算它的平均性能,这样得出的K值有更好的效果和更少的噪音。例如,在5交叉验证,把训练集分成等大的5份,4份作为训练集,1份作为验证集。我们之后迭代验证集,测试性能,最后得出平均性能。






5交叉验证得到参数K的例子。每个K中前4个组合做训练,测试第5个。因此,对于每个K我们能得到5个验证集组的准确度(准确度在y轴上,每个结果都是一个点)。趋势线画的是每个K标准误差的误差线。注意,在这种特殊情况下,交叉验证表明,k = 7这个特定的数据集效果最好的值这(对应的峰值)。如果我们使用的交叉验证组数超过5个,我们能得到更流畅(噪音少)的曲线。


实际中,人们更喜欢不用交叉验证而喜欢将验证集单独分出来,因为交叉验证会造成计算上的浪费。人们喜欢用50%-90%作为训练集剩下的作为验证集。但是这取决于多种因素:例如,如果超参数的数量多人们会用更大的验证集,如果验证集中的数据不多(可能只有几百个),用交叉验证更安全。典型的交叉验证有3交叉验证、5交叉验证、10交叉验证。



常见的数据分割,训练集和测试集已经给定。训练集分成几组(例如在这里是5组)。1到4组变成训练集。1组(黄色的标记)变成验证组来超参数调优。交叉验证继续迭代决定哪个是验证集,分别从1到5组。这被称为5交叉验证。最后一次训练时最好的超参数已经被确定,之后模型对测试集进行验证(红色)。


分析一下最近邻分类器(Nearest Neighbor Classifier)的优缺点是很有必要的。显然,一个优势是很容易实现和理解。此外,这种分类器不需要用时间来进行训练,因为它只需对训练集进行存储和索引。但是,我们会在测试时花很多时间,因为进行分类的时候需要比较每个训练集数据。这是一个缺点,因为在实践中对比训练效率我们更关心测试效率。事实上,在我们以后讲的神经网络中会转到另一个极端:花很多时间来训练,花很少的时间进行测试。(p.s.博主曾经训练模型花了4天,测试数据5分钟)这种方式在实践中更可取。

说句题外话,最近邻分类器的计算复杂度是一个活跃研究领域,几种像近似最近邻分类器(ANN)算法和函数库的存在,可以加速最近邻查询数据集(FLANN,链接:http://www.cs.ubc.ca/research/flann/)这些算法允许在检索过程中权衡最近邻检索空间/时间复杂度正确性,通常依赖于一个预处理/索引阶段,包括构建k-d(kdtree),或运行k - means算法。

最近邻分类器在一些设置(特别是低维数据)有时可能是一个不错的选择,但却很少适合使用在实际图像分类设置。一个问题是图像是高纬的(也就是它们通常包含很多像素),计算高纬空间的距离很有可能和我们想象中的不一样。下图说明了基于我们在上面建立的像素的L2相似性与视觉上的相似不一样:




基于像素的距离在高维数据(尤其是和图像)可以非常直观。基于L2距离,原始图像()和其他三个图像都是等距的显然,像素距离跟我们看到图片的感觉不同。


这还有可视化程度更高的例子可以说服你,只用像素距离是远远不够的。我们可以用一个技术叫t-SNE(链接:http://lvdmaaten.github.io/tsne/)处理CIFAR-10的图片,把它们嵌入2维空间中,这样他们(当地)两两距离可以得到最好的保存在这个可视化技术上,图片可以这样显示:




用t-SNE技术CIFAR-10图片被嵌入2维中。上图中距离近的图片可被认为是L2距离近。注意背景的强烈影响而非视觉上对图片分类的差异。(大图:http://cs231n.github.io/assets/pixels_embed_cifar10_big.jpg)

特别注意图像附近的彼此更一般的函数图像的颜色分布,或背景的类型,而不是他们的语义特征。例如,狗可能被看成青蛙因为碰巧都是白色背景。理想情况下,我们希望所有的10类的图像形成自己的集群,这样同一个类的图像互相靠近并忽略无关的特征和变化(如背景)。然而,为了获得这个属性,我们必须超越原始像素的限制




发表评论

0个评论

我要留言×

技术领域:

我要留言×

留言成功,我们将在审核后加至投票列表中!

提示x

机器翻译知识库已成功保存至我的图谱现在你可以用它来管理自己的知识内容了

删除图谱提示×

你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?

删除节点提示×

无法删除该知识节点,因该节点下仍保存有相关知识内容!

删除节点提示×

你确定要删除该知识节点吗?