做机器学习分类问题,难免遇到Biased-Data-Problem, 例如
对此,以下套路可以缓解:
今天要聊的就是一种针对该问题精心设计的loss函数——「Focal Loss」
先来回顾一下常用的 BinaryCrossEntropyLoss
公式如下
不难看出,CE是个“笨学生”。
考前复习的时候,「他不会划重点,对所有知识点 “一视同仁”」。
如果教科书上有100道例题,包括: 90道加减乘除 + 10道 三角函数。CE同学就会吭哧吭哧的“平均用力”反复练习这100道例题,结果可想而知——他会精通那90道个位数加减乘除题目,然后其他题目基本靠蒙。那10道他不会的题,往往还是分值高的压轴题。
嗯,大概就是这么个症状。
机智如你,想必已经有办法了 —— 「给他指个方向,别再“平均用力”就好了」
每个【科目】的难度是不同的;你要花 30%的精力在四则运算,70%的精力在三角函数。--- 老师告诉CE同学 第一个技巧
对应到公式中,就是针对每个类别赋予不同的权重,即下述
这是个简单粗暴有效的办法。
每道【题目】的难度是不同的;你要根据以往刷类似题时候的正确率来合理分配精力。 --- 老师告诉CE同学 第二个技巧
观察CE中的
,它反映了模型对这个样本的识别能力(即 “这个知识点掌握得有多好”);显然,对于
越大的样本,我们越要打压它对loss的贡献。
FL是这么定义的:
这里有个超参数
; 直观来看,
越大 打压越重。如下图所示:
, 纵轴是
时,FL退化成CE,即蓝色线条
很大时,线条逐步压低到绿色位置,即各样本对于总loss的贡献受到打压;中间靠右区段承压尤其明显
基于Keras实现
from keras import backend as K
def focal_loss(alpha=0.75, gamma=2.0):
""" 参考 https://blog.csdn.net/u011583927/article/details/90716942 """
def focal_loss_fixed(y_true, y_pred):
# y_true 是个一阶向量, 下式按照加号分为左右两部分
# 注意到 y_true的取值只能是 0或者1 (假设二分类问题),可以视为“掩码”
# 加号左边的 y_true*alpha 表示将 y_true中等于1的槽位置为标量 alpha
# 加号右边的 (ones-y_true)*(1-alpha) 则是将等于0的槽位置为 1-alpha
ones = K.ones_like(y_true)
alpha_t = y_true*alpha + (ones-y_true)*(1-alpha)
# 类似上面,y_true仍然视为 0/1 掩码
# 第1部分 `y_true*y_pred` 表示 将 y_true中为1的槽位置为 y_pred对应槽位的值
# 第2部分 `(ones-y_true)*(ones-y_pred)` 表示 将 y_true中为0的槽位置为 (1-y_pred)对应槽位的值
# 第3部分 K.epsilon() 避免后面 log(0) 溢出
p_t = y_true*y_pred + (ones-y_true)*(ones-y_pred) + K.epsilon()
# 就是公式的字面意思
focal_loss = -alpha_t * K.pow((ones-p_t),gamma) * K.log(p_t)
return focal_loss_fixed
model = ...
model.compile(..., loss=focal_loss(gamma=3, alpha=0.5))
反映了“方法一、分科复习”时,各科目的难度比率
sample_weight
概念,可以按照样本占比,适度加权相当于关掉该功能
反映了 “方法二、刷题战术”时,对于难度的区分程度
相当于关掉该功能; 即不考虑难度区别,一视同仁
越大,则越重视难度,即专注于比较困难的样本。建议在
范围尝试
根据类别加权
根据难度加权