代价敏感学习初探 - 有偏损失函数设计
1. 代价敏感学习简介
0x1:如何将业务场景中对FP和FN损失的不同接受程度,调整我们的损失函数
1. 什么场景下需要代码敏感学习
在很多真实业务场景中,包括笔者所在的网络安全领域,误报造成的损失常常比漏报来的要大,原因很简单,如果一个IDS系统每天都在产生大量虚警,那么对事件响应处理人员的压力就会非常大,时间久了,大家对IDS的信任度就会下降,同时真实的有效告警也可能被淹没在海量的虚警中,反而导致更多和更严重的漏报。
但另一方面,可能有人会质疑说漏报的影响不是更恶劣吗?难道不应该秉着”宁可错杀一千,不可放过一个可疑“的方针吗?
根据笔者目前的从业经验来看,没有必要这样。一个好的做法是构建多层次的纵深检测体系,大白话就是在一个KILLCHAIN的每一个环节都有针对性地部署一个IDS,同时追求每个IDS的精确度,对于单个IDS来说,尽量少误报,对于整体系统来说,所有IDS综合起来构成了一个纵深体系,攻击者想要穿透这个体系而不引发任何的告警,就需要非常高超的技巧和小心翼翼的动作,而这有时候也反过来限制了攻击者所能做的动作,例如内网扫描这件事。
说完了代码敏感学习的应用场景,接下来的问题肯定是:代价敏感函数是什么?简单的回答是:代价敏感学习是在原始标准代价损失函数的基础上,增加了一些约束和权重条件,使得最终代价的数值计算朝向一个特定的方向偏置(bias),而这个偏置就是具体业务场景更关注的部分。
我们先来看一下常规的代价函数是如何计算的。
2. 常规损失函数的数值计算
一般来说,机器学习领域的检测分类算法所关注的仅仅是如何得到最高的正确率(acc),以2-class分类为例,我们可以使用一个二维矩阵来描述分类算法的预测结果,如下图所示:
表中的列表示实际数据所属类别,行表示分类算法的预测类别
不管使用了损失函数是何种形式,形式上,算法的预测错误的即是 FP 和 FN 两部分所表示,即:Loss = Loss(FP)+ Loss(FN)。
从损失函数的通用数学公式角度来看,损失函数的求导优化对 FP 和 FN 是没有任何偏置的。分类算法所关心的是如何使得 FP+FN 的值尽可能的小,从而获得较高的分类准确率。对于 FP、FN 两部分各自在错误实例中所占的比重,传统损失函数并没有加以任何的考虑和限制。
换句话说,传统损失函数实际上是建立在下述假设的基础之上的:
在所有情况下,分类算法做出 FN 判断和做出 FP 判断对于实际结果的影响因子是完全相同的。所以我们可以不对两种误判所占的比例加以约束。
3. 业务场景中的损失函数计算
然而在现实世界中,这一假设往往是并不成立的,在实际的业务场景中,FN 和 FN 所带来的实际损失往往是不同的。一个被广为接受的上述假设的反例就是银行借贷问题。
-
从机器学习算法的角度考虑,算法的分类结果应该使得错误分类率降到最低;
-
而在实际应用中,算法的分类结果应该保证分类结果给银行造成的损失较小;
在实际情况中,银行做出 FP 判断(即没有将贷款贷给符合条件的申请人),所造成的损失要远小于其做出 FN 判断(即将贷款贷给不符合条件的申请人所造成的损失的)
如果我们用下图来描述银行做出不同决定所造成的不同的代价的的cost matrix:
分类算法导致的实际损失等于:
Loss-real = FP * C01 + FN * C10
假设现在有两个分类已训练好的算法 A 和 B,对于 10 个贷款申请人进行分类。
我们令:
loss(FN)c10 = 10; loss(FP)c01 = 1;
若 A/B 算法的分类结果为下图
从数值计算的角度,A 算法的分类正确性要优于 B 算法:Acc(A)= 80% > Acc(B)= 60%
但是从实际业务场景角度,银行实际损失,A 算法的性能却不如 B 算法:Loss(A)= 20 > Loss(B)= 4
这就是 cost-sensitive 分类算法所关注和研究的问题。
0x2:代价敏感学习公式
利用 cost matrix,我们可以将 cost-sensitive 分类问题转化一个最优化问题,对于二分类分类算法来说只有2种判断结果,所以优化目标为让下式 L(x, i)达到最小值:
-
其中 x 是一个实例;
-
(x, i)表示将其分为 i 类;
- P(j | x) 表示在算法 A 中获得的 x 属于类别 j 的后验概率,这个概率越小,反之 P(i | x) 就越大,这就是求对偶问题的反面;
- c(i,j) 表示算法 A 将类别 i 的样本错判为类别 j 的实际损失(real-cost)
上述公式中,因为损失权重偏置因子c(i,j)的加入,使得我们的目标使得模型不再单纯关注如何获得 P(j|x)的最大值,而是要综合考虑预测结果P(j | x)以及不同预测结果造成的损失c(i,j)。这两个因子互相牵制。
1. Cost-sensitive中cost的定义与表示
通常情况下,我们使用 cost matrix 描述待分类数据集上的 cost 信息。Cost matrix C 是一个N × N的矩阵,其中 N 代表待分类数据集中的类别的数量。
C 中的条目 c(i, j)表示分类算法将实际属于 j 类得到实例分为 i 类所造成的 cost。当 i = j 时代表分类算法正确预测了实例的类别,而i ≠ j的条目对应于不正确的分类结果。
在对 c(i, j)进行赋值的过程中,一般会遵循合理性原则:
-
即错误分类的造成的 cost 肯定要大于正确分类造成的 cost;
-
cost matrix 中的 c(i, j) = 0 , when i = j,而对于其他错误的分类结果对应的条目 c(i, j), 我们赋其值为正数,用于表示其代价;
- 而具体FN和FP如何赋值,取决于具体业务场景。例如如果我们认为误报的损失大于漏报的损失,则赋值:cFN > cFP;
2. Cost-sensitive损失权重因子对损失函数的影响
从宏观角度来看,代价损失权重因子c(i, j)可以理解为一种先验知识。加入损失权重因子c(i, j)后,目标函数L(x,i)从最大似然估计转变为了最大后验估计。
笔者认为,cost sensitive的本质是“对不同的后验预测结果,的条件概率 P(y|x) 赋予不同的惩罚因子”。
在 2-class 分类中,cost-sensitive 分类算法将实例 x 分为 1 类的条件是当且仅当将 x 分为 1 类所造成的预期 cost,不大于将 x 分为 0 类的损失。即
当式子取等号时,这就是所谓的分类器最优决策面(auc曲线本身)。可以看到,cost matrix的不同取值,影响了这个分类器最优决策面的位置,具体如何分析这种影响,我们文章的后面会继续深入讨论。
0x3:代价敏感损失的泛化讨论
笔者希望和大家一起更深入思考一下,代价敏感损失的本质是什么。
虽然大量的paper上都提出了cost-sensitive是对不同的predict result给予不同的损失因子,这是离散的范畴。但是笔者认为这个概念可以继续延展到连续无限的范畴,即对对数几率回归损失(sigmoid)这种损失函数也同样起作用,sigmoid本质上也体现了cost sensitive的思想。
一个标准的sigmoid函数如下:
sigmoid函数的输出代表了对发生几率的预测,目标y为 {1,0},即发生于不发生,而sigmoid的输出介于【0,1】之间。
从函数曲线上可以很容易看出:
-
对于靠近【0,1】两侧的预测值,预测值和实际值的差值是比位于中间概率的差值要小的,因此这个区域中的1阶导数相对就较低,这进而导致了例如GD算法的最终损失较低;
-
而位于【0,1】中间位置的地方,1阶导数相对较高,进而导致最终损失较高;
通过这种函数特性,sigmoid函数会“驱使”模型将预测结果向靠近 0 或者靠近 1 的方向前进,或者说sigmoid函数不喜欢模糊地带,而偏爱(bias)确定性的结果。
0x4:Cost-Sensitive的几何意义
如果我们画出ROC曲线,横轴和纵轴分别是 acc 和 recall,可以现象,曲线是一个形如下图的曲线:
损失函数的极值点就是最终的决策分界面和ROC曲线的“切线点”(图中的绿点)。
我们可以做一个直观的想象:cost() 函数起到的作用是“拉伸”原始的ROC曲线,使其向acc或者recall的方向拉伸,拉伸的结果会导致超分界面“提早”和 acc 或者 recall 方面相切。
-
对误报的惩罚加大:cost()对FN的因子比例增大,则使roc曲线朝上图中 true positive rate 方向拉伸,即向上,则相切点会更靠下,最终的效果是获得更低的 true positive rate;
-
对漏报的惩罚加大:cost()对FP的因子比例增大,则使roc曲线朝上图中 false positive rate 方向拉伸,即向左,则相切点会更靠右,最终的效果是获得更低的 false positive rate;
以上的函数几何视角,启发我们一点:
cost-sensitive Loss Function 的作用本质上是通过“拉伸”损失函数的函数形式,进而影响模型的最优决策面。对 acc 和 recall 的不同权重因子,最终影响了 roc 曲线中向 acc 和 recall 方向的偏离程度。
当然,cost-sensitive只是影响了模型最优决策面的位置,最优决策面并不是最终的决策函数。
如果将auc函数看成是【x,y】坐标系的话,不同的x,y取值代表了最终决策不同的偏向,例如我们取x=0.1,即追求低误报,则相对的,y值肯定也低,即召回也低了。如果我们稍微放低一些对低误报的追求,则召回也能对应提高。在实际的tensorflow开发中,这通过对最终模型预测的p的threshold来实现。
那cost-sensitive在这里又扮演了什么角色呢?
简单来说可以这么理解,在相同的threshold前提下,误报敏感损失函数会使得模型获得更低的误报,漏报敏感损失函数会使得模型会的更低的漏报。
但是要始终牢记的是:最终的模型效果是两部分组成的,AUC函数的形式+认为设定的threshold决策策略。
0x5:实现cost sensitive思想的不同方式
解决 cost-sensitive 分类问题的核心在于解决的优化问题,即如何使得分类算法的结果能够获得有倾向性的L值。
目前,在如何获得有倾向性的分类算法这一问题上,目前有几种比较主流的方法:
1. Train set sample rescaling
通过有意调整,将训练数据集的正负例呈现不同数量比,以提高分类算法对于某些分类结果的敏感性。
例如如果想获得更高的acc,更低的误报,我们在训练集中,调整反例的数量是正例的数倍。相关的讨论,可以参阅另一篇blog。
2. Class membership probability - cost matrix reweighted
通过修改不同分类在算法中的 class membership probability,以获得具有一定数据倾向性的分类算法。这种算法被称为reweighted。这种方法也是本文主要讨论的。
0x6:代价敏感函数在不同机器学习算法中的应用
目前,分类决策树、神经网络、支持向量机(SVM)、boosting等常用学习算法都有其对应的代价敏感扩展算法。
各个方法在具体形式上各不相同,但其核心思想是一致的,这里以AdaCost为例举例说明。
AdaCost 算法由 AdaBoost 分类算法改进而来,也是一种通过 reweighted 方式获取 cost-sensitive 分类算法的方法。
AdaCost 的基本思想是使用若干个较弱的分类器以投票方式集成出一个分类器,各个分类器的权值由评价函数调整确定。在 AdaBoost 算法中,评价函数仅和算法的分类准确性相关。W.Fan 等人在 AdaBoost 的评价函数中引入了 cost 的元素,使得分类算法能够有效降低分类结果的 cost 值。
具体来说 AdaCost 算法添加了一个评价分类结果的 cost 性能的函数β:y × X × c → ℝ+,使得训练出来的弱分类器的权值集合能够符合 cost-sensitive 的要求。下面给出其伪码:
Relevant Link:
http://202.119.32.195/cache/4/03/lamda.nju.edu.cn/d2623fb33f624a2a05033bb5d0e23d45/qiny.pdf http://lamda.nju.edu.cn/huangsj/dm11/files/qiny.pdf https://homes.cs.washington.edu/~pedrod/papers/kdd99.pdf https://cling.csd.uwo.ca/papers/cost_sensitive.pdf
2. 在贝叶斯Bayes最优决策理论下,代价敏感损失函数设计准则
上个章节中,我们谈到了一个词,”分类器最优决策面“,这个东西是什么呢?这个章节我们来详细讨论。
0x1:贝叶斯最优决策理论
1. 贝叶斯最优分类(Bayes optimal classification)
对于二分类问题, 定义2级代价矩阵(cost matrix):
,其中是将b类样本预测为a类样本的分类代价。
据Bayes决策理论,最优决策的目标是最小化期望分类代价(最大后验概率估计),即给定样本x,若下式成立,则预测其类别为正;否则,预测其类别为负。
,其中 p(x)为后验概率,p(x) = P(y = +1|x),1 − p(x) = P(y = −1|x)。
上式的意思是:当,Loss(正例判对+负例误报为正例)<= Loss(正例漏报,负例判对)时,模型预测最终结果为正。
将上式移项后可重写为:
其中:
-
:表示正样本的分类代价;
-
:表示负样本的分类代价;
因此贝叶斯最优分类器输出的分类器函数为(这里不考虑人为设定的threshold决策函数):
因此,Bayes分类器也可写作:
当正负类别分类代价相等时,即c+ = c−,Bayes分类器退化为普通的无偏损失函数;而当c+和c-存在不对称偏置时,Bayes分类器也成为有偏损失函数。
2. 贝叶斯决策错误理论上界
cost matrix本质上是改变了Bayes分类器的损失理论上界。
0x2:代价敏感损失设计准则(Design criterions of cost-sensitive loss)
1. 准则一
代价敏感损失函数需满足Bayes一致性,即单调性。
新设计的代价敏感损失函数满足下述特性:
2. 准则二
对应的条件代价敏感风险在Bayes分类边界(分类器最优决策面)处取得最大值。
Bayes分类器的期望分类代价为:
在分类边界处,将样本x判为正、负类别的分类代价相等并达到最大值,此时最难预测样本类别。
Relevant Link:
https://www.cnblogs.com/Determined22/p/6347778.html http://jcta.cnjournals.com/cta_cn/ch/reader/view_abstract.aspx?doi=10.7641/CTA.2015.40519 https://www.bilibili.com/read/cv112928/ https://www.zhihu.com/question/27670909 《统计决策论及贝叶斯分析》- J.O.伯杰
3. 代价敏感损失函数的函数特性分析
这个章节,我们来真正进入代价敏感函数的内部原理,探究一下cost matrix是如何影响原始损失函数的形态,进而影响原始损失函数的性质的。
0x1:原始无偏损失函数函数性质
我们前面说过,当正负类别分类代价相等时,即c+ = c−,Bayes分类器退化为普通的无偏损失函数;而当c+和c-存在不对称偏置时,Bayes分类器也成为有偏损失函数。
所以这里先来看下我们熟悉的原始无偏损失函数的函数性质。
具体探讨以下损失函数:平方损失、指数损失、对数损失、SVM损失。这几种损失函数均可表示为间隔yF(x)的函数, 即,如下图所示:
下标给出了各个损失函数对应最优决策函数及其条件风险:
下图给出了各个损失函数的最优解和最优解处的条件风险:
可以看到,在无偏损失情况下,与贝叶斯分类器等价,最优解处的条件风险与最小分类误差一致,即在p(x) = 1/2 处取得最大值。
这和我们的认识是一致的,怎么理解呢?简单来说可以这么理解。
如果我们使用逻辑斯蒂回归训练一个二分类器,那么我们可以设定预测的阈值p为0.5,当>=0.5时,预测结果为正例,当<0.5时预测结果为负例。这是最优的阈值,任何偏离0.5的阈值p都会增加损失:
-
决策阈值向0.5左偏:整体损失的增加主要来自误报增加。
-
决策阈值向0.5右偏:整体损失的增加主要来自漏报增加,当然在某些情况下,漏报可能是我们可以接受的,因为这换取了一定的误报降低。
讨论完了原始的无偏损失函数,接下来我们要开始讨论有偏的代价损失函数,现有算法有以下两类常用分类代价引入策略:
1) 分类代价在损失函数外:cyL(yF(x)); 2) 分类代价在损 函数内:L(ycyF(x));
0x2:分类代价在损失函数外(Classification cost outside of loss function)
这种类型的代价敏感损失将分类代价与原始损失相乘。
下表给出了各个代价敏感损失此时的最优决策和最优决策处的条件代价敏感风险。
我们取c+ = 1.5,c− = 0.5这种偏置组合,此时,Bayes分类器为
绘制各个损失函数的最优解和最优解处的条件风险:
可以看出,4种代价敏感损失均满足准则1,即最优分类器为Bayes分类器。
另一方面,除代价敏感SVM损失外,其余损失均不满足准则2, 最优解处的条件代价敏感风险没有在Bayes分类边界处取得最大值,而是有不同程度的偏移。
0x3:分类代价在损失函数内(Classification cost inside of loss function)
这种类型的代价敏感损失将分类代价引入损失函数内部,将其与判决函数F相乘。
下表列出了各个代价敏感损失此时的最优决策和最优决策处的条件代价敏感风险
我们取c+ = 1.5,c− = 0.5这种偏置组合,绘制各个损失函数的最优解和最优解处的条件风险:
可看出,4种代价敏感损失均满足准则1和准则2,即最优分类器为Bayes分类器,最优解处的条件代价敏感风险在Bayes分类边界处取得最大值。
这种损失偏置条件下,模型的漏报会倾向于低漏报。
综上,虽然这4种损失函数代价敏感化之后并未都严格满足准则1/2,但是总体上,cost sensitive实际上扭曲了原始损失函数的函数曲线,使其发生了偏置。
Relevant Link:
http://jcta.cnjournals.com/cta_cn/ch/reader/create_pdf.aspx?file_no=CCTA140519&flag=1&journal_id=cta_cn&year_id=2015
4. 在Keras中开发适合业务场景的代价敏感函数
在具体工程项目中,因为TensorFlow/Keras并没提供原生的代价敏感函数,我们可以通过继承和重写原有的损失函数的方式,对原始的经典损失函数进行改造(函数内/函数外)
# 方式一 def vae_loss(x, x_decoded_mean): xent_loss = objectives.binary_crossentropy(x, x_decoded_mean) kl_loss = - 0.5 * K.mean(1 + z_log_sigma - K.square(z_mean) - K.exp(z_log_sigma), axis=-1) return xent_loss + kl_loss vae.compile(optimizer='rmsprop', loss=vae_loss)
Relevant Link:
https://stackoverflow.com/questions/45961428/make-a-custom-loss-function-in-keras https://blog.csdn.net/A_a_ron/article/details/79050204 https://blog.csdn.net/xiaojiajia007/article/details/73274669