
简介面向银行信用卡业务的风险评估模型设计资料包围绕申请人评级、行为评级、收款催收评级与欺诈评级四个核心模型展开分别基于个人与财务信息、历史还款行为、违约催收场景及欺诈特征识别构建完整风控体系。该资料包适用于模式识别课程设计、金融风控入门或相关竞赛项目实施过程完整覆盖数据预处理与分析、模型构建、模型评估与优化三个阶段涉及缺失值处理、重复值与异常值检测、特征选择、数据可视化并采用降维处理与随机森林等机器学习算法训练模型。包体共18个文件以8个CSV数据文件、4个Python源码、4个训练完成的pkl模型文件为主另含1个字体文件与1个Markdown说明文档压缩包约7.67MB目录结构便于按阶段查阅。目前已吸引130人学习下载读者可依据源码与文档理解各模型的设计思路、评估指标及改进建议快速迁移到自己的风险评估项目中。1. 这套课设难在哪儿四个风险模型对应四个咬合的资金决策时点模式识别课设“面向银行信用卡的风险评估模型设计”最常见的翻车方式是把四个模型当成“四个分类器各训一遍”。申请人评级、行为评级、收款评级催收评级、欺诈评级对应的是资金链上四个决策时点进件批不批、额度调不调、逾期先催谁、申请是真是假。标签定义和特征来源完全不同硬套模板实验成绩好看答辩一问就露馅。这篇笔记按课设验收路径推进先拆四个模型的任务边界与样本设计再给 WOE 分箱、打分卡换算和异常检测的 Python 实现最后集中讲时间切分、样本不均衡、WOE 单调性这几个真正决定模型能不能用的坑。新手可以照步骤完整复现熟手直接看验证与进阶。2. 四个评级模型的分工与样本设计先给每个模型定标签再谈算法课设数据量通常不大四个模型共用一个数据集是常态关键是同一个字段在不同模型里的角色完全不同额度使用率在行为评级里是核心风险特征在收款评级里只是排序参考。这四种分类、回归和异常检测任务正是模式识别课程的典型落点。下面按业务发生时序逐个拆每个模型都要先回答三个问题给谁打分、用什么标签、特征靠什么区分。2.1 申请人评级模型进件入口的信用打分卡申请人评级模型在审批环节起作用回答“这位新客户未来会不会变坏”。样本是所有提交申请的进件客户标签的常见口径是“获批后 12 个月内是否发生一次 90 天以上逾期”。这里有个课设里高频踩坑的点标签观察期的起点是批卡日不是申请日。如果数据里只有申请日很多客户获批后还没满 12 个月观察期根本不够坏样本被误标成好样本模型区分度被严重稀释。特征侧重点在静态资质年龄、收入、职业稳定性、已有负债、持卡数量与总额度、征信查询次数。没有真实征信数据时用公开匿名信贷申请数据也能跑通重点是讲清每个特征的业务含义。算法首选逻辑回归配 WOE 编码原因是打分卡要求每个特征单独可解释答辩时能明确说出“月收入每上升一个分箱分数增加多少”。特征数量控制在 8 到 15 个先用 IV 排序再做相关性去重这一步在 3.1 完成。延伸一点真实业务里申请被拒的客户没有表现期标签直接用通过样本建模会带来拒绝推断偏差。课设里通常不要求解决但文档说明里要写明“模型仅基于历史获批样本开发”这比假装没这个问题要严谨得多。2.2 行为评级模型存量客户按月重算的违约预警行为评级服务于已经批卡的存量客户按月滚动评分回答“未来 3 到 6 个月会不会出现 30 天以上逾期”。特征换成动态行为账单还款率、额度使用率、取现频率、最近 3 个月还款金额的波动、额度调整次数、商户类别集中度。它的建模不是一条样本一个客户而是一个客户多个月份多条样本时间口径是重灾区。观察期和表现期必须严格错位。常见做法是用第 1 到 6 个月的行为特征预测第 7 到 9 个月是否逾期每个月生成一条样本时特征窗口和表现期整体往后滑动。如果直接用 1 到 6 月的消费行为预测 1 到 6 月的逾期特征里已经带了结果信息随机切分时 AUC 会被顶上 0.95按时间切分马上掉到 0.78 左右这就是第四章要展开的时间穿越。算法上逻辑回归仍可用但行为数据非线性强不少课设会加 XGBoost 做对照通常能高 0.02 到 0.04 个点代价是可解释性变差变成一个黑匣子。2.3 收款评级模型逾期账户按回收率排序的催收优先级收款评级模型课程里也叫催收评级模型的触发条件是客户已经逾期回答“这批逾期账户先催谁”。样本是所有逾期账户标签有两种口径分类口径是“未来 90 天是否至少还到当前状态”回归口径是“未来 90 天回收金额 / 当前逾期金额”。建议课设选回归口径因为催收要的不只是能不能收回而是能收回来多少分类模型会把逾期 500 元的账户和逾期 5 万元的账户混在一起排队。特征包含逾期天数账龄 M1 到 M3 分桶、逾期金额、联系成功率、承诺还款但失约的次数、历史催收次数、额度使用率。这类样本有个隐蔽的选择偏差催收次数越多的客户往往是被反复催都收不回的直接把催收次数当特征模型会学到“催得越多越坏”的循环逻辑。课设层面可做的缓解是把催收强度作为控制变量放进特征或者限定同一催收策略下的样本建模并在文档说明里交代这个局限。2.4 欺诈评级模型绕过信用评分的异常检测欺诈评级和前三个模型最大的区别是预测对象从“还不上”变成“压根没打算还甚至身份是伪造的”。欺诈样本在真实业务里通常只有万分之几课设里给出 1% 以内就算慷慨。特征不再是资质而是异常痕迹同一设备短时间多笔申请、申请 IP 频繁跳变、填写的单位电话与征信记录矛盾、首刷交易在深夜高频发生。速度特征是核心比如最近 1 小时同设备申请次数、最近 24 小时同 IP 申请次数这类特征窗口很短不需要跨月历史正好绕开行为模型需要长观察期的限制这也是欺诈模型能独立成模的原因。建模思路分两种欺诈标注足量时用 XGBoost 这类监督模型配合代价敏感训练标注极少时用孤立森林做无监督打分设定拦截线。课设数据量不大时无监督的 AUC 往往不如监督模型但优势是不依赖正样本数量而且业务逻辑好讲。四个模型的整体对比见表 1建议在文档说明开头放这张表评委扫一眼就知道整个项目架构。表 1 四个模型的任务边界对比模型决策时点样本对象典型标签特征类型首选算法申请人评级审批进件新申请客户12 个月观察期 90 逾期静态资质逻辑回归 WOE 打分卡行为评级月度滚动监测存量持卡客户未来 3~6 个月 30 逾期动态行为逻辑回归或 XGBoost收款评级逾期后催收排序已逾期账户回收金额 / 逾期金额逾期 催收过程回归或排序模型欺诈评级申请与首刷阶段申请及异常交易欺诈人工标签关联异常 速度特征XGBoost 或孤立森林3. Python 实现WOE 分箱、打分卡换算与异常检测代码直接可改标题里的 Python 源码部分核心可以拆成三段WOE 编码、打分卡换算、行为与欺诈建模。文档说明部分需要配套变量字典、标签定义和阈值决策表后两处会在第 4、5 章补上。下面所有代码基于 pandas、scikit-learn 和 XGBoost可直接粘贴修改。3.1 WOE/IV 计算分箱、单调性与特征筛选一次做完WOEWeight of Evidence把连续变量分箱后用每箱坏样本占比相对好样本占比的对数替代原始取值输入到逻辑回归中能同时缓解非线性和共线性问题IV 是对每个特征 WOE 贡献的加权求和用来排序筛特征。先给单特征计算函数import numpy as np import pandas as pd def woe_iv(data, feature, target, bins10): 计算单特征的 WOE 与 IV返回分箱明细表。 target 为标签列名约定 1 表示坏样本。 df data[[feature, target]].copy() # 取值太离散的直接按类别分箱连续值优先等频异常时退化为等宽 if df[feature].nunique() bins: df[bin] df[feature].astype(str) else: try: df[bin] pd.qcut(df[feature], qbins, duplicatesdrop) except ValueError: df[bin] pd.cut(df[feature], binsbins) total_bad int(df[target].sum()) total_good len(df) - total_bad # 按箱聚合好/坏样本数 grouped df.groupby(bin, observedTrue)[target].agg([count, sum]) grouped.columns [count, bad] grouped[good] grouped[count] - grouped[bad] # 分子分母都加 1e-6避免某箱坏样本为 0 时取对数报错 grouped[woe] np.log( (grouped[bad] / total_bad 1e-6) / (grouped[good] / total_good 1e-6) ) grouped[iv] ( (grouped[bad] / total_bad - grouped[good] / total_good) * grouped[woe] ) return grouped # 用法示例查看“月收入”的分箱效果 woe_table woe_iv(data, month_income, label, bins8) print(woe_table.sort_values(bin)) print(total IV , round(woe_table[iv].sum(), 4))逻辑说明函数先判断特征是离散还是连续离散值直接按类别分箱连续值优先等频qcut 因为重复分位点报错时退化为等宽。随后按箱聚合计算每箱坏样本率、WOE 和 IV。WOE 为正表示该箱坏样本占比高于总体水平风险偏高反之偏低。参数说明bins 建议取 6 到 10超过 10 后每箱样本太少WOE 波动大。使用阶段先看每一箱的样本量是否大于总体的 1%再看 WOE 是否大体单调最后按总 IV 筛特征IV 小于 0.02 基本可丢0.02 到 0.1 是弱变量0.1 以上是主要区分变量。类别变量里某个类别的 WOE 明显偏离时可以手动合并相近类别这一步是打分卡项目里最花时间的环节。3.2 概率转打分卡基准分 600、PDO50 的刻度换算逻辑回归输出概率但银行风控要的是“分数越高风险越低”的整数刻度。换算公式里有业务定义的基准分和 PDOodds 翻倍对应增加的分数。常见定义是 odds 为 1:1 时给 600 分odds 每翻一倍加 50 分from sklearn.linear_model import LogisticRegression # 训练特征是 WOE 编码后的矩阵每一行是一个样本每一列是一箱的 WOE lr LogisticRegression(C0.1, solverlbfgs, max_iter1000) lr.fit(X_woe_train, y_train) # 刻度定义odds1:1 对应 600 分PDO50 表示 odds 翻倍加 50 分 base_score, pdo 600.0, 50.0 factor pdo / np.log(2) # 约 72.13 offset base_score - factor * np.log(1.0) # 基准 odds 不是 1:1 时按实际值算 def card_score(row, flipFalse): # flipTrue 时把“高 logit高风险”反转为“高分数低风险” logit lr.intercept_[0] np.dot(lr.coef_[0], row.values) if flip: logit -logit return round(offset factor * logit, 1) test_scores X_woe_test.apply(lambda r: card_score(r, flipTrue), axis1)逻辑说明factor 是线性换算的斜率由 PDO 和自然对数 2 算出offset 是平移量由基准分和基准 odds 算出。card_score 里 logit 是逻辑回归的线性部分flip 参数解决方向问题——如果标签 1 是坏样本logit 越大风险越高为了得到“高分低风险”的惯例刻度需要把 logit 取负。参数说明C 是逻辑回归正则化强度课设样本量不大时取 0.1 到 1太小容易欠拟合太大系数被压得过小。基准分和 PDO 本身不改变模型区分度只改变刻度选择多少带点业务上的玄学成分答辩时要说清这是业务约定不是算法推导。很多课设画出来的分数方向反了都是在这里漏了符号。3.3 行为与欺诈模型XGBoost 配时间切分孤立森林做无监督兜底行为评级的核心不是换算法而是切分方式。用时间序列切分训练集永远在验证集之前杜绝时间穿越from xgboost import XGBClassifier from sklearn.model_selection import TimeSeriesSplit # 行为评级先按月份排序再做时间序列切分取最后一次切分训练 X_beh behavior_data.sort_values(month).reset_index(dropTrue) y_beh X_beh[label] X_beh X_beh.drop(columns[month, label]) splits list(TimeSeriesSplit(n_splits3).split(X_beh)) tr_idx, va_idx splits[-1] # 最后一次切分训练集最大 X_tr, X_va X_beh.iloc[tr_idx], X_beh.iloc[va_idx] y_tr, y_va y_beh.iloc[tr_idx], y_beh.iloc[va_idx] # 正样本少时用 scale_pos_weight 做代价补偿 pos_weight (y_tr 0).sum() / (y_tr 1).sum() xgb XGBClassifier( n_estimators200, max_depth4, learning_rate0.05, subsample0.8, colsample_bytree0.8, scale_pos_weightpos_weight, eval_metricauc, n_jobs-1, random_state42, ) xgb.fit(X_tr, y_tr, eval_set[(X_va, y_va)], verboseFalse)逻辑说明先把数据按月份排序再用 TimeSeriesSplit 切分取最后一次切分的索引做训练和验证。这样训练集中最晚的月份仍然早于验证集模拟了模型上线后面对未来数据的真实场景。scale_pos_weight 把正负样本权重压到接近 1:1损失函数里少数类的贡献不会被淹没。参数说明max_depth 取 4 到 6行为数据噪声大树太深容易记住偶然的还款波动learning_rate 0.05 配 200 棵树是个稳妥组合。subsample 和 colsample_bytree 都取 0.8防过拟合的同时不会明显掉 AUC。eval_set 给验证集之后训练过程可以在 verboseFalse 下观察验证集 AUC 曲线验证指标开始抬升的位置就是早停点。欺诈模型在标注稀疏时走无监督路线from sklearn.ensemble import IsolationForest # 欺诈评级欺诈标签稀疏时用无监督异常检测 iso IsolationForest( n_estimators200, contamination0.01, # 按业务先验或真实欺诈率估计 max_features0.8, bootstrapTrue, random_state42, ) iso.fit(X_fraud) # score_samples 越小越异常取负后越大越像欺诈 fraud_score -iso.score_samples(X_fraud) # 有欺诈标注时用 PR 曲线下的 AP 评估拦截能力 from sklearn.metrics import average_precision_score print(AP , round(average_precision_score(y_fraud, fraud_score), 4))逻辑说明IsolationForest 用随机划分特征和阈值的方式隔离异常点异常点路径短score_samples 返回负值且越小越异常取负后就得到越大越可疑的欺诈分数。标注存在时用 average_precision_score 评估排序能力因为欺诈场景关心的是“高分数段能不能拦住大部分欺诈”而不是整体准确率。参数说明contamination 是模型假设的异常比例按业务先验或真实欺诈率估计给大了会误杀正常客户给小了会漏掉欺诈。max_features 限制每棵树用的特征数对高维欺诈特征有稳定效果。无监督模型的分数绝对值没有业务含义上线前必须用带标签的验证集划一条拦截线比如“分数排名前 1% 的申请必查”。4. 避坑排查时间穿越、样本不均衡、WOE 不单调每一条都能毁掉整套模型这章是多次课设验收和实际项目里攒下的血泪经验全部按“现象、原因、解决”写每一条都值得在提交前对照检查一遍。4.1 随机切分让 AUC 虚高近 0.1行为模型必须按月切现象行为评级用 train_test_split(random_state42) 切分验证集 AUC 0.92自我感觉良好改成按月份前 80% 训练、后 20% 验证AUC 掉到 0.78前后对不上。原因行为数据是面板数据同一客户的多个月记录高度自相关。随机切分把后面的月份混进训练集模型等于提前看到了未来还款结果时间穿越让指标虚高。解决先 sort_values(month) 排序再切分或直接用 TimeSeriesSplit。动手建模前先把观察期和表现期写进文档说明否则改标签的时候没有后悔药后面所有实验结果都要重跑。4.2 欺诈样本不到 1%准确率 99% 是假象现象欺诈模型准确率 99.2%逐条检查验证集的欺诈样本一条都没拦下来全部被预测成正常客户。原因类别极度不均衡模型只要全预测多数类就有 99% 准确率损失函数里欺诈样本的贡献被淹没模型实际上什么都没学。解决评估口径换成召回率、PR 曲线和 AP 值业务指标定成“分数最高的前 1% 里覆盖多少欺诈”。训练侧用 scale_pos_weight 或过采样但只对训练集做验证集和测试集必须保持原始分布否则评估结果全部作废。4.3 WOE 不单调、缺失值乱跳打分卡刻度直接失真现象月收入分箱后 WOE 序列是 0.3、0.5、0.2、0.8先升后降再升缺失值单独一箱的 WOE 是 -1.2明显违背收入越高风险越低的常识。原因等频分箱把长尾分布切碎了或者缺失值占比高却没单独处理qcut 把缺失值排除后剩余样本重新划分分箱边界被打乱。解决分箱后先看每箱样本量和坏样本率把 WOE 突变方向的相邻箱合并缺失值单独设一箱单独计算 WOE不要用中位数填充后再分箱缺失本身可能就是风险信号。课设里 WOE 允许末箱小幅回落但要能解释为什么。提示合并分箱只看坏样本率不要为了单调而硬合并样本量差异过大的箱否则模型上线后波动会很大。4.4 收款评级用分类还是回归催收排序结果天差地别现象用“是否回收”训练分类模型得到排序却在实际场景里不可用催收员按分数打了 100 个电话回收金额没有明显提升。原因分类丢掉了金额维度。大额坏账回收概率低但金额巨大小额坏账回收概率高但金额可忽略分类模型把两类混在一起排队排序脱离了业务目标。解决改成回归口径标签是“未来 90 天回收金额 / 当前逾期金额”评估用排序指标。如果坚持分类就把金额作为样本权重并且展示累计回收率曲线而不是只看 AUC这页能直接证明你理解了催收业务。4.5 审批阈值拍脑袋600 分一划拒绝率冲到 60%现象把打分卡阈值定在 600模拟审批发现 60% 申请人都被拒业务不可接受降到 550 后坏账率又超标怎么做都不对。原因阈值不是模型的产物而是业务容量和坏账预算的平衡点。不同数据分布下同一个 600 分的累计通过率差异巨大脱离业务谈阈值就是拍脑袋。解决把分数从高到低排序逐段累计通过率和坏账率反推阈值。课设里这张决策表建议直接放进文档说明作为阈值设定的依据分数区间累计通过率区间坏账率累计坏账率800 分以上12%0.5%0.5%750~80028%1.2%0.9%700~75046%2.8%1.6%650~70068%5.1%2.9%650 以下100%9.3%4.6%按目标累计坏账率不超过 3% 反推阈值可以定在 650 到 700 之间再结合通过率目标微调。这一步做完整套模型的业务闭环就站住了。5. 验证与进阶KS、PSI 之外把四个模型串成一条审批决策流5.1 两段短代码KS 看区分度PSI 看分数稳定性KS 是风控里比 AUC 更常用的区分度指标含义是按分数排序后累计坏样本占比与累计好样本占比的最大间隔PSI 衡量训练集与上线后分数分布的一致性。两段代码都不长def ks_stat(y_true, y_score): KS累计好坏分布的最大间隔 df pd.DataFrame({y: y_true, score: y_score}).sort_values(score) total_bad df[y].sum() total_good len(df) - total_bad df[cum_bad] df[y].cumsum() / total_bad df[cum_good] (1 - df[y]).cumsum() / total_good return round((df[cum_bad] - df[cum_good]).abs().max(), 3) def psi(base, current, bins10): PSI两组分数分布的距离超过 0.25 需要告警 base_q pd.qcut(base, qbins, duplicatesdrop) base_dist base.groupby(base_q).size().sort_index() / len(base) cut base_q.cat.categories cur_dist pd.cut(current, binscut).value_counts().sort_index() / len(current) cur_dist cur_dist.replace(0, 1e-6) # 避免空箱取对数报错 return round(((cur_dist - base_dist) * np.log(cur_dist / base_dist)).sum(), 4)解读课设至少报 AUC 和 KS 两个指标KS 在 0.3 以上算有区分度0.4 以上算优秀。PSI 小于 0.1 表示分布稳定0.1 到 0.25 需要关注超过 0.25 说明上线后客群结构变了要考虑重训。这个习惯是真实项目里每月例行的事写进课设文档会让流程严谨度明显上一个档次。5.2 进阶方向四个模型按业务顺序串成闭环时间富余的话把四个模型按业务顺序串起来申请人评级先做审批通过后入池行为评级每个月给存量客户滚动评分分数跌破阈值触发降额或预警客户真正逾期后收款评级按回收率对逾期账户排序催收资源优先投给排在前面的账户欺诈评级在申请环节和首刷交易两个节点各拦一次。四个模型共用一个特征预处理管道但标签、阈值、训练参数各自独立。我的习惯是给每个模型单独存一份 WOE 分箱表、一份训练参数、一份阈值决策表任何模型调参都不牵连其他三个。这个习惯救过我几次某次课设提交前发现行为模型的标签窗口算错了 1 个月直接改标签重算其他三个模型完全没受影响。打分卡这类项目模型在精不在多能把四个模型的数据口径讲清、阈值决策表拿出来、闭环顺序讲明白已经超过大多数作业了。希望帮到你。本文还有配套的精品资源点击获取