4、评分分布分析:绘制评分分布直方图、分析好坏样本的分布差异
阈值调优这件事,说白了就是找那个「砍一刀」的位置。但这一刀往哪砍,不能拍脑袋。你得先看看你的评分长什么样,好人和坏人在评分轴上是怎么分布的。
我个人习惯,拿到一个新模型的第一件事,不是看AUC,不是看KS,而是先画分布图。为什么?因为分布图能告诉你很多「数字背后的故事」。我记得有一次,一个模型AUC高达0.85,但分布图一画出来,好样本的评分集中在700-750,坏样本集中在650-700,两个峰几乎挨在一起。这种模型你敢直接用吗?
4.1 为什么要做评分分布分析?
评分分布分析,核心就三个目的:
- 验证模型区分度:好样本和坏样本的评分分布是否明显分离?如果重叠严重,阈值怎么调都没用。
- 发现异常模式:比如评分出现双峰、长尾、或者某个区间样本量异常,都可能是模型或数据的问题。
- 指导阈值选择:通过观察好坏样本在不同评分段的占比变化,找到「性价比最高」的切分点。
核心原则:好的评分分布,应该是「好样本集中在高分段,坏样本集中在低分段」,中间有一个清晰的过渡带。如果这个过渡带太宽,或者两个分布完全混在一起,那模型本身就有问题。
4.2 绘制评分分布直方图
先上代码。我习惯用 matplotlib 配合 numpy 来做,简单直接。这里假设你已经有了评分数据和标签。
import numpy as np
import matplotlib.pyplot as plt
# 假设数据
# scores: 所有样本的评分(0-1000)
# labels: 0=好样本, 1=坏样本
scores = np.random.normal(650, 100, 10000) # 模拟数据
labels = np.random.binomial(1, 0.3, 10000) # 30%坏样本
# 分离好坏样本
good_scores = scores[labels == 0]
bad_scores = scores[labels == 1]
# 设置分箱
bins = np.arange(0, 1001, 20) # 每20分一个箱
plt.figure(figsize=(12, 6))
# 绘制直方图
plt.hist(good_scores, bins=bins, alpha=0.6, label='好样本', color='#2ecc71', density=True)
plt.hist(bad_scores, bins=bins, alpha=0.6, label='坏样本', color='#e74c3c', density=True)
plt.xlabel('评分')
plt.ylabel('密度')
plt.title('评分分布直方图(好坏样本对比)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
这里我用了 density=True,把频数转成了密度。为什么?因为好坏样本的数量通常不平衡,直接比频数没有意义。密度图能让你看到「在这个评分段,好样本和坏样本各自占自己群体的比例是多少」。
小技巧:分箱宽度很关键。太宽会丢失细节,太窄会噪声太大。我一般先试20分一箱,再根据样本量调整。如果样本量超过10万,可以缩到10分一箱。
4.3 分析分布差异的量化指标
光看图还不够,你得有数字。我常用的几个指标:
| 指标 | 计算公式 | 说明 |
|---|---|---|
| 均值差 | mean(good) - mean(bad) | 越大越好,一般要求>50分 |
| KS统计量 | max(|CDF_good - CDF_bad|) | 衡量最大分离程度,通常>0.3可接受 |
| 重叠系数 | ∫min(p_good, p_bad) dx | 越小越好,一般要求<0.3 |
| 分位数差 | Q90(good) - Q10(bad) | 衡量高分好样本与低分坏样本的间隔 |
代码实现也很简单:
from scipy.stats import ks_2samp
# 均值差
mean_diff = np.mean(good_scores) - np.mean(bad_scores)
print(f'均值差: {mean_diff:.2f}')
# KS统计量
ks_stat, p_value = ks_2samp(good_scores, bad_scores)
print(f'KS统计量: {ks_stat:.4f}')
# 重叠系数(近似计算)
hist_good, bin_edges = np.histogram(good_scores, bins=50, density=True)
hist_bad, _ = np.histogram(bad_scores, bins=bin_edges, density=True)
overlap = np.sum(np.minimum(hist_good, hist_bad)) * (bin_edges[1] - bin_edges[0])
print(f'重叠系数: {overlap:.4f}')
注意:KS统计量虽然常用,但它只关注「最大差距」这一个点。如果两个分布只在某个点分得很开,其他地方完全重叠,KS值可能很高,但实际区分能力很差。所以一定要结合重叠系数一起看。
4.4 知识体系:评分分布分析的核心逻辑
下面这张图,是我自己总结的评分分布分析框架。每次做阈值调优前,我都会过一遍这个流程。
4.5 实战中的坑与经验
做分布分析这么多年,踩过的坑不少。分享几个典型的:
- 样本量不足时别用直方图:我曾经有个项目,坏样本只有200多个,画出来的直方图全是锯齿。后来改用核密度估计(KDE),效果就好多了。代码里把
hist换成gaussian_kde就行。 - 注意评分尺度的偏移:有一次我发现好样本的分布整体右移了50分,一开始以为是模型变好了。后来一查,是线上特征工程出了bug,导致评分整体偏高。所以每次上线新模型,一定要和旧模型的分布做对比。
- 不要只看均值:两个分布均值差很大,不代表区分度好。我见过均值差80分,但两个分布的标准差都很大,重叠区域依然不小。一定要看分位数和重叠系数。
我的习惯:每次分析完分布,我会把下面几个数字记下来:好样本的P10/P50/P90、坏样本的P10/P50/P90、重叠系数、KS值。这些数字在后续调阈值时,都是重要的参考依据。
4.6 分布分析的进阶用法
如果你已经熟练了基础的分布分析,可以试试这几个进阶方向:
- 分群分布分析:按用户群(比如新客/老客、不同渠道)分别画分布图。你会发现,同一个模型在不同群体上的表现可能天差地别。
- 时间维度对比:把每个月的好/坏样本分布画在一起,观察分布是否稳定。如果某个月分布突然偏移,大概率是数据质量出了问题。
- 评分卡分段分析:把评分分成几个区间(比如每100分一段),计算每个区间的好/坏样本占比和坏账率。这个在后续做阈值调优时特别有用。
嗯,分布分析这块,说白了就是「先看后算,看图说话」。别急着调阈值,先把分布看清楚。分布对了,阈值调起来才有底气。