Published on

CS231n笔记-第二部分

Authors
  • avatar
    Name
    NorthSecond
    Twitter

线性分类

在上一篇笔记中,我们介绍了图像分类问题,就是从已有的固定分类标签集合中选择一个并分配给一张图像。我们还介绍了k-Nearest Neighbor (k-NN)分类器,该分类器的基本思想是通过将测试图像与训练集带标签的图像进行比较,来给测试图像进行标记。如我们所见,k-Nearest Neighbor分类器存在以下不足:

  • 分类器必须记住所有训练数据(将其存储起来)以供将来与测试数据进行比较。在空间上来说这是非常低效的,因为数据集的大小很可能会以Gb计。
  • 进行测试时需要将测试对象和所有的训练图像进行比较,将会消耗非常大的算力资源。

概述

我们将要实现一种更强大的方法来解决图像分类问题,该方法可以自然地延伸到神经网络和卷积神经网络上。这种方法主要有两部分组成:一个是评分函数(score function),它是原始图像数据到类别分值的映射。另一个是损失函数(loss function),它是用来量化预测分类标签的得分与真实标签之间一致性的。该方法可转化为一个最优化问题,在最优化过程中,将通过更新评分函数的参数来最小化损失函数值。

从图像到标签的参数化映射

这种方法的第一步就是定义评分函数,其作用是将图像映射到对应各个分类标签的得分,得分高低代表着图像属于该类别的可能性的高低。下面用一个具体例子展示这个方法。

和上面的内容一样,我们假设由一个包含图像的训练集 xiRDx_i \in R^D,对应的每一个图像 xix_i 都有一个标签 yiy_i ,并且由 i=1,2,,N i = 1,2,\cdots ,N 并且 y1Ky\in 1\cdots K 。也就是说我们有 NN 个图像样例(每个图像有一个维度 DD ),共有 KK 种不同的类别。

例如,举例来说,在CIFAR-10中,我们有一个 N=50000N =50000 的训练集,每个图像有 D=32x32x3=3072D=32x32x3=3072个像素,而 K=10K=10 ,这是因为图片被分为10个不同的类别(狗,猫,汽车等)。我们现在定义评分函数为: f:RDRKf:R^D\rightarrow R^K ,该函数是原始图像像素到分类分值的映射。

线性分类器

在这个模型中,我们将从最简单的概率函数开始,即线性映射

f(xi,W,b)=Wxi+bf(x_i, W, b) = W x_i + b

在上面的公式中,我们假设图像 xix_i 都被展平成为一个形状为 [D×1][D\times 1] 的列向量,矩阵WW (形状为 [K×D][K\times D] )和向量 bb (形状为 [K×1][K\times 1] )是这个函数的参数。我们还是以 CIFAR-10 为例,训练集的 N=500000N = 500000 ,对应每个图像 xix_i 包含的像素信息被拉伸成为一个 [3072×1][3072\times 1] 的列向量,WW 对应的大小为 [10×3072][10\times 3072]bb 对应的大小为 [10×1][10\times 1]。所以对于上述概率函数,我们输入3072个数字(即列向量的尺寸),得到10个数字(对应十种分类的分值)。参数 WW 被称为权重,参数 bb 被称为偏差向量因为它会影响输出的分数但是和输入的参数 xix_i 无关。但是在实际使用的过程中,你会经常听见人们混用权重参数这两个术语。

有几点需要注意:

  • 首先,一个单独的矩阵乘法 WxiWx_i 就高效地并行评估10个不同的分类器(每个分类器针对一个分类),其中每个类的分类器就是W的一个行向量。

  • 注意我们认为输入数据 (xi,yi)(x_i,y_i) 是给定且不可改变的,但参数 WWbb 是可控制改变的。我们的目标就是通过设置这些参数,使得计算得到的对应分值结果和训练集中图像数据的真实类别标签相符。在接下来的部分,我们将详细介绍如何做到这一点,但是目前只需要直观地让正确分类的分值比错误分类的分值高即可。

  • 该方法的一个优势是训练数据是用来学习得到最优的参数 WWbb 的,一旦训练完成,训练数据就可以丢弃,留下学习到的参数即可。这是因为一个测试图像可以简单地输入函数,并基于计算出的分类分值来进行分类。

  • 最后,请注意,我们使用这种方法得到分类结果只需要做一个矩阵乘法和一个矩阵加法就能对一个测试数据分类,这比 k-NN 中将测试图像和所有训练数据做比较的方法要快得多。

Foreshadowing: Convolutional Neural Networks will map image pixels to scores exactly as shown above, but the mapping ( f ) will be more complex and will contain more parameters.

预告:卷积神经网络将图像像素映射到完全如上所示的分数,但映射 (f) 会更复杂,并且会包含更多参数。

理解线性分类器

我们注意到线性分类器得到对应分类分值的方法是将 3 个颜色通道中所有像素的值都和对应的权值矩阵相乘。根据我们对权值的一些设定,对于图像中的某些位置的某些颜色,函数具有表现出“喜欢”或者“厌恶”(取决于权重的符号)的能力。例如,我们可以想象“船”分类就是被大量的蓝色所包围(对应的就是水)。那么“船”分类器在蓝色通道上的权重就有很多的正权重(它们的出现提高了“船”分类的分值),而在绿色和红色通道上的权重为负的就比较多(它们的出现降低了“船”分类的分值)。


这是一个将图像映射到分类分值的例子,为了便于可视化说明,我们假设图像只有四个像素(4个黑白像素,为简介起见,在本例中不考虑颜色通道),并且有三个分类 (红色(猫)、绿色(狗)、蓝色(船))。(注意:这里的颜色仅代表分类,和 RGB 通道没有什么关系)。我们将图像像素拉伸为一个列向量并执行对应的矩阵乘法得到每个类别对应的分数。请注意,这并不是一个好的权值矩阵 WW ,因为猫分类的得分非常低,反而狗的得分非常高。


将图像类比为高维的点

既然图像被拉伸成为了一个高维的列向量,我们就可以将每张图像看作高维空间中的一个点(例如,在 CIFAR-10 中,每张图片都是在 32×32×3=307232\times 32\times 3 = 3072 维空间中的一个点)。而整个数据集就被看做为一个每个元素都带有对应标签的点集。

我们定义每个类别对应的分数时使用的是权重和图像所有像素的矩阵乘法,因此,每个分类对应的分数就是这个高维空间中的一个线性函数的函数值。我们没有办法可视化 3072 维的空间,但是如果我们假设将所有这些维度都压缩为两个维度,我们就可以可视化的观察分类器在做什么了:

这是图像空间的示意图,其中每一个图像在空间中对应一个点,有三个可视化的线性分类器。以红色的汽车分类器为例,红线表示空间中汽车分类分数为0的点的集合,红色的箭头表示分值上升的方向。所有红线右边的点的分数值均为正,且线性升高。红线左边的点分值为负,且线性降低。


从上面可以看到,WW 的每一行都是一个分类类别的分类器。对于 WW 的一个集合解释是:当我们改变 WW 的其中一行的时候,像素空间中分类器对应的超平面会沿着不同方向进行旋转。而偏差 bb 则允许我们的分类器平移对应的超平面。需要注意的是,如果偏差项 b=0b = 0 ,无论权重的值如何,在 xi=0x_i = 0 时总有分类器对应的得分为0,分类器对应的超平面就会不得不经过原点。

将线性分类器理解为模板匹配

关于权重 WW 的另一种解释是它的每一行都代表着对应的分类的模板(有时候也叫做原型)。一张图像对应不同分类的得分是通过使用内积(也叫做点积)来比较图像和模板,然后找到和图像最近似的模板。从这个角度来看,线性分类器就是在利用学习到的模板,针对图像做模板匹配。从另一个角度来看,可以认为这种方法实质上还是在使用k-NN,不同的是我们没有使用训练集的所有图像来计算距离进行比较,而是对于每个类别,我们学习获得一个代表新的图像(是学习得到并生成的,而并不是指训练集中的某一张),而且我们使用(负)内积来计算向量间的距离,而不是使用L1或者L2距离。


一点点超前内容:这是CIFAR-10训练集,学习结束后的权重示例。注意,例如,船的模板和与其一样包含很多蓝色像素。如果图像是一艘船行驶在大海上,那么这个模板利用内积计算图像将给出很高的分数。

此外,我们可以看到马的模板看起来似乎由左右两个头,这是训练集中的马的图像中马头朝向各有左右造成的,线性分类器将这两种情况融合到了同一模板之中。类似的,汽车的模板看起来也是将几个不同的模型融合到了一个模板中,并以此来分辨不同方向不同颜色的汽车。这个模板上的车是红色的,这是因为 CIFAR-10 训练集中的车大多是红色的。因此,这样一个线性分类器对于不同颜色的车的分类能力是很弱的,但是我们稍后可以看到神经网络可以完成这项任务。展望未来,神经网络允许我们在隐藏层中实现中间神经元来探测不同种类的车(比如绿色车车头向左,蓝色车车头朝前等)。而下一层的神经元通过计算不同的该层探测神经元的加权和来获得更加准确的汽车分数。

偏差和权重的合并技巧

在进行进一步的学习之前,我们要涉及一下这个经常使用的小技巧,它能够将我们在线性分类器中常用的参数 WWbb 合并在一起。回忆一下,我们将评分函数定义为:

f(xi,W,b)=Wxi+bf(x_i, W, b) = W x_i + b

在实际操作过程中,分开处理这两个参数(权重 WW 和偏差 bb)显得会有些麻烦。一个常用的技巧就是将两组参数合并成为一个矩阵,同时给向量 xix_i 增加一个维度,这个维度的数值是常数1,这就是默认的偏差维度。上式就可以化简如下:

f(xi,W)=Wxif(x_i, W) = W x_i

那么我们还是以 CIFAR-10 为例,此时 xix_i 的形状就变成了 [3073×1][3073\times 1] 而不是 [3072×1][3072\times 1],其包含了默认偏差维度,此时 WW 的大小也变成了 [10×3073][10\times 3073],因为偏移量合并到偏差当中去了。具体可以用下面这张示例图片做说明:


这是偏移量合并技巧的举例图。左边是没有进行合并的计算方式,我们先算矩阵乘法再算加法;而右边是进行合并之后的计算形式,我们在权值举证中加入了一个表示偏差的列,这样我们只需要做一个矩阵乘法就行了。因此,如果我们进行偏移量合并,我们只需要学习一个权重矩阵,而不是两个保存权值和偏差的矩阵。


图像数据预处理

简要说明,在上面的例子中,我们所有图像使用的都是图像的原始像素值(值域为 [0255][0\cdots 255])。在机器学习中,一种常见的技巧是对输入的特征进行归一化(normalization)处理(在图像分类的例子中,图像上的每个像素可以看做一个特征)。在实践中,非常重要的一步操作是对于输入的每一个特征减去其平均值及就是进行归一化处理。在图像的例子中,该步骤意味着根据训练集中的所有图像计算出一个平均的像素值图像(即每一个点的像素值是训练集中所有该点像素的平均值),然后每个图像都减去这个平均像素值,这样图像的像素值就分布在 [127127][-127\cdots 127] 之间了。下一个常见的预处理步骤是让所有数值分布的区间范围变为 [0,1][0,1]零均值的中心化操作是非常重要的,在学习梯度下降之后,我们将会对其进行详细解释。

损失函数(Loss function)

损失函数(Loss function)

在上一节中我们定义了从图像像素值到所属分类的评分函数(score function),这个函数的参数是权值矩阵 WW 。在函数中,数据 (xi,yi)(x_i,y_i) 是给定的,不能修改。但是我们可以调整的是权值矩阵这个参数,使得评分函数的结果和训练集中的真实类别一致,即评分函数在对应的分类上获得最高的分数(score)。

例如,我们回到之前那张关于猫的图像分类的例子,针对“猫”、“狗”和“船”三个类别的分数,我们看可以看到例子中的权重矩阵非常差,因为“猫”分类对应的得分非常低(-96.8),而“狗”(437.9)和“船”(61.95)对应的分数反而很高。我们使用损失函数(Loss function)(有时也被叫做代价函数(Cost function)或者目标函数(Objective function))来衡量我们对结果的不满意的成都。直观来讲,评分函数得到的结果和真实结果之间的差异越大,对应的损失函数越大,损失函数越大,差异就越小。

多类SVM(Multiclass Support Vector Machine Loss)

损失函数的具体形式多种多样。作为第一个例子,我们介绍常用的多类支持向量机(SVM)损失函数。为了得到正确的结果,SVM的损失函数的目标是在正确分类的得分上始终比不正确分类上面得到的得分高出一个边界值 Δ\Delta。请注意,为了便于理解,我们可以把损失函数想象成一个人,他“想要”一个损失函数更低的结果,这对应了一个更好的结果。

让我们再精确一些。回忆一下,第 ii 个数据包含图像 xix_i 的像素和代表正确的标签类别 yiy_i。评分函数输入像素数据,然后通过评分函数 f(xi,W)f(x_i,W) 来计算不同分类类别的分值。这里我们将分值简写为 ss。例如,针对第 jj 个元素:sj=f(xi,W)js_j = f(x_i,W)_j。针对第 ii 个数据的多类SVM的损失函数定义如下:

Li=jyimax(0,sjsyi+Δ)L_i = \sum_{j\neq y_i}max(0, s_j-s_{y_i} + \Delta)

*举例:*我们用一个例子演示这个公式如何让计算。假设一共有三个分类,并且得到了对应的分值 s=[13,7,11]s = [13, -7, 11]。其中第一个类别是正确类别,即有 yi=0y_i = 0。同时我们假设 Δ=10\Delta = 10(后面会详细介绍这个超参数)。上面的公式是将所有的错误类别(jyij\neq y_i)对应的分数求和,所以我们得到两个部分,有:

Li=max(0,713+10)+max(0,1113+10)L_i = max(0,-7-13+10)+max(0,11-13+10)

可以看到这个多项式第一项是0,这是因为 713+10<0-7-13+10<0,则在 max()max() 函数之后得到0。则这一对类别分数和标签的损失值是0,这是因为正确的分类分数13和错误的分类分数-7的差是20,高于边界值10。而SVM只关心差距至少要大于10,更大的差值还是算作损失值为0。第二个部分计算 [1113+10][11-13+10] 得到8.虽然正确分类比不正确分类的得分要高 (13>11)(13>11),但是比我们预设的边界值10要小,分差只有2,这就是分数值等于8的原因。简而言之,SVM的损失函数想要正确分类类别 yiy_i 的分数比不正确的类别分数高,而且至少要高 Δ\Delta 。如果不满足这点,就开始计算损失值。

那么在线性分类模型中,我们面对的是线性评分函数(f(xi,W)=Wxif(x_i,W) = W x_i),所以我们可以将损失函数的公式改写成为:

Li=jyimax(0,ωjTxiωyiTxi+Δ)L_i = \sum_{j\neq y_i}max(0,\omega_j^Tx_i-\omega_{y_i}^Tx_i+\Delta)

其中,ωj\omega_j 是权重 WW 的第 jj 行,被转置成为列向量。然而,一旦开始考虑更加复杂的评分函数 ff 的公式,可能就不是这个做法了。

在结束这一小节之前,我们最后提到的是关于0的阈值:max(0,)max(0, -) 函数,它常被成为 折页损失(hinge loss)。有时候会听到人们使用使用平方折页损失SVM(即L2-SVM),它使用的是 max(0,)2max(0,-)^2,将更强烈(平方而不是线性)地惩罚过界的边界值。不适用平方是更标准的版本,但是在某些数据集中,平方折页损失表现得更好。我们可以通过交叉验证来确定哪个方法表现得更好。

损失函数可以量化我们对于预测训练集的分类标签的不满


多类SVM“想要”正确的类别对应的分类分数比其他不正确的分类对应的分数要高,并且至少要高出 Δ\Delta 的边界值。如果其他分类的分值进入到了上图的红色区域甚至更高,那么就会计算累计损失,如果没有这些情况,对应的损失值为0。我们的目标就是找到一个合适的权重矩阵,它们既能够让训练集中的数据样例满足这些限制,也能让总的累积损失尽可能低。


正则化(Regularization)

上面介绍的损失函数中存在一个问题。我们假设有一个数据集和一个权重集 WW 能够正确地分类每个数据(即所有的边界都满足对于i[1N]\forall i\in[1\cdots N],有 Li=0L_i = 0)。问题在于这个 WW 不唯一:可能存在很多相似的 WW 都能正确分类所有的数据。一个简单的例子:如果 WW 能够正确分类所有的数据,即对于每个数据,损失值都是0。那么当 λ>1\lambda > 1 时任何数乘以 λW\lambda W 都能使得损失值为0,因为这个变化将所有分值的大小都均等的扩大了,所以他们之间的绝对差值也扩大了。进一步举例,如果一个正确分类的分值和举例它最近的错误分类的分值的差距是15,对 WW 乘以2才使得差距变成30。

换句话说,我们希望能向特定的权重 WW 中添加一线偏好,对于其他权重则不添加,以此来消除我们上面所提到的模糊性。我们的实现方法是在损失函数中添加一项正则化惩罚(regularization penalty),这部分记作R(W)R(W)。最常用的正则化惩罚是L2范式,L2范式通过对所有参数进行逐元素的平方惩罚来抑制大数值的权重:

R(W)=klWk,l2R(W)=\sum _k\sum _lW_{k,l}^2

在上面的表达式中,我们对于 WW 中的所有元素平方后求和。注意正则化函数和数据本身没有啥关系,它仅仅是权重的一个函数。包含正则化惩罚之后,就能得到完整的多类SVM损失函数了,它由两个部分组成:数据损失(data loss),即所有样例的平均损失 LiL_i,以及正则化损失(regularization loss)。完整公式如下所示:

L=1NiLidata loss+λR(W)regularization lossL = \underbrace{ \frac{1}{N} \sum_i L_i }_\text{data loss} + \underbrace{\lambda R(W) }_\text{regularization loss}

将其展开有完整公式为:

L=1Nijyi[max(0,f(xi;W)jf(xi;W)yi+Δ)]+λklWk,l2L = \frac{1}{N} \sum_i \sum_{j\neq y_i} \left[ \max(0, f(x_i; W)_j - f(x_i; W)_{y_i} + \Delta) \right] + \lambda \sum_k\sum_l W_{k,l}^2

其中,有 NN 是训练集的数据量。

现在我们把正则化惩罚添加到了损失函数里面,并使用超参数 λ\lambda 来计算其权重。这个超参数是没有办法直接确定的,需要我们通过检查验证再获取。

除了上述的这些里有之外,引入正则化惩罚还可以给我们带来很多理想的属性,其中许多将在我们后面的部分中讨论。例如事实证明,包含 L2 惩罚后,SVM 们就有了最大边界这种良好的性质(如果感兴趣,可以查看CS229课程讲义以获得完整信息)。

其中最好的性质就是对大权重进行惩罚,这可以提高模型的泛化能力,因为这意味着没有哪个维度可以肚子对于整体的分类得分产生过大的影响。举个例子,假设我们输入的向量 x=[1,1,1,1]x = [1,1,1,1],两个权重向量 ω1=[1,0,0,0]\omega_1=[1,0,0,0]ω2=[0.25,0.25,0.25,0.25]\omega_2=[0.25,0.25,0.25,0.25]。那么有 ω1Tx=ω2T=1\omega_1^T x = \omega_2^T = 1,两个权重向量都能得到同样的内积,但是 ω1\omega_1 对应的 L2 惩罚为1.0,而 ω2\omega_2 对印的 L2 惩罚是0.25。因此,根据 L2 惩罚来看,ω2\omega_2 更好,因为它对应的正则化损失更小。从直观上看,这是因为 ω2\omega_2 对应的权值更小,而且更分散。既然 L2 惩罚倾向于更小更分散的权重向量,这就会鼓励分类器最终将所有维度上的特征都利用起来,而不是只依赖其中几个少数维度。在后面的课程中我们可以看到,这一效果将会显著地提升分类器的泛化能力,并避免过拟合

需要注意的是,和权重不同,偏差并没有这样的效果。因为偏差不控制输入维度上面的影响强度。因此通常只对权重 WW 正则化,而不对偏差 bb 进行正则化处理。在实际操作中我们可以发现,这通常只会产生微不足道的影响。最后,请注意,由于正则化惩罚,我们永远无法在所有的例子上得到 0 的损失值,这是因为只有当且仅当 W=0W = 0 的特殊情况下,才能得到损失值为 0.

代码

下面是一个损失函数(无正则化)的 Python 实现,有非向量化和半向量化两种形式:

def L_i(x, y, W):  
  """  
  unvectorized version. Compute the multiclass svm loss for a single example (x,y)  
  - x is a column vector representing an image (e.g. 3073 x 1 in CIFAR-10)  
    with an appended bias dimension in the 3073-rd position (i.e. bias trick)  
  - y is an integer giving index of correct class (e.g. between 0 and 9 in CIFAR-10)  
  - W is the weight matrix (e.g. 10 x 3073 in CIFAR-10)  
  """  
  delta = 1.0 # see notes about delta later in this section  
  scores = W.dot(x) # scores becomes of size 10 x 1, the scores for each class  
  correct_class_score = scores[y]  
  D = W.shape[0] # number of classes, e.g. 10  
  loss_i = 0.0  
  for j in range(D): # iterate over all wrong classes  
    if j == y:  
      # skip for the true class to only loop over incorrect classes  
      continue  
    # accumulate loss for the i-th example  
    loss_i += max(0, scores[j] - correct_class_score + delta)  
  return loss_i  
  
def L_i_vectorized(x, y, W):  
  """  
  A faster half-vectorized implementation. half-vectorized  
  refers to the fact that for a single example the implementation contains  
  no for loops, but there is still one loop over the examples (outside this function)  
  """  
  delta = 1.0  
  scores = W.dot(x)  
  # compute the margins for all classes in one vector operation  
  margins = np.maximum(0, scores - scores[y] + delta)  
  # on y-th position scores[y] - scores[y] canceled and gave delta. We want  
  # to ignore the y-th position and only consider margin on max wrong class  
  margins[y] = 0  
  loss_i = np.sum(margins)  
  return loss_i  
  
def L(X, y, W):  
  """  
  fully-vectorized implementation :  
  - X holds all the training examples as columns (e.g. 3073 x 50,000 in CIFAR-10)  
  - y is array of integers specifying correct class (e.g. 50,000-D array)  
  - W are weights (e.g. 10 x 3073)  
  """  
  # evaluate loss over all examples in X without using any for loops  
  # left as exercise to reader in the assignment  

在本小节的学习中,我们一定要记得 SVM 损失采用了一种特殊的方法,使得能够衡量对于训练数据预测分类和实际分类的标签的一致性。还有,对于训练集中的数据做出准确分类预测和损失函数最小化这两个任务实质上是等价的。

我们接下来要做的,就是找到能够使损失函数最小化的权重了

实际考虑

设置Delta

你可能会注意到,在上面的内容中,我们对于超参数 Δ\Delta 及其设置都是一笔带过,那么他应该被设置成什么值?需要通过交叉验证来求得吗?事实证明,这个超参数在绝大多数情况下都可以安全的设置为 Δ=1.0\Delta = 1.0 都是安全的。超参数 Δ\Deltaλ\lambda 看起来是两个完全不同的超参数,但是实际上它们在进行这一场权衡的控制:即损失函数中的数据损失和正则化损失之间的权衡。要理解这一点,关键是要理解权重大小WW 对于分类对应的分值有直接的影响(当然对于他们的差异也会有直接的影响):当我们缩小里面的所有值时 WW 分数差异会变小,反之亦然。因此,Δ\Delta 的具体值(比如说 Δ=1\Delta = 1Δ=100\Delta = 100)从某些角度上来看其实是没有意义的,因为权重可以任意地缩小和拉伸差异。因此,真正的权衡是我们允许权重能够变大到何种程度(即通过正则化强度 λ\lambda 来控制)。

和二元支持向量机(Binary Support Vector Machine)的关系

在本课程的学习之前,你可能对于二元支持向量机有些经验,它对于第 ii 个数据的计算公式是:

Li=Cmax(0,1yiωTxi)+R(W)L_i= C max(0,1-y_i\omega^T x_i) + R(W)

其中,CC 是一个超参数,并且 yi{1,1}y_i\in \{-1,1\}。可以认为上述公式其实是本章节的 SVM 公式的一个真子集,上述公式是多类支持向量机且只有两个分类类别的特例。也就是说,如果我们要分类的类别只有两个,那么公式就化为了二元的 SVM 公式。这个公式中的 CC 和多类 SVM 公式中的 λ\lambda 都控制着同样的权衡,而且他们之间的关系是 C1λC \propto \frac{1}{\lambda}

**备注:在初始形式中进行最优化。**如果在学习这门课程之前学习过 SVM,那么对于 kernels,duals,SMO算法等将有所耳闻。在本课程中(主要是神经网络相关),损失函数的最优化始终限制在非初始状态下进行。很多损失函数从技术上来说都是不可微的(比如当 x=yx = y 时,max(x,y)max(x,y)函数就是不可微的),但是在实际操作中这并不是问题,使用次梯度可以解决这个问题。

**备注:其他多类SVM公式。**需要指出的是,本课程中展示的多类 SVM 公式只是多种 SVM 公式的一种。另一种常用的公式是 One-Vs-All(OVA) SVM,它针对每一个类和其他类训练一个独立的二元分类器。还有一种更少使用的叫做 All-Vs-All(AVA) SVM。我们的公式遵循 Weston & Watkins 1999 (pdf) 版本,这是一个比 OVA 性能更强的版本(在构建有一个多类数据集的情况下,这个版本可以在损失值上面取到0,但是 OVA 却不行,感兴趣的话可以在论文中查阅对应的细节) 。最后一个需要提到的公式是 Structured SVM,它将正确分类对应的分类分值和非正确分类的最高分值的边缘最大化。理解这些公式的差异超出了本课程的范围。本笔记中介绍的版本同样可以在实践中安全地使用,而被论证为最简单的 OVA 策略在时间中可能也有效果(正如 Rikin 等人在 2004 年的论文 In Defense of One-Vs-All Classification (pdf) 中可以查到)。

Softmax 分类器

softmax 函数

SVM是最常用的两种分类器之一,另一种是 Softmax 分类器,它的损失函数和 SVM 的损失函数不同。对于学习过二元逻辑回归分类的读者来说,Softmax 分类器可以理解为逻辑分类器对于多个分类的一般化的归纳。SVM 将输出 f(xi;W)=Wxif(x_i;W) = W x_i 作为每个分类对应的每个分类的评分(因为没有一个对照,所以不太好解释)。与 SVM 不同,Softmax 的输出(归一化的分类概率)更加直观,并且可以从概率上解释。这一点我们在后面会进行解释。在 Softmax 分类器中,函数映射 f(xi;W)=Wxif(x_i;W) = W x_i 保持不变,但我们现在把这个分数看作对于每个分类未归一化的对数概率,并且将*折页损失(hinge loss)*替换为交叉熵损失(cross-entropy loss)。公式如下:

Li=log(efyijefj)or equivalentlyLi=fyi+logjefjL_i = -\log\left(\frac{e^{f_{y_i}}}{ \sum_j e^{f_j} }\right) \hspace{0.5in} \text{or equivalently} \hspace{0.5in} L_i = -f_{y_i} + \log\sum_j e^{f_j}

上式中,我们使用 fjf_j 来表示评分向量 ff 中的第 jj 个元素。和以前一样,整个数据集的损失值是数据集中所有样本对应的损失值 LiL_i 的均值和正则化损失 R(W)R(W) 的和。其中函数 fj(z)=ezjkezkf_j(z) = \frac{e^{z_j}}{\sum_k e^{z_k}} 被称作 softmax函数:其输入值是一个向量,向量中的任意元素为任意实数的的评分值 i[0z]\forall i \in [0\cdots z],函数对其压缩并输出一个向量,其中每个元素的值都在0到1之间,且所有元素的和为1.所以包含 softmax 函数的完整交叉熵看起来唬人,实际上还是比较容易理解的。

信息论视角

在“真实”分布 pp 和估计分布 qq 之间的交叉熵定义如下:

H(p,q)=xp(x)log(q(x))H(p,q) = -\sum_xp(x)log(q(x))

因此,Softmax分类器的目标就是最小化在估计分类概率(即为上面提到的 q=efyi/jefjq = e^{f_{y_i}} / \sum_j e^{f_j})和所谓的“真实”分布之间的交叉熵,在这个解释当中,所谓的“真实”分布就是值所有的概率密度都分布在正确的类别上(比如说,p=[0,,1,,0]p=[0,\cdots,1,\cdots,0] 中在 yiy_i 的位置上就有一个单独的1)。还有,既然交叉熵可以写成熵和Kullback-Leibler差异(Kullback-Leibler Divergence,也叫做相对熵(Relative Entropy),衡量相同事件空间里的两个概率分布的差异情况)的表达式 H(p,q)=H(p)+DKL(pq)H(p,q) = H(p) + D_{KL}(p\lvert \rvert q),并且 Δ\Delta 函数 pp 对应的熵就是0,那么就能够等价的看作是对两个分布之间的相对熵做最小化操作。换句话说,交叉熵对应的损失函数的目的就是将预测得到的所有概率都对应到真确的分类类别上。

概率论解释

先看下面的公式:

P(yixi,W)=efyijefjP(y_i\vert x_i,W) = \frac{e^{f_{y_i}}}{\sum_j e^{f_j} }

这个公式可以解释为给定的图像数据集 xix_i,以 WW 为参数,分配给正确标签 yiy_i 的归一化概率。为了便于理解,我们可以回顾一下我们将 softmax 分类器将对应的输出向量解释为没有归一化的对数概率。那么对于这个对数概率求指数幂就得到了没有归一化的概率,而除法操作则对数据进行了归一化处理,使得这些概率的和为1。从概率论的角度上来看,损失函数中对应的正则化部分 R(W)R(W) 可以被看做是权重矩阵 WW 的高斯先验,这里进行的是最大后验估计(MAP)而不是最大似然估计。我们讲到这些解释只是为了让读者能够形成一个简单的直观印象,具体细节就有点超纲了。

实践问题:数值稳定

通过编程实现 softmax 计算的过程中,中间项 efyie^{f_{y_i}}jefj\sum_j e^{f_j} 存在指数计算,所以数值可能会非常大。然后除大数的时候可能会导致数值的不稳定,所以一个非常重要的操作是进行数据的归一化处理。如果我们给分式的分子和分母都乘以一个常数 CC,并把它变换到求和之中,就能得到一个在数学上等价的公式:

efyijefj=CefyiCjefj=efyi+logCjefj+logC\frac{e^{f_{y_i}}}{\sum_j e^{f_j}} = \frac{Ce^{f_{y_i}}}{C\sum_j e^{f_j}} = \frac{e^{f_{y_i} + \log C}}{\sum_j e^{f_j + \log C}}

我们可以自由地选择 CC 的值,这是一种不会影响计算结果,但是可以提高计算中的数值稳定性的技巧。我们通常将 CC 设为 logC=maxjfjlog C = -\max_j f_j。简单来说就是将向量 ff 中的数值进行对应的平移,使得最大值为0。一个典型的代码实现如下:

f = np.array([123, 456, 789]) # example with 3 classes and each having large scores  
p = np.exp(f) / np.sum(np.exp(f)) # Bad: Numeric problem, potential blowup  
  
# instead: first shift the values of f so that the highest number is 0:  
f -= np.max(f) # f becomes [-666, -333, 0]  
p = np.exp(f) / np.sum(np.exp(f)) # safe to do, gives the correct answer  

费解的命名规则

准确来讲,SVM 分类器使用的是折页损失(hinge loss),或者说是最大边界损失(max-margin loss)。Softmax 分类器使用的是交叉熵损失(cross-entropy loss)。这个名字是从 softmax 函数中得到的,这个函数将原始分类得分变成正的归一化的数值,所有数值的和为1,这样处理之后的交叉熵损失才能够应用。特别需要注意到的是,从技术上来说,所谓的“softmax 损失(softmax loss)”是没有意义的,因为 softmax 函数只是一个压缩数值归一化的函数。但是这个命名却常常用来被作为简称。

SVM vs Softmax

下面的这张图片可能有助于阐明 Softmax 和 SVM 分类器之间的区别:

这是一个 SVM 和 softmax 不同分类器对于一个数据点的不同处理方式的例子。两个分类器都计算了同样的分值向量 ff (线性分类模型中通过矩阵乘来实现)。不同之处在于对于 ff 中的分值的解释:SVM 分类器将它们看作分类类别对应的评分,它对应的损失函数鼓励正确的分类(这张图片中是蓝色的类别2)的分值比其他的分类对应的得分高出至少一个边界值 Δ\Delta。Softmax 分类器将这些数值看作是每个分类对应的没有归一化的对数概率,鼓励正确分类的归一化的对数概率变高,其余的变低。SVM 得到的最终损失函数值为1.58,Softmax的最终的损失为0.452,但要注意这两个数值没有直接的可比性,只有在给定在同样数据,同样的分类器的损失值的计算过程中,它们才有意义。


Softmax分类器给每个分类都提供了“可能性”

SVM 的计算是没有标定的,而且难以对于所有的分类对应的评分值给出一个直观的解释。Softmax 分类器则不同,它允许我们计算出对于每一个分类标签的可能性。举个例子,针对给出的一个图像,SVM 可能会给你一个 [12.5,0.6,23.0][12.5,0.6,-23.0] 对应分类“猫”,“狗”和“船”。而 softmax 分类器可以计算出这三个标签对应的“概率”是 [0.9,0.09,0.01][0.9,0.09,0.01],这里就能看出对于不同类的结果准确性的把握。为什么我们要给“概率”打个引号呢?这是因为这个概率分布的集中和离散程度是由正则化参数 λ\lambda 直接决定的,λ\lambda 是一个你可以直接控制的超参数,举个例子,假设3个分类对应的原始分数是[1,-2,0],那么softmax函数就会计算:

[1,2,0][e1,e2,e0]=[2.71,0.14,1][0.7,0.04,0.26][1,-2,0]\rightarrow [e^1,e^{-2},e^0]=[2.71,0.14,1]\rightarrow [0.7,0.04,0.26]

现在,如果正则化参数 λ\lambda 更大,那么权重 WW 就会被惩罚的更多,然后它对应的权重数值就会变得更小。这样算出来的分数也会更小。例如,假设权重小了一半([0.5,1,0][0.5,-1,0]),那么 softmax 函数对应的计算就变成了:

[0.5,1,0][e0.5,e1,e0]=[1.65,0.73,1][0.55,0.12,0.33][0.5,-1,0]\rightarrow [e^{0.5},e^{-1},e^0]=[1.65,0.73,1]\rightarrow [0.55,0.12,0.33]

现在看起来,概率的分布就更加分担了。此外,由于 λ\lambda 代表的正则化强度非常强,权重数值都会越来越小,最后输出的概率会更接近于均匀分布。这就是说,softmax 分类器算出来的概率最好可以看作成一种对于分类正确性的“自信程度”。和 SVM 一样,数字间比较得到的大小顺序是可以解释的,但是它们的绝对值则是难以直观解释的。

实际应用中的相似性

通常来说,在实际使用中,SVM 和 Softmax 通常是相似的。两种分类器在通常情况下的表现差别都是很小的,不同的人对于哪个分类器更好有不同的看法。相对于 Softmax 分类器,SVM 更加“局部目标化(local objective)”,这既是一个特性,也可以看作是一个劣势。考虑到一个评分是 [10,2,3][10,-2,3] 的数据,其中的正确分类是第一个。那么一个 SVM (Δ=1\Delta = 1)会看到正确分类相较于不正确的分类,已经得到了比边界值还更高的分数,它就会认为损失值是0。SVM 对于数字个体的细节是不关心的:如果分数是 [10,100,100][10,-100,-100] 或者 [10,9,9][10,9,9],对于 SVM 来说,并没有什么不同,只要超过边界值 Δ=1\Delta = 1,那么损失值就为0。

而对于 softmax 分类器,情况就有所不同了。对于 [10,9,9][10,9,9] 来说,计算出的损失值就远远高于 [10,100,100][10,-100,-100] 的。换句话来说,softmax 分类器对于分数是不会知足的:正确分类总能得到更高的可能性,错误分类总能得到一个更低的可能性,损失值只能更小。但是,SVM 只要边界值被满足了就感到满意了,就不会细微地操作具体的分数。这被看作是 SVM 的一种特性。举例来说,一个汽车的分类器应该把他的精力放在如何分辨小轿车和大卡车上,而不应该纠结于如何区分车辆和青蛙,因为此时青蛙的得分就变得足够低了。

基于Web的可交互线性分类器Demo

我们实现了一个交互式的 Web Demo,来帮助读者直观地理解线性分类器。这个 Demo 将损失函数进行可视化,画面表现的是对于2维数据的3种类别的分类。Demo 在课程进度上稍微超前,展现了最优化的内容,最优化将在下一节课讨论。

小结

综上所述,本章:

  • 定义了从图像像素映射到不同类别的分类评分的评分函数。在本节中,评分函数是一个基于权重W和偏差b的线性函数。
  • 与 k-NN 分类器不同,参数方法的优势在于一旦通过训练学习到了参数,就可以将训练数据丢弃了。同时该方法对于新的测试数据的预测非常快,因为只需要与权重W进行一个矩阵乘法运算。
  • 介绍了偏差技巧,让我们能够将偏差向量和权重矩阵合二为一,然后就可以只跟踪一个矩阵。
  • 定义了损失函数(介绍了 SVM 和 Softmax 这两个线性分类器最常用的损失函数)。损失函数能够衡量给出的参数集与训练集数据真实类别情况之间的一致性。在损失函数的定义中可以看到,对训练集数据做出良好预测与得到一个足够低的损失值这两件事是等价的。

现在我们知道了如何基于参数,将数据集中的图像映射成为分类的评分,也知道了两种不同的损失函数,它们都能用来衡量算法分类预测的质量。但是,如何高效地得到能够使损失值最小的参数呢?这个求得最优参数的过程被称为最优化,将在下节课中进行介绍。

扩展阅读

下面的内容可以根据兴趣选择性地阅读