首页 > 代码库 > 斯坦福大学卷积神经网络----Module 1 Lesson 1 图像分类
斯坦福大学卷积神经网络----Module 1 Lesson 1 图像分类
图像分类
动机:在这个部分我们会介绍图片分类问题,这是一个为输入图片从一系列分类中挑选一个合适的标签的过程。这是计算机视觉的核心问题之一,除了它看上去的简单外表之外,还有非常多的实际应用之处。除此之外,后面课程可能看到的其他的计算机视觉任务(例如目标检测,分割)都可以回归为图像分类问题。
例:下图中一个简单的图像分类模型,有一个图片以及四个可能的标签,{猫,狗,帽子,杯子}。记住,对于计算机来说,图片是一个非常大的三维数组。在这个例子里,猫的图片是248像素宽,400像素高,并且有RGB三个通道,也就是一共297600个数字。每个数字都是一个范围在[0,255]的整型数字。我们的任务是把这近30万个数字(quarter of a million numbers)变成一个简单的标签,例如“猫”。
图像分类的任务是为所给的图片赋予一个标签(或者一个标签列表中的分布情况,以显示置信度)(or a distribution over labels as shown here to indicate our confidence)。图片是取值范围从0到255的三维数组,宽x高x3,这里的3表示红,绿,蓝三个通道
挑战:识别猫这样一个视觉概念对于人来说简直易如反掌,对于计算机视觉算法来说可能非常艰难。下面列出了一个不详尽的困难列表,记住,原始图片仅仅是一个代表亮度值的三维数组。
视角变换:一个简单的物体从不同角度看可能有不同的样子
尺度变换:视觉类常展现出他们的大小变化(真实世界中的尺寸,不仅仅是图片中的拉伸)
扭曲:许多目标物体不是规格的样子,可能极度的扭曲
遮挡:目标物体可能被遮挡,有的时候只有几个像素可见
光照条件:光照影响在像素级别上尤为严重
背景干扰:目标物体可能混杂在背景中,很难分辨
内部变换:目标类可能相对宽广,例如椅子。椅子有很多种类,每种都有不同的样子。
一个好的图像分类模型一定要对这些因素交叉生成的内容保持不变,同时还要保持不同类之间分类的敏感性。
数据驱动方法:我们要怎样写一个算法,将图片分为不同的类?写一个算法识别猫和写一个算法去对数组排序是不一样的。因此,我们不去尝试将每个感兴趣的类该长什么样在代码里描述出来,这样做与小孩子无异,我们向计算机提供不同的类中不同的图片样本,再开发学习算法,从这些样本中学习每个类别应该长成什么样。这种方法被称作数据驱动方法是因为它依赖有标签的训练数据的堆叠。这里是一个数据集的例子。
一个分为四类的训练集,在实际情况中我们可能有几千种类别,每种类别有几十万张图片
图片分类流程:我们知道图片分类是要将图片代表的一个数组中的像素转换为一个标签,所以整个流程可以是如下几步:
输入:我们的输入包括N个带有标签的图片,一共K种标签。我们将这数据称为训练集。
学习:我们的任务是用训练集学习每个类别应该长什么样子。我们将这一步称为训练一个分类器,或者学习一个模型。
评估:最后,我们用这个模型预测这个网络从来没有接触过的图片的标签,以评估分类器的效果。比较真实的标签以及分类器预测的结果。直观地说,我们希望绝大多数预测标签与真实标签相同(也就是我们说的ground truth)(具体可见链接https://www.zhihu.com/question/22464082)
最近邻分类器
基于第一种方法,我们开发了一种我们称作“最近邻分类器”的方法。这种分类器和卷积神经网络没有关系,实际中基本不会用到。但是给我们带来了图片分类问题的基本想法。
图片分类数据集:CIFAR-10:一种很受欢迎的图片分类数据集。包括了6万张32*32像素的图片,共分为10类,每张照片都有一个标签(例如飞机,汽车,鸟等)。这6万被分成5万张训练图片以及1万张测试图片。下图可以看到10个分类中随机的10个图片。
左:CIFAR-10数据集中的示例图片。右:第一列是一些测试图片,后面跟的是根据像素级别的区别排列的10个最近邻图片
假设我们得到了5万张图片的数据集,每个种类有5000张图片,我们希望对剩下的1万张图片进行标记。最近邻分类器会拿出一张测试图,与50000张图片逐一对比,再将最相似的训练图片的标签作为预测标签。在上右图中你可以看见一个十张测试图片的示例结果。这里注意到大约只有三个被取回,剩下的七个都发生了错误。例如,第八行马头最近邻的图片是一辆红色的车,可能是因为强烈的黑色背景。最后,一张马的照片会被错误的标记为一辆车。
大家可能发现了我没有详细提到的我们比较两个图片,也就是两个32*32*3的块的方法。一种最简单的方法就是一像素一像素的比较,并将所有的不同都加起来。也就是说,给定两张图片,表示为I1,I2,比较他们的一个很好的方法就是L1 distance
总和是从每一个像素中得来的。下面是可视化的过程。
用L1 distance方法,在像素尺度上比较两张图片的例子(图中示例为一个通道的内容)。两张图片逐像素相减,并将所有差加到一个数字上。若两张图完全相等,则结果是零。但是如果两张图非常不同,结果会非常大。
我们来看下如何代码实现这个分类器。首先,将CIFAR-10数据以4个数组的形式读入内存:训练数据、标签以及测试数据、标签。在下述代码中,Xtr(大小为50000x32x32x3)存储了所有的训练集,对应的一维数组Ytr(长度为50000)则保存了训练标签(从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),将数据和标签集中在一起,进行训练。同时,类中应该有对于标签的模型分类,以及如何从数据中预测标签。然后,还有一个predict(x)函数,接受新数据,预测标签内容。当然,我们没有提及分类器本身。下面是一个满足模板的简单的使用L1 distance的最近邻分类器。
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%)差了太多。
距离的选择。计算向量距离的方法还有很多。另一个选择是用L2 distance代替。也就是欧氏距离。
也就是说,我们会像以前一样逐像素计算两张图的不同之处,但是这回我们会将结果取平方,再相加,最后再开根。在numpy包中,上面的式子只需要用一句话代替
distances = np.sqrt(np.sum(np.square(self.Xtr - X[i,:]), axis = 1))
注意我在上面引用了np.sqrt,但是在一个实际的最近邻应用中我们可能舍弃平方根,因为平方根函数是单调函数。也就是说,它会改变绝对距离的尺度,但是会保持相对顺序,所以最近邻计算时是否包括平方根计算都是一样。如果你用L2 distance运行了CIFAR-10数据集,会得到大约35.4%的精度,比使用L1 distance稍微低一些。
L1还是L2。这两个方法的比较很有趣。特别是,L2 distance在向量距离方面比L1 distance好很多。That is, the L2 distance prefers many medium disagreements to one big one.L1和L2 distance(或者说L1、L2范式下两张图片的不同之处)是p范式中最常用的两个特殊情况。
K-最近邻分类器
你可能注意到希望预测最相似图片时只取最近的图片标签是非常奇怪的。确实,如果使用K-最近邻分类器的时候总是会得到更好的结果。想法非常简单:与其在数据集中找到最相近的图片,我们会寻找最相近的k个图片,再让他们决定测试图片的标签。特别的,当k=1的时候,就回归成最近邻分类器了。直观的说,较高的k值对极端值有滤波效应。
最近邻分类器和5-最近邻分类器的例子,由二维点组成,共3个类。着色的区域是分类器使用L2 distance获得的决策边界。白色的区域中的点只有模糊的分类(最终得到两个或更多的分类)。在最近邻分类器中,极端值可能会导致可能的错误预测,而5-最近邻分类器则将极端值过滤,在测试集中的归一化很好。Also note that the gray regions in the 5-NN image are caused by ties in the votes among the nearest neighbors (e.g. 2 neighbors are red, next two neighbors are blue, last neighbor is green).
我们几乎都会想要用K-最近邻分类器,但是如何取K值呢?后面再说。
用于超参数调节的验证集
K-最近邻分类器需要一个参数K,但是到底用什么数字才最好呢?此外,我们还有多种距离函数可以用,例如L1 distance、L2 distance,甚至一些我们根本不会考虑的方法。这些选项都被叫做超参数,在许多从数据中进行机器学习的算法中非常常见。有的时候我们应该选择的数值以及设置不是很清楚。
你可能被怂恿着去逐一尝试不同的值,以期找到最好的一个。这种方法无可厚非,也是我们要做的,但是要非常仔细。但是,我们不能用测试集去调整这些超参数。无论如何你设计一个机器学习算法的时候,测试集都是一个非常宝贵的资源,不到最后千万不能碰。不然,超参数就会被调整得非常适合你的测试集,一旦部署了你的模型,你就会发现效果减退非常明显。在实际情况中,我们称这种状况叫对测试集的过拟合。从另一个角度看就是,你在测试集上调整你的超参数,实际上你把测试集当成训练集在使用。因此表现起来非常好。(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. )但如果只在最后使用一遍测试集,它仍是一个很好的衡量分类器普适性的代表(后面会深入讨论普适性)。
只在最后使用一次测试集评估分类器
幸运的是,有一种方法可以调整超参数,并且完全不会动用测试集。方法就是,将训练集分为两部分:非常小的那部分被称为验证集。用CIFAR-10做一个例子,我们可以用49000个图片做训练图片,留下1000个图片做验证。验证集用作假的测试集,只用来调整超参数。
如下是代码实现
# 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值最好,我们固定住这个K值,再使用测试集评估。
将训练集分为训练集和验证集,用验证集调整超参数。再用测试集测试一遍,得到最终的报告。
交叉验证:如果你的测试集(也包括你的验证集)过小,有的时候我们会用比较精妙的方法—-交叉验证,去调整超参数。在前例中,我们不会随机挑选1000个数据做验证集,剩下的做训练集,而是在不同的验证集上迭代,再取平均值,作为k值取值表现的评估。例如在5折交叉验证中,我们将训练集等分为5份,使用其中四个进行训练,剩下的一个做验证。然后分别使用不同的等分作为验证集。评估它的表现,最后取平均值。
5折交叉验证参数k的例子。每个k都会用四个折训练,并用剩下的一折进行验证。因此,每个k都接收5个精确度值。被连起来的都是五个精确度的平均值。在这种情况下,交叉验证结果说明k=7时在这个数据集中效果最好(即峰值)。如果我们使用更多的折,可能会得到一个更加平缓的曲线。
在实践中,交叉验证会被尽量避免而简单的分割出验证集,以为交叉验证的计算量过于巨大。验证集占训练集的比例大约在50-90%。但是这也有很多影响因素:如果超参数的数量巨大,你可能需要比较大的验证集。如果验证集中的样本数量非常小(只有几百个),交叉验证显得更加安全。实践中,典型的折数为3折,5折,10折。
常见的数据分割。训练集,测试集都给出了。训练集被分为5折。1-4折作为训练集,第5折作为验证集去调整超参数。交叉验证让第1到第5折轮流作为验证集。这就是5折交叉验证。最后模型的超参数已经被调整到最佳状态,再使用测试集对其进行一次评估。
最近邻分类器的优劣
思考最近邻分类器的优劣是非常必要的。一个很好的地方就是非常容易实现,也非常容易懂。另外,分类器不需要训练,因为所有需要做的就是存储训练数据(或者还有给训练数据编索引)。但是我们把所有计算成本都花费在测试时,因为对测试样例分类需要与每一个训练样例比较。这都是后话,因为我们常常关心的是测试的时间效率,而不是训练的时间效率。事实上,在深度神经网络中,我们将这种情况转化成另外一个极端:他们的训练非常耗时,但是一旦训练成功,对一个新的测试样例进行分类就非常简单。这样的模式在实际工作中非常令人满意。
另外,对于最近邻分类器的计算复杂性研究也是研究热门。几个相似最近邻(Approximate Nearest Neighbor, ANN)算法和库都可以加速数据集中寻找最近邻的过程(例如FLANN)。这些算法能够让最近邻正确率和空间、时间复杂度相互转换,常依赖一个预处理过程,这个过程包括建立一个k维树(kd-tree、k-dimensional树)或者运行一个k-means算法。
高维数据(特别是图片)中的像素距离可能会非常直观。左边是原图,右边三个图片在L2像素距离中与其都有非常远的距离。很显然,像素尺度的距离完全不能代表直观相似度。
CIFAR-10的图片被t-SNE放置到两个维度中,图中相邻的两个图片被认为在L2像素尺度距离中也很相近。注意到背景对于这张图的排列影响远大于图片想要表达的内容对于排列的影响。
特别的,距离相近的图片可能只是因为图片中像素排列较为相似。或者是背景发挥了较大的作用,而不是图片要表达的东西。比如,一只狗可能与一只青蛙比较相似只是因为他们都是白色背景。理想状态是我们希望10个分类都有他们自己独立的簇,这样同一类中的图片都聚在一起,而无论不相关的特征怎样变换(例如背景)。但是如果想要做到这种程度,我们就得离开像素级的处理。
斯坦福大学卷积神经网络----Module 1 Lesson 1 图像分类