策略模型中的 KS 和 LIFT 指标详解
KS 和 LIFT 是风控、营销等策略模型中最核心的两个评估指标,用于衡量模型的排序能力和业务提升效果。
一、KS(Kolmogorov-Smirnov)
1.1 定义
KS 衡量的是模型区分好样本和坏样本的能力,即模型能把"好人"和"坏人"分开的程度。
KS=max(∣TPR−FPR∣)KS=max(∣TPR−FPR∣)
TPR(真正率):坏样本被正确识别的比例(召回率)
FPR(假正率):好样本被误判为坏样本的比例
1.2 直观理解
import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.metrics import roc_curve # 示例:模型预测分数 np.random.seed(42) n_bad = 300 # 坏人 n_good = 700 # 好人 # 坏人分数较高(模型认为坏人概率高),好人分数较低 bad_scores = np.random.beta(5, 1, n_bad) * 0.5 + 0.5 # 坏人分数 0.5-1.0 good_scores = np.random.beta(1, 5, n_good) * 0.5 # 好人分数 0-0.5 scores = np.concatenate([bad_scores, good_scores]) labels = np.concatenate([np.ones(n_bad), np.zeros(n_good)]) # 计算 KS fpr, tpr, thresholds = roc_curve(labels, scores) ks_value = max(tpr - fpr) ks_threshold = thresholds[np.argmax(tpr - fpr)] print(f"KS 值: {ks_value:.4f}") print(f"最佳阈值: {ks_threshold:.4f}") # 绘制 KS 曲线 plt.figure(figsize=(10, 6)) plt.plot(fpr, tpr, 'b-', linewidth=2, label=f'ROC (KS={ks_value:.4f})') plt.plot([0, 1], [0, 1], 'r--', label='随机') plt.fill_between(fpr, tpr, fpr, alpha=0.3) plt.xlabel('FPR (好人误判率)') plt.ylabel('TPR (坏人识别率)') plt.title('KS 曲线') plt.legend() plt.show()1.3 KS 评估标准
| KS 范围 | 模型质量 | 说明 |
|---|---|---|
| < 0.2 | 不可用 | 几乎没有区分能力 |
| 0.2 - 0.3 | 勉强可用 | 需要优化 |
| 0.3 - 0.5 | 良好 | 工业界常用标准 |
| 0.5 - 0.6 | 优秀 | 非常好的模型 |
| > 0.75 | 过高 | 可能过拟合 |
风控行业标准:
信贷评分卡:KS > 0.3 可上线
反欺诈模型:KS > 0.4 较好
二、LIFT(提升度)
2.1 定义
LIFT 衡量的是:用模型筛选出来的目标群体,比随机选择"好多少倍"。
LIFT=模型选中范围内的坏样本率整体坏样本率LIFT=整体坏样本率模型选中范围内的坏样本率
2.2 计算示例
def calculate_lift(y_true, y_pred_proba, top_percent=10): """ 计算 LIFT 值 Parameters ---------- y_true : array 真实标签 y_pred_proba : array 预测为正类的概率 top_percent : float 取前百分之几的用户 Returns ------- lift : float 提升度 """ # 按预测概率降序排序 df = pd.DataFrame({'label': y_true, 'proba': y_pred_proba}) df = df.sort_values('proba', ascending=False).reset_index(drop=True) # 整体坏样本率 overall_bad_rate = df['label'].mean() # 前 top_percent% 的坏样本率 top_n = int(len(df) * top_percent / 100) top_bad_rate = df.iloc[:top_n]['label'].mean() # LIFT lift = top_bad_rate / overall_bad_rate if overall_bad_rate > 0 else 0 return lift, top_bad_rate, overall_bad_rate # 示例 lift_10, top_rate, overall_rate = calculate_lift(labels, scores, top_percent=10) print(f"整体坏样本率: {overall_rate:.4f}") print(f"前10%坏样本率: {top_rate:.4f}") print(f"LIFT@10%: {lift_10:.2f}倍") # 计算不同百分位的 LIFT for pct in [5, 10, 20, 30]: lift, _, _ = calculate_lift(labels, scores, top_percent=pct) print(f"LIFT@{pct:2d}%: {lift:.2f}倍")2.3 LIFT 曲线
def plot_lift_curve(y_true, y_pred_proba): """绘制 LIFT 曲线""" df = pd.DataFrame({'label': y_true, 'proba': y_pred_proba}) df = df.sort_values('proba', ascending=False).reset_index(drop=True) overall_bad_rate = df['label'].mean() lift_values = [] percentiles = range(1, 101) for p in percentiles: top_n = int(len(df) * p / 100) top_bad_rate = df.iloc[:top_n]['label'].mean() lift = top_bad_rate / overall_bad_rate if overall_bad_rate > 0 else 0 lift_values.append(lift) plt.figure(figsize=(10, 6)) plt.plot(percentiles, lift_values, 'g-', linewidth=2) plt.axhline(y=1, color='r', linestyle='--', label='基准线 (LIFT=1)') plt.fill_between(percentiles, 1, lift_values, alpha=0.3) plt.xlabel('用户百分比 (%)') plt.ylabel('LIFT 值') plt.title('LIFT 曲线') plt.legend() plt.grid(alpha=0.3) plt.show() plot_lift_curve(labels, scores)三、在你的分类模型中添加 KS 和 LIFT
import pandas as pd import numpy as np from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import roc_curve, roc_auc_score import joblib import sys def calculate_ks(y_true, y_pred_proba): """计算 KS 值""" fpr, tpr, thresholds = roc_curve(y_true, y_pred_proba) ks = max(tpr - fpr) # 获取 KS 对应的阈值 ks_threshold = thresholds[np.argmax(tpr - fpr)] return ks, ks_threshold def calculate_lift(y_true, y_pred_proba, top_percent=10): """计算 LIFT 值""" df = pd.DataFrame({'label': y_true, 'proba': y_pred_proba}) df = df.sort_values('proba', ascending=False).reset_index(drop=True) overall_bad_rate = df['label'].mean() top_n = int(len(df) * top_percent / 100) top_bad_rate = df.iloc[:top_n]['label'].mean() lift = top_bad_rate / overall_bad_rate if overall_bad_rate > 0 else 0 return lift, top_bad_rate, overall_bad_rate def calculate_lift_curve(y_true, y_pred_proba, percentiles=[5, 10, 20, 30, 50]): """计算多个百分位的 LIFT""" results = {} for p in percentiles: lift, top_rate, overall = calculate_lift(y_true, y_pred_proba, p) results[p] = { 'lift': lift, 'top_bad_rate': top_rate, 'overall_bad_rate': overall } return results def main(): # 读取数据 input_file = sys.argv[1] if len(sys.argv) > 1 else 'data.csv' output_file = sys.argv[2] if len(sys.argv) > 2 else 'model.pkl' df = pd.read_csv(input_file) X = df.drop('target', axis=1) y = df['target'] # 划分数据 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) # 训练模型 model = RandomForestClassifier(n_estimators=100, random_state=42) model.fit(X_train, y_train) # 预测概率 y_pred_proba = model.predict_proba(X_test)[:, 1] # 计算 KS ks, ks_threshold = calculate_ks(y_test, y_pred_proba) # 计算 AUC auc = roc_auc_score(y_test, y_pred_proba) # 计算 LIFT lift_10, _, _ = calculate_lift(y_test, y_pred_proba, top_percent=10) lift_curve = calculate_lift_curve(y_test, y_pred_proba) # 输出结果 print(f"\n{'='*50}") print(f"策略模型评估指标") print(f"{'='*50}") print(f"AUC: {auc:.4f}") print(f"KS: {ks:.4f}") print(f"KS阈值: {ks_threshold:.4f}") print(f"LIFT@10: {lift_10:.2f}倍") print(f"\nLIFT 曲线:") for p, v in lift_curve.items(): print(f" LIFT@{p:2d}%: {v['lift']:.2f}倍 (坏样本率: {v['top_bad_rate']:.2%})") print(f"{'='*50}\n") # 保存模型 joblib.dump(model, output_file) print(f"模型已保存到 {output_file}") return ks, lift_10 if __name__ == "__main__": main()四、KS 和 LIFT 的业务应用
4.1 确定拒绝阈值(风控场景)
def find_optimal_threshold_by_ks(y_true, y_pred_proba, target_ks=None): """ 根据 KS 找到最优阈值 Parameters ---------- y_true : array 真实标签 y_pred_proba : array 预测概率 target_ks : float, optional 目标 KS 值,如果指定则找到达到该 KS 的最小阈值 Returns ------- optimal_threshold : float 最优阈值 """ fpr, tpr, thresholds = roc_curve(y_true, y_pred_proba) ks_values = tpr - fpr if target_ks: # 找到达到目标 KS 的最小阈值 idx = np.where(ks_values >= target_ks)[0] if len(idx) > 0: optimal_idx = idx[0] else: optimal_idx = np.argmax(ks_values) else: # 找到最大 KS 对应的阈值 optimal_idx = np.argmax(ks_values) optimal_threshold = thresholds[optimal_idx] # 计算拒绝率和通过率 reject_rate = (y_pred_proba >= optimal_threshold).mean() pass_rate = 1 - reject_rate # 计算拒绝样本中的坏样本率 rejected_mask = y_pred_proba >= optimal_threshold rejected_bad_rate = y_true[rejected_mask].mean() if rejected_mask.any() else 0 print(f"最优阈值: {optimal_threshold:.4f}") print(f"拒绝率: {reject_rate:.2%}") print(f"通过率: {pass_rate:.2%}") print(f"拒绝样本坏样本率: {rejected_bad_rate:.2%}") return optimal_threshold # 使用 threshold = find_optimal_threshold_by_ks(y_test, y_pred_proba)4.2 根据 LIFT 确定营销目标人群
def find_target_population_by_lift(y_true, y_pred_proba, min_lift=2.0): """ 根据最小 LIFT 确定目标人群比例 Parameters ---------- y_true : array 真实标签 y_pred_proba : array 预测概率 min_lift : float 最小期望提升度 Returns ------- target_percent : float 目标人群比例 """ df = pd.DataFrame({'label': y_true, 'proba': y_pred_proba}) df = df.sort_values('proba', ascending=False).reset_index(drop=True) overall_bad_rate = df['label'].mean() for p in range(1, 101): top_n = int(len(df) * p / 100) top_bad_rate = df.iloc[:top_n]['label'].mean() lift = top_bad_rate / overall_bad_rate if overall_bad_rate > 0 else 0 if lift < min_lift: target_percent = p - 1 break else: target_percent = 100 print(f"要达到 {min_lift} 倍提升,应筛选前 {target_percent}% 的用户") return target_percent # 使用 target_pct = find_target_population_by_lift(y_test, y_pred_proba, min_lift=2.0)五、KS vs LIFT 对比总结
| 维度 | KS | LIFT |
|---|---|---|
| 核心问题 | 模型能把好坏分开吗? | 模型比随机选择好多少? |
| 计算公式 | max(TPR - FPR) | (选中群体坏率) / (整体坏率) |
| 取值范围 | 0 ~ 1 | 0 ~ ∞(通常 1~3) |
| 判断标准 | > 0.3 可用 | > 1.5 有价值 |
| 业务含义 | 模型区分能力 | 业务提升倍数 |
| 主要用途 | 评估模型质量 | 确定营销/拒绝策略 |
| 关注点 | 模型整体性能 | 前百分之几的用户 |
一句话记忆
KS:风控模型能不能把坏客户"揪出来"?
LIFT:用模型筛人,比瞎蒙高效多少倍?
风控策略应用:
KS > 0.3:模型可以上线
LIFT@10% > 2.0:拒绝前10%的用户是划算的
