说明:
-
本系列文章翻译斯坦福大学的课程:Convolutional Neural Networks for Visual Recognition的课程讲义 原文地址:http://cs231n.github.io/。 最好有Python基础(但不是必要的),Python的介绍见该课程的module0。
-
本节的code见地址:
https://github.com/anthony123/cs231n/tree/master/module1-2 -
如果在code中发现bug或者有什么不清楚的地方,可以及时给我留言,因为code没有经过很严格的测试。
这节课的主要内容:
- 介绍线性分类
- 线性分数函数
- 解释线性分类器
- 损失函数
- 多类别SVM
- Softmax分类器
- SVM vs Softmax
- 线性分类的交互性网页示例
- 总结
线性分类
上一节课介绍了线性分类问题,它从一个固定的标签集合中选择一个标签,赋予一张图片。而且,我们介绍了KNN分类器,它通过将输入图像与训练集中的图像比较的方法赋予图像标签。我们知道,kNN有如下几个缺点:
- 分类器必须记住所有的训练数据,并把它存储起来,将来与测试集中的图像进行比较。这种方式非常耗费空间,因为训练集往往在GB数量级。
- 对一张图片进行分类非常耗费时间,因为它需要与训练集中所有的图片进行比较。
概要
我们将要开发一个更强大的图像分类算法,它最终能够被扩展到整个神经网络和卷积神经网络。这个算法有两个主要的部分:1. 把原始数据映射到种类分数的分数函数;2.评估预测分数与真实标签一致性的损失函数。我们最终会把分类问题看成一个优化问题,即通过改变分数函数的参数来最小化损失函数。
图像到标签分数的参数化映射
这个算法的第一个组成部分是定义一个分数函数,它将图像的像素值映射到对每个类的置信分数(confidence scores)。我们会以一个具体的例子来说明。和以前一样,我们假设一个图片训练集 x_i ∈ R^D, 每张图片都和一个标签 y_i,其中i=1…N,y_i∈1…K 相对应。也就是说,我们有N个例子(每个有D个维度)和K个不同的种类。比如,CIFAR-10测试集 N=50,000,每张图片的维度D=32x32x3=3072,K=10,因为我们有10类物品(狗,猫,车等)。我们现在定义一个分数函数
它将图像的原始像素映射到种类分数。
线性分类器
我们从一个最简单的线性函数出发,一个线性映射:
在上面的表达式中,我们假设图像 Xi 所有的像素都放在一个行向量中,它的形状为[Dx1]。矩阵W(大小为[KxD])和向量b(大小为[Kx1])都是函数的参数,在CIFAR-10中,Xi 包含第i张图片的所有像素,其大小为[3072x1], W的大小为[10x3072],b的大小为[10x1]。所以输入3072个数字到函数中,输出10个数字。W中的参数称之为权重, b称之为偏置向量(bias vector),因为它虽然影响输出,但是它并不和实际的数据交互。在实践中,人们经常将权重和参数互换使用。
下面有几件事情需要说明一下:
-
单独的乘积 WXi 能够并发地同时评估10个分类器,其中,W的每一行是一个分类器
-
注意我们将输入数据(Xi,yi)视作固定的,而我们可以控制W和b的值。我们的目标是设置它们的值,使得对整个训练集图片的预测和它们的真实标签尽可能一致。我们以后会讨论如何做到这一点,但是从直觉上说,我们希望正确的类别分数比错误类别的分数要高。
-
这个算法的一个优点是训练集用来学习参数W,b,一旦学习结束,我们可以丢弃整个训练集,只保留学习的参数。测试图片可以通过分数函数的输出结果来获得分类的标签。
-
对测试图片分类只包括了一个简单的矩阵乘法和加法,它比将它与所有的训练集比较要快的多。
卷积神经网络和上面描述的方法那样,将图像像素映射成类别分数,但是映射函数(f)会更复杂,参数也会更多。
解释线性分类
线性分类器通过求一张图片三个通道所有像素的权重和来计算计算类别分数。 通过设置这些权重值,类别函数可以“喜欢”或“不喜欢”图片特定位置的像素颜色。比如, 你可以想象类别为“船”的图像,它周边的蓝色像素值比较高,所以你可以预期类别船在蓝色通道有很多正的权重(蓝色的出现提高了类别船的分数),而在红色/绿色通道有很多负的权重(红色/绿色的出现降低了船的分数)。
将一张图片映射到类别分数的一个例子。为了显示的简单性,我们假设这张图只有四个像素,三个类别(红色(猫), 绿色(狗)和蓝色(船))。我们把图像的像素放入一个列向量中,进行矩阵乘法来获得每个类别的分数。注意 这个W设置的并不好,把这张猫的图片赋予猫类别的分数很低,而且这个参数W似乎认为它更像一条狗图像与高维度点集的类比
因为图像可以看成高维度的列向量,我们可以把每张图看做成在这个空间中的一个点(比如, 在CIFAR-10中,图像可以看做是3072维度上的一个点),所以整个数据集可以看成是一个点的集合。
因为我们把类别的分数看成这张图片的所有像素权重和,那么每个类别分数可以看成是在这个空间内的一个线性函数。我们无法直观地感受3027维度的空间,但是如果我们能把所有的点放在二维空间内,那我们就可以直观地看到这个分类器做了些什么:
图像空间的卡通展示,其中每张图片是一个点,三条直线表示三个分类器。以汽车分类器(红色)为例:红线表示所有汽车类别分数为0的点。红色的箭头表示增长的方向,所以在红色线的右边的点,其汽车类别分数为正(离的越远,其类别分数值越高), 所有在红色线左边的点,其汽车类别分数为负(离的越远,其类别分数值越小)我们知道, W的每一行都是一个类别的分类,这些数字的几何解释是,当我们改变W中一行中的某些数字,在像素空间中对应的直线会沿不同方向上旋转。偏置量b可以使得这些直线的位移发生变化。特别地,如果没有偏置量,而且Xi=0,那么不管权重取什么值,得到的分数都是0。所有的直线都被迫穿过原点。
把线性分类器作为模型匹配的解释
权重W的另外一个解释是W的每一行都对应于一个类别的模型(有的时候也称之为原型)。一个图像的类别分数通过內积来比较图像与各个模型的匹配程度。使用这种术语, 线性分类器就是模型匹配,这些模型都是通过学习获得的。另一种思考方式是它在做最近邻分析。不同的是,之前是和训练集中所有的图像进行比较,现在只需要和一张图像比较(这张图片不一定是训练集中的一张图片)。同时,我们使用(负)內积来作为距离,而不是L1或者L2距离。
CIFAR-10的学习权重,以船类别为例,与预期的一样,船类别包含许多蓝色的像素,所以如果与在海洋中的船图像做內积,将会得到一个更高的类别分数。从上图我们可以观察到,马的模型好像包含一个双头马,这是因为在数据集中马既有朝左的,也有朝右的。线性分类器把数据集中马的这两种模式都编码到一个模型当中。类似的,车分类器似乎融合了各个朝向和颜色的车。特别地, 这个车模型的颜色是红色。说明在CIFAR-10中,红色的车远多于其他颜色的车。这个线性分类器在分类不同颜色的车时比较弱,在后面我们会看到神经网络可以使得我们很好地识别不同颜色的车。先剧透一下,神经网络可以在隐藏层里有中间的神经网络,它可以识别特定模式的车(比如,朝左的绿色的车, 朝前的蓝色的车等)。而下一层的神经元可以把这些特征都结合起来,通过一个权重和,获得一个更准确的车类别分数。
偏置的小技巧
在继续进行下去之前,我们来讲一个将W和b合并成一个参数的小技巧,回忆一下,我们将分数函数定义为
我们之后会发现要记录两个参数W和b会有些麻烦,一个常用的小技巧是将W和B表示为一个矩阵,它通过将Xi扩展一个维度,并且这个维度的值一直为1。增加一个维度之后,新的分数函数将被简化为一个矩阵乘法:
以CIFAR-10为例,Xi 现在是[3073x1],而不是[3072x1](多出来的维度里面的值为1)。W现在是[10x3073]而不是[10x3072]。W中多出来的一列对应于偏置值b。下面的图示可以帮助理解:
偏置技巧的示意图。先做一个矩阵乘法,然后在加上一个偏置向量(如左边所示)与 如下做法是等价的,即在输入向量中加入一个维度,将其值设为1,将权重矩阵也扩充一列,将这列的值设为偏置向量的值,再将这两个矩阵相乘。所以,如果我们对数据进行以上的预处理,那么我们只需要操作一个单独的矩阵了图像数据预处理
在上面的例子中,我们使用的是原始的像素值([0...255])。 在机器学习中,将输入特征归一化(在图像的例子中,每个像素可以看做是一个特征)是一个常见的做法。特别地, 通过减去每个特征的平均值,将每个特征中心化非常重要。在图像的领域中,对应的做法是计算训练集的平均图片,再将每张图片减去这个平均图片,就可以得到像素值在[-127,127]范围内的像素矩阵。更常见的做法是将像素值再缩放到[-1,1]的范围内。其中,零中心化非常重要,但是我们要等到我们理解了梯度下降的动态性,我才会证明它的重要性。
损失函数
在上一节中我们通过权重W,定义了一个将像素值映射到类别分数的函数。我们无法改变数据值(Xi,yi),但是我们可以控制这些权重,我们想要通过设定它们的值,使得我们预测的类别分数能够和真实的标签一致。
以之前的猫的例子为例,它的类别函数在猫,狗和船类别中做出预测。我们发现那个权重设置的并不好,我们输入一只猫的像素,但是猫的类别分数(-96.8)并不比其他两类高(狗的分数为437.9,船的分数为61.95)。我们用损失函数(有时候也称之为花费函数或目标函数)测量我们对结果的不满意。从直觉上来说,如果损失函数大,那么我们分类结果不是很好,反之,如果分类结果好,则损失函数的值就会很低。
多类别支持向量机损失
有很多定义损失函数的方法,第一个例子是常用的损失函数:多类别支持向量机损失(Multiclass Support Vector Machine Loss)。SVM损失希望正确的类别分数比不正确的类别分数至少高Δ。有时候把损失函数做上面那样的类比是正确的,即SVM希望最终的损失值比较小。
现在我们具体来说明,对于第i张图片,我们已知这张图片的所有像素Xi和它的类别标签yi,分数函数输入图像的像素,输出一个类别分数的向量,我们用s表示。比如,第j个类别的分数我们可以表示为Sj, 那么第i张图片的多类别SVM损失可以表示为
例子
让我们用一个例子来说明。假设我们有三个类别,它们的分数是s=[13,-7,11]。其中 第一个类别是正确的类别。我们假设Δ(超参数,后面我们会详细讨论)为10。上面的公式是把所有不正确的类别分数加起来,我们得到两项:我们可以发现第一项等于零,因为[-7-13+10]为负数。所以这一项的损失为0,因为正确类别的分数(13)至少比不正确类别分数(-7)大10.实际上, 它们之间的差距是20,比10大得多。但是我们只关心它是否比10大。第二项[11-13+10]为8,虽然错误类别分数比正确类别分数小,但是它们之间的差异小于10.所以SVM损失希望正确类别的分数至少比错误类别的分数大Δ,如果不是这种情况,那么损失就会累积。
在这个特殊的例子中, 我们使用的是线性分数函数f(xi;W) = Wxi,所以我们可以把损失函数改写为:
其中Wj是W的第j行。注意当我们考虑更复杂的f时,情况就会不同。
在结束这一节之前,我们再介绍一个术语:铰链损失(hinge loss)。它指的就是max(0, -)定义的损失函数。你有时候会看到人们使用平方铰链损失(或者L2-SVM),它使用max(0, -)^2来加强损失的程度。标准铰链损失是一种常见的做法,但是有时候平方铰链损失能达到更好的结果, 这可以通过交叉验证来决定。
多类别支持向量机“希望”正确类别的分数至少比其他的分数高Δ, 任何在红色区域的类别分数(或更高)都会累积损失值,其他情况,损失值为0。我们的目标是找出一个权重值,使得在训练集中所有的图像都满足这个限制,并且整个损失越低越好。损失函数用来把我们对测试集的测试不满意度量化。
正则项
我们上面给出的损失函数有一个错误。假设我们有一个数据集和可以将每一类都正确分类参数W。那么这样的W不是唯一的。还有很多相似的W可以将数据集中的例子正确分类。一个简单的例子:如果W能够正确地将例子分类,那么W的倍数λW(λ>1)也能使损失为0,因为这个变换将所有的分数都增加了,它们的差异分数也是。比如,如果一个正确的类与一个最近的错误的类之间的差异分数为15,那么把W中所有的权重乘2,新的差异就变为30。
我们将通过一种方法使得W能够去除这种不确定性。即通过在损失函数中增加正则惩罚(regularization penalty)R(W). 最常见的正则惩罚是L2,它通过逐元素的平方惩罚能够识别数值较大的权重。公式如下:
在上面的表达式中,我们把W所有权重的平方加起来了。注意正则函数不是关于数据的函数,它只基于权重。加入权重惩罚之后,多类别支持向量机公式才完成了,它包括两个部分:一个是数据损失(它是所有例子的平均损失),一个是正则损失。最终完整的多类别SVM损失函数为:
或者把它扩展为一个完整的形式:
其中N为训练例子的数目。你可以看到,我们将正则惩罚项加入到了损失函数中,并使用λ控制它的权重。没有一个简单的方式设置这个超参数,它经常是使用交叉验证的方式来确定。
除了以上提到的原因外,加入正则项还有其他的好处。我们会在后面的内容讲到。例如,加入L2项可以使得SVM容易获得最大的边界(max margining)。
惩罚数值大的权重的一个最大的好处是可以提高可扩展性。它意味着没有一个单独的输入维度可以对结果产生过大的影响。比如:我们有一个输入向量:x=[1,1,1,1]和两个权重向量 w1=[1,0,0,0]和w2=[.25,.25,.25,.25]。它们都与输入向量的点积都为1, 但是w1的L2惩罚项为1.0 而w2的L2惩罚项为0.25。因此,w2是更好的权重向量。直观上,我们可以看出w2中权重值更小,也更分散,最终的分类器也依赖于更多的权重。在之后的课程我们可以看到,这个行为可以增加可扩展性,减少过度拟合。
注意偏置项并没有相同的效果。因为,不像权重值,它们没有控制输入维度的影响力。因此,通常只对W进行正则化。但是,在实践中,如果也将偏置项正则化,影响也可以忽略不计。最后,由于我们加入了正则惩罚,损失项永远不可能达到0,除非W中的权重都为0.
代码
这是用Python实现的损失函数的代码(没有正则项),既有非向量化也有半向量化形式:
def L_i(x,y,w):
"""
非向量化版本。计算一个例子(x,y)的多类别SVM损失。
-X 表示一张图片的列向量(比如,在CIFAR-10中为3073x1)
-y 一个整数,表示正确类别的索引值(比如,在CIFAR-10中为0~9)
-W 为权重矩阵(比如,在CIFAR-10中,表示10x3073)
"""
delta = 1.0
scores = w.dot(x) #每一类的分数
correct_class_score = scores[y]
D = W.shape[0]
loss_i = 0.0
for j in xrange(D): #遍历所有的错误种类
if j == y:
continue #跳过正确的种类
loss_i += max(0, socres[j] - correct_class_score + delta)
return loss_i
def L_i_vertorized(x,y,W):
"""
更快的,半向量化实现。半向量化是指一个单独的例子中没有for循环,但是还是有一个大循环
“””
delta = 1.0
scores = W.dot(x)
#一个向量操作计算所有类别的边缘值
margins = np.maximun(0, scores - scores[y] + delta)
#把正确的边缘值置0
margins[y] = 0
loss_i = np.sum(margins)
return loss_i
def L(X,y,W):
"""
全向量化,留给读者完成
"""
这一节的主要内容是SVM损失, 它使用一个方法来测量预测结果与真实标签的一致性。此外,对训练集做一个好的预测相当于最小化损失值。
我们现在需要做的就是想出一种方法来求出使得损失最小化的权重值
实践考虑
设置delta
我们没有讲解超参数Δ及如何设置它,我们必须要通过交叉验证的方式设置吗?实际上,我们可以安全地将它的值设为1.0 。超参数Δ和λ看起来是两个不同的参数,其实它们都是用来控制一个平衡:即数据损失和正则项之间的平衡。理解这件事情的关键在于W的值直接影响了分数值(进而影响分数之间的差异):当我们减少W中所有的值,那么差异值就会减少,反之差异值就会增大。因此,分数之间的边缘值(比如:Δ=1 或Δ=100)在某种程度上是没有意义的因为权重值可以随意增大或减少差异值。所以,真正的平衡点在于我们允许权重值增加到多大程度(通过正则项的参数λ)。
与二元SVM的关系
你可能之前接触过二元SVM,第i个样本的损失值可以写为:
其中,其中C是一个超参数,y_i ∈{-1,1}。 你可以说服自己,以上那个公式就是我们这节所讲的公式的一个特殊的情况(种类为2),同时上式中的C与我们公式的λ都是用来控制平衡,它们是倒数关系。
在基本式上优化
如果你之前已经有SVM的知识,那么你可能听过核(kernel)对偶(dual)和SMO算法。但是这这个课堂我们只是求在没有限制条件下的基本式优化值,所以很多目标函数都不可微(比如 MAX(x,y)函数不可微,因为当x=y时,不可微)。但是在实践中,这并不是一个问题,因为我们经常用子梯度。
其他多种类SVM公式
值得注意的是在这节课上呈现的多类别SVM的公式只是多种多类别SVM公式的一种,另外一种经常使用的公式是OVA(One-Vs-All)SVM, 它给每个种类及所有的种类SVM训练二元的SVM。还有一种是AVA(All-As-All) SVM,相比OVA,我们的公式是一个更有效的公式。还有一种是结构化SVM(structured SVM),它最大化正确类别和最高的错误类别分数之间的差异。理解他们之间的差异不是本门课的内容,本节课使用的公式是一个比较安全的公式,但是最简单的OVA公式也能工作的很好。
Softmax Classifier
SVM是两个最常见的分类器之一。另外一个常见的选择是Softmax 分类器。如果你以前听过二元逻辑回归分类器的话,那么Softmax分类器是扩展到多种类别的一个更一般化的版本。与SVM将f(xi,W)的结果看做成每个类别的分数不同,Softmax分类器给出了一个更直觉化的结果,即使用概率论的知识解释得到的结果。在softmax分类器中,函数f(xi:W) = Wxi 保持不变,但是我们现在把这些分数解释为每一类的未标准化的log可能性。并将铰链损失替换为交织熵损失(cross-entropy loss),它的形式如下:
或者等价于
其中 fj 是指第j个类别的分数。和之前一样,最终的损失值是Li的平均值加上一个正则项R(W). 函数
称之为softmax函数:它输入一个任意的实数分数组成的向量,把它变成数值在0~1,并且所有数值加起来为1的向量。涉及Softmax函数的完整的交叉熵损失函数第一次看可以有点吓人,但是其实它非常容易理解。
信息论视角
真实的分布p与估计的分布q之间的交叉熵可以定义为:
Softmax分类器就是用来最小化预测的类别概率
与真实分布(这里指的是正确分类的分布)之间的交叉熵。而且因为交叉熵可以写成熵的形式, 而Kullback-Leibler散度可以写成
delta函数P的熵为0,这也等价于最小化两个分布(距离)KL散度。换句话说,交叉熵的目标就是希望预测的分布尽可能与正确分布一致。
概率解释
仔细观察下面这个表达式,我们可以发现
可以解释为已知图像xi和参数W,求它被赋予正确标签yi的(标准化)概率。我们已知Softmax分类器将分数解释为一个非标准化的log概率向量f,除法操作是标准化操作,使得所有的概率加起来为1. 使用概率学的术语,我们最小化正确类别的负log可能性,它也可以被解释为执行最大似然估计(Maximum Likelyhood Estimation, MLE)。 这个视角的一个好的特征是我们现在可以把正则项R(W)解释为关于W的高斯先验知识。所以我们不将它解释我MLE,而是解释为最大后验概率(maximum a posteriori, MAP)。我们讲这些是为了使你们从直观上更容易理解,但是关于这些细节却超出了本书的范围。
实践问题:数值稳定
当我们写代码计算Softmax时,中间变量
和
由于指数的原因,可能会很大。大数字之间的除法可能会不稳定,所以使用一个标准化的技巧很重要。我们如果在分子和分母同时乘以一个常数C,并加在指数函数的参数上,我们可以得到下面等价的式子:
我们可以自由选择C的数值而不会改变最终的结果,但是我们可以使用这个值来提高计算过程中的数值稳定性。C的一个常见的选择是设置为
这样设置我们就可以把最大值设为0,在代码中实现如下:
f = np.array([123,456,789]) #共有三个类别,每一类都有一个大的分数
p = np.exp(f)/np.sum(np.exp(f)) #会出现数值问题
# 我们将数值平移,使得最大数为0
f -= np.max(f) #f 变成[-666,-333,0]
p -= np.exp(f) / np.sum(np.exp(f)) #很安全,能得到正确答案。
易混淆命名方式
准确来说,SVM使用铰链损失,有时也称为最大边界损失(max-margin loss)。Softmax分类器使用交叉熵损失,Softmax分类以Softmax函数命名,它可以将原始的类别分数转换为总和为1的标准正数值,以便交叉熵能够使用。所以,从技术上说,讨论交叉损失是没有意义的,因为它只是个转换函数,但是为了方便,我们经常这么使用。
SVM VS. Softmax
下面这张图能够帮你理清Softmax与SVM分类器之间的区别
Softmax分类器和SVM分类器的例子Softmax给每个类别提供了“概率”解释
不像SVM,很难准确解释每个类别分数的意义,Softmax分类器允许我们计算所有标签的类别。比如,输入一张图,SVM针对类别猫,狗和船给出分数[12.5, 0.6, -23.0]。而SVM分类器能够计算三个类别的概率[0.9, 0.09, 0.01],这些概率可以用来解释我们对将该图分类到每一类的信心。我们将“概率”放入双引号内,是因为概率的聚合或分散直接取决于我们定义的正则参数λ,而这是我们可以控制的。比如,三个类别的未标准化的log概率为[1,-2,0]。那么softmax函数会如下计算:
如果提高λ的值,那么对于W的惩罚会更多,这会导致W减少。比如,假设权重减少至一半[0.5, -1, 0] 那么Softmax的计算如下:
在这种情况下,概率值更加分散。而且,当由于很大的正则参数λ导致了权重的值很小时,最终输出的概率可能会近似于均匀分布。所以,Softmax分类器计算的是置信度(confidence),它们之间的顺序是有意义的,但是数值却不是真正意义上的概率。
在实践中,SVM和Softmax效果相当
SVM与Softmax的表现差别很小。不同的人对二者谁表现好有不同的看法。与Softmax分类器相比,SVM计算的是更局部的目标,它可以看成是一个缺点,或者可以看成是一个特征。比如一个图片例子的分数向量为[10, -2, 3], 那么第一个类别是正确的类别。SVM(△=1)知道第一类是正确答案,并且损失为0。所以SVM并不考虑各个分数的细节。如果分数矩阵被替换为[10,-100,-100]或者[10,9,9],对SVM来说,并没有差别,因为损失值仍为0。然而 Softmax分类器却不是如此。相比[10,-100,-100],分数向量[10,9,9]会累计更高的损失值。 也就是说,Softmax分类器用于不会完全满足生成的分数值,正确类别永远有一个更高的概率,而错误类别永远有一个更低的概率。所以损失值永远会偏大。比如,一辆小汽车分类器可能会将大部分的努力放在与卡车区别开来,但这些努力不会影响对青蛙类别的判断,因为它已经被赋予了一个很低的值。
交互性网页demo
一个线性分类器的直观的网页交互例子总结
- 我们定义了一个分数函数,它可以将像素映射到类别分数(在这一节中,指的是一个线性函数,它由W和b组成)。
- 不像kNN分类器,参数化方法的优点在于一旦我们学习了这些参数,那么我们可以丢弃训练集。而且,对一个新的测试图片的预测很快,因为它只需要一个与W的矩阵乘法,而不是与训练集中每一个例子比较。
- 我们引入了偏置技巧,我们可以将偏置向量也放入W中,所以我们只需要一个矩阵就行。
- 我们定义了一个损失函数(我们介绍了两个线性函数的损失函数SVM和Softmax),它可以测量我们学习的参数是否可以准确预测类别。而且准确预测以为损失量小。
我们现在看到一种基于参数的将图片信息映射到类别信息的方法,我们也已经学习了两种损失函数来测量预测效果。但是我们如何找出这些能给出最低损失是的参数呢? 这个过程称之为优化过程。这也是下一节课的内容。
推荐阅读:
Deep Learning using Linear Support Vector Machines from Charlie Tang 2013 presents some results claiming that the L2SVM outperforms Softmax.
网友评论