简介:这份资源面向模式识别课程学习者和金融风控入门者,围绕银行信用卡场景完整落地四类评级模型:申请人评级、行为评级、收款(催收)评级与欺诈评级,帮助读者理解从信用违约预测到催收策略、欺诈识别的全流程建模思路。压缩包共18个文件,约7.67MB,包含8个csv数据文件、4个Python源码、4个pkl模型文件,以及字体与说明文档,覆盖数据、代码与训练产物,便于直接复现与二次修改。项目按数据预处理与分析、模型构建、模型评估与优化三阶段推进,涉及缺失值、重复值、异常值处理、特征选择与可视化,并采用降维和随机森林等机器学习算法,通过交叉验证测试准确度与稳定性。已有130人学习,适合作为课程设计、实验报告或风控建模练手参考,能帮助读者掌握评级模型的设计逻辑、评估方法与改进方向。
1. 从一份银行风控课设说起:四个评级模型到底在评什么
信用卡风控不是单点判断,而是一条从申请到催收的完整链路。这份模式识别课程设计把链路拆成了四个模型:申请人评级、行为评级、收款评级(催收评级)、欺诈评级。每个模型解决一个独立问题,但共享同一套数据预处理和评估框架。申请人评级看的是“这个人还没拿到卡,该不该批”;行为评级看的是“已经持卡的人,未来会不会逾期”;收款评级看的是“已经逾期的人,该用什么力度催”;欺诈评级看的是“这笔申请是不是假的”。四个模型串起来,覆盖了信用卡生命周期的主要风险节点。
资源包里是 Python 源码加文档说明,目录结构清晰:Fraud_rating.py、applicant_rating.py、Collection_rating.py、Behavior_rating.py四个脚本对应四个模型,model目录放训练产物,file_processed放预处理后的数据,file_list是原始文件清单,SimHei.ttf用来解决 matplotlib 中文显示问题。技术栈是 MindSpore 加 scikit-learn 混合,随机森林是主力算法,降维处理用在特征选择阶段。适合正在做模式识别课程设计的学生,也适合想了解银行风控建模全流程的 Python 开发者。下面按“数据怎么洗 → 模型怎么搭 → 坑怎么避 → 怎么验证”的顺序拆一遍。
2. 数据预处理与特征工程:从原始字段到模型可吃的矩阵
2.1 缺失值、重复值与异常值的处理顺序
拿到银行信用卡数据,第一件事不是急着往模型里灌,而是把数据洗干净。这份课设的预处理流程写在文档说明里,顺序是:理解数据 → 缺失值处理 → 重复值处理 → 异常值检测与处理 → 特征选择 → 数据可视化。顺序不能乱,因为缺失值填充会改变分布,如果先做异常值检测,填充后的值可能被误判为异常。
缺失值处理分两类:数值型字段用中位数填充,类别型字段用众数填充。中位数比均值抗异常值,众数在类别不平衡时更稳。重复值直接去重,但要注意:银行数据里同一个人可能有多条申请记录,去重前得确认业务主键是什么。异常值检测用 IQR 方法,超出 Q1-1.5IQR 和 Q3+1.5IQR 范围的值标记为异常,但不要直接删,先看业务含义——有些“异常”其实是高净值客户的大额交易。
import pandas as pd import numpy as np # 读取原始数据 df = pd.read_csv('file_list/credit_data.csv') # 缺失值:数值型用中位数,类别型用众数 num_cols = df.select_dtypes(include=[np.number]).columns cat_cols = df.select_dtypes(include=['object']).columns for col in num_cols: df[col].fillna(df[col].median(), inplace=True) for col in cat_cols: df[col].fillna(df[col].mode()[0], inplace=True) # 重复值:按业务主键去重 df.drop_duplicates(subset=['applicant_id'], keep='first', inplace=True) # 异常值:IQR 标记,不直接删 for col in num_cols: Q1 = df[col].quantile(0.25) Q3 = df[col].quantile(0.75) IQR = Q3 - Q1 lower = Q1 - 1.5 * IQR upper = Q3 + 1.5 * IQR df[f'{col}_outlier'] = ((df[col] < lower) | (df[col] > upper)).astype(int)这段代码的逻辑是:先填充缺失值保证数据完整,再去重保证样本独立,最后用 IQR 标记异常值而不是删除。参数上,keep='first'保留第一条记录,适合申请场景;如果是行为数据,可能要保留最新一条。1.5是 IQR 的经典系数,调到 3 会放宽异常判定,适合长尾分布明显的字段。
2.2 特征选择与降维:为什么随机森林之前要先做这一步
原始字段可能有几十上百个,直接扔进随机森林不是不行,但训练慢、解释性差、容易过拟合。课设里用了降维处理,常见做法是先用方差过滤去掉低方差特征,再用皮尔逊相关系数剔除高度共线的字段,最后用随机森林的 feature_importances_ 做一轮筛选。
from sklearn.feature_selection import VarianceThreshold from sklearn.ensemble import RandomForestClassifier # 方差过滤:去掉取值变化极小的特征 selector = VarianceThreshold(threshold=0.01) X_selected = selector.fit_transform(X) # 随机森林特征重要性 rf = RandomForestClassifier(n_estimators=100, random_state=42) rf.fit(X_selected, y) importances = pd.Series(rf.feature_importances_, index=selector.get_feature_names_out()) top_features = importances.nlargest(20).index.tolist()threshold=0.01是经验值,低于这个方差的特征对模型几乎没贡献。n_estimators=100是随机森林的常用起点,树太少不稳定,树太多训练慢,100 到 300 之间比较平衡。top_features取前 20 个,具体数量看业务能接受多少字段——申请人评级通常 15 到 25 个特征就够,欺诈评级可能需要更多。
注意:降维不是必须的,如果数据量小、特征少,直接上随机森林也能跑。课设里做降维是为了讲清楚流程,实际项目中要看数据规模和业务解释成本。
3. 四个评级模型的构建:从申请人到欺诈的代码落地
3.1 申请人评级模型:二分类与阈值调整
申请人评级本质是二分类:违约或不违约。课设用随机森林做基模型,输出概率后按阈值切分。默认阈值 0.5 在风控场景往往不合适,因为违约样本少,模型倾向于预测“不违约”。常见做法是调低阈值提高召回率,宁可错杀不可放过。
from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, roc_auc_score # 假设 X 是特征矩阵,y 是标签(1=违约,0=正常) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y ) rf_applicant = RandomForestClassifier( n_estimators=200, max_depth=10, min_samples_leaf=5, class_weight='balanced', random_state=42 ) rf_applicant.fit(X_train, y_train) # 预测概率并调整阈值 y_prob = rf_applicant.predict_proba(X_test)[:, 1] threshold = 0.35 # 低于默认 0.5,提高违约召回 y_pred = (y_prob >= threshold).astype(int) print(classification_report(y_test, y_pred)) print('AUC:', roc_auc_score(y_test, y_prob))class_weight='balanced'让模型自动调整类别权重,缓解样本不平衡。max_depth=10控制树深,防止过拟合。min_samples_leaf=5保证叶子节点至少 5 个样本,避免模型记住噪声。阈值 0.35 是示例,实际要用验证集画 KS 曲线或 PR 曲线来定。AUC 是评估排序能力的核心指标,风控里通常要求 0.75 以上。
3.2 行为评级模型:时间窗口与滑动特征
行为评级看的是历史还款记录、逾期次数、额度使用率等。和申请人评级最大的区别是:行为数据有时间维度,不能简单把每个月的记录当独立样本。课设里的做法是构造滑动窗口特征,比如“过去 3 个月平均逾期天数”“过去 6 个月最大逾期次数”。
# 假设原始行为表有 customer_id, month, overdue_days, credit_usage behavior = pd.read_csv('file_processed/behavior_data.csv') # 按客户分组,构造滑动窗口特征 behavior['overdue_3m_avg'] = behavior.groupby('customer_id')['overdue_days'].transform( lambda x: x.rolling(3, min_periods=1).mean() ) behavior['overdue_6m_max'] = behavior.groupby('customer_id')['overdue_days'].transform( lambda x: x.rolling(6, min_periods=1).max() ) behavior['usage_3m_std'] = behavior.groupby('customer_id')['credit_usage'].transform( lambda x: x.rolling(3, min_periods=1).std() ) # 取每个客户最后一条记录作为当前状态 latest = behavior.sort_values('month').groupby('customer_id').tail(1)rolling(3)表示 3 个月窗口,min_periods=1保证第一个月也有值。transform保持原表行数不变,方便后续合并。取最后一条记录是因为行为评级预测的是“未来会不会逾期”,用最新状态最合理。如果直接用所有月份的数据训练,会引入时间泄漏——用未来的信息预测过去。
3.3 收款评级模型:多分类与催收策略映射
收款评级(催收评级)的输出不是简单的“违约/不违约”,而是“该用什么力度催”。课设里把它设计成多分类:轻度催收、中度催收、重度催收。标签来自历史催收记录中的实际措施和回收率。
from sklearn.ensemble import RandomForestClassifier from sklearn.preprocessing import LabelEncoder # 标签编码:轻度=0,中度=1,重度=2 le = LabelEncoder() y_collection = le.fit_transform(collection_labels) rf_collection = RandomForestClassifier( n_estimators=150, max_depth=8, min_samples_leaf=10, random_state=42 ) rf_collection.fit(X_collection_train, y_collection_train) # 预测并映射回策略 y_pred_collection = rf_collection.predict(X_collection_test) strategy_map = {0: '短信提醒', 1: '电话催收', 2: '上门催收'} predicted_strategies = [strategy_map[i] for i in y_pred_collection]max_depth=8比申请人模型更浅,因为催收策略的决策边界不宜太复杂,否则业务方没法解释。min_samples_leaf=10保证每个策略至少覆盖足够样本。多分类的评估不能用 AUC,要用混淆矩阵和 macro-F1。实际落地时,还要考虑催收成本——重度催收成本高,模型不能把所有客户都推到重度。
3.4 欺诈评级模型:不平衡样本与代价敏感
欺诈样本通常极少,可能只占 1% 到 5%。直接用随机森林会偏向多数类。课设里的处理方式是组合采样加代价敏感学习。常见做法是 SMOTE 过采样少数类,同时设置 class_weight 为 balanced_subsample。
from imblearn.over_sampling import SMOTE from sklearn.ensemble import RandomForestClassifier # SMOTE 过采样 smote = SMOTE(random_state=42) X_fraud_res, y_fraud_res = smote.fit_resample(X_fraud_train, y_fraud_train) rf_fraud = RandomForestClassifier( n_estimators=300, max_depth=12, min_samples_leaf=3, class_weight='balanced_subsample', random_state=42 ) rf_fraud.fit(X_fraud_res, y_fraud_res) # 欺诈场景更关注召回率 y_fraud_prob = rf_fraud.predict_proba(X_fraud_test)[:, 1] y_fraud_pred = (y_fraud_prob >= 0.3).astype(int)SMOTE在少数类样本之间插值生成新样本,缓解不平衡。balanced_subsample是在每棵树训练时动态调整权重,比全局 balanced 更细。阈值降到 0.3 是为了提高欺诈召回——漏掉一个欺诈客户的损失远大于误判一个正常客户。但阈值不能无限低,否则误报太多,人工审核扛不住。
提示:SMOTE 要在训练集上做,测试集保持原始分布。如果在全量数据上过采样再划分,测试集会被污染,评估结果虚高。
4. 模型评估与调参:交叉验证、KS 与稳定性检查
4.1 交叉验证与分层采样
课设里用了交叉验证评估模型稳定性。风控数据类别不平衡,必须用 StratifiedKFold 保证每折的类别比例一致。
from sklearn.model_selection import StratifiedKFold, cross_val_score skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) scores = cross_val_score(rf_applicant, X, y, cv=skf, scoring='roc_auc') print('AUC 均值:', scores.mean(), '标准差:', scores.std())n_splits=5是常用折数,数据量小可以调到 10。shuffle=True打乱顺序,避免数据按时间排序导致的偏差。scoring='roc_auc'关注排序能力,比准确率更适合不平衡场景。标准差大于 0.05 说明模型不稳定,要检查特征或增加数据。
4.2 KS 值与风控专用指标
AUC 是通用指标,风控里更常用 KS(Kolmogorov-Smirnov)。KS 衡量的是好客户和坏客户累计分布的最大差值,值越大区分能力越强。一般 KS 大于 0.3 算可用,大于 0.4 算不错。
from scipy.stats import ks_2samp def calculate_ks(y_true, y_prob): good = y_prob[y_true == 0] bad = y_prob[y_true == 1] ks_stat, _ = ks_2samp(good, bad) return ks_stat ks = calculate_ks(y_test, y_prob) print('KS:', ks)ks_2samp直接算两组分布的 KS 统计量。好客户和坏客户的概率分布差距越大,KS 越高。如果 KS 低于 0.2,模型基本不可用,要回头检查特征工程。
4.3 模型对比与选型依据
课设里四个模型都用了随机森林,但实际项目中可以对比逻辑回归、XGBoost、LightGBM。随机森林的优势是解释性好、不容易过拟合、对缺失值不敏感;劣势是训练慢、对高维稀疏数据表现一般。逻辑回归可解释性最强,但拟合非线性关系能力弱。XGBoost 和 LightGBM 在结构化数据上通常表现最好,但调参复杂。
| 模型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 逻辑回归 | 可解释性强、训练快 | 非线性拟合弱 | 申请人评级基线 |
| 随机森林 | 稳定、抗过拟合 | 训练慢、解释性中等 | 行为评级、欺诈评级 |
| XGBoost | 精度高、支持自定义损失 | 调参复杂 | 竞赛、高精度要求 |
| LightGBM | 速度快、内存占用低 | 小数据容易过拟合 | 大规模行为数据 |
选型没有绝对答案,要看数据规模、业务解释要求和团队技术栈。课设选随机森林是因为它平衡了效果和可解释性,适合教学场景。
5. 避坑与排查:四个模型跑通前最容易翻车的地方
5.1 中文显示乱码:SimHei.ttf 没生效
现象:matplotlib 画图时中文变成方框。原因:系统没有 SimHei 字体,或者代码里没指定字体路径。解决:把SimHei.ttf放到项目目录,在绘图前设置plt.rcParams['font.sans-serif'] = ['SimHei'],并加上plt.rcParams['axes.unicode_minus'] = False解决负号显示问题。如果还不行,用font_manager.FontProperties(fname='SimHei.ttf')显式加载。
5.2 数据泄漏:用未来信息预测过去
现象:模型在测试集上 AUC 0.95,上线后效果暴跌。原因:特征里包含了标签之后的信息,比如用“最终还款状态”预测“是否逾期”。解决:构造特征时严格按时间切分,训练集用 T 时刻之前的数据,测试集用 T 之后的数据。滑动窗口特征只允许用当前及之前月份的数据。
5.3 类别不平衡:准确率虚高但召回率极低
现象:欺诈模型准确率 99%,但一个欺诈客户都没抓到。原因:欺诈样本只占 1%,模型全预测“正常”也能到 99% 准确率。解决:不要看准确率,看召回率、F1 和 AUC。用 SMOTE 过采样或 class_weight 调整权重。阈值从 0.5 降到 0.3 甚至 0.2,根据业务能承受的误报率来定。
5.4 特征共线性:随机森林也会受影响
现象:两个高度相关的特征同时入模,特征重要性被稀释,模型解释性变差。原因:随机森林虽然对共线性不如线性模型敏感,但共线性特征会分摊重要性,导致业务方看不懂。解决:计算皮尔逊相关系数矩阵,相关系数大于 0.8 的特征只保留一个。或者用 VIF(方差膨胀因子)筛选,VIF 大于 10 的剔除。
5.5 模型保存与加载:路径和版本不一致
现象:训练好的模型保存后,换台机器加载报错。原因:Python 版本、库版本不一致,或者保存路径用了绝对路径。解决:用joblib.dump保存模型,加载时确保 scikit-learn 版本一致。路径用相对路径,或者把模型文件和代码放在同一目录。课设里model目录就是干这个的,但要注意.pkl文件不要跨大版本使用。
6. 从课设到落地:四个模型串成评分卡流水线的技巧
把四个模型单独跑通只是第一步,真正有价值的是把它们串成一条流水线。我的习惯是写一个统一的pipeline.py,按“申请 → 行为 → 催收 → 欺诈”的顺序调用,每个模型输出一个分数,最后加权汇总成客户风险总分。权重不是拍脑袋定的,用逻辑回归在验证集上拟合四个分数到最终违约标签,得到的系数就是权重。
import joblib import numpy as np # 加载四个模型 rf_applicant = joblib.load('model/applicant_rf.pkl') rf_behavior = joblib.load('model/behavior_rf.pkl') rf_collection = joblib.load('model/collection_rf.pkl') rf_fraud = joblib.load('model/fraud_rf.pkl') def risk_pipeline(customer_data): # 四个模型分别输出概率 score_applicant = rf_applicant.predict_proba(customer_data['applicant'])[:, 1] score_behavior = rf_behavior.predict_proba(customer_data['behavior'])[:, 1] score_collection = rf_collection.predict_proba(customer_data['collection'])[:, 1] score_fraud = rf_fraud.predict_proba(customer_data['fraud'])[:, 1] # 加权汇总,权重来自逻辑回归拟合 weights = np.array([0.35, 0.25, 0.20, 0.20]) scores = np.vstack([score_applicant, score_behavior, score_collection, score_fraud]).T final_score = scores @ weights return final_score权重[0.35, 0.25, 0.20, 0.20]是示例,实际要用验证集拟合。申请人评级权重最高,因为申请环节是风险第一道闸门。欺诈评级权重不低,因为欺诈损失通常大于违约损失。这个流水线的好处是每个模型可以独立迭代,换掉一个不影响其他。
验证方法上,我一般会做三件事:第一,用时间外样本测试,比如用 2023 年数据训练,2024 年数据测试;第二,画 KS 和 Lift 曲线,看模型在不同分数段的区分能力;第三,做压力测试,把某个特征人为调高或调低,看分数变化是否符合业务直觉。压力测试最容易发现“模型学了伪相关”的问题。
还有一个技巧:把四个模型的输出分数做分箱,比如等频分 10 箱,然后看每箱的实际违约率是否单调。如果单调性不好,说明模型排序能力有问题,要回头调特征或换算法。这个检查比 AUC 更直观,业务方也更容易理解。
从那以后我每次做完风控模型,都强制走一遍时间外验证和压力测试,不再只看交叉验证的 AUC。课设里的代码是起点,真正落地还要补很多工程细节。希望帮到你。
本文还有配套的精品资源,点击获取