1、回顾线性分类器
2、SVM线性分类器损失函数定义
在实践中,一些定义损失函数的方法被证明是有效的,例如以多类SVM损失为例,其定义如下图所示,对于某个训练样本,其损失值的计算是通过不正确类别和正确类别的评分之差加1和零比较取极大值得到的,这个函数具有一些自身的性质,因为优化的目标是减小损失函数,而该损失函数的最小值是零,所以当所有不正确类别的评分减正确类别的差小于-1或者说正确类别分数减去所有不正确类别每个类别分数都大于1的时候,损失函数就能取到极小值,这里1是一个安全边界。
上面的例子中三个样本的损失值通过计算可以得知分别如下:
当我们期望降低损失值时,期望降低是上面三个损失值即对这些训练样本损失值的和,这里有几个问题需要思考:问题1是如果我们允许公式中的j和yi相等会有什么结果,这个答案是比较明显的,这样就会在每个损失值上加1。这不会改变寻找降低损失值的最优W,因为在降低损失值时我们关注的是取不同参数时两次损失值的差。问题2是我们不求损失值的和,而是求损失值的平均值,同样的这样的计算方式,是在损失值上整体除以了样本的数量N,也不改变寻找最优W的过程。问题3比较复杂,如果将损失函数形式做一个替换,对结果会有什么影响。
以上面这个形式进行替换后,对结果会造成什么影响?直观上可见的,对于单个样本损失值来说,以1为界,原来大于1的损失值将会在整体损失值的计算中占据更大的比重,而小于1越接近0的损失值对整体产生的影响越小。但另一方面,如果我们寻找到的W值参数如果能够使得所有损失值减小到零,那么这种情况下两个公式的损失函数是一致的,当损失值减小至零后,W的变化对损失函数的大小不再产生影响。通常上面式子中的左式被称为hinge loss,右边的式子称为hinge square loss,有些情况下使用右式会取得更好的效果。一般情况下还是hinge loss较为常用。
问题4是上面的损失函数的最大值和最小值分别是什么,最小值显然是零的,而最大值可能为正无穷(尽管实际上其数值受限于可取数值的上界),这看起来是一个糟糕的性质。
问题5:通常在对上述损失函数进行寻优时W采用较小的值进行初始化,此时所有的评分值都接近于零,在这种情况下损失将会是多少?答案是:接近于样本数减1这个数值。这个结果主要用来对程序校验,如果我们初始化后损失函数不是这个值,那么就可以推断可能是哪里写的不正确。
下图是基于numpy实现hinge loss的代码,其中y是整数,既是作为区分不同类的标签,又可以作为数组的索引用于取出某一类别的分数值。
进行到这一步,我们再认真审视一下W的取值,实际上对于能够使损失函数下降为0的W,如果W的各个参数都同时乘以一个大于1的正数,将不改变其损失函数,如下图所示,乘以2以后,对于汽车这一类的损失值并未改变,仍然是零。
这样做就会带来一个问题,如果找到一个W值,其能够使几个损失值都降到零,那么有可能对它整体进行缩放都是可以满足损失函数要求的。从数学的角度,由前述分类器的齐次形式 W’=[W b], X=[x 1] (参见学习笔记第二篇)可知,W’是可以整体缩放的(齐次形式不改变损失函数的值,因为b项在损失值相减时会被消去)。这样的话W的可行解是一簇值,这对于求解来说并不是一件好事,如果我们能给W加上一个约束,可以得到一个更加稳定一致的结果。另一方面,前述的安全边界设置为1是一个给定值,这个设定同样和W的整体约束是对应的(比如可以约束所有系数平方和小于某个值),因为如果将安全边界设为某个正整数值,只要对W进行等比例缩放,得到的分类器和之前还是完全相同的)。总之,对于前面定义的损失函数,我们还需要引入正则化项为W加上约束,如下式所示(其中hinge loss采用了平均值的形式)
加入正则化项后的损失函数的两项会在训练中互相制衡,第一项要求W中的参数值尽可能适应训练集,这样的话该项能够取到极小值,而正则化项则要求这些参数要满足该项的约束,从而使获得的W参数对于同类问题都能适用,即正则化项的存在提高了参数的在同类数据集上的泛化能力。在实际中能够观察到,正则化项的引入有时会使训练错误变大,但同时会使获得的参数值在测试集上表现的更好。正则化项可以采用多种形式,常见形式如下:
在实际中较为常用的就是L2正则化。下面举一个实际的例子说明这一点,如下图所示,w1和w2均能使得样本x与W乘积等于1,但是在L2正则化条件下,w2对应正则化项比w1对应正则化项值要小,因此最终结果会更加接近w2。
按照另一种解释观点,L2正则化实际上要求我们尽可能平均的考虑样本在各个维度上的值,第一项损失值使得W不可能都取接近于零的数值,因为这种情况下损失值较大(参见前面的问题5),在这个前提下,如果将参数尽可能平均化,能够降低L2正则化项的值。特别的,使用L1会使获得的参数值稀疏化,这些结果在后面的学习中将会看到。
3、Softmax分类器(多项式逻辑斯蒂回归)
使用Softmax分类器时,损失函数与前面SVM分类器损失函数有所不同,这里采用的观点是前面计算得到的评分值实际上是未被归一化的对数概率值。因此我们需要将其转化为概率值然后进行优化,转换的公式被称为softmax函数。如下图所示:
具体计算过程如下:
几个问题需要思考:问题1:损失函数的最大值和最小值分别是多少?这个答案是显然的,由于损失值是由概率值的负对数计算得到的,因此最小值为1的对数零,最大值是正无穷。问题2:这个问题和前述SVM损失相关,当初始化W中所有参数约等于零时,损失函数的值是多少?当各个评分值都接近于零时,求其以e为底的指数函数值约为1,所以概率值约为1/N,N是类别数,而损失值则约等于对N求对数。同样的,这个数值也用来校验是否程序初始化是否正确。
下图为SVM线性分类和Softmax线性分类之间的对比图,softmax计算损失的方法也被称为交叉熵损失(cross-entropy loss)。从图中我们注意到事实上两者之间差异主要就是体现在损失计算方式不同,在实际应用中,两者之间的这种差异不会对计算结果产生根本性的影响。计算速度也相差不大,softmax方式因为计算复杂一些,因此可能会耗费略多的时间。
当然两者细节还是存在着差异,如图所示,当调整样本数据,使得评分值分别取如图所示三种情况数值时,对于hinge loss,虽然样本对于第二类,第三类的评分值发生了改变,但对于第一类来说算得的损失值均为零。但在前述的高维空间中,样本到分类器距离是有变化的,只是损失值的计算方式使得该值为零。而cross-entropy loss的计算结果则不然,上述评分值的差异会导致其损失值结果也产生差异。直观上看,SVM具有额外的稳定性,样本远离现有分类器一定程度后,不会对分类器造成影响。
授课老师Andrej Karpathy给出了其制作的一个交互式Demo:http://vision.stanford.edu/teaching/cs231n-demos/linear-classify/用来对上述W矩阵中的参数以及损失进行可视化。从交互页面我们可以看到即使随机初始化所有的参数值,通过最小化损失函数,最终也能获得一个比较好的分类器,可以正确的将三类不同数据分开。当然,线性分类器没有解决所有问题,比如下图中的数据点形成一种线性不可分的分布,举例说某种颜色的数据点形成一个环状,而另一种颜色的数据点在这个环内部也形成一个环状,这样用像图中所示的线性分类器(直线)是无法将其正确分割的。这一问题在后面使用具备非线性性质的神经网络就可以解决了。
至此,我们所学习到的内容可以整理如下图所示:
接下来我们需要做的就是寻找好的W参数,最容易想到的策略是随机搜索,也就是不停的随机产生W中各参数的值,如果其计算得到损失值小于上一次的损失,那么记录下这次的损失值和对应的W中参数值。显然这是一种非常低效的方法,我们暂时不要考虑。随机搜索的代码如下:
在测试集上能够达到的准确率为15.5%,测试代码如下:
另一种思路是利用梯度下降的思想,这个思路其实也非常自然,我们期望寻找能使损失函数值最小的W,那么如果我们在当前W值附近选择几个接近的值,就能够发现某种趋势。这个好比登山,假如我们期望寻找的W值用二维平面上的坐标表示,在某一坐标处山的高度表示该W值对应的损失值,那么在当前坐标附近朝各个方向走几步,我们就能发现其实朝某个方向走,高度下降的相对比较快,而如果我们朝着这个方向走,很有可能就能找到一个谷底,也就是对应损失函数值局部最小的一个坐标,就像图中所示意的,而我们之前随机搜索的方法像是蒙着眼睛在山上漫无目的的乱逛。
事实上,用数学公式表示的话,这个下降最快的方向就是所谓的梯度方向,当然前提是损失函数在当前位置任何方向上都是可导的。对于一维的函数,我们可以用下面这个公式来近似表示点x处的梯度。
对于高维的W向量,我们可以分别对其中各个维度增加微小的偏移量,计算损失函数对该变量的偏导数,如下图所示为计算损失函数对第一个变量的偏导数:
继续计算对第二个变量的偏导数:
如此下去,依次计算,我们能够算得所有的偏导数,其组成的向量即为当前的梯度值,从上面的计算过程我们也能看到,负梯度的方向是函数值下降的方向。下图所示为上面计算过程对应的代码。
在实际中,使用上述数值解法计算梯度,效率是非常低下的。事实上我们注意到损失函数对于W的解析式是可以写出的,因此其导数的解析形式也可以写出,这样的话,就可以基于解析式直接计算梯度的数值,而不必先计算损失函数值,再用其值间接计算梯度了。
如下图所示,可以利用导数公式直接求得梯度的解析表达式,然后直接计算梯度值
那么前面所使用的数值解法是否就没有作用呢,也并不是如此,数值解法虽然效率低下,但是由于推导梯度的解析表达式容易出错,所以可以利用数值解法对解析表达式所计算出的结果进行校验,这被称为“Gradient Check”。也是Andrej Karpathy老师推荐的方法。
在梯度被求出后,我们可以利用梯度对W参数进行更新,如图所示为用梯度值更新参数的代码,其中step_size是步长,我们注意到前面乘以了负号,因为“负梯度的方向是函数值下降的方向”。注意负梯度不代表梯度值本身的正负,梯度值是一个向量,是一个有向的数学变量,其中每个分量的值可正负。负梯度表示梯度向量的反方向。
在实际的训练过程中,step_size以及正则化参数lamada是两个比较令人头疼的超参数,这两个参数的最优值并没有固定的寻优方法,需要根据具体问题进行分析计算。
另一方面,在计算损失函数时,如果要每次对所有的数据进行计算,计算效率也是比较低的,而且如果我们使用GPU进行训练,每次能够计算的数据量还受到GPU内存大小的限制。因此我们通常采用较小的批量(batch)数据去计算梯度,典型batch大小为32/64/128,Krizhevsky ILSVRC ConvNet中使用的批量大小为256。
使用的梯度大小不同时,损失函数收敛到局部极小值的方式也不同,如图所示,假设W只包含两个参数,热力图代表其对应损失函数值的大小,越接近红色的部分函数值越小。由图可见采用大小不同的batchsize时,算得的梯度方向噪声程度不同,一般来说,batchsize越小,噪声越显著,但是其整体趋势仍是趋近于局部极小值的。所以batchsize和计算效率之前存在一种平衡,减少batchsize可以有效提高计算梯度的效率,但是需要的迭代次数增多,如果迭代次数过多,那么收敛到局部极小的时间也可能变长,因此实际训练中,需要折衷选择其大小。
下面图中左图显示了在较小batchsize下,损失函数的变化情况,可以看到其中包含很多噪声,注意横轴是以epoch为单位的,在数据集被以batchsize分割之后,需要多轮迭代才能完成数据集整体的一次训练,整体训练完一次为一个epoch。而learning rate(学习率)的选择即对应前面的step_size也会造成训练过程的变化,一般来说,前期应该采用较大的学习率,而接近局部极小值时应该选择较小的学习率。
事实上,在Update这一步有多种方法可以选择,例如momentum, Adagrad,RMSProp, Adam等等。
其收敛的情况如下图例子所示,其中SGD方法就是前面所述的方法,其它方法在后面课程中将会涉及。
最后内容是一部分对图像数据处理和特征提取的补充讲解,图像识别一般是按照如下图所示的范式进行的,从图像中提取一些特征(如图中红色、绿色和蓝色分别表示不同的特征),然后将这些特征组合成一个很大的向量,这些向量被用于训练分类器,如前述的线性分类器以确定线性分类器的参数,之后要测试的样本也就可以通过这个分类器进行识别了。
下图所示为一个具体的特征——直方图,直方图是一种比较简单的特征,它统计图像中各种颜色的像素个数,形成如下图所示的统计数据,矩形的高度表示像素的个数,这种特征可能并不是十分可靠,例如我们要识别不同形状的物体时,其颜色的分布与形状几乎是相对独立的。因此不能只依赖直方图对图像进行可靠的识别。
要对图像中物体的形状进行分析,可以采用其它一些特征,例如SIFT/Hog等,这些特征的基本思想是在一个小的邻域范围内对图像中的边缘等底层数据统计分析形成特征,SIFT主要关注某局部极大值附近的方位,Hog统计领域内的边缘方向分布,这些特征都是高维的,对于视角和光照等具有一定的鲁棒性。
上述特征可以被进一步加工,在进行图像识别时,上述的各种特征可以形成所谓视觉词向量,而同一个图片中不同区域的词向量可以聚类,也就是建立字典,根据聚类的结果,略去那些不能对识别起到显著效果的特征,也即是略去很少被使用的视觉词向量,保留一些显著的视觉词向量,统计剩余这些词向量可以形成视觉词向量的直方图,如图所示为1000维的词向量直方图,这个结果就可以被用于训练分类器以及进行分类识别。
上述过程就是没有应用深度学习之前,进行图像识别的典型处理流程,如下图中上半部分所展示的,这个时期的论文主要讨论如何去提取一个好的特征,而在我们采用具有深度学习结构的神经网络识别算法后,主要需要关心的是其结构的设计,而其训练过程主要通过一个损失函数就可以实现,如果结构设计合理,它可以自动提取认为有用的特征,这样大大减轻了设计特征的负担,而且实际中也更容易获得高的准确率。