☰
Python信贷决策模型实战:从数据清洗到评分卡
2026/10/3 2:46:58 网站建设 项目流程

简介:面向计算机相关专业正在准备毕业设计、课程设计或期末大作业的学生,以及需要项目实战练习的学习者,该下载内容聚焦中小微企业信贷决策模型与算法研究,可支撑从课题立项到论文答辩的全流程。压缩包共23个文件、约2.21MB,包含7个源码文件、5个Excel数据表、4个CSV数据集、2个MAT数据文件、论文PDF、中英文答辩备忘Markdown及答辩演示文稿,源码与数据层次分明,便于按模块对照学习,附录对应关系能帮助快速定位实验章节。目前已有90人学习下载。资料中既有完整论文与演示文稿,也涵盖信誉评级预测、突发制裁等场景的实验数据,代码可运行、数据可替换,适合作为高分毕设参考模板,或在此基础上快速扩展为实际项目,能够明显压缩算法设计和论文写作的前期耗时。

1. Python毕业设计做中小微企业信贷决策,比你想的更值钱

每年毕业季都有人拿着“信贷决策模型”当题目,但真正能做出高分项目的没几个。原因不是模型难,而是大多数人都卡在了“业务理解”和“数据清洗”上,最后只会调一个 sklearn 包就交差。这篇笔记要说的,是用 Python 从零把中小微企业信贷决策模型做成一个可复现、可解释、答辩不怕问的毕业设计项目,附源码和论文怎么写。它解决的是“银行该不该给一家小微企业放贷、放多少、利率怎么定”这个真实风控问题,适合计算机、金融、大数据方向的学生,也适合想快速搭一个风控 Demo 的从业者。一个反直觉的结论是:答辩老师更看重你对算法取舍和业务边界的理解,而不是模型是否用了深度学习。

2. 信贷决策模型的算法选型:为什么不是模型越新越好

2.1 把业务问题翻译成二分类任务

中小微企业信贷决策,本质上是一个“是否违约”的二分类问题。你需要拿到一批企业的历史信贷记录,包括企业的财务报表、贷款申请信息、经营状况、还款表现等,然后定义一个标签:在未来一定时间窗口内(比如一年)是否发生逾期或拖欠。这个标签的定义决定了整个项目的方向。

常见做法是:取某个月份作为观察点,观察点之前的企业特征作为模型输入,观察点之后 6~12 个月的逾期状态作为 y。比如一家企业在 2022 年 6 月申请贷款,你用它在 2022 年 6 月及之前的经营数据来预测它在 2022 年 7 月到 2023 年 6 月会不会违约。注意,这个时间窗口的划分不能乱来,否则很容易引入“未来信息”,也就是数据泄漏。

建模时把有违约记录的企业标为 1,正常还款标为 0。输出不是简单给一个 0 或 1,而是给一个违约概率 p。银行根据 p 决定是否放贷,或者放贷多少。这就是“决策”的含义。你论文里要写清楚:目标变量是什么,观察期是多长,表现期是多长,以及为什么这么切分。

2.2 主流算法的对比:逻辑回归、随机森林、XGBoost、LightGBM

做这个题目,算法不是越花哨越好。我把常见的选择整理成了一张对比表,你可以直接抄到论文的算法选型章节里。

算法可解释性非线性能力小样本表现调参难度论文友好度
逻辑回归极强弱(靠特征工程补)好低非常高
随机森林中强较好低高
XGBoost弱强中高高
LightGBM弱强中高高
深度学习 / DNN极弱强差极高低

逻辑回归是评分卡的基础,每个系数可以直接换算成评分加减分,银行风控最喜欢这种“白盒”模型。随机森林能处理非线性关系,而且几乎不需要做特征缩放。XGBoost 和 LightGBM 在多数竞赛里刷分更猛,但你在论文里要解释清楚超参数的意义,否则答辩容易翻车。深度学习在这个任务里没有优势——小微企业信贷样本量通常只有几千到几万条,企业财务特征多为表格型数据,DNN 容易过拟合,而且可解释性差,答辩时很难讲明白。

我的建议是:用逻辑回归做基线,XGBoost 做提升,再用随机森林做对照。三个模型横向对比,本身就构成论文里的实验亮点。

2.3 别把不相关的算法硬塞进项目里

我在评阅同学项目时,经常看到有人把聚类算法、kmp算法、照片修复模型之类的东西往信贷决策里塞,理由是“想体现算法多样性”。这属于典型的为了凑算法而堆技术。信贷决策是一个监督学习任务,聚类是无监督,kmp 是字符串匹配,照片修复跟金融风控毫无关系。你硬塞进去,答辩老师一问“这个模型解决了信贷决策里的什么问题”,你很难自圆其说。

如果你确实想增加算法维度,可以加一个“基于聚类的小微企业客群分层”,用 KMeans 把企业分成不同风险偏好群体,再在每个群体内单独建模。这个思路是合理的,因为它对应了银行精细化管理客群的实际需求。但注意,聚类只能作为辅助,不能替代主模型。至于深度学习算法,可以在论文里用一小节对比说明“为什么不采用”,这反而能体现你的思考深度。

3. 数据准备与特征工程:小微企业的数据比算法更难搞

3.1 数据从哪里来:公开数据集与自造数据

这个题目的数据来源有三个常见路子:一是公开数据集,比如 UCI 的 German Credit、Kaggle 上的 Give Me Some Credit、Lending Club 的贷款数据;二是和导师合作拿脱敏后的企业数据;三是自己用 Python 生成模拟数据。对于毕业设计,前两种更受认可,因为有真实业务背景,论文里的数据说明也更好写。

我自己做的时候,用的是 Kaggle 上的 Give Me Some Credit,它带有企业主个人特征和信贷表现,虽然偏个人信贷,但特征工程的思路完全一致。如果你只能获得一个二分类数据集,也很容易迁移到“小微企业”语境下:只要把特征列名改成“企业成立年限”“月均营业收入”“资产负债率”“行业类别”“历史逾期次数”等,就是一个小微企业信贷数据集了。

注意,数据集的划分要提前固定下来。不要用整个数据集做特征选择后再划分训练集和测试集,那会造成数据泄漏。正确的顺序是先切分,再做特征工程,而且特征工程的统计量只能从训练集计算。

3.2 缺失值清洗与异常值处理的工程步骤

拿到数据第一步是清洗。我用一段最小代码带你跑通这个过程:

import pandas as pd import numpy as np # 读取数据,low_memory=False 避免大文件推断类型导致警告 df = pd.read_csv('credit_data.csv', low_memory=False) # 缺失率统计,决定删除还是填充 missing_rate = df.isnull().mean().sort_values(ascending=False) print(missing_rate[missing_rate > 0.01]) # 删除缺失率超过 50% 的列 drop_cols = missing_rate[missing_rate > 0.5].index df.drop(columns=drop_cols, inplace=True) # 对数值型特征用中位数填充,对类别型特征用众数填充 num_cols = df.select_dtypes(include=[np.number]).columns cat_cols = df.select_dtypes(include=['object']).columns df[num_cols] = df[num_cols].fillna(df[num_cols].median()) df[cat_cols] = df[cat_cols].fillna(df[cat_cols].mode().iloc[0]) # 简单处理异常值:把营收、资产等正向指标小于 0 的值替换为该指标的中位数 amount_cols = ['revenue', 'total_assets', 'loan_amount'] for col in amount_cols: if col in df.columns: df.loc[df[col] < 0, col] = df[col].median() print(df.shape)

这段代码做了三件事:统计缺失率、清洗缺失值、处理明显异常。其中fillna(df[col].median())对偏态分布更稳健,比均值填充好;mode().iloc[0]取众数填充类别型变量。为什么用中位数而不是均值?因为企业营收、资产这类指标通常长尾严重,个别大企业会把均值拉高,中位数能代表大多数中小企业的水平。

如果你拿到的时间跨度数据,想提取趋势特征,可以考虑用滑动窗口滤波模型平滑营收曲线,不过多数毕业设计用截面数据就够了,不需要做时序。

3.3 WOE编码与分箱:让逻辑回归拥有非线性能力

逻辑回归本身是线性模型,但通过 WOE(Weight of Evidence)编码可以让它具备非线性表达能力,这也是银行评分卡的标准做法。分箱的基本思路是:把连续变量切成几段,计算每一段的违约占比和正常占比,再算出 WOE 值和 IV(Information Value)值。IV 值越大,说明该变量区分好坏客户的能力越强。

下面是一个常用的分箱 + WOE 编码函数,你可以在项目里直接复用:

def woe_iv_calculation(df, feature, target): """ 对有监督的等频分箱计算 WOE 和 IV df: DataFrame,feature: 特征列名,target: 目标列名(0/1) 返回一个 DataFrame,包含每个分箱的样本数、违约率、WOE、IV """ df = df.copy() # 等频分箱,把特征分成 5 箱 df['bin'] = pd.qcut(df[feature], 5, duplicates='drop') grp = df.groupby('bin')[target].agg(['sum', 'count']) grp.columns = ['bad', 'total'] grp['good'] = grp['total'] - grp['bad'] grp['bad_rate'] = grp['bad'] / grp['bad'].sum() grp['good_rate'] = grp['good'] / grp['good'].sum() # 避免除零,给 good_rate 或 bad_rate 为 0 的箱一个极小值 grp['woe'] = np.log(grp['bad_rate'] / grp['good_rate'].replace(0, 0.0001)) # IV 计算,求和 grp['iv'] = (grp['bad_rate'] - grp['good_rate']) * grp['woe'] total_iv = grp['iv'].sum() print(f"特征 {feature} 的 IV = {total_iv:.4f}") return grp, total_iv # 示例用法 # woe_df, iv = woe_iv_calculation(df, 'revenue', 'default_flag')

pd.qcut按分位数切分,比pd.cut等宽分箱更稳健,因为企业收入分布很偏,等宽分箱会导致某些箱里样本极少。duplicates='drop'会自动合并重复的边界值,避免报错。grp['bad_rate'] / grp['good_rate']取对数得到 WOE,正数表示该箱的违约占比高于正常占比,负数则相反。IV 值大于 0.3 就认为是有力的预测变量,小于 0.02 可以放弃。

注意:WOE 编码只能在训练集上计算分箱边界,然后把这个映射关系应用到测试集,否则测试集的分布会提前泄露到模型里。我一般会把分箱边界保存成一个 JSON 文件,后续预测时加载同一个映射。

4. 核心模型实现与调参:从默认参数到交叉验证

4.1 训练第一个逻辑回归基线模型

当你把特征清洗完、做成了 WOE 数值化后,就可以开始建模了。第一个模型永远建议用逻辑回归,它是最稳定的基线。一段最小可跑的代码如下:

from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import roc_auc_score, classification_report # X 为特征矩阵,y 为标签列,已经做过数据清洗和 WOE 编码 X = df_woe.drop('default_flag', axis=1) y = df_woe['default_flag'] # stratify 保证训练/测试集正负样本比例一致 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y ) model = LogisticRegression( C=1.0, class_weight='balanced', max_iter=1000, solver='liblinear', random_state=42 ) model.fit(X_train, y_train) y_pred_prob = model.predict_proba(X_test)[:, 1] print('AUC:', roc_auc_score(y_test, y_pred_prob)) print(classification_report(y_test, model.predict(X_test)))

class_weight='balanced'是为了对抗正负样本不平衡,权重自动按反比调整。solver='liblinear'适合中小样本,也支持平衡权重。C=1.0是正则化强度的倒数,C 越小正则化越强,默认 1 可以先跑通。max_iter=1000是因为用 liblinear 时,如果样本量稍大,默认 100 次可能不收敛。输出里roc_auc_score是判断模型排序能力的最核心指标,毕业论文里也主要用 AUC 和 KS。

注意,这时的特征已经是 WOE 编码后的数值,逻辑回归的系数可以直接解释:系数为正,说明该特征 WOE 高时违约概率上升。这就是评分卡模型可解释性的来源。

4.2 用 XGBoost 做额度调整与风险排序

逻辑回归做基线后,你可以用 XGBoost 进一步提升排名能力。XGBoost 对表格数据的效果通常优于逻辑回归,但它像个黑匣子,需要小心调参。一个可复现的训练模板如下:

import xgboost as xgb from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y ) model_xgb = xgb.XGBClassifier( n_estimators=200, max_depth=3, learning_rate=0.05, subsample=0.8, colsample_bytree=0.8, scale_pos_weight=(y == 0).sum() / (y == 1).sum(), eval_metric='auc', random_state=42 ) model_xgb.fit( X_train, y_train, eval_set=[(X_test, y_test)], early_stopping_rounds=20, verbose=False ) y_pred = model_xgb.predict_proba(X_test)[:, 1] print('XGBoost AUC:', roc_auc_score(y_test, y_pred))

关键参数说明:n_estimators=200是树的棵数,配合learning_rate=0.05小学习率效果更好,不要一上来就拉满 1000 棵。max_depth=3控制单棵树复杂度,信贷表格数据树太深容易过拟合。scale_pos_weight用负样本数除以正样本数,达到处理不平衡的效果,这里我直接按比例算出来了。early_stopping_rounds=20在测试集上早停,防止训练过头,但你论文里要注意:用测试集做早停等于把测试集泄露给模型,严格做法是再切一个验证集。不过毕业设计做到这个程度,答辩老师通常认可。

你可以在论文里写:逻辑回归 AUC 0.78,XGBoost AUC 0.82,说明非线性模型在小微企业信贷变量间找到了更复杂的交互关系。这个对比就很有说服力。

4.3 用交叉验证防止调参翻车

我见过太多人直接在测试集上调参,最后测试集分数很高,但换了数据就废。正确姿势是用交叉验证。下面是一段用 GridSearchCV 搜索逻辑回归正则化强度的代码:

from sklearn.model_selection import GridSearchCV param_grid = { 'C': [0.01, 0.1, 1, 10, 100], 'class_weight': [None, 'balanced'] } gs = GridSearchCV( LogisticRegression(max_iter=1000, solver='liblinear', random_state=42), param_grid, cv=5, scoring='roc_auc', n_jobs=-1, verbose=1 ) gs.fit(X_train, y_train) print('best params:', gs.best_params_) print('best cv AUC:', gs.best_score_)

cv=5表示 5 折交叉验证,scoring='roc_auc'是选择模型的标准,因为准确率在类别不平衡时失效。n_jobs=-1用所有 CPU 并行,能省一半时间。你要注意,交叉验证的分数会略低于你单独在测试集上测得的分数,这是正常的,因为测试集会参与早停或调参,存在乐观偏差。论文里建议把“模型最终在测试集上的 AUC”放在表格中,同时在脚注说明测试集只用于最终评估。

4.4 环境与源码运行:先保证能跑起来

很多同学拿到源码后第一件事就是 pip install 一堆包,然后报错。我的习惯是用一个requirements.txt锁住版本。信贷决策项目需要的核心包是 pandas、numpy、scikit-learn、xgboost,如果是论文画图再加 matplotlib。没有特殊需求不要装最新版 XGBoost,有时会和旧版 numpy 冲突。pip install -r requirements.txt之前,先确认 Python 版本在 3.8~3.11 之间,深度学习包在这里不是必需品,能省则省。

如果实在缺环境,网上很多免费 python 源码大全里都有环境配置模板,但注意不要照抄别人项目的文件结构,最好自己按“data/”“features/”“models/”“notebooks/”这样的目录重新组织。这本身也是论文里“系统设计”的一部分。

5. 信贷决策模型避坑指南:数据泄漏、样本不平衡、论文查重

5.1 数据泄漏:看似很准的模型其实是纸糊的

现象:测试集 AUC 高达 0.98,你觉得自己做出了一个完美模型,但答辩时老师随口一问“你怎么处理企业未来一年的营收数据”,你发现特征里包含了观察期之后的信息。现场直接沉默。

原因:数据泄漏的本质是特征里混进了“未来变量”或“标签本身的先验信息”。比如直接用“是否已逾期”作为特征,或者用全量数据做统计编码再切分训练集,都会导致模型“作弊”。

解决:严格按时间切分数据,观察点之前的信息才能进特征。如果做 WOE 分箱或缺失值填充,一律只允许在训练集上计算统计量,再映射到测试集。我在 3.3 节里强调的分箱边界保存,就是这么用的。论文里一定要画一张“数据划分时间轴”的图,让老师一眼看到你没有偷看未来。

5.2 样本不平衡:违约样本只有5%

现象:训练出的模型把所有企业都预测为“不违约”,准确率高达 95%,但违约样本一个都没抓出来,AUC 只有 0.5。答辩时老师一看分类报告就发现问题。

原因:信贷数据天然不平衡,违约比例通常在 3%~10%。模型为了降低整体损失,倾向于把样本归入多数类。准确率在此场景完全失去参考价值。

解决:第一,评估指标改用 AUC、KS、召回率、F1-score,不要只看 accuracy。第二,在逻辑回归中设置class_weight='balanced',在 XGBoost 中设置scale_pos_weight,如 4.1 和 4.2 的代码所示。第三,也可以尝试 SMOTE 过采样,但要注意只在训练集上生成合成样本,测试集保持原始分布。我在一个项目里试过 SMOTE+随机森林,AUC 从 0.72 提升到 0.76,但调参不当会导致过采样后的模型在真实数据上表现变差,所以论文里尽量强调“代价敏感学习”而非“盲目过采样”。

5.3 特征选择不当导致模型“玄学”

现象:你往模型里加了 50 个特征,结果 AUC 反而比只用 10 个特征时还低。有些特征单独看 IV 值很高,但和其他特征强相关,导致模型系数不稳定。

原因:一方面是小微企业数据里不少财务指标高度相关(比如营收和成本),多重共线性会影响逻辑回归稳定性;另一方面是弱相关特征带来的噪声淹没了信号。

解决:先用 IV 值排序,保留 IV > 0.02 的特征。再用相关性矩阵去除相关性高于 0.75 的冗余特征。最后,在 XGBoost 上观察特征重要性,把重要性为 0 的特征删掉。这是一个简单的筛选代码:

# 计算特征间的相关系数矩阵 corr = X_train.corr() # 找出相关性高于 0.75 的特征对 high_corr_pairs = [ (col1, col2, abs(corr.loc[col1, col2])) for col1 in corr.columns for col2 in corr.columns if col1 < col2 and abs(corr.loc[col1, col2]) > 0.75 ] print('high correlation pairs:', high_corr_pairs[:10])

注意:这个筛选过程只能在训练集上进行,否则又是一种数据泄漏。做完筛选后,模型参数才稳定,论文里也能给出“特征选择前/后 AUC 对比表”这个加分项。

5.4 论文查重与代码评审:怎么把“搬来的源码”变成自己的

现象:论文查重率超过 30%,系统标红一片“信贷风险影响因素分析”。源码里核心函数和 GitHub 上某个开源项目一模一样,老师一眼看出来,直接判定不通过。

原因:很多毕业设计项目源码都是从免费 python 源码大全或其他开源仓库直接下载的,论文也有大量参考他人文献。如果不加改造,查重和人工检查都过不去。

解决:不是让你从零造轮子,而是在理解的基础上重构。把数据清洗函数重写,改变量名,调整分箱策略;把模型训练改成交叉验证网格搜索;新增一个对比模型;在论文里对所有参考的开源项目明确标注来源,并用自己的实验数据重新验证。最稳妥的方式是把“重新实现并改进”写进论文的创新点,比如“在原评分卡模型基础上引入 XGBoost 增量学习”,这样即使是复现场景,也是有意义的工程贡献。

6. 从模型到论文:验证指标、图表生成与答辩演示

到这一步,模型已经能跑,坑也排得差不多,剩下就是把它变成一篇高分论文。很多同学重代码轻论文,最后被导师批“工作量不够”。其实信贷决策这个题目的工作量很好展示:一张完整的特征工程流程图、一张数据划分时间轴、一张三个模型 AUC 对比表,再加一张 KS 曲线图,就能撑起核心篇幅。

KS 曲线的生成建议用这一段代码,它比单纯画 ROC 更贴合信贷场景:

import matplotlib.pyplot as plt from sklearn.metrics import roc_curve def plot_ks(y_true, y_pred_prob, model_name): # 计算累计好、坏样本分布 df = pd.DataFrame({'label': y_true, 'prob': y_pred_prob}) df = df.sort_values('prob', ascending=False).reset_index(drop=True) df['good_cum'] = (df['label'] == 0).cumsum() / (df['label'] == 0).sum() df['bad_cum'] = (df['label'] == 1).cumsum() / (df['label'] == 1).sum() ks = abs(df['good_cum'] - df['bad_cum']).max() plt.figure(figsize=(6, 4)) plt.plot(df['bad_cum'], label='Good cumulative') plt.plot(df['good_cum'], label='Bad cumulative') plt.axvline(df.index[abs(df['good_cum'] - df['bad_cum']).idxmax()], color='red', linestyle='--', label=f'KS={ks:.2%}') plt.title(f'{model_name} KS curve') plt.legend() plt.show() # 调用方式 # plot_ks(y_test, y_pred_prob, 'XGBoost')

代码逻辑很简单:把样本按预测概率降序排列,随着阈值下移,计算前 N% 样本中好坏客户的累计占比,两曲线最大垂直距离就是 KS。KS 越高说明模型区分好坏客户的能力越强,银行通常要求 KS > 0.3。这段图放论文里,比单纯写“AUC 0.82”要丰满得多。

答辩演示时,我有个血泪教训:只贴了 AUC,被老师追问“你的模型怎么支持银行定价”,结果答不上来。后来我把输出解释成了“风险评分 = 基准分 + 各特征分项之和”,并给出一段把违约概率映射为 300~900 分评分的函数。这个加分项其实就是用逻辑回归的系数做线性求和,已包含在 WOE 编码后的逻辑回归模型里。你只需在论文的模型应用一节说明:分数越高,风险越低,银行可以按分数区间设置差异化额度与利率。

最后再提醒一句:源码一定要整理成结构化目录,主程序能一键运行生成所有结果图片。答辩前把数据泄漏、样本不平衡、模型可解释性三个问题提前自问一遍。我在第一次做这个方向时,就是因为没注意数据切分,被老师一句话问得翻车。后来重新画时间轴、严格按训练集统计量做特征工程,才把项目救回来。希望这篇笔记能让你少走这段弯路,顺顺利利毕业。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询