来源:https://mp.weixin.qq.com/s/8s785MfmVznNgQyy38YnWw
世事如茶,初入喉肠涩且苦,再回味时甘且甜
# 概要 #
在笔者金融风控的日常工作中,很多时候需要根据数据集内的诸多特征(有很多其他称呼,比如因子、变量、自变量、解释变量等)来挖掘一些有用的规则和组合策略,在保证通过率的基础上尽可能多的拒绝坏客户。面对成千上万的特征,如何从数据集中找到有效的规则和组合策略,一直以来都是金融风控搬砖工的日常工作。
本文旨在帮助读者快速从高维数据中提取出有效的规则和组合策略。
仓库地址:https://github.com/itlubber/pdtr
博文地址:https://itlubber.art/archives/auto-strategy-mining
pipy包:https://pypi.org/project/pdtr/
零
背景简介
金融场景风险大致可以概括为三种:系统性风险、欺诈风险(无还款意愿)、信用风险(无还款能力),而作为一名风控搬砖工,日常工作中有大量的数据挖掘工作,如何从高维数据集中挖掘出行之有效的规则、策略及模型来防范欺诈风险和信用风险每个搬砖工的基操。本文由笔者基于网上开源的一系列相关知识,结合实际工作中遇到的实际需求,整理得到。
金融搬砖日常工作:配决策、取数据、对需求、搞分析、挖策略、做模型、搞汇报、配决策、~~~,循环往复没完没了。本文旨在为诸位仁兄提供一个便捷、高效、赏心悦目的决策树组合策略挖掘报告,及一系列能够实际运用到风险控制上的策略,同时也希望能够解决诸位在策略挖掘过程中的一些小问题:
- 简单策略挖掘(单个特征)
- 组合策略挖掘(多个特征)
- 决策树可视化
- 分箱图可视化
- 策略在不同数据集上的数据表现
壹
代码说明
00
代码结构
- 代码仓库https://github.com/itlubber/pdtr
- 代码结构
pdtr.| README.md # 说明文档| setup.py # 打包发布文件| LICENSE # 开源协议| requirements.txt # 项目依赖包+—-examples # 演示样例| | combine_rules_cache # 缓存文件| | combine_rules_cache.svg # 缓存文件| | pdtr_samplts.ipynb # 演示样例程序| -—model_report # 模型报告输出文件夹| | 决策树组合策略挖掘.xlsx # 策略挖掘报告| +—-auto_mining_rules # 组合策略可视化存储文件夹| | combiner_rules_0.png # 决策树可视化图片| | ……| -—bin_plots # 简单策略可视化存储文件夹| bin_vars_A.png # 变量分箱可视化图片| ……-—pdtr # PDTR 源码包 template.xlsx # excel 模版文件 excel_writer.py # excel写入公共方法 matplot_chinese.ttf # matplotlib 中文字体 transforme.py # 策略挖掘方法
- 代码说明
example 文件夹中 pdtr_samplts.ipynb 为示例文件,演示了如何使用pdtr包中的方法,model_report 中的文件为运行过程中存储的文件,包括报告文档、决策树、分箱图等。
pdtr 文件夹中的文件为 pdtr 包源码文件。其中,transforme 中封装了策略挖掘的方法 ParseDecisionTreeRules,包含了大部分策略挖掘相关的方法;excel_writer 中封装了操作 excel 文件相关的方法,用以支持格式化报告输出;template.xlsx 为初始化 excel 模版文件;matplot_chinese.ttf 为包中提供的中文字体文件,用以支持 matplotlib 输出的图像中正常显示中文。
- 安装 pdtr
pip install -r requirements.txt -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
Looking in indexes: http://mirrors.aliyun.com/pypi/simple/
......
Installing collected packages: webencodings, six, pytz, colour, zipp, tomli, tinycss2, threadpoolctl, python-dateutil, pyparsing, pycparser, pluggy, pillow, packaging, numpy, kiwisolver, joblib, iniconfig, graphviz, fonttools, exceptiongroup, et-xmlfile, defusedxml, cycler, scipy, pytest, patsy, pandas, openpyxl, importlib-resources, cssselect2, contourpy, cffi, statsmodels, scikit-learn, matplotlib, cairocffi, dtreeviz, category-encoders, CairoSVG
Successfully installed CairoSVG-2.7.0 cairocffi-1.5.1 category-encoders-2.6.0 cffi-1.15.1 colour-0.1.5 contourpy-1.0.7 cssselect2-0.7.0 cycler-0.11.0 defusedxml-0.7.1 dtreeviz-2.2.1 et-xmlfile-1.1.0 exceptiongroup-1.1.1 fonttools-4.39.4 graphviz-0.20.1 importlib-resources-5.12.0 iniconfig-2.0.0 joblib-1.2.0 kiwisolver-1.4.4 matplotlib-3.7.1 numpy-1.22.2 openpyxl-3.0.7 packaging-23.1 pandas-1.5.3 patsy-0.5.3 pillow-9.5.0 pluggy-1.0.0 pycparser-2.21 pyparsing-3.0.9 pytest-7.3.1 python-dateutil-2.8.2 pytz-2023.3 scikit-learn-1.2.2 scipy-1.10.1 six-1.11.0 statsmodels-0.14.0 threadpoolctl-3.1.0 tinycss2-1.2.1 tomli-2.0.1 webencodings-0.5.1 zipp-3.15.0
贰
简单策略挖掘
01
实现思路
简单策略挖掘(即单特征规则挖掘)通过对数据集中的全部特征(或筛选后的特征)进行分析,找到特征最优的cutoff点,以有效的区分好坏样本。目前比较常见方法:等频分箱、等距分箱、卡方分箱、决策树分箱、CART二叉树分箱、最优KS分箱、最大IV分箱、阈值搜索等。
笔者粗浅的认为,从某种意义来讲,简单策略挖掘本质上是在单个特征分箱结果的基础上,选择某箱能够有效区分好坏客户且命中率在可接受范围内、从业务角度有较好解释性的策略。
笔者在前述认知的基础上,对 toad 和 optbinning 包中的分箱功能进行封装,以支持上述大部分常见的分箱方法(后续会新增更多的分箱方案),并基于分箱结果输出每个分箱上的汇总信息,后续结合专家经验可以筛选出很多有效的简单策略,在保证通过率的同时降低坏账成本。
02
相关代码实例
代码说明
使用示例 ```
class ParseDecisionTreeRules:
def __init__(self, target="target", labels=["positive", "negative"], feature_map={}, nan=-1., max_iter=128, output="model_report/决策树组合策略挖掘.xlsx", writer=None, combiner=None):
"""决策树自动规则挖掘工具包
:param target: 数据集中好坏样本标签列名称,默认 target
:param labels: 好坏样本标签名称,传入一个长度为2的列表,第0个元素为好样本标签,第1个元素为坏样本标签,默认 ["positive", "negative"]
:param feature_map: 变量名称及其含义,在后续输出报告和策略信息时增加可读性,默认 {}
:param nan: 在决策树策略挖掘时,默认空值填充的值,默认 -1
:param max_iter: 最多支持在数据集上训练多少颗树模型,每次生成一棵树后,会剔除特征重要性最高的特征后,再生成树,默认 128
:param output: excel 挖掘报告保存的路径, 默认 model_report/决策树组合策略挖掘.xlsx
:param writer: 在之前程序运行时生成的 ExcelWriter,可以支持传入一个已有的writer,后续所有内容将保存至该workbook中,默认 None
:param combiner: 可以传入提前训练好的 combiner,支持 toad.transform.Combiner 和 笔者重写的 Combiner
"""
......
def feature_bin_stats(self, data, feature, rules={}, min_n_bins=2, max_n_bins=3, max_n_prebins=10, min_prebin_size=0.02, min_bin_size=0.05, max_bin_size=None, gamma=0.01, monotonic_trend="auto_asc_desc", desc="", method='chi', verbose=False, ks=False):
"""简单策略挖掘:特征分箱统计信息
:param data: 需要进行挖掘的数据集
:param feature: 传入需要计算的特征名称
:param desc: 特征的中文含义或者其他相关注释内容
:param method: 可以选择的分箱方法 ['dt', 'chi', 'quantile', 'step', 'kmeans', 'cart'], 默认为 chi
:param min_bin_size: 最小样本占比,默认为 0.05
:param max_bin_size: 最大样本占比,传入 None 时不限制,默认为 None
:param min_n_bins: 最小分箱数,默认 2
:param rules: 自定义分箱规则,默认为 {}
:param verbose: 是否开启调试模式,会答应更多东西,默认为 False
:param ks: 是否输出特征每个分箱上的分档ks指标,默认为 False
:param max_n_prebins: method 为 cart 时支持,预分箱时的最大分箱数,默认 10
:param min_prebin_size: method 为 cart 时支持,预分箱时的最小样本占比,默认为 0.02
:param max_n_bins: method 为 cart 时支持,最大分箱数,传入 None 时不限制,默认为 3
:param gamma: method 为 cart 时支持,正则化参数,默认 0.01
:param monotonic_trend: method 为 cart 时支持,特征分箱的模式,支持 optbinning 中的所有 monotonic_trend, 默认 auto_asc_desc,即单增或单减
:return: pd.DataFrame, 特征分箱的统计信息
"""
def bin_plot(feature_table, desc="", figsize=(10, 6), colors=["#2639E9", "#F76E6C", "#FE7715"], max_len=35, save=None):
"""简单策略挖掘:特征分箱图
:param feature_table: 特征分箱的统计信息表,由 feature_bin_stats 运行得到
:param desc: 特征中文含义或者其他相关信息
:param figsize: 图像尺寸大小,传入一个tuple,默认 (10, 6)
:param colors: 图片主题颜色,默认即可
:param save: 图片保存路径
:return Figure
"""
def query_feature_rule(self, data, feature, desc="", bin_plot=False, figsize=(10, 6), save=None, *args, **kwargs):
"""传入数据集和其中一个特征名称,输出简单策略挖掘统计信息
:param data: 数据集
:param feature: 特征名称
:param desc: 特征中文含义或其他相关信息
:param bin_plot: 是否可视化特征分箱图
:param figsize: 图像的尺寸
:param save: 图像保存的路径
:return: pd.DataFrame, 特征分箱的统计信息
"""
- 使用示例
%matplotlib inline
import matplotlib.pyplot as plt
pdtr_instance.init_setting()
for col in data.columns.drop(“target”): featuretable_train = pdtr_instance.feature_bin_stats(train, col, desc=”训练数据集”, ks=True, min_bin_size=0.15, max_bin_size=3, method=”dt”) feature_table_test = pdtr_instance.feature_bin_stats(test, col, desc=”测试数据集”, ks=True, rules=pdtr_instance.combiner.rules[col]) display(feature_table_train) display(feature_table_test) fig = pdtr_instance.bin_plot(feature_table_train, desc=col, save=f”model_report/bin_plots/bin_vars{col}.png”) plt.show()
# train_feature_table = pdtr_instance.query_feature_rule(data, col, desc="单变量策略挖掘", bin_plot=True, ks=True, min_bin_size=0.15, max_bin_size=3, method="dt")
# display(train_feature_table)
![](https://cdn.nlark.com/yuque/0/2023/png/21930551/1685595062874-76c82fbb-13c0-4ff8-a121-ea2d75fb51a0.png#averageHue=%23224e53&clientId=u92209ee6-b6fc-4&from=paste&id=uf398266f&originHeight=850&originWidth=1080&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ubb41f7a2-bd8b-4c34-8323-601e051c4d6&title=)
叁<br />** 组合策略挖掘 **<br />01<br /> 实现思路 <br />组合策略挖掘本质上是将多个弱特征集成为一个强特征,通过综合利用几个维度的信息来更有效的抓取坏客户。从某种角度来讲,组合策略也可以看作是引入了非线性,故而通常挖掘出来的组合策略具有很高的 LIFT值,但缺点也很明显,很容易造成过拟合。在实际工作过程中,如何权衡过拟合带来的策略泛化能力不足和提升历史回溯数据集上各项指标,也是一个很考验风控人是否优秀的一个维度。<br />组合策略挖掘在业界常规来讲有几种方式:
- 基于德尔菲法组合特征构建策略
- 基于AHP对多个特征进行组合构建策略
- 基于决策树的组合策略挖掘方法
- 基于统计学的组合策略挖掘方法
- ......
本文主要就笔者最常用的基于决策树的组合策略挖掘方法进行演示,其他方法可自行探索。如果是两个变量组合,也可以参考 optbinning 中 OptimalBinning2D 进行实现。<br />基于决策树的组合策略挖掘方法实现思路如下:
- 设置树的最大深度为最多需要组合特征个数
- 初始化决策树,初始化叶子结点最小样本占比、样本个数等限制
- 在训练数据集上训练决策树模型
- 通过遍历树模型每一条路径上的节点,拼接成组合策略(能够直接支持 pandas dataframe query 语句进行筛选数据)
- 根据路径末端叶子结点上的好坏样本个数、样本总数统计该组合策略的各项统计指标
- 通过pandas的query语句在其他数据集上查询满足策略的样本并统计各项指标
- 输出组合策略挖掘报告
02<br /> 相关代码实例
- 代码说明
class ParseDecisionTreeRules: …… def fit(self, x, y=None, max_depth=2, lift=0., max_samples=1., min_score=None, verbose=False, args, *kwargs): “””组合策略挖掘
:param x: 包含标签的数据集
:param max_depth: 决策树最大深度,即最多组合的特征个数,默认 2
:param lift: 组合策略最小的lift值,默认 0.,即全部组合策略
:param max_samples: 每条组合策略的最大样本占比,默认 1.0,即全部组合策略
:param min_score: 决策树拟合时最小的auc,如果不满足则停止后续生成决策树
:param verbose: 是否调试模式,仅在 jupyter 环境有效
:param kwargs: DecisionTreeClassifier 参数
"""
def insert_all_rules(self, val=None, test=None):
"""组合策略插入excel文档
:param val: 验证数据集
:param test: 测试数据集
:return: 返回每个数据集组合策略命中情况
"""
- 使用示例
import numpy as np import pandas as pd from sklearn.model_selection import train_test_split
feature_map = {} n_samples = 10000 ab = np.array(list(‘ABCDEFG’))
data = pd.DataFrame({ ‘A’: np.random.randint(10, size = n_samples), ‘B’: ab[np.random.choice(7, n_samples)], ‘C’: ab[np.random.choice(2, n_samples)], ‘D’: np.random.random(size = n_samples), ‘target’: np.random.randint(2, size = n_samples) })
train, test = train_test_split(data, test_size=0.3, shuffle=data[“target”])
pdtr = ParseDecisionTreeRules(target=”target”, feature_map=feature_map, max_iter=8) pdtr.fit(train, lift=3., max_depth=2, max_samples=0.1, verbose=False, min_samples_split=8, min_samples_leaf=5, max_features=”auto”) pdtr.insert_all_rules(test=test) pdtr.save() ```
肆
参考资料