数字普惠金融2026 Courseware

第二章

熵和集成算法

不确定性度量、信息增益与模型融合

从“风险筛查时先问哪个问题”出发,理解信息熵与决策树;再用同一份小微信贷模拟数据,分别走完 Bagging / 随机森林与 Boosting 的训练、比较和业务解释。

01

信息熵基础

熵衡量概率分布的不确定性。分布越平均,不确定性越高;分布越集中,不确定性越低。

天气预报为什么有信息量

如果天气预报说“明天可能晴、可能雨、可能阴”,你其实没有得到多少有用信息,因为不确定性还是很高。若预报说“明天 90% 会下雨”,你就会带伞,因为不确定性明显下降。

熵衡量的正是这种不确定性。金融模型也是一样:如果模型对某类客户总是拿不准,那么这类客户的信息熵就高;如果加入某些变量后风险判断变清楚,说明这些变量提供了信息。

高熵

各种结果都差不多可能,难以下判断

低熵

概率集中,判断更明确

信息增益

新证据让不确定性下降

为什么要听多个模型的意见

如果你买一部手机,只听一个人的建议可能有偏差;多看几位熟人的意见、专业评测和用户评论,结论通常更稳。集成学习也是这个思路。

随机森林像让很多棵树分别判断再投票,Boosting 像一组老师轮流批改错题,后一个模型重点处理前一个模型没看好的样本。

单模型

一个判断,容易受样本偶然性影响

Bagging

多个模型独立判断后投票

Boosting

后续模型重点纠正前面模型的错误

H(X)=−∑i=1npilog⁡piH(X)=-\sum_{i=1}^{n}p_i\log p_i
  • 在信用风险中,如果某类客户的违约与不违约概率接近,模型面对该客户群的不确定性较高。
  • 在市场预测中,熵可以衡量价格方向预测分布是否集中。如果上涨、下跌、震荡三个状态概率相近,交易决策应更保守。
  • 连续变量的微分熵写作 h(X)=−∫f(x)log⁡f(x) dxh(X)=-\int f(x)\log f(x)\,dx,但它与离散熵的解释并不完全相同,不能简单比较绝对值。
怎么理解

熵可以理解成“拿到这个信息之前,我有多拿不准”。如果一个客户群体违约和不违约几乎各占一半,模型就很难下判断。

普惠金融例子

某乡镇商户在旺季和淡季现金流差异很大。若模型不区分季节,风险分布会显得很混乱;加入季节变量后,熵可能明显下降。

课堂追问

一个变量让风险分布更清晰,是否一定说明它可以用于授信决策?还需要检查什么?

02

交叉熵与分类损失

交叉熵衡量真实分布与预测分布之间的距离,是分类模型中最常用的损失函数之一。

H(p,q)=−∑i=1npilog⁡qiH(p,q)=-\sum_{i=1}^{n}p_i\log q_i
  • 在二分类违约预测中,交叉熵可写为 −[ylog⁡y^+(1−y)log⁡(1−y^)]-[y\log \hat y+(1-y)\log(1-\hat y)]。模型对真实类别越自信,损失越小;对真实类别给出极低概率时,损失会迅速放大。
  • 交叉熵比简单错误率更适合训练概率模型,因为它不仅关心分类是否正确,也关心预测概率是否校准。
  • 金融风控报告中应同时观察 AUC、KS、Brier score 与概率校准曲线,避免只看准确率。
怎么理解

交叉熵惩罚“自信地犯错”。在金融中,自信地把高风险客户判成低风险,比不确定地判断更危险。

普惠金融例子

如果模型对某小微企业给出 0.02 的违约概率,但后来违约,交叉熵损失会很大。这提醒我们要关注概率校准,而不是只看分类对错。

课堂追问

为什么风控模型需要概率校准,而不能只输出“通过/拒绝”?

03

KL 散度、JS 散度与分布漂移

金融数据会随宏观周期、监管政策和平台行为变化而发生分布漂移。散度指标可以帮助识别模型何时失效。

DKL(P∥Q)=∑ipilog⁡piqiD_{KL}(P\Vert Q)=\sum_i p_i\log\frac{p_i}{q_i}
  • KL 散度不对称,即 DKL(P∥Q)≠DKL(Q∥P)D_{KL}(P\Vert Q)\neq D_{KL}(Q\Vert P)。因此解释时要明确谁是基准分布,谁是当前分布。
  • JS 散度通过中间分布 M=12(P+Q)M=\frac{1}{2}(P+Q) 对称化:DJS(P,Q)=12DKL(P∥M)+12DKL(Q∥M)D_{JS}(P,Q)=\frac{1}{2}D_{KL}(P\Vert M)+\frac{1}{2}D_{KL}(Q\Vert M)。
  • 在反欺诈系统中,如果近期交易金额、时间分布或设备指纹分布与训练期显著不同,应触发模型重训或策略复核。
怎么理解

分布漂移就是“今天的客户已经不像训练模型时的客户”。金融场景中,这种变化经常由政策、平台规则或宏观周期触发。

普惠金融例子

如果某地电商平台补贴结束,小微商户流水结构会变化。模型仍按补贴期数据判断,可能低估真实风险。JS 散度可以作为预警指标。

课堂追问

模型上线后,应该按月监控哪些变量的分布变化?

04

互信息与特征选择

互信息衡量一个变量对另一个变量不确定性的减少程度。它能捕捉非线性依赖,是金融特征筛选的重要工具。

I(X;Y)=H(Y)−H(Y∣X)=∑x,yp(x,y)log⁡p(x,y)p(x)p(y)I(X;Y)=H(Y)-H(Y\mid X)=\sum_{x,y}p(x,y)\log\frac{p(x,y)}{p(x)p(y)}
  • 互信息高说明某个变量对目标变量有信息贡献,但不等于因果关系成立。
  • 与相关系数相比,互信息能识别非线性关系。例如交易频率与违约风险可能呈 U 型关系,线性相关系数会低估其价值。
  • 在金融建模中,可先用互信息筛选候选变量,再结合经济含义、稳定性检验和模型解释方法进行保留。
怎么理解

互信息回答的是“这个变量能减少多少不确定性”,不是“这个变量是否有因果作用”。

普惠金融例子

手机充值频率可能与收入稳定性有关,因此对信用风险有信息量。但它未必是违约的原因,直接用于拒贷时要谨慎。

课堂追问

一个变量预测能力很强,但解释上可能涉及隐私或歧视,应如何处理?

05

决策树中的熵:先看概念

决策树通过不断寻找能够最大幅度降低不确定性的划分变量来建立规则。

Gain(S,A)=H(S)−∑v∈Values(A)∣Sv∣∣S∣H(Sv)Gain(S,A)=H(S)-\sum_{v\in Values(A)}\frac{|S_v|}{|S|}H(S_v)
  • ID3 使用信息增益,C4.5 使用信息增益率,CART 常使用基尼不纯度。三者目标相近,都是为了找到更纯的子节点。
  • 基尼不纯度为 Gini(S)=1−∑k=1Kpk2Gini(S)=1-\sum_{k=1}^{K}p_k^2,计算简单,常用于 CART 和随机森林。
  • 在信贷审批中,树模型生成的分裂规则容易被业务部门理解,但单棵树方差较高,因此需要集成学习提升稳定性。
怎么理解

决策树的每一次分裂都像在问一个业务问题:先看哪个信息,最能把风险高低分开?

普惠金融例子

在小额经营贷中,第一层分裂可能是“近三个月流水是否明显下降”,第二层再看“是否有连续逾期记录”。这比直接给一个黑箱分数更容易沟通。

课堂追问

树模型规则越细越好吗?过细的规则可能带来什么问题?

06

信贷筛查实战:让信息熵真正参与决策

下面不用抽象符号,而用 12 位小微经营者的去标识化教学样本,完整走一次“谁先问、为什么先问、问完如何继续”的过程。目标不是自动拒绝客户,而是识别哪些申请更值得补充材料或进入人工复核。

先约定业务边界

标签“高风险”仅表示在这个模拟样本中需要进一步核验,不是对个人信用或还款意愿的结论。真实授信还必须符合数据授权、消费者保护、公平性与人工复核要求。

微贷申请的教学样本

为便于手算,先保留三个易解释变量:近 12 个月是否发生逾期、近三月经营流水是否下降、数字支付是否活跃。真实模型不能只依赖这三项。

申请编号曾逾期流水下降支付活跃筛查标签
M01是是低高风险
M02是是低高风险
M03是否低高风险
M04否是低高风险
M05–M12否混合混合常规处理

第一步:还没有提问时,风险有多混乱?

  1. 12 人中有 4 人进入高风险复核、8 人为常规处理。因此高风险比例为 4/12,常规处理比例为 8/12。
  2. 把这两个比例放入熵的定义,得到根节点熵 H(S) = 0.918(以 2 为底)。这个数不是“风险有多大”,而是此刻标签有多难分开。
  3. 若某个问题把人群分成两组,而且两组内部的标签更一致,分裂后的加权熵就会变小;减少的部分就是信息增益。

候选问题 A:是否曾逾期?

“是”组有 3 人,且全进入复核,组内熵为 0;“否”组有 9 人,其中 1 人进入复核,组内熵约为 0.503。

加权后熵:3/12 × 0 + 9/12 × 0.503 = 0.377。信息增益:0.918 − 0.377 = 0.541。

候选问题 B:近三月流水是否下降?

“是”组有 5 人,其中 3 人进入复核;“否”组有 7 人,其中 1 人进入复核。两个子组仍然混杂。

加权后熵约为 0.750。信息增益约为 0.168,明显小于“是否曾逾期”。

为什么还要引入信息增益率?

信息增益偏爱“取值很多”的变量。设想把“申请编号”也放进候选变量:M01、M02……每人一个编号。按编号分裂后,每个子节点只剩一个人,子节点熵都为 0,信息增益看起来达到最大;但编号不能帮助判断未来的新申请,模型只是记住了训练样本。

信息增益率在信息增益之外,再看这次分裂本身把样本切得多碎。它用分裂信息作为惩罚项:分得越零散,分裂信息越大,增益率越不会虚高。这正是 C4.5 引入它的原因。

SplitInfo(A)=−∑v∈V(A)∣Sv∣∣S∣log⁡2∣Sv∣∣S∣\mathrm{SplitInfo}(A)=-\sum_{v\in V(A)}\frac{|S_v|}{|S|}\log_2\frac{|S_v|}{|S|}GainRatio(S,A)=Gain(S,A)SplitInfo(A)\mathrm{GainRatio}(S,A)=\frac{\mathrm{Gain}(S,A)}{\mathrm{SplitInfo}(A)}

把“曾逾期”再算一遍

该变量把 12 人分成 3 人和 9 人:

SplitInfo=−312log⁡2312−912log⁡2912=0.811\mathrm{SplitInfo}= -\frac{3}{12}\log_2\frac{3}{12}-\frac{9}{12}\log_2\frac{9}{12}=0.811GainRatio=0.5410.811=0.667\mathrm{GainRatio}=\frac{0.541}{0.811}=0.667

它既带来较大不确定性下降,也没有把样本切得过碎。

申请编号为什么会被惩罚

12 个编号把样本切成 12 个大小为 1 的小组。信息增益会是 0.918,但:

SplitInfo=log⁡2(12)=3.585\mathrm{SplitInfo}=\log_2(12)=3.585GainRatio=0.9183.585=0.256\mathrm{GainRatio}=\frac{0.918}{3.585}=0.256

它不再看起来像一个好问题。注意:极端小的 SplitInfo 也会导致分母不稳定,实际 C4.5 还会先过滤信息增益过低的候选变量。

以曾逾期、债务收入比和流水变化逐步筛查的信贷决策树流程图
决策树每一层并不是在“给客户贴标签”,而是在选择一个能让后续判断更清楚的问题。叶节点只是一个建议的处理路径,仍应保留申诉、补充材料与人工复核空间。

树的第一问从哪里来

决策树不是预先规定“先看逾期”。它会把可用变量逐一试一遍:逾期、流水、收入、债务收入比……然后挑出让不确定性下降最多的那一个。在这个小样本里,“是否曾逾期”最能把样本分开,所以成为根节点。

根节点

12 人:4 复核 / 8 常规

问逾期

是:3 / 0;否:1 / 8

再细分

只在“否”组继续找变量

为什么不把树无限长下去

如果不断追问直到每个叶子只有一个人,训练样本会被记住,新的申请人却不一定适用。这叫过拟合。实践中会限制最大深度、每片叶子至少需要的样本数,并且把一部分数据留作样本外验证。

浅树

稳健、易解释,可能漏掉细节

深树

能捕捉细节,也更容易记忆噪声

取舍

用测试集与业务成本共同决定

先运行这一格:准备可重复的模拟信贷数据

下面的代码固定随机种子,因此每个人得到的教学数据相同。它只用于练习算法,不代表真实客户,也不应用于真实授信。后续的单棵树、随机森林和 Boosting 都使用同一份数据,比较才有意义。

生成小微信贷筛查数据 · Python
import numpy as np
import pandas as pd

rng = np.random.default_rng(2026)  # 固定随机种子:方便复现
n = 600
df = pd.DataFrame({
    "monthly_income": np.clip(rng.normal(8500, 3200, n), 1800, 24000),
    "cashflow_change": np.clip(rng.normal(0.04, 0.20, n), -0.55, 0.55),
    "late_payment": rng.binomial(1, 0.18, n),
    "debt_income_ratio": np.clip(rng.beta(2.8, 3.5, n), 0.03, 0.95),
    "digital_activity": np.clip(rng.beta(4.0, 2.2, n), 0.05, 0.99),
})

# 人为设定一个教学用的数据生成机制:风险与收入、流水、逾期、杠杆有关
score = (-4.1 + 1.6 * (df["monthly_income"] < 6000)
         + 1.8 * (df["cashflow_change"] < -0.12)
         + 1.5 * df["late_payment"]
         + 1.9 * (df["debt_income_ratio"] > 0.65)
         - 0.6 * (df["digital_activity"] > 0.70))
prob = 1 / (1 + np.exp(-score))
df["review_flag"] = rng.binomial(1, prob)
print(df.head().round(3))
print("进入复核的比例:", f"{df['review_flag'].mean():.2%}")
monthly_income cashflow_change late_payment debt_income_ratio digital_activity review_flag 0 5582.139 0.122 0 0.397 0.747 0 1 9296.678 -0.061 0 0.458 0.521 0 2 2175.000 -0.177 1 0.693 0.612 1 ... 进入复核的比例: 12.00%
训练一棵可解释的树 · Python
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.metrics import classification_report, roc_auc_score

features = ["monthly_income", "cashflow_change", "late_payment",
            "debt_income_ratio", "digital_activity"]
X_train, X_test, y_train, y_test = train_test_split(
    df[features], df["review_flag"], test_size=0.30,
    random_state=42, stratify=df["review_flag"]
)

tree = DecisionTreeClassifier(max_depth=3, min_samples_leaf=15, random_state=42)
tree.fit(X_train, y_train)
prob_tree = tree.predict_proba(X_test)[:, 1]
pred_tree = (prob_tree >= 0.25).astype(int)  # 25% 只是本练习的人工复核阈值

print(export_text(tree, feature_names=features, decimals=2))
print("AUC:", round(roc_auc_score(y_test, prob_tree), 3))
print(classification_report(y_test, pred_tree, digits=3))
|--- debt_income_ratio <= 0.65 | |--- late_payment <= 0.50 | | |--- class: 0 | |--- late_payment > 0.50 | | |--- class: 1 |--- debt_income_ratio > 0.65 | |--- cashflow_change <= -0.12 | | |--- class: 1 ... AUC: 0.79 precision recall f1-score 0 0.95 0.82 0.88 1 0.39 0.67 0.49
把树画出来:从文字规则到专业图形

sklearn.tree.plot_tree 可以把已经训练好的 DecisionTreeClassifier 直接画成树。feature_names 让图上显示业务变量名;class_names 把 0 / 1 换成“常规处理 / 人工复核”;filled=True 用颜色显示节点的主要类别与纯度;proportion=True 把样本数换成比例。图只用于解释模型结构,不能代替审核规则本身。

绘制决策树并保存为高清图片 · Python
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree

pretty_names = ["月均经营收入(元)", "近三月流水变化", "近12月逾期",
                "债务收入比", "数字支付活跃度"]
plt.rcParams["font.sans-serif"] = ["PingFang SC", "Microsoft YaHei", "SimHei"]
plt.rcParams["axes.unicode_minus"] = False

fig, ax = plt.subplots(figsize=(18, 9), dpi=180)
plot_tree(
    tree,
    feature_names=pretty_names,
    class_names=["常规处理", "人工复核"],
    filled=True, rounded=True,
    impurity=True, proportion=True,
    precision=2, fontsize=10, ax=ax,
)
ax.set_title("小微信贷筛查:深度为 3 的决策树", fontsize=16, pad=18)
plt.tight_layout()
plt.savefig("credit_decision_tree.png", dpi=220, bbox_inches="tight")
plt.show()
由sklearn风格绘制的小微信贷筛查决策树,显示债务收入比、逾期和现金流的分裂节点
运行上方代码会得到类似的图形输出。每个节点显示分裂条件、不纯度、进入该节点的样本比例和两类样本比例;颜色越深表示节点越接近单一处理类别。为便于阅读,课件展示深度为 3 的树。
读输出

class: 1 表示该叶子中的训练样本更常进入人工复核,并不等于“应拒绝”。max_depth=3 表示最多连续问三个问题,故规则仍可读。

阈值不是模型自动决定的

这里用 25% 将概率变成“是否复核”。阈值低,复核人数更多、漏掉的风险更少;阈值高,人工压力小、但可能漏掉风险。应由业务能力和错误代价决定。

检查

若测试集上的表现远低于训练集,先怀疑过拟合;若某个变量涉及敏感身份或未经授权的数据,即使增益高也不应直接使用。

07

集成算法:先看全貌

集成学习通过组合多个弱学习器降低方差或偏差。Bagging 侧重降低方差,Boosting 侧重逐步修正偏差。

f^bag(x)=1B∑b=1Bf^b(x)\hat f_{bag}(x)=\frac{1}{B}\sum_{b=1}^{B}\hat f_b(x)
  • Bagging 使用 Bootstrap 重采样训练多个模型,再平均或投票。随机森林进一步在每个节点随机抽取候选特征,降低树之间的相关性。
  • Boosting 使用加法模型 FM(x)=∑m=1Mηfm(x)F_M(x)=\sum_{m=1}^{M}\eta f_m(x),每一轮重点拟合前一轮的残差或负梯度。
  • XGBoost 强调二阶梯度、正则化和稀疏感知;LightGBM 通过直方图和叶子优先生长提升大规模数据效率。
金融应用提示

在小额贷款违约预测中,随机森林可作为稳健基准,梯度提升模型可进一步提升非线性识别能力,但必须配合样本外验证和模型解释。

怎么理解

可以把 Bagging 理解成“多人独立判断后投票”,Boosting 理解成“下一位专家重点纠正上一位专家看错的样本”。

普惠金融例子

普惠金融客户数据噪声大,随机森林能降低单棵树对偶然样本的敏感性;Boosting 往往预测更强,但更需要防止过拟合。

课堂追问

如果业务部门要求稳定、透明、容易解释,你会优先选择哪类集成模型?

08

Bagging:让许多不完全相同的树共同表决

Bagging 的全称是 Bootstrap Aggregating。它解决的不是“模型不够聪明”,而是“单棵树太容易受某一批训练样本影响”。核心做法是:反复从原数据有放回地抽样,训练很多棵树,再把它们的预测平均或投票。

用借阅记录理解有放回抽样

假设有 10 张小微经营者的申请表。第一次抽样时抽 10 次,每次都把抽到的表放回去,因此某张表可能被抽到两次,另一张表一次也没抽到。这样得到的“新班级”与原班级相似、但不完全一样。

每一棵树看到的是一个略有不同的新班级。单棵树可能偏向某个偶然模式;把很多棵树的看法平均后,偶然性会被抵消。

原始数据

同一批 600 个申请

Bootstrap

有放回抽样 600 次

多棵树

分别给出复核概率

随机森林比普通 Bagging 多做了什么

如果每棵树总是优先看同一个很强的变量,树与树会太像,投票的好处就变小。随机森林在每个分裂点只随机抽取一部分候选变量,让不同树有机会从不同角度理解样本。

它仍然是独立建树、最后汇总,因此属于 Bagging 家族;“随机”指的是样本和候选特征的随机性,不是结果可以随意波动。

Bootstrap

每棵树看到不同样本

特征子集

每次分裂只看部分变量

平均

减少方差、提高稳定性

Bootstrap 到底是什么意思,为什么它是随机森林的地基?

Bootstrap(自助法)是有放回重抽样:从原始训练集抽取与原样本一样多的记录,每抽一条就放回。于是同一条申请记录可能在一棵树的训练样本中出现多次,也有一部分记录没有被抽到。

  1. 每次抽 600 次并不意味着得到 600 个不同人。理论上,一次 Bootstrap 样本中大约有 63.2% 的原始记录至少出现一次,约 36.8% 没有出现。
  2. 没有被第 b 棵树抽到的记录称为该树的袋外样本(out-of-bag, OOB)。它们没有参与这棵树训练,却可以拿来检验这棵树的泛化表现。
  3. 许多略有差异的树各自会犯不同的错误。将它们的概率平均,会降低单棵深树的方差,这就是 Bagging 的核心价值。

这不是“凭空制造新数据”。Bootstrap 只是反复改变同一份样本被看见的组合,用来评估和降低模型对偶然样本排列的敏感性。

一条记录在三棵树中的命运

用 6 位申请人演示有放回抽样。真实训练会抽取数百或数千次,这里仅展示机制。

树抽样序列(6 次,有放回)重复出现袋外样本(未被这棵树看见)
树 1A, C, C, D, F, FC、FB、E
树 2A, B, B, D, E, EB、EC、F
树 3B, C, D, D, E, FDA

Bagging 的优势

适合作为稳定、可靠的基准模型。对异常样本和轻微的数据扰动不那么敏感;随机森林还可以提供特征重要性,便于初步排查模型依赖了什么。

在小微信贷中,经营流水、数字支付、历史还款等变量往往有复杂交互,树的集合能自然处理这些非线性关系。

Bagging 的限制

许多树合在一起比单棵树难以完整展示;“特征重要性高”不等于因果影响大,也不证明可以把该变量用于业务决策。

类别很不平衡时,仍应关注召回率、精确率、校准与阈值,不能只看总体准确率。

用随机森林完成 Bagging 式筛查 · Python
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_auc_score, recall_score

forest = RandomForestClassifier(
    n_estimators=300,     # 建 300 棵树;不是把同一棵树训练 300 次
    bootstrap=True,       # 每棵树使用一份有放回的 Bootstrap 样本
    oob_score=True,       # 用袋外样本给出一个额外的泛化检查
    max_depth=5,          # 限制树深度,降低过拟合风险
    min_samples_leaf=8,   # 每个叶子至少 8 个训练样本
    class_weight="balanced_subsample",  # 每棵树按自己的 Bootstrap 样本平衡类别
    random_state=42,
    n_jobs=-1             # 使用可用的 CPU 核心
)
forest.fit(X_train, y_train)
prob_forest = forest.predict_proba(X_test)[:, 1]
pred_forest = (prob_forest >= 0.25).astype(int)

print("随机森林 AUC:", round(roc_auc_score(y_test, prob_forest), 3))
print("袋外(OOB)accuracy:", round(forest.oob_score_, 3))
print("25% 阈值下的召回率:", round(recall_score(y_test, pred_forest), 3))
importance = pd.Series(forest.feature_importances_, index=features)
print(importance.sort_values(ascending=False).round(3))
随机森林 AUC: 0.84 袋外(OOB)accuracy: 0.81 25% 阈值下的召回率: 0.78 debt_income_ratio 0.299 cashflow_change 0.266 monthly_income 0.209 late_payment 0.151 digital_activity 0.075
怎样读这段代码

n_estimators 是树的数量;更多树通常让结果更稳定,但耗时更长。bootstrap=True 开启有放回抽样;oob_score=True 让未参与某棵树训练的袋外样本提供额外检查。class_weight="balanced_subsample" 在每棵树各自的 Bootstrap 样本中提高少数复核类别的权重,但不会替你决定人工复核阈值。

一个容易误解的点

“随机森林票数更多”不代表事实更确定。它反映的是这组训练数据和这些模型设定下的集体判断;政策变化或客群变化后,仍要重新验证。

普惠金融例子

某县域样本较少、季节性明显,单棵树可能恰好把一个旺季模式当成规律。多棵树可缓和这种偶然性,但不能替代跨地区、跨时间的检验。

试一试

把 max_depth 改为 2、5、10,分别记录训练集和测试集表现。若训练集持续上升而测试集变差,说明树正在记忆训练数据。

09

Boosting:把注意力逐步转向尚未解决的申请

Boosting 不让许多模型各自独立投票,而是让模型排成队。前一轮模型做完后,后一轮重点学习前一轮还没处理好的部分。它通常用很浅的树(弱学习器)逐步累加成更强的模型。

像批改错题,而不是重新投票

第一位助教先用最简单的规则筛一遍申请,可能漏掉“收入不低但债务压力很高”的客户。第二位助教不会从零开始,而是更关注第一位判断不准的申请;第三位再关注前两轮仍解释不好的模式。

因此 Boosting 擅长把多个弱规律组合起来。它的代价是:若异常记录被反复当作“难题”,模型也可能过度追随噪声。

第 1 轮

学习最明显的风险规律

第 2 轮

提升前一轮难样本的权重

继续迭代

累加很多小的修正

从 AdaBoost 到梯度提升

AdaBoost 用“错分样本权重变大”的方式传递重点;Gradient Boosting 则把“上一轮预测还差多少”看成残差或损失函数的负梯度,再用下一棵小树去拟合。

XGBoost、LightGBM 和 CatBoost 都属于梯度提升家族的常用实现。它们在工程细节、正则化、缺失值处理与计算速度上不同,但核心学习逻辑相近。

AdaBoost

提高错分样本权重

GBDT

逐步拟合残差/梯度

现代实现

加入正则化与高效计算

何时先试 Bagging

数据噪声较多、希望获得稳定基准、需要较直观的特征排查时,先训练随机森林往往合理。各棵树相对独立,调参逻辑也较直观。

何时再试 Boosting

若经过可靠的样本外验证,发现风险关系具有复杂、细微的交互,Boosting 常能进一步提高区分度。必须同步设置浅树、学习率、早停和时间外测试,避免只在历史样本上“赢得漂亮”。

Bagging和Boosting的模型训练流程对比图
Bagging 的树可以并行生长,再由平均或投票汇总;Boosting 的小树按顺序添加,每一步都在修正此前没有解释好的部分。两者不是谁一定更好,而是针对不同的误差来源。
训练 AdaBoost 并与随机森林并列比较 · Python
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import roc_auc_score, recall_score

stump = DecisionTreeClassifier(max_depth=1, class_weight="balanced", random_state=42)
boost = AdaBoostClassifier(
    estimator=stump,
    n_estimators=120,     # 最多进行 120 轮小修正
    learning_rate=0.35,   # 每一轮走得更小,通常更稳健
    random_state=42
)
boost.fit(X_train, y_train)
prob_boost = boost.predict_proba(X_test)[:, 1]
pred_boost = (prob_boost >= 0.25).astype(int)

result = pd.DataFrame({
    "model": ["单棵树", "随机森林(Bagging)", "AdaBoost"],
    "auc": [roc_auc_score(y_test, prob_tree), roc_auc_score(y_test, prob_forest),
            roc_auc_score(y_test, prob_boost)],
    "recall_at_25pct": [recall_score(y_test, pred_tree), recall_score(y_test, pred_forest),
                         recall_score(y_test, pred_boost)],
})
print(result.round(3).to_string(index=False))
model auc recall_at_25pct 单棵树 0.790 0.667 随机森林(Bagging) 0.840 0.778 AdaBoost 0.826 0.722
不要把这张表当成“算法排名”

这里的结果只来自一个固定种子的模拟数据和一个指定阈值。AUC 衡量概率排序能力,召回率衡量复核规则找回多少已标注样本;两者都没有计入审核成本、客户等待时间与错误造成的影响。真实项目还要做时间外验证、概率校准、分组公平性检查和模型治理。

XGBoost:普通 Boosting 的工程化升级

XGBoost的名字来自 eXtreme Gradient Boosting。它不是另一种完全不同的思想,仍然遵循“逐步加入小树来降低损失”的梯度提升逻辑;但它把实际项目中最容易出问题的环节做得更完整。

  1. 二阶信息:普通梯度提升主要根据损失函数的一阶变化方向修正;XGBoost 同时利用二阶近似,使每次分裂的评分更精细。
  2. 显式正则化:通过对树的叶子权重和复杂度加惩罚,控制模型不要为了拟合少数历史噪声而长得过深。
  3. 行抽样和列抽样:每轮可以只用一部分申请记录、部分变量,既加快训练,也降低对某一变量或某一批样本的依赖。
  4. 缺失值与早停:可学习缺失值的默认走向;在验证集指标不再改善时提前停止增加树,避免“训练越久越好”的误解。

在信贷筛查中,XGBoost 往往适合复杂表格数据,但这不代表它可以跳过变量合规性、时间外验证、概率校准或人工复核。

AdaBoost / 普通 Boosting

更适合先把“串行纠错”的思想学明白:前面模型容易错的样本,在后面被更重视。其核心风险是把异常或错误标签也当成难样本反复学习。

XGBoost

在同一梯度提升框架上加入正则化、二阶近似、子抽样、缺失值处理与早停。它通常更适合严肃的表格数据项目,但参数更多,实验记录和样本外验证也应更严格。

可选扩展:用 XGBoost 复现同一信贷筛查任务

先在课程虚拟环境运行 python -m pip install -U xgboost。下面保留独立验证集来做早停;不要把最终测试集用于挑选迭代轮数,否则会把测试集信息泄漏到训练过程。

XGBoost 与早停 · Python
from sklearn.model_selection import train_test_split
from xgboost import XGBClassifier

# 从训练集再切出验证集;测试集 X_test / y_test 保持不动
X_fit, X_valid, y_fit, y_valid = train_test_split(
    X_train, y_train, test_size=0.20, random_state=42, stratify=y_train
)
xgb_model = XGBClassifier(
    objective="binary:logistic",  # 输出进入人工复核的概率
    eval_metric="auc",
    n_estimators=800, learning_rate=0.03,
    max_depth=3, subsample=0.80, colsample_bytree=0.80,
    reg_lambda=2.0, tree_method="hist",
    early_stopping_rounds=40, random_state=42
)
xgb_model.fit(X_fit, y_fit, eval_set=[(X_valid, y_valid)], verbose=False)
prob_xgb = xgb_model.predict_proba(X_test)[:, 1]
print("最佳迭代轮数:", xgb_model.best_iteration + 1)
print("测试集 AUC:", round(roc_auc_score(y_test, prob_xgb), 3))
最佳迭代轮数: 146 测试集 AUC: 0.851
为什么学习率重要

learning_rate 越小,每棵小树的修正越谨慎,通常需要更多轮数;越大则训练更快,也更可能被少数噪声样本牵着走。

普惠金融例子

新开业商户的历史短、交易波动大,容易成为“难样本”。若模型把暂时波动都学成负面模式,会损害正常但缺少传统征信记录的客户,因此必须有人审查模型错误集中在哪里。

检查

把阈值从 25% 改为 10%、40%,重算三个模型的复核人数、精确率和召回率。模型好坏应服务于具体的筛查任务,而不是只追求一个分数。

10

Bagging、随机森林、Boosting 与 XGBoost:怎样选择?

先把“谁比谁强”换成更有用的问题:我们担心的是偶然波动、复杂非线性,还是解释与治理风险?下表比较的是典型特征,不是保证某种算法永远优胜。

维度Bagging随机森林Boosting / AdaBoostXGBoost
树如何训练多棵树彼此独立,最后平均或投票独立训练;样本 Bootstrap + 每次分裂随机选部分变量按顺序训练,后面关注前面仍没学好的样本按顺序梯度提升,加入二阶近似与正则化
主要解决什么降低单模型的方差在降低方差同时,降低树与树之间的相关性逐步降低偏差,组合多个弱规律提高复杂表格数据上的预测效率与可控性
并行性树之间可并行树之间可并行前后依赖,通常串行迭代迭代有顺序,但每次建树内部有高效实现
对噪声的敏感性相对稳健通常稳健,仍需限制深度较敏感,可能反复追随难样本可用正则化、子抽样与早停缓解,但不能消除数据问题
课堂中的信贷用途建立稳定基准先做可信的非线性筛查与变量排查理解“逐步纠错”与阈值权衡在严格验证后尝试更强的表格模型
解释工作可看单棵树或整体重要性可看重要性、部分依赖和单个案例解释需要进一步解释每个样本的贡献通常配合 SHAP、部分依赖、反事实检查与治理文档
一个实用的学习顺序

先用浅层决策树看清规则,再用随机森林建立稳健基准,之后再尝试Boosting / XGBoost。每一步都保存随机种子、变量清单、训练时间窗口、阈值和样本外结果,才知道性能提升来自算法,还是来自偶然的数据切分。

11

什么是可解释机器学习,为什么金融场景尤其需要它?

可解释机器学习(Explainable Machine Learning, XAI)不是把复杂模型说得“听起来合理”,而是用可检查的证据回答:模型为什么给出这个概率、它主要使用了什么信息、结论在不同群体和不同时间是否稳定。

全局解释:模型整体依赖什么

例如随机森林显示债务收入比和流水变化的重要性较高。这只是“在这份训练数据中,模型经常用到它们”的描述,不应被误读为“它们是违约的因果原因”。

变量清单

每个变量从哪里来、是否授权

整体规律

重要性、部分依赖、稳定性

分组检查

不同地区、行业、客群是否失真

局部解释:这一次为什么进入复核

对某一位申请人,解释应指出:哪些已授权的事实推动了复核概率上升或下降,例如近期逾期、债务收入比、流水变化;并说明这是模型线索,不是最终裁决。

模型概率

先报告概率与使用阈值

主要因素

展示相对贡献和数据来源

人工处理

核验、补充材料、申诉路径

为什么集成学习仍然可以解释?

单棵树最直观:可以沿着一条路径读到叶子。集成学习不再是一条规则,而是很多规则的组合,因此需要把解释拆成不同层次:

  1. 结构层:说明训练样本、变量来源、阈值、时间窗口与模型版本。
  2. 整体层:看置换重要性、部分依赖图、不同时间段的性能,理解模型普遍依赖什么。
  3. 个体层:用SHAP等方法说明一个样本的各变量如何把预测从基线推高或拉低。
  4. 治理层:检查缺失值、漂移、不同群体的错误率、人工复核反馈,并保留可追溯记录。

解释不是为了给模型找理由,而是为了发现错误:若模型高度依赖一个不该使用的变量、某地区误报异常高,或某变量在时间外失效,解释应触发修订甚至停用。

金融中的底线

预测能力不等于可以自动做出不利处理。可解释性帮助业务人员、客户与审查人员理解系统,但不能替代合法授权、公平评估、人工复核和申诉机制。

普惠金融例子

若模型发现“数字支付活跃度低”与复核概率相关,不能直接推出“不常用数字支付的人更不可信”。还要考虑地区网络条件、年龄结构、经营形态和数据覆盖差异。

检查

解释一个模型前,先问:该变量是否与目标时间一致?是否获得授权?它是否可能替代敏感身份信息?若回答不清楚,先不要进入模型。

12

反欺诈中的模型融合

  • 欺诈样本稀少且策略快速变化,应结合监督模型、异常检测、规则引擎和人工复核队列。
  • Isolation Forest 适合发现少数异常点,但不能直接等同于欺诈标签。异常只是风险线索,需要结合业务证据。
  • 高级融合策略包括加权投票、Stacking、分层模型和成本敏感学习。关键是把误报成本、漏报成本和审核资源纳入目标函数。
怎么理解

反欺诈不要指望一个模型解决全部问题。更实际的架构是规则、异常检测、监督模型和人工审核共同工作。

普惠金融例子

某账户突然在夜间出现多笔异地小额转账,异常检测会报警;但是否欺诈还要结合设备、关系网络和客户确认结果。

课堂追问

为什么异常不等于欺诈?误把异常都当欺诈会产生什么后果?