4、决策树与规则提取:树结构可视化、决策路径分析、规则集生成
决策树这东西,说白了就是风控模型里最“讲人话”的一个。你想想看,一个神经网络给你输出个0.87,你很难跟业务同事解释为什么这个人就是坏客户。但决策树不一样——它能把判断过程一条条列出来,像法官判案一样清晰。
我个人习惯,在项目初期一定会先跑一棵决策树。不是为了追求最高精度,而是为了快速理解数据里的规律。今天我们就聊聊怎么把树“看明白”,以及怎么从树里抽出一套可执行的规则。
4.1 树结构可视化——让模型“长什么样”一目了然
我刚开始做风控时,拿到一棵深度为10的树,直接打印出来有几十页。后来学乖了,只可视化前3到4层。为什么?因为业务人员只看前几层就能理解核心分叉逻辑。
常用的可视化工具,我推荐graphviz配合sklearn的plot_tree。下面这段代码,是我在信贷评分卡项目中经常用的模板:
from sklearn.tree import DecisionTreeClassifier, plot_tree
import matplotlib.pyplot as plt
# 训练一棵浅树,深度设为3
clf = DecisionTreeClassifier(max_depth=3, min_samples_leaf=50, random_state=42)
clf.fit(X_train, y_train)
# 可视化
plt.figure(figsize=(20, 10))
plot_tree(clf,
feature_names=['收入', '负债率', '征信查询次数', '年龄', '工作年限'],
class_names=['好客户', '坏客户'],
filled=True,
rounded=True,
fontsize=12)
plt.show()
这里有个坑,我提醒一下:节点颜色深浅代表纯度。深蓝色节点表示大部分是好客户,深橙色表示坏客户集中。如果某个叶子节点颜色很浅,说明这个分叉没把两类分开——嗯,这时候就要考虑剪枝了。
可视化要点:
- 深度控制在3-4层,超过5层普通人就看不懂了
- 每个节点显示:样本数、类别分布、主导类别
- 用颜色区分好坏客户,一目了然
4.2 决策路径分析——追踪每个样本的“判案过程”
树结构看整体,决策路径看个体。我在做贷后分析时,经常需要回答一个问题:“为什么这个客户被拒了?”这时候就要走决策路径。
每一条从根节点到叶子节点的路径,就是一个完整的判断逻辑。sklearn里有个好用的方法叫decision_path,能返回每个样本经过的节点索引:
# 获取某个样本的决策路径
sample_idx = 0 # 第一个样本
path = clf.decision_path(X_test[sample_idx].reshape(1, -1))
print(path.indices)
# 输出示例:[0, 3, 7, 14]
# 表示这个样本经过了节点0 → 节点3 → 节点7 → 节点14
我曾经遇到过一个案例:一个客户收入很高,征信也好,但被模型判为坏客户。业务同事跑来质疑模型有问题。我顺着决策路径一看,发现他在“负债率”这个节点被分到了高风险分支——原来他刚买了房,短期负债暴增。嗯,这就是决策路径的价值,能让你精准定位到“哪一步出了问题”。
我的小技巧:把决策路径转成自然语言描述。比如“收入>1.5万 → 负债率<40% → 征信查询次数<3次 → 好客户”。这样业务同事一看就懂,不需要任何技术背景。
4.3 规则集生成——把树变成可执行的业务规则
树结构可视化是给人看的,规则集是给系统用的。我在多家银行做过规则引擎对接,发现一个规律:决策树转规则,比直接部署模型更受风控运营欢迎。为什么?因为规则可以手动调整,模型是个黑盒。
规则提取的核心思路很简单:遍历每一条从根到叶子的路径,把路径上的判断条件组合起来。下面是我自己写的一个规则提取函数:
def extract_rules(tree, feature_names):
"""从决策树中提取规则集"""
tree_ = tree.tree_
feature_name = [
feature_names[i] if i != -2 else "undefined!"
for i in tree_.feature
]
rules = []
def recurse(node, depth, conditions):
# 如果是叶子节点
if tree_.feature[node] == -2:
# 获取该节点的主导类别
class_dist = tree_.value[node][0]
dominant_class = class_dist.argmax()
class_name = '好客户' if dominant_class == 0 else '坏客户'
sample_count = int(class_dist.sum())
rules.append({
'conditions': conditions.copy(),
'class': class_name,
'samples': sample_count,
'purity': max(class_dist) / sum(class_dist)
})
return
# 非叶子节点,继续递归
feat = feature_name[tree_.feature[node]]
threshold = tree_.threshold[node]
# 左分支:特征 <= 阈值
conditions.append(f"{feat} <= {threshold:.2f}")
recurse(tree_.children_left[node], depth+1, conditions)
conditions.pop()
# 右分支:特征 > 阈值
conditions.append(f"{feat} > {threshold:.2f}")
recurse(tree_.children_right[node], depth+1, conditions)
conditions.pop()
recurse(0, 0, [])
return rules
# 使用示例
rules = extract_rules(clf, ['收入', '负债率', '征信查询次数', '年龄', '工作年限'])
for r in rules[:5]:
print(f"条件: {' 且 '.join(r['conditions'])}")
print(f"结果: {r['class']} (样本数: {r['samples']}, 纯度: {r['purity']:.2%})")
print("---")
避坑指南:我曾经吃过一次亏——直接拿全量规则去部署,结果发现规则之间互相矛盾。后来我加了一个优先级排序:纯度高的规则优先,样本量大的规则优先。这样规则引擎跑起来才不会打架。
4.4 知识体系总览
下面这张图,是我自己总结的决策树解释性分析的核心逻辑。你可以把它当作本章的“地图”:
4.5 规则质量评估——别让垃圾规则上线
规则提取出来之后,不能直接扔给业务。我一般会做三件事来评估规则质量:
| 评估维度 | 指标 | 我的经验阈值 | 说明 |
|---|---|---|---|
| 覆盖度 | 规则覆盖的样本比例 | > 5% | 覆盖太少,规则没有统计意义 |
| 纯度 | 主导类别占比 | > 80% | 纯度太低,规则区分能力弱 |
| 稳定性 | 跨时间窗口的纯度变化 | < 10% | 变化太大,规则可能已经失效 |
| 可解释性 | 规则中条件数量 | < 5个 | 条件太多,业务人员记不住 |
我的经验:规则不是越多越好。一棵深度为5的树,最多能产生32条规则。但我通常只保留前10条覆盖度最高的规则,剩下的合并或丢弃。为什么?因为业务规则引擎的维护成本很高,每多一条规则,就多一个潜在的维护坑。
4.6 实战中的几个坑
最后,分享几个我在项目中踩过的坑,希望能帮你少走弯路:
- 坑一:树太深,规则爆炸。深度为10的树,理论上能产生1024条规则。实际上大部分规则只覆盖几十个样本,毫无意义。我的建议是:先限制深度,再根据覆盖度过滤。
- 坑二:连续特征分叉太多。比如“收入”这个特征,可能在树里被切了七八次。转成规则后,会出现“收入>1.2万 且 收入<=1.5万”这种奇怪的条件。我一般会做离散化处理,或者合并相邻的分叉。
- 坑三:规则之间互相矛盾。比如规则A说“收入>2万 → 好客户”,规则B说“收入>2万 且 负债率>80% → 坏客户”。这时候要明确优先级:条件多的规则优先级更高,因为更具体。
嗯,决策树和规则提取这块,说白了就是“把模型翻译成人话”。你掌握好可视化、路径分析和规则生成这三板斧,基本上就能应对大部分风控场景的解释性需求了。
公众号:蓝海资料掘金营,微信deep3321