摘要:接下来就要设计一个损失函数,以衡量模型的输出结果距离正确值的距离。如果模型的预测值与正确值是 完全一致 的,则相减为0,它不会给这两个损失函数指导下的学习过程带来任何损失。

本来是不想起这么大而空的名字的,但是今天确实特别自闭,所以想来个刺激一点标题刺激一下自己,也刺激一下别人。

时至今日,距离上一篇blog更新已经过去快九个月了,距离我在MSRA实习已经快八个月了,这么一看好像我的blog是为了找实习而准备的一样

实际上是公司活儿太多了

,为了不让blog继续这么僵下去,还是决定来更新点东西。

其实也有很多东西可以写,比如对tf和pytorch的认识,一些对CRF及其decode的理解,一些BERT调参心得,或者Nested NER或GNN方面的survey。之所以确定写这个,一个是好久没碰技术型blog了,一时甚至不知道怎么来写,另一方面纯粹是因为我发现每次要用到交叉熵的时候,都需要跑到别人博客里重新看一遍这到底是什么玩意儿,忘性太大。后来我一想,反正都要看,那我不如自己写个blog,自产自销搞个闭环,增加自己的点击量呢。

是为序。

损失函数及其意义

损失函数,顾名思义就是衡量损失的函数。比如小明家里进了小偷,小明报了警,警察来了以后小明就抹着眼泪和警察说哪些东西不见了,价值是多少。这时候警察脑子里就有个损失函数:电脑没了+5000,电视没了+3000,拖鞋没了+30,肥皂没了+8,最后小明的损失就是8838。在这个参照下,损失函数的 计算逻辑 是加和,损失的 参照对象 是被偷之前的小明。

换言之,损失函数实际上是 衡量两个分布之间的距离的 。其中一个分布应当是原始分布,或者正确的分布(ground truth),而另一个分布则是目前的分布,或者模型拟合的分布(prediction)。在以上情况中,正确的分布是快乐的小明,目前的分布是悲伤的小明。

从机器学习的角度来考虑,假设小明想要训练一个模型进行猫狗的图片分类,在获得了一个自己比较满意的模型后,他兴冲冲地跑去找小红。小红把她家的茶杯犬往模型前面一放,模型认为这只生物有40%的概率是狗,60%的概率是猫(我们假设模型的输出是经过softmax的)。因为这是个分类模型,所以它最后告诉小红这是猫,小红气得当场就哭了。

小明沮丧地跑回家,继续迭代它的模型。

在小明继续他的工作之前,我们先简单总结一下损失函数需要具备的特点:

  • 首先,损失函数是一个能够计算距离的函数,它的输入应该是两个分布,输出应当是一个值,即这两个分布之间的距离(损失)。
  • 在某些情况下,损失函数被期许是一个凸函数:因为凸函数具有唯一的全局最优解,同时不存在局部最优解。换言之,如果你使用梯度下降来试图减少一个凸性损失函数的值,它必将带着你走到最终的全局解。
  • 在某些情况下,损失函数被期许对某些比较难分辨的类别具有更好的识别效果。换言之,对于类别不均衡的问题,这样的损失函数将提升模型在少数类样本上的学习效果。
  • 在某些情况下,损失函数被期许能同时考虑多个类别,而不仅仅计算对某一个类别的距离。例如模型能够识别出一个人既是男性,又是长者,又戴着眼镜,损失函数需要以某种方式聚合这些类的损失。

因此,在这篇blog中

预计

会出现的损失函数有以下几个:

  • 均方误差系列损失函数(MSE/RMSE/MAE/Smooth)
  • 适用于多分类问题的交叉熵(Cross Entropy)
  • 适用于多标签分类问题的交叉熵(Sigmoid with CE / BCE)
  • 适用于类别不平衡问题的交叉熵(Focal Loss)

本来想简单说一下模型的评价指标的(confusion matrix / $R^2$ 之类的东西),但是现在已经五点多了,一会儿还有IG的比赛,不知道能不能写到那时候,要不就先搁着吧。

小明的模型迭代之旅

1. 三天前的小明与逻辑回归

正如本科阶段的我一样,三天前的小明在听说机器学习和它的应用之后倍加振奋,似乎发现了振兴中华的秘诀。小明想,我是不是可以写一个分类的模型,来帮我判断一个女孩好不好追呢?

拍了拍脑袋,小明先提出了两个可能会影响女孩是否好追的因素:

  1. 身高
  2. 体重

这个模型应该输入一个女孩的身高$x_1$与体重$x_2$,然后输出这个女孩是不是容易追求$y$。这是小明的建模目标。

有了自变量与因变量,小明马上想到了数学老师昨天刚教的二元一次方程:如果我自己定义两个系数$\alpha_1$与$\alpha_2$,构建一个方程$y=\alpha_1x_1+\alpha_2x_2+\beta$,如果y的输出值大于0,我就认为这个女孩比较好追,否则就不好追,这不就是一个最简单的分类模型吗?这个模型简单又实用,可不能告诉别人。

BTW, 我没记错的话,这个模型应该叫 线性回归

小明输入了班级里几位女生的数据

??? 作为训练样本,兴致勃勃地手动标了她们的标签:对于好追的女孩,她的y值是1,不好追的女孩y值为0。现在这个模型应该输出一个0到1之间的值,来衡量一个女孩的好追程度。但是一个线性函数的因变量与自变量是正相关的,怎么才能 把一个线性函数的输出映射到01之间

呢?

一个简单的方式就是 在外面再套一个映射函数

给出前提:如果这个模型输出大于0的值,我们就认为模型将一个女孩预测为比较好追,如果这个值越趋向于正无穷,则这个女孩就越好追;反之亦然。一个sigmoid函数就能实现这个功能:

上式的$x$就是模型的输出。因变量$y$随着自变量$x$的变化会呈现下图的变化:

这是一个标准的逻辑回归模型,当模型输出0的时候,输出的取值正好是0.5,即这个女孩介于好追与不好追之间。此外,模型的设计者还能通过增加正则项的方式来手动改变模型的输出大小,即使用一个阈值来控制模型更倾向于将一个女生判断为好追型还是不好追型。

接下来就要设计一个损失函数,以衡量模型的输出结果距离正确值的距离。一个最简单的思路是,直接把模型预测值与正确值相减,取绝对值做累加,所获得的总和取平均就是模型在这组样本上的损失。在此预期下,该损失被称为 L1-norm ,也被称为 MAE (平均绝对误差),它的公式形式为:

其中$\hat{Y}$是模型的输出,$Y$是样本的真实值,样本数量为$m$个。和它一样憨的另一个可选损失函数叫 L2-norm ,也被称为 MSE (均方误差),其公式形式为:

两者的差异仅在于计算真实与预测的差值之后是否要取个平方,看起来似乎相差不大,但是在具体使用中,还要根据情况来选择使用哪一个损失函数:从直观上来讲,MSE的求值方式决定了它的值会更容易被异常点所影响。具体来说:

  • 如果模型的预测值与正确值是 完全一致 的,则相减为0,它不会给这两个损失函数指导下的学习过程带来任何损失;
  • 如果模型的预测值与正确值不一致,但是 相差值不超过1 ,平方之后这个值会被进一步缩小:由此看来MSE会倾向于缩小一个与正确值相差较小的预测值给整个模型带来的损失。
  • 如果模型的预测值与正确值不一致,并且 相差值超过了1 ,则这个差值在MSE中会被进一步放大,极端而言,如果一个离群点与正确值相差很远(当然距离肯定超过了1),即便最终的损失值取了均值,这个值仍会成为其中主要的损失来源。

在此分析基础上,我们可以得出一个结论:如果在模型训练过程中,更期望它能尝试把所有的点尽量分对,那可以选择MSE损失;而如果实际业务中允许忽略一部分离群点,不要求对整体的准确度,则可以选择MAE损失。

我们还能从另一个角度比较一下这两个函数:是否容易求导及求导后的梯度。这里部分使用了来自 知乎文章 此处的图片,以便直接说明(如有侵权,我会马上删掉)。

给出预测值变化下的损失变化图:

左边是MAE的预测值和误差值的变化关系,右边是MSE的变化。容易观察到,在学习过程中,MAE的导数是始终不变的,即在学习(梯度下降)过程中,无论预测值距离正确值的差距多大,每次的下降方向不会改变。如果我们选择一个固定的学习率,在预测值与正确值比较接近的情况下,很可能会发生振荡。而右图的MSE在预测值趋近与正确值时,它的方向也会趋近平缓,这样的MSE天然具有模型学习上的亲近性。而前者则需要设置动态的学习率。

最后也简单提一句 RMSE (均方根误差)的公式及使用:

和MSE相比,它在公式上只多了一个全局的根号,在名称上也只多了一个根号(R-ROOT)。在使用上,RMSE能够完全保留误差所具有的意义,在需要理解模型误差含义时能够被很好地使用。在好追程度模型上这个意义并不明晰,我们假设小明想要训练一个模型预测人的生存年龄。如果使用了RMSE,小明可以对小红说:我的模型的平均误差现在是6.3年;而如果使用MSE,小明对小红说:我的模型现在平均误差是39.69平方年,小红一想什么玩意儿,就不和小明继续玩了。

在图像处理领域也有一系列其他损失函数的提出,它们或多或少解决了上面所提到部分问题,我们走马观花一下:

FastRCNN 中使用的是 Smooth L1-loss 。与一般的L1-loss(MAE)而言,它对这个损失函数做了一个平滑操作,以使它在0点处的导数更加平滑,不会影响模型的收敛效果:

求导得到:

这个损失函数尽管被称为Smooth L1,实际上更像是对L1和L2的结合:在正负1区间内,使用L2的形式,使得导数平滑;在大于1区间,使用L1的形式,使得损失不至于因为少数离群点而呈现出指数爆炸的形式。

正则化 也是对两种loss进行魔改的一个好用的技巧。简单来说,我们在原本的loss基础上后面跟一个与模型复杂程度相关的正则项,这样能够有效防止因为模型设计得过于复杂,loss很低但在实际应用时却效果很差的过拟合现象。

2. 一天前的小明与卷积神经网络

在尝试了两天手撸模型之后,小明终于放弃了。他上网了解到scikit-learn上已经存在封装好的逻辑回归模型,只需要使用 LogisticRegression 来调用就好了,他顿时对实现这个模型感到兴致缺缺。另外,他也同时意识到一个很严重的问题:正如硬币落地前你就会知道你的选择一样,在标数据的时候,他已经对班级里的女孩做出了想要的筛选。 那我还要这个模型干嘛呢 ,小明这样想。

好追程度分类模型的构建无疾而终,小明觉得自己间接地导致了人类文明无法更进一步,心里十分愧疚,但他很快把注意力转移到了自己标注的数据上。在所有的女孩子里,小红是最不好追的那一个,同时身高与体重也十分符合自己的理想型。

在机器学习引领时代潮流的今天,小明觉得自己有必要搞一个有应用价值的模型来宣示自己的学习能力与未来潜力,他看中了一个toy dataset: MNIST。因为逻辑回归在炫技层面的价值实在不值一哂,而CNN family所代表的一系列图像分类模型能够直观地展现出模型的落地价值,他决定依葫芦画瓢,搞一个图像分类模型出来。

这次他已经充分了解到:使用pytorch或keras等深度学习框架,就能像搭乐高一样搭出一个想要的模型来。他写下以下几行:

import torch.nn as nn nn.Sequential(  nn.Conv2d(in_channels=3, out_channels=128, kernel_size=3)  nn.Relu(),  nn.Dropout(0.3),  nn.Linear(128, 2) )

乐高真好玩,他想。

这个模型旨在输入一个$32\times32\times3$的小图像,模型将会输出这个模型属于猫狗之间的哪个类。

又到了选择损失函数的时候,这次小明选择的是 Cross Entropy Loss ,中文称其为 交叉熵 损失。作为一个损失函数,交叉熵(along with Adam optimizer)是最不容易出问题的那种:它可以在多分类问题中被使用,也可以在多标签问题中使用,在特殊情况下也能够手动指定每个类别对最终损失所应该贡献的权重。

import torch.nn as nn loss = nn.CrossEntropyLoss()
炼金术士的生活,就是这么的枯燥,无聊,且乏味。

让我们把小明丢在一旁,仔细理解一下交叉熵的理论逻辑:

熵是信息论中的一个概念,用来衡量一种事物的混乱程度;在模型构建中,所对应的概念是随机变量的不确定程度。在建模过程中,我们往往需要衡量的是两个分布(正确的分布与预测得到的分布)之间的混乱程度,所以我们需要用到相对熵(relative entropy,也叫KL散度)。

怎么计算一个分布的熵呢,

Reference

CNN:MNIST讲交叉熵,模型改进讲权重

模型迭代Object Detection讲Focal Loss

模型迭代讲BCE

相关文章