cs231n学习记录 Part1 Image Classification

此为学习斯坦福cs231n课程所做笔记。

图像分类

动机

本节主要讲的是图像分类问题,它是一个从固定的类别集合里分配一个标签(label)给输入图像的任务。这是计算机视觉的核心问题,虽然看似简单,但是有很多的实际应用。其他许多看似不同的计算机视觉任务(比如说:目标检测,图像分割)都可以归类为图像分类问题。

举例

example
举个栗子,上图中,图像分类模型接收了一个图像,然后分配给它4个标签(cat、dog、hat、mug)。如图所示,对于计算机来说,图像被视为是一个大型的3维数组。在这里,猫图有248像素宽,400像素高,且有三个颜色通道:Red、Green、Blue(即RGB)。因此,该图像是由 248 x 400 x 3 个数字组成,也就是总共297600个数字。其中每个数字都是一个在0(black)到255(white)范围内的整数值。图像分类的任务就是将这组几十万的数字转换成单个标签label,比如说“cat”。

挑战

因为对于人类来说识别一个视觉目标(比如说,猫)是非常容易的,但是从计算机视觉算法的角度上来考虑,其中会涉及到许多挑战。下面列出部分挑战:

  • Viewpoint variation(视角变化):一个目标实体对于照相机来说可以多种方式进行定向。
  • Scale variation(规模变化):视觉目标通常会存在多种大小变化(真实世界中的大小,不仅仅是图像中的大小程度)
  • Deformation(形变):许多目标实体不是那种外形固定的物体,可能会以极端方式发生变形。
  • Occlusion(遮挡):目标实体可能会被遮挡,有时候目标只有一小部分(只有少数像素)可见。
  • Illumination conditions(光照条件):光照条件在像素级上影响较大。
  • Background clutter(背景混乱):目标对象可能混入其环境之中,使得他们难以被识别出来。
  • Intra-class variation(类内变化):目标对象所属的类别可能有多种外观,比如说椅子。这些目标是不同的类型,且每个类型都有自己的外观。

variation
一个好的图像分类模型必须是对这些变化的交叉组合保持不变性,同时对类间变化保持敏感性。

数据驱动方法

提供给计算机诸多类,其中每一类都包含许多样本,之后研发出一个学习算法去训练这些样本,学习每一类的视觉外观。这种方法被称为数据驱动方法,因为它依赖于收集标注过的图像训练数据集。如下图所示:
dataset

图像分类流程

图像分类的任务是输入代表图像的像素数组,然后输出一个分配给它的标签值。

完整的图像分类流程大致如下:

  • 输入:输入N个图像,其中每个图像都被标注为K个不同类别中的一个,称之为训练集
  • 学习:任务是学习训练集,学习每一类外观是什么样子的。可称这个步骤为训练一个分类器,或学习一个模型
  • 评估:最后,通过预测新的图像集合中的图像标签来评估这个分类器的性能

最近邻分类器

最近邻分类器,与卷积神经网络无关,但却非常的实用,它给出了图像分类问题的一个基本解决方法的理念。

CIFAR-10数据集

图像分类数据集CIFAR-10包含了60000张32x32像素的小图片,每个图片都被标注为10个类别中的一个。这6万张图片被划分为含有50000张图片的训练集和含有10000张图片的测试集。

下图中可以看到数据集中每类各10张的随机图像样本:
cifar-10
上图左侧是CIFAR-10数据集的样例图像;右侧中第一列是测试图像,之后的列中是根据像素级距离来判断的与第一列中测试图像距离最近的10张训练图像。

最近邻分类器接收一个测试图像,将之与训练集中的每个图像进行比较,之后输出距离最近的训练图像的标签值,即为分类器的预测值。在上图的右侧中我们可以看到根据测试图像生成的距离最近的10张训练图像,从右侧的那些距离最近的训练图像中我们可以发现,测试图像中只有少数几个检索到同一类的目标图像,其余的都不是该类图像。比如说,在第8行中,与测试图像(马)距离最近的训练图像中却是一辆车,这可能是因为两幅图中大范围的黑色背景所导致。因此,该马类图像在这种情况下被错误地标注为是一辆车。

L1 Distance

接下来介绍下如何对两幅图像进行比较,也就是两个32x32x3的块之间的比较。一个最简单的方法是逐个比较图像的像素,并对其中所有的差值求和。换句话说,给定两张图像,分别表示为$I_1$、$I_2$,比较它们的一个合理方法便是L1距离
$$
d_1 (I_1, I_2) = \sum_{p} \left| I^p_1 - I^p_2 \right|
$$
其中求和操作是面向所有像素的。

过程可视化:
NN
上图是一个在像素级上使用L1距离对两张图像进行比较的简单例子(例子中只是列出一个通道)。两幅图像各元素对应值相减,然后将所有的差值相加,得到单个数值。如果两张图像相同,则这单个数值将会是0;如果两张图像差异较大,则该值相对较大。

代码实现

首先载入数据为4个数组:训练数据/标签、测试数据/标签。Xtr(size: 50000x32x32x3)包含了训练集中的所有图像,对应的一维数组Ytr(length: 50000)包含了训练标签(0~9):

1
2
3
4
Xtr, Ytr, Xte, Yte = load_CIFAR10('data/cifar10/') # 魔法函数,获取数据
# 扁平化处理
Xtr_rows = Xtr.reshape(Xtr.shape[0], 32 * 32 * 3) # Xtr_rows 50000 x 3072
Xte_rows = Xte.reshape(Xte.shape[0], 32 * 32 * 3) # Xte_rows 10000 x 3072

之后便是训练与评估:

1
2
3
4
5
nn = NearestNeighbor() # 创建一个最近邻分类器的实例
nn.train(Xtr_rows, Ytr) # 在训练集和标签上训练分类器
Yte_predict = nn.predict(Xte_rows) # 在测试图像上预测标签值
# 打印输出分类准确度
print 'accuracy: %f' % ( np.mean(Yte_predict == Yte) )

注:
1)一般都是用准确度来作为评估标准,预测正确的数量占比。
2)所有分类器都有一个公共的接口:train(X, y)方法,表示从数据和标签中学习模型。
3)predict(X)方法,接收新的数据,并预测其标签值。

下面是一个使用L1距离的简单最近邻分类器的代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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) # 初始化预测值全为0
# 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

距离度量的选择

向量之间有多种方式计算它们的距离。另一种常见的方法便是L2距离,其具有计算两个向量之间欧氏距离的几何解释。距离公式如下:
$$
d_2 (I_1, I_2) = \sqrt{\sum_{p} \left( I^p_1 - I^p_2 \right)^2}
$$
也是先计算像素级别上的差值,然后对其值求平方,之后将所有的值相加,最后对值开根号。
代码方面,只要对之前的代码做些许修改即可,将计算距离的那行代码替换为如下一行:

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

上面代码中使用了np.sqrt,但在实际最近邻应用中,我们省去了开根号这一操作,因为开根是一个单调函数。也就是说,它控制了距离的绝对大小的量级,但仍保留其排序,所以有或没有开根,最近邻的比较没有影响(其实就是开根操作没有影响单调性)。

k近邻分类器

k近邻分类器:找出与测试图像最相近的k个训练图像,然后根据投票规则(哪一类数量多,就选哪类),预测出测试图像的标签。特例,当k=1时,就变成最近邻分类器。直观上,较大的k值具有平滑效果,会使得分类器对异常值更具抵抗性,分类的准确度会更高。
knn
上图中,有色区域代表的是根据L2距离分类器做出的决策边界。在最近邻中,由于可能不正确的预测而导致出现一些与周围颜色不一致的“岛屿”,5-近邻中却将这些异常值做了相应处理,使得这些颜色边界更加平滑,这可能对测试数据会产生更好的泛化。右图中的灰色区域是由投票规则所导致的(2票红色,2票蓝色,最后一票绿色)。

超参数调优、验证集

超参数

k近邻分类器中的k值该如何选择,才会让分类器效果最优。之前距离度量函数的选择,包括L1、L2范数,这些选择都被称为“超参数”。

一个最简单的想法是,我们尝试许多不同的值,然后看它们中哪一个会让分类器效果更好。但是这不能通过使用测试集来调整超参数。

1
Evaluate on the test set only a single time, at the very end.

验证集

所以换一种方式去调整超参数:将训练集划分成两个集合,一个是稍微变小的训练集,另一个则是验证集。用CIFAR-10来做个栗子,我们可以使用训练集中的49000张图像来训练,剩下的1000张图像用来验证。验证集就好比是一个伪测试集,用来对超参数进行调优的。

CIFAR-10进行验证集划分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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))

交叉验证

有时候训练集的规模会比较小,这时候我们可以使用一种叫交叉验证的常用方法。比如说,5折交叉验证中,我们将训练集划分为5个相等数量的子集,使用其中的4个组成训练集,剩下的一个则为验证集。之后我们迭代选择其中一个为验证集,评估每一次的性能,最后对这5次评估取平均值。
cross-validation
上图是k近邻分类器使用5折交叉验证的样例。对于k的每一个值,都是训练4个子集组成的训练集,在第5个子集合上评估。因此,每个k值都得到了5个准确度值。图中的折线是由每个k的平均准确度相连得到。图中表明,在交叉验证的情况下,当k=7时,准确度相对最高(峰值)。

实际应用中,由于交叉验证在计算上消耗太大,人们一般都偏向于选择将训练数据中的50%-90%作为训练集,其余的作为验证集。但是若是你的训练样本非常少,建议使用交叉验证。常见的交叉验证的折数可以是3折、5折或10折。
5-fold

最近邻分类器的优缺点

优点:

  • 易于理解和实现
  • 不需要时间去训练,训练过程中所需要做的只是存储和对训练数据进行索引

缺点:

  • 测试时计算消耗太大,因为需要去将测试图像与每一个训练图像进行比较。测试时间的效率比训练时更重要
  • 计算复杂度较高

虽说在某些情况下,最近邻的确是一个很好的选择,但是它却很少被应用于实际图像分类环境中去。其中的一个问题便是,图像是一个高维对象(通常包含许多像素),在这高维空间中距离很难被直观地表现出来。

下图便阐明了由之前基于L2距离的相似与感官上的相似有很大不同这方面。

最左侧是原图,右侧三幅图分别对原图做了一些变化。然而这三幅图在L2像素距离上与原图有很大的差值。因此,像素级上的距离与感官或语义上的相似是完全不相符的。

总结

  • 图像分类,训练集,测试集
  • 最近邻分类器,超参数
  • 验证集
  • 交叉验证
  • 评估
  • L1、L2距离
------------- 本 文 结 束 感 谢 您 的 阅 读 -------------
坚持原创技术分享,您的支持将鼓励我继续创作!
0%