美文网首页数据挖掘
评分卡类模型的概率对齐和分数映射,代码实现

评分卡类模型的概率对齐和分数映射,代码实现

作者: xiaogp | 来源:发表于2020-12-22 19:19 被阅读0次

摘要:评分卡机器学习

为什么要做概率对齐和分数映射

一般的机器学习二分类问题输出为概率在0-1之间的小数值,概率对齐指将模型输出的概率和真实的事件概率进行比对和对齐,尤其对一些极端模型概率进行修正,分数映射指的对模型的概率输出再做一层变换使结果更加契合业务理解和分析,一般是线性映射不影响排序不影响分布


需要对模型概率值做二次加工的原因:

  • 概率对齐:
    (1) 评分卡模型的输出要好看,不论得分高低都需要输出给用户体验,区别于一般的机器学习模型只需要一刀切取阈值以上并且一视同仁,评分卡的模型每一个输入数据的预测输出都需要合理,需要连续稠密饱满而不能极高极低过于离散
    (2) 训练样本和实际样本的标签分布差异较大。一般评分卡都是对好坏客户的预测,坏客户的自然比率可能只有几个百分点,但是机器学习模型都要求样本的正负比率接近1:1,而测试和应用用真实比率,这必然导致实际应用过程中概率失真,虽然不影响结果的排序
  • 分数映射
    (1) 分数映射主要是处于业务考虑,主要来修正分数的上下限以及分数的间隔的大小,不影响分数的分布,目的是将分数映射到一个业务需要的范围,并且将分数的上下浮动和概率的成倍变化联系起来,增加一定的可解释性和比较依据

概率对齐

概率对齐参考度小满的一篇分享


概率标准化.png

思路是直接对模型的预测坏概率和真实坏比率在做一次拟合,几个细节:

  • 排序并分段:理论上拟合的数据是一对一对的模型概率和真实比率,分段的目的是平滑,先排序将相邻的进行分段,每一段各自的均值形成一对拟合样本,防止某个点的单个类别的占比太大
  • 概率取logit:此处将概率转化为log(odds)ln(p/(1-p)),评分卡出现了,不管什么算法在这一步全部手动转化为odds做对齐,以为下一步做分数映射做铺垫,换句话说如果本身模型就能输出odds就可以直接使用跳过此步
  • 尝试一次或二次拟合:个人建议至少二次拟合,一次拟合相当于还是线性映射,确实能修正到真实odds,但是对修正极值几乎没有效果,二次拟合才能其他改变分数形状的能力,由于下一步分数映射已经确定是线性映射,因此此步骤应该用至少二次拟合

代码实现

模型使用逻辑回归,并且弃用框架的predict方法,将模型的权重和偏置拿出来写入配置,预测直接wx+b得到log(odds)也就是预测概率的logit,先写一个函数实现wx+b输出log(odds),其中params和feature分别是字段为key,权重和特征值为value的字典

def get_odds(feature):
    res = 0
    for i in feature_columns:
        res += params[i] * feature[i]
    res += params["intercept"]
    return res

取测试集,测试集的正负样本比例应该等于自然比例,输出模型预测的logit和实际的label,这份测试集的质量非常重要,因为需要基于他做事实的拟合

test = pd.read_csv("data/tmp_test.txt")
res6 = []
for i in range(len(test)):
    score = [get_odds(test.loc[i].to_dict()), test.loc[i]["标签"]]
    res6.append(score)

下一步通过模型预测logit和实际label的结果计算每个测试点的实际坏率,可以采用等频分箱

# 等频分箱
bin_num = int(len(res6) / 100)  # 分为100箱
bin_data = []
pair_data = []
# 根据模型预测输出排序
for line in sorted(res6, key=lambda x: x[0], reverse=True):
    bin_data.append(line)
    if len(bin_data) == bin_num:
        # 计算平均预测值
        mean_predict = np.mean([x[0] for x in bin_data])
        # 真实坏率
        bad = len([x for x in bin_data if x[1] == 1])
        good = len([x for x in bin_data if x[1] == 0])
        actual_odds = np.log(bad / good)
        pair_data.append([mean_predict, actual_odds])
        bin_data.clear()

也可以采用等距分箱

# 等距分箱
max_predict = max([x[0] for x in res6])
min_predict = min([x[0] for x in res6])
bin = (max_predict - min_predict) / 30  # 等距离分为30箱
pair_data = []
for i in range(30):
    bin_start = min_predict + i * bin
    bin_end = bin_start + bin
    bin_data = [x for x in res6 if bin_start <= x[0] < bin_end]
    # 平均预测值
    mean_predict = np.mean([x[0] for x in bin_data])
    # 真实坏率
    bad = len([x for x in bin_data if x[1] == 1 ])
    good = len([x for x in bin_data if x[1] == 0])
    actual_odds = np.log(bad / good)
    pair_data.append([mean_predict, actual_odds])

以上两种分箱方法都是为了平滑,需要注意某分箱可能存在分子或者分母为0的情况,需要剔除或者调整分箱或者其他处理,本例中采用另一种即不做分箱不做平滑处理,每个点都生成一对模型结果和实际坏率进行拟合,但是对每个点阈值以上数据量有数据要求,如果数据量太小不作为拟合样本,代码如下

# 每个点都拟合
res7 = []
count = 0
for score, label in res6:
    count += 1
    threshold_data = [x for x in res6 if x[0] >= score]
    # 限制数据量
    if len(threshold_data) >= 50:
        bad = len([x for x in threshold_data if x[1] == 1])
        good = len([x for x in threshold_data if x[1] == 0])
        # 限制都非0
        if bad and good:
            odds = np.log(bad / good)
        res7.append([score, odds])

以模型的输出logit作为x,实际坏率的logit(odds)作为y,做散点图,观察两者的关系

x = []
y = []
for line in res7:
    if -6.5 <= line[0] <= 6.5:
        x.append(line[0])
        y.append(line[1])
plt.scatter(x, y, s=2)
plt.show()
模型概率和实际坏率的关系.png
  • 整体呈线性,可见模型的预测效果不错如果只看排序模型的结果可以直接使用
  • 两边呈现向水平方向收敛,可见此时模型的预测结果在走向极值,但是实际坏率并没有变极端而是趋于不再变化,因此至少要对模型输出的极大极小点进行对齐修正
  • logit(odds)失真,横轴是模型结果在[-6, 6]之间,纵轴是真实比例的坏率结果在[-4.5, 0.5]之间,因此需要拟合修正,另一方面修正后的odds并不对称,因此分数映射的基础分可能不是中间分

进行拟合,尝试多次后使用三次拟合

# 拟合三次多项式
np.plotfit(x, y, 2)  # 系数 array([-0.00282885, 0.00503271, 0.50497722, -2.06355245])
p = np.plot1d(np.plotfit(x, y, 2))

查看拟合曲线和之前的散点图,可以看到拟合了线性关系,将概率修正到真实坏率,并且在头尾处有修正

plt.scatter(x, y, s=2)
plt.scatter(x, [p(i) for i in x], s=2)
plt.show()
拟合图.png

获得系数后编写一个函数获得修正后的log(odds)

plot_params = [-0.00282885, 0.00503271, 0.50497722, -2.06355245]
def get_plotfit_odds(x):
    # 把原输出修正到[-9, 9],防止三次函数作怪
    if x < -9:
        x = -9
    if x > 9:
        x = 9
    return polt_params[0] * x ** 3 + poly_params[1] * x ** 2 + poly_params[2] * x + poly_params[3]

测试修正之后的logit(odds)

get_plotfit_odds(5)  # 0.2335451499
get_plotfit_odds(-1)  # -2.56066811
get_plotfit_odds(3)  # -0.57970535

分数映射

完成了第一步分数对齐,下一步分数映射就比较固定了,此步骤是一个线性映射,即确定w和b,度小满给出的公式中:

  • b=400代表基础分,可以理解为中间分,平均水平,但是当映射之前logit(odds)不两边对称时可能这个值不能是中间分
  • w= -35 / ln2,35代表每提升35分好坏比数比上升为原来的2倍,基本等同于风险上下降2倍负号的原因是分数越低坏率越高,而后面的因子中的p是坏率,后面的因子越大坏率越大,如果希望分数越高坏率越高,不需要负号
  • 分数的总区间在[0, 800],这就要求logit(odds)必须在[-8, 8]之间否则就会出现负分,一般而言logit(odds)不会超过这两个界限,但是为了分数异常还需要对天花板做一下限制

编写分数映射函数,此处期望分数在[0, 100]之间,并且分数越大风险越高,由于在上一步已知修正后的logit(odds)在[-4.5, 1]之间,所以计算可得要把分数放到100的区间,w=100 / (4.5 + 1) = 18,此时b=80差不多可以把100分填满,分数上升18分风险变为原来的2倍

def get_score(feature, w=18, b=80):
    odds = get_plotfit_odds(get_od ds(feature))
    score = int(round(w * odds + b, 1))
    if score > 100:
        score = 100
    if score < 0:
        score = 0
    return score

这个函数将模型预测,概率对齐,分数映射,异常修正全部包装在一起,现在应用到test数据集中查看分数结果

res4 = []
for i in range(len(test)):
    score1 = get_odds(test.loc[i].to_dict())  # 模型输出
    score2 = get_plotfit_odds(score1)  # 概率对齐
    score3 = get_score(test.loc[i].to_dict())  # 分数映射
    res4.append([score1, score2, score3])

查看原始概率的分布

plt.hist([x[0] for x in res4])
plt.show()
原始模型输出.png

查看概率对齐+分数映射后的最终得分

plt.hist([x[-1] for x in res4])
plt.show()
分数二次加工后的最终得分.png

相关文章

  • 评分卡类模型的概率对齐和分数映射,代码实现

    摘要:评分卡,机器学习 为什么要做概率对齐和分数映射 一般的机器学习二分类问题输出为概率在0-1之间的小数值,概率...

  • 【金融风控】信用评分卡模型

    本文转载自公众号:大数据风控与机器学习 一、简介 所谓信贷评分卡指的是,将违约概率映射成得分的模型。最传统、应用最...

  • 评分卡模型

    (一)评分卡模型简介 1.1 信贷场景评分卡模型介绍 (1)以分数的形式来衡量风险几率的一种手段 (2)是对未来一...

  • 风控建模流程

    以下以申请评分卡(A卡)建模为例,描述模型开发的基本流程:1.数据准备:定义目标变量,整合样本特征;划分数...

  • 申请评分卡目录

    申请评分卡(1)——相关概念申请评分卡(2)——评分卡模型开发申请评分卡(3)——建模(R)

  • MyBatis的逆向工程

    逆向工程:从代码和成UML图形;是通过从特定实现语言的映射而把代码转换为模型的过程。 MyBatis逆向工程创建 ...

  • 评分卡模型的评分标准

    模型的区分度 模型的准确度 模型的稳定性 从概率到分数

  • Hibernate映射关系

    映射组成关系 建立域模型和关系型数据库模型有着不同的出发点域模型:由程序代码组成,通过细化持久化类的粒度可以提高代...

  • 评分卡模型

    信用评分卡分类A卡:申请评分卡,侧重贷前,在客户获取期,建立信用风险评分,预测客户带来违约风险的概率大小;B卡:行...

  • 请使用Js代码写出一个类继承的模型

    // 请使用Js代码写出一个类继承的模型// 请使用Js代码写出一个类继承的模型,需包含以下实现:// 定...

网友评论

    本文标题:评分卡类模型的概率对齐和分数映射,代码实现

    本文链接:https://www.haomeiwen.com/subject/bzosnktx.html