资讯动态

风控模型评估指标解析:GINI、AUC与CAP的关系与计算

发布时间:2026/10/14 8:38:53 来源:尧图企业网站定制
做风控模型这行绕不开三个英文缩写GINI、CAP、AUC。我入行第一周翻前辈留下的模型评审报告满屏都是这三样当时对着公式勉强能读但真正想明白“为什么GINI恒等于2倍AUC减1”“CAP曲线跟ROC曲线到底差在哪”是在自己造了一批数据、从零手算一遍之后。这篇文章就把这套评估体系的来龙去脉完整讲清楚。内容包括三个指标的统计含义与业务解读、GINI / AR / AUC 之间的精确换算关系、用 Python 从零计算与绘图的完整代码以及我在真实建模和评审中踩过的坑。适合刚接触信用评分卡、反欺诈模型、营销响应模型的同学也适合模型报告写得出来、但被业务方追问一句“这个GINI到底在说什么”就卡壳的朋友。1. 三个指标各自是什么从信贷业务场景建立第一印象1.1 AUC衡量排序能力的一把总评分AUC 全称 Area Under the ROC Curve中文叫 ROC 曲线下面积。二分类模型评估的本质不是看预测准不准而是看模型能不能把“坏客户”和“好客户”分开——信贷行业里就是违约户和正常户反欺诈场景里就是欺诈交易和正常交易。模型输出的是一个分数或概率分数越高预测的坏的概率越大。AUC 的统计定义非常干净随机抽取一个实际为坏的样本和一个实际为好的样本坏样本得分高于好样本的概率。这句话是理解一切的关键。打个比方。假设你有个面试打分模型满分 100。现在从最终表现优秀的候选人里随机抽一个 A从表现差的候选人里随机抽一个 B模型给 A 打的分数比 B 高的概率就是 AUC。这个概率是 0.9说明模型十次有九次能分对是 0.5就跟抛硬币没区别。ROC 曲线的画法不复杂把分数从高到低逐个设为阈值每个阈值下计算两个量——真正率 TPR坏人里被正确抓出来的比例和假正率 FPR好人里被误伤的比例。以 FPR 为横轴、TPR 为纵轴描点连线就是 ROC 曲线。曲线越往左上角凸模型区分能力越强AUC 越接近 1。实务中AUC 的取值区间通常这样解读AUC业务含义0.5随机水平没有区分度0.6~0.7偏弱勉强可用0.7~0.8合格线信贷建模的常见区间0.8~0.9优秀区分度很好0.9 以上极好但需要警惕大概率有数据泄漏或过拟合1.2 GINI 系数洛伦兹曲线搬进风控GINI 系数最早是经济学家用来衡量收入分配公平程度的。把人口按收入从低到高排序横轴是人口累计百分比纵轴是收入累计百分比画出来的曲线叫洛伦兹曲线。曲线越偏离 45 度对角线说明收入越不平均GINI 越大。风控直接借用了这个框架。把客户按模型预测的风险从低到高排序横轴是客户累计占比纵轴是坏客户累计占比画出来也是一条洛伦兹曲线。模型区分能力越强坏客户越快被集中揪出来曲线鼓得越厉害。GINI 系数就是描述这条曲线“鼓起来”程度的数值。用行业惯例直接给结论GINI 2 × AUC − 1。这个等式不是近似是精确关系第 2 节我会把推导过程完整放出来。按这个换算AUC 0.7 对应 GINI 0.4AUC 0.8 对应 GINI 0.6。信贷建模里 GINI 在 0.4 到 0.6 之间属于常见合格水平0.6 以上算优秀跟 AUC 的解读完全对应。1.3 CAP 累计准确度曲线给业务方看的那张成绩单CAP 全称 Cumulative Accuracy Profile中文叫累计准确度曲线有些文档里也叫 Power Curve 或提升曲线。它和 GINI 其实是同一套数据画出来的同一张图业界习惯把图叫 CAP 曲线把曲线提炼出来的数值叫 ARAccuracy Ratio准确率比值。画法同样简单全量客户按模型分数从高到低排序看“前百分之多少的客户里累计覆盖了多少比例的坏客户”。横轴累计客户占比纵轴累计坏客户占比起点是 (0,0)终点必须是 (1,1)。曲线冲得越快越高说明模型越能精准命中坏人。图上通常会画两条参考线。一条是 45 度对角线代表随机模型——不看模型乱抓人抓到的坏客户比例跟抓的人比例完全一致。另一条是完美模型线——模型把坏客户全部排在最前面曲线先竖着冲到坏率对应的位置再横着走到 (1,1)。模型曲线夹在两条参考线之间越靠左上越强。把 CAP 曲线下面积扣掉随机面积再除以完美面积扣掉随机面积得到的比值就是 AR。而 AR 在数值上严格等于 GINI也就是 2AUC − 1。所以你在不同地方看到 GINI、AR、2AUC−1本质上说的是同一个东西。2. 数学关系拆解为什么 GINI 恒等于 2AUC 减 12.1 从 AUC 的概率解释说起AUC 等于坏样本得分高于好样本的概率这个结论可以用秩统计量严格证明我在这里只讲直觉。把坏样本和好样本两两配对成组如果模型排序是随机的坏样本有 50% 的概率排在好样本前面如果模型是完美的这个概率变成 100%。所以 AUC 天然落在 0.5 到 1 之间0.5 是下限1 是上限。实际建模中偶尔会遇到 AUC 低于 0.5 的情况最常见的原因是模型方向搞反了——得分越高反而越是好客户。这时候不需要急着否定模型把打分方向反过来新分数 负的旧分数AUC 就能回到 0.5 以上。这一点也解释了为什么大家都把 0.5 当作分界线而不是 0。2.2 从 CAP 面积推导 AR 与 AUC 的关系这一步是整个体系里最有意思的部分。记坏样本占比为 p模型在某个阈值下的 TPR 为 yFPR 为 x。CAP 曲线的横轴是“全量客户累计占比”它跟 ROC 的横轴 FPR 不是一回事两者满足全量累计占比 (1 − p) × FPR p × TPR也就是说CAP 曲线的横轴是从 ROC 坐标做了一个加权变换得到的。因此 CAP 曲线下面积 S 可以写成沿曲线积分的形式S ∫ y d[(1−p)x py]拆开之后这个积分等于 (1−p) 乘以 ROC 曲线下面积 AUC加上 p 乘以 ∫ y dy。后者从 0 积到 1结果恒等于 1/2。所以S (1 − p) × AUC p / 2接下来算 AR。随机的 CAP 曲线是对角线面积是 1/2完美的 CAP 曲线先竖直冲到 (p, 1) 再横着到 (1, 1)面积是 1 − p/2。AR 的定义是模型面积扣随机面积再除以完美面积扣随机面积AR (S − 1/2) / (1 − p/2 − 1/2)把上面的 S 代进去化简后 (1−p) 正好被约掉得到AR 2 × AUC − 1注意这个结果跟坏样本率 p 完全无关。这说明不管正负样本有多么不均衡只要 AUC 稳定AR 就稳定。也正因为这样行业里才放心大胆地把 CAP 曲线和 ROC 曲线当作两种等价表示来用。2.3 三种指标互相验证的实操习惯既然 GINI、AR、2AUC−1 是同一个人穿了不同马甲我建议你在建模脚本里把这几个数全打印出来。不是多此一举而是留一个自检手段如果代码里算出的 AR 和 2AUC−1 对不上说明积分程序、排序方向或者标签映射一定有一个地方写错了。这个方法救过我很多次。有一次我本地跑出的 GINI 是 0.52但 AR 算出来 0.37数据明显对不上。排查后发现是 CAP 曲线累积坏客户时少了 sort_values 这步数据没排序曲线退化成一条接近对角线的乱序折线。自检公式一秒钟就暴露了 bug比对着图表肉眼找可高效多了。3. 实操用 Python 从零手算 AUC、GINI 和 CAP3.1 构造模拟数据先搞定标签和分数方向真实信贷数据通常拿不到学习阶段造一份模拟数据足够了。思路是先生成坏客户标签再按标签叠加一个分数增量让坏客户整体得分偏高模拟出一个有区分度的模型输出。import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.metrics import roc_auc_score, roc_curve, auc np.random.seed(2024) n 20000 p_bad 0.08 # 生成标签8%的坏客户 is_bad np.random.binomial(1, p_bad, n) # 模型分数坏客户均值偏高加一点噪声更像真实分数 score np.random.normal(3, 1, n) 2.0 * is_bad np.random.normal(0, 0.5, n) sigmoid_score 1 / (1 np.exp(-score)) df pd.DataFrame({score: sigmoid_score, is_bad: is_bad}) print(df.head()) print(坏样本率:, round(df[is_bad].mean(), 4))注意分数方向这里分数越大表示坏的概率越高后续所有排序都必须按分数从高到低。如果建模时分数含义是“客户质量分”分数越大越好那排序方向要反过来用分数从低到高。方向反了GINI 就是负数这个坑第 5 节会专门讲。3.2 手写 AUC 和 GINI再和 sklearn 对照先直接用 sklearn 算 AUC再手写一遍。手写方案用的是秩方法正好对应 AUC 的“坏样本得分高于好样本概率”定义。from scipy.stats import rankdata # sklearn 结果 auc_sk roc_auc_score(df[is_bad], df[score]) print(sklearn AUC:, round(auc_sk, 4)) # 手写U统计量 r rankdata(df[score]) n_pos df[is_bad].sum() n_neg n - n_pos auc_manual (r[df[is_bad] 1].sum() - n_pos * (n_pos 1) / 2) / (n_pos * n_neg) print(手写 AUC:, round(auc_manual, 4)) # GINI gini 2 * auc_sk - 1 print(GINI:, round(gini, 4))秩方法的原理不复杂把所有样本按分数从小到大赋予排名把所有坏样本的排名加总减去坏样本内部排名的理论上限剩下的值再除以好坏样本对的组合数得到的就是“坏样本排在好样本前面”的占比即 AUC。两个结果一般在小数点后十几位才会出现浮点差异可以直接互相验证。3.3 绘制 CAP 曲线并计算 ARCAP 曲线的核心是排序后做两个累计累计客户占比和累计坏客户占比。画图时要补上原点 (0,0)否则积分面积会少一块三角区域。df_sorted df.sort_values(score, ascendingFalse).reset_index(dropTrue) df_sorted[cum_pop] (df_sorted.index 1) / len(df_sorted) df_sorted[cum_bad] df_sorted[is_bad].cumsum() / df_sorted[is_bad].sum() x np.concatenate([[0], df_sorted[cum_pop].values]) y np.concatenate([[0], df_sorted[cum_bad].values]) # CAP曲线下面积 cap_auc auc(x, y) # 参考线面积 bad_rate df[is_bad].mean() random_area 0.5 perfect_area 1 - bad_rate / 2 # AR GINI ar (cap_auc - random_area) / (perfect_area - random_area) print(CAP面积:, round(cap_auc, 4)) print(AR/GINI:, round(ar, 4)) # 画图 fig, ax plt.subplots(figsize(6, 6)) ax.plot(x, y, lw2, labelModel CAP) ax.plot([0, 1], [0, 1], k--, lw1, labelRandom) ax.plot([0, bad_rate, 1], [0, 1, 1], r--, lw1, labelPerfect) ax.set_xlabel(Cumulative population proportion) ax.set_ylabel(Cumulative bad proportion) ax.set_title(fCAP Curve (AR{ar:.3f})) ax.legend() plt.show()跑完这组代码你会看到 sklearn 算出的 AUC 转成 GINI 后跟 CAP 曲线算出的 AR 小数点后前四位完全一致。这个自检通过就说明你的曲线和积分代码没问题后面怎么用都放心。3.4 封装成一个一键评估函数项目里不可能每次都重新写一遍。我习惯把上面这些逻辑封装成一个函数输入标签和分数直接输出 AUC、GINI、AR并画好 CAP 和 ROC 两张图。伪代码如下可以根据自己项目改。def model_eval_report(y_true, y_score, plotTrue): auc_value roc_auc_score(y_true, y_score) gini 2 * auc_value - 1 df pd.DataFrame({y: y_true, score: y_score}) df_sorted df.sort_values(score, ascendingFalse).reset_index(dropTrue) df_sorted[cum_pop] (df_sorted.index 1) / len(df_sorted) df_sorted[cum_bad] df_sorted[y].cumsum() / df_sorted[y].sum() x np.concatenate([[0], df_sorted[cum_pop].values]) y np.concatenate([[0], df_sorted[cum_bad].values]) cap_auc auc(x, y) bad_rate df[y].mean() ar (cap_auc - 0.5) / (1 - bad_rate / 2 - 0.5) if plot: # 这里画ROCCAP双拼图 pass return {auc: round(auc_value, 4), gini: round(gini, 4), ar: round(ar, 4)}输出里加上一个断言也很重要abs(gini − ar) 大于 1e-6 就报警告提醒检查代码。这个小习惯能省掉大量排查时间。4. 指标体系在建模和监控中的落地用法4.1 建模选型区分度不是唯一标准很多新人选模型只看 GINI 高不高这是典型的误区。GINI 描述的是排序能力完全没回答“分数校准得好不好”和“稳定性好不好”。一个 GINI 0.6 的模型如果分数分布跟实际坏率严重不对齐风控额度策略根本没法用一个 GINI 0.55 但很稳定的模型反而可能让业务少操很多心。我的经验是建模选型至少同时看三组东西第一组是区分度AUC/GINI/KS第二组是校准度预测概率和实际频率的校准曲线、Brier 分数第三组是稳定性跨时间样本上 GINI 的衰减幅度和 PSI。三组都过关模型才能进评审。训练集、验证集、OOT 样本上的 GINI 一定要放在一张表里对比。常见经验值是验证集与训练集 GINI 相对衰减控制在 10% 以内OOT 上允许再放宽些。如果训练集 0.75、OOT 直接掉到 0.3这不是模型不行是你选的特征里带了时间漂移上线很容易翻车。4.2 上线后监控GINI 的衰减趋势比绝对值更关键模型上线不是终点。评分卡模型通常要求按月或者按季度监控 GINI 和 CAP 曲线观察区分度是否在衰减。单体看某个月的 GINI 绝对值意义有限核心是趋势连续三个月下滑或者某个月跌破预设阈值就要触发预警。我见过一个零售信贷项目上线头三个月 GINI 一直稳定在 0.45 附近第四个月突然掉到 0.31。翻日志发现是外部数据源换了一个版本早期特征分布全变了。如果没有月度 GINI 监控这个问题至少还要两个月才会被投诉暴露出来。做监控报表时CAP 曲线比 ROC 曲线更适合放进管理层报告因为横轴“累计客户占比”业务方容易理解可以直接指着图说“我们评分最高的 20% 客户覆盖了 70% 的坏账”。而 ROC 的横轴 FPR 对非技术同学来说要绕一层沟通成本高不少。4.3 和 KS、PSI 这些指标怎么分工配合除了 GINI 和 AUC风控建模里还常用 KSKolmogorov-Smirnov和 PSIPopulation Stability Index。这四个指标容易混我用一句话分别定位GINI 是全局排序能力的面积型指标KS 是排序能力最大截点的极值型指标PSI 是分数分布稳定性的指标AUC 跟 GINI 本质是同一个东西的两种算法。KS 等于 TPR 减去 FPR 的最大值它告诉你模型在哪个分数段区分能力最强。GINI 0.5 的模型KS 可能在 0.3 也可能在 0.4两者组合能发现一些奇怪行为。比如某个区间出现异常凸起GINI 变化不大但 KS 飘移明显往往意味着某个特征在特定分段上出了状况。PSI 管的是“分数分布有没有变”GINI 管的是“排序能力有没有变”两者没有因果关系。分布稳定不等于区分度稳定分布漂移也不一定立刻导致 GINI 下跌。我的习惯是每次监控同时看 PSI 和 GINI分别超过阈值才定位问题避免单看一个指标误判。5. 实战中的坑与排查经验5.1 AUC 贴近 0.5先查方向再看特征AUC 等于 0.5 几乎等于告诉你有地方出了系统性错误。第一步查方向标签是不是定义反了分数是不是含义反了。把预测概率取个负号再看 AUC如果从 0.48 变成 0.52基本上就是方向问题。第二步查特征泄漏的“反向泄漏”有些特征本身包含了标签信息但因为清洗错误导致相关性被抹掉。比如一个“近 30 天逾期次数”的变量如果归一化前忘了处理缺失值缺失值被填成极端值排序关系反而破坏了AUC 就会掉到接近随机。这种情况特征重要性可能还很高特别迷惑人。第三类情况是目标期没对齐。训练时用了未来 12 个月的违约标签但特征里有个变量其实是未来信息在样本外表现就崩。AUC 在训练集上虚高 0.9验证集只有 0.51这种落差基本就是时效性泄漏。5.2 GINI 变成负数八成是排序方向反了CAP 曲线的左上角是“高风险客户”所以必须按分数从高到低排序。如果你的分数是信用分、芝麻分这类“越高越好”的变量却按降序排等于把好客户当坏客户排在了前面曲线会掉到对角线下方AR 和 GINI 都是负数。这种情况模型本身可能完全没问题UI 报告却会给出一个负 GINI评审会上非常尴尬。处理方式很简单统一在评估函数入口做一次方向校验——先随机抽几百个样本看高分段坏率是否显著高于低分段不满足就自动把分数取反。这个自动校验能挡住大多数低级错误。5.3 坏样本率极低CAP 曲线容易“飘”信贷场景坏率常常只有 1% 到 3%。坏样本太少的时候累计坏客户曲线是由少数几十个样本的跳跃撑起来的曲线锯齿感非常强AR 的小数位波动也大。同一份数据换个随机种子切验证集GINI 可能差出 0.05这对决策来说已经是不小的差距。低坏率下的应对经验有几个一是用 AUC 而非单纯依赖 GINI 做细微比较AUC 的秩统计对极端不均衡更稳健二是计算结果务必带上区分度的置信区间简单做法是 Bootstrap 重采样几百次看 GINI 的 95% 分位范围三是在报告里标注坏样本绝对数量GINI 0.52 基于 3000 个坏样本和基于 300 个坏样本可信度完全不是一个量级。5.4 训练集 GINI 很漂亮验证集直接腰斩这是最常见的过拟合信号。入模几百个特征、树模型不限制深度、正则项调大轻了训练集上 GINI 轻轻松松 0.85OOT 一测只有 0.4。这时候大部分人会立刻砍模型复杂度但我的建议是先查数据泄漏再做正则。先按时间切样本确认训练集和测试集没有重叠的时间窗再逐特征做时间稳定性分析把 PSI 过高的特征替换掉。排除泄漏后再调模型结构比如限制最大深度、增加最小叶子样本数、加大 L2 正则。顺序反了容易白忙一场——数据泄漏不解决光剪枝是压不住那个虚高的 GINI 的。5.5 拒绝样本偏差CAP 指标虚高的隐形推手信贷建模有个绕不开的问题模型是基于审批通过的客户训练的这些客户本身就是上一版策略筛出来的“好人”真正的高风险客户早就被拒之门外没有表现数据。拿这个样本算出来的 GINI反应的是模型在这批“幸存者”里的区分度天然偏乐观。这就是拒绝推断Reject Inference要处理的场景。如果只拿通过客户建模而不做任何校正CAP 曲线看起来很不错上线后面对全量客群立刻现原形。我见过一个项目开发样本 GINI 0.58上线三个月后实际监控只有 0.36。后来用简单的软分类法做了拒绝样本标注回补再训练出来的 GINI 虽然只有 0.42但跟线上监控基本对上了。所以看到特别高的 GINI 时先问三个问题这个样本是全量客群还是审批通过客群坏样本定义窗口够不够完整有没有做拒绝推断这三个问题在评审会上几乎是必问的。最后分享两个小习惯。第一我所有评估代码都会同时输出 AUC、GINI、AR 三个值并加上“AR 与 2AUC−1 偏差过大就报警”的断言。第二给业务方汇报时永远用 CAP 曲线而不是 ROC 曲线配图横轴换成“累计客群占比”之后非技术的同事也能指着图说清楚模型价值。这套指标体系的数学推导不难难的是每个数字背后对应的业务判断希望这篇整理能帮你把这一环补齐。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑