导语:评分卡解释性强,但抓不住复杂交叉。想提精度,就用树模型(XGBoost/LightGBM)。代价是:黑箱。
一、它解决什么问题
捕捉"收入中等 + 多头多 + 夜间活跃"这类非线性交叉风险,把 KS/AUC 往上提几个点。
二、原理(直觉)
- 决策树:按"哪个变量切一刀最能分开好坏"递归分裂,直到纯净或到限制。
- 集成(GBDT/XGBoost):训很多棵弱树,每棵修正前一棵的残差,加权求和。树多→拟合强。
三、信贷里怎么做
- 特征工程(行为/交易/征信/设备),不用先分箱(树自己切)。
- 训练:调
max_depth(树深,防过拟合)、learning_rate(小一点稳)、subsample(行采样)。 - 评估:KS、AUC,重点看**训练集 vs 验证集 vs 时间外样本(OOT)**是否一致。
- 可解释:用 SHAP 看每个变量贡献,或把树提取成规则给业务用。
四、代码骨架
python
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score
import xgboost as xgb
---------- 1. 数据准备 ----------
假设 df 已包含特征列和标签列 y(0=好客户,1=坏客户)
按时间切分:观察期样本用于训练/验证,表现期之后的样本作为 OOT
df_train_val = df[df["month"] <= "2024-06"] # 观察期
df_oot = df[df["month"] > "2024-06"] # 时间外样本(OOT)
features = ["income", "multi_loan_cnt", "night_active_ratio", "device_risk_score"]
X = df_train_val[features]
y = df_train_val["y"]
训练集 / 验证集(按时间顺序切,避免未来信息泄漏)
X_train, X_val, y_train, y_val = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
X_oot, y_oot = df_oot[features], df_oot["y"]
---------- 2. 参数设置 ----------
params = {
"max_depth": 4, # 树深:控制模型复杂度,防止过拟合
"learning_rate": 0.05, # 学习率:调小一点更稳,但需要更多树
"subsample": 0.8, # 行采样:每棵树只用 80% 样本,增强泛化
"colsample_bytree": 0.8, # 列采样:每棵树只用 80% 特征
"eval_metric": "auc",
"objective": "binary:logistic",
"n_estimators": 500,
"early_stopping_rounds": 50, # 验证集 AUC 连续 50 轮不提升则提前停止
}
---------- 3. 训练 ----------
model = xgb.XGBClassifier(**params)
model.fit(
X_train, y_train,
eval_set=[(X_val, y_val)],
verbose=False,
)
---------- 4. KS / AUC 评估 ----------
def ks_score(y_true, y_prob):
"""计算 KS 统计量:好坏样本累计分布的最大差距"""
df_ks = pd.DataFrame({"y": y_true, "prob": y_prob})
df_ks = df_ks.sort_values("prob", ascending=False).reset_index(drop=True)
df_ks["cum_bad"] = (df_ks["y"] == 1).cumsum() / (df_ks["y"] == 1).sum()
df_ks["cum_good"] = (df_ks["y"] == 0).cumsum() / (df_ks["y"] == 0).sum()
return (df_ks["cum_bad"] - df_ks["cum_good"]).abs().max()
for name, X_eval, y_eval in [
("训练集", X_train, y_train),
("验证集", X_val, y_val),
("OOT", X_oot, y_oot),
]:
prob = model.predict_proba(X_eval)[:, 1]
auc = roc_auc_score(y_eval, prob)
ks = ks_score(y_eval, prob)
print(f"{name}: AUC={auc:.4f}, KS={ks:.4f}")
重点:训练集 / 验证集 / OOT 的 KS、AUC 应基本一致,
若 OOT 明显下滑,说明存在过拟合或特征泄漏,需回查特征与切分逻辑。
五、常见坑
- KS 虚高:特征里混进"未来信息"(如用了表现期才有的变量)→ 线上必崩。严格按观察/表现期切。
- 过拟合:树太深,训练 KS 高、OOT 掉。看 OOT。
- 黑箱难解释:监管/业务要理由,务必配 SHAP 或转规则。
- 训练/线上不一致:特征的缺失、分箱逻辑上线要对齐。
六、小结
树模型提精度,但必须配可解释和 OOT 验证。评分卡管"稳",树模型管"准",实战常并用:树模型做主模型,评分卡做可解释备份/兜底。