
简介面向零基础入门金融风控的学习者一个基于Python的贷款违约预测项目包含完整源码与项目说明可直接用作金融风控方向的课程设计或期末大作业。项目经导师指导并获97分高分覆盖数据探索、特征工程、模型训练与集成融合等多个核心环节适合希望快速上手完整赛题方案的初学者参考。资源共10个文件压缩包约380KB主要包含6个Python脚本分别封装LightGBM、XGBoost、CatBoost与集成模型等算法、1份EDA探索性分析Notebook、1份Word版项目说明手册以及特征重要性可视化图等目录结构清晰便于按模块阅读。目前已有131人学习下载。下载即可运行配合说明文档可以快速理解特征构造思路、模型参数调优与结果评估逻辑既能用于课程设计报告撰写也能支撑期末项目答辩演示。1. 零基础入门贷款违约预测这份 Python 风控源码包里到底有什么做金融风控的期末大作业最怕的不是模型不会调而是从零搭一套完整流程数据清洗、特征工程、三四个模型对比、融合、写报告每一步都是一整夜的坑。这份零基础入门金融风控的贷款违约预测源码包把这条路直接铺平了——解压之后你能看到gen_feas.py特征生成、lgb.py/xgb.py/catboot.py三个单模型、ensemble.py融合脚本、EDA.ipynb探索分析外加一份 97 分项目说明手册。它不是只有训练脚本的玩具代码而是把特征、建模、评估、文档串起来的完整工程。适合三类人要交课程设计的本科生、想快速入门金融风控建模的转行者、需要一套 baseline 跑对比实验的研究生。2. 先把特征工程做厚EDA 与 gen_feas.py 里的衍生特征套路2.1 EDA.ipynb 先看什么分布、缺失与违约率的单调性拿到贷款数据第一步不是建模而是先理解数据长什么样。这个项目的EDA.ipynb是我见过比较务实的探索流程核心就四件事看形状和类型、看数值分布、看缺失情况、按违约标签分组看特征差异。其中最关键的一步是看每个特征分箱后违约率的变化趋势——如果某个特征从低分段到高分段违约率是单调递增的这个特征进模型基本稳了如果忽高忽低没有规律多半是噪音或者分箱方式不对。# EDA.ipynb 里的核心检查片段 import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(loan_data.csv) print(df.shape) print(df.dtypes.value_counts()) # 按贷款金额分箱观察违约率变化 df[amount_bin] pd.qcut(df[loan_amount], q5, duplicatesdrop) grouped df.groupby(amount_bin, observedTrue)[is_default].mean() print(grouped) # 缺失率超过 40% 的列列出来 missing_ratio df.isnull().mean() print(missing_ratio[missing_ratio 0.4])这段代码的逻辑是用qcut把连续变量切成五档然后算每档的违约率均值。observedTrue这个参数是 pandas 2.x 处理分类分箱时防止警告用的老版本可以不加。qcut和cut的差别在于前者按分位数切样本量更均匀后者按固定数值区间切适合有业务含义的边界。看违约率曲线时我会额外画一张折线图比打印数值直观得多。2.2 gen_feas.py 拆解缺失值插补与衍生特征的一段可复用代码gen_feas.py是这套源码里含金量最高的文件因为期末作业的分数差距基本都拉在这里。贷款风控场景里有一批固定套路衍生特征负债收入比每月债务支出除以月收入、信用历史长度、利率与信用分的交叉项以及无抵押记录这种有意义的缺失。这些特征在业务上都有明确解释——负债收入比衡量还款压力信用历史长度衡量信用积累都是银行审批时真正会看的指标。# gen_feas.py 核心片段贷款场景的衍生特征与缺失值处理 import numpy as np import pandas as pd def gen_features(df): # 负债收入比每月债务支出 / 月收入分母加极小值防除零 df[dti] df[monthly_debt] / (df[monthly_income] 1e-6) # 信用历史长度以月为单位 df[credit_history_months] ( df[report_date] - df[first_credit_line_date] ).dt.days / 30 # 有意义的缺失单独成类没有房贷记录的人标记为 0 df[has_mortgage] np.where(df[mortgage_amount].isnull(), 0, 1) df[mortgage_amount] df[mortgage_amount].fillna(0) # 利率与信用分的交叉两者对违约有交互作用 df[rate_score_ratio] df[loan_rate] / (df[credit_score] 1e-6) return df这段代码有三个细节值得抄。第一1e-6加在分母上是防御性编程防止某个样本月收入恰好是 0 导致除零报错或者产生无穷大值。第二has_mortgage这个特征非常重要——没有房贷记录不等于没有贷款能力可能是租房或者无房直接fillna(0)会把这个信息丢掉拆成两个特征反而让模型自己学。第三rate_score_ratio是典型的交叉特征利率高且信用分低的人违约概率显著上升单一特征很难捕捉这种联合效应。做特征的时候我一般会把衍生特征都放一起最后统一做标准化避免量纲影响树模型的分裂效果。2.3 特征筛选的粗筛与精筛相关性矩阵 IV 排序特征做多了之后必须做筛选否则噪音特征会拖低模型表现。这套项目里用的是粗筛加精筛两层粗筛用相关性矩阵去掉相关系数超过 0.8 的冗余特征精筛用 IV 值Information Value排序保留 IV 大于 0.02 的特征。IV 是风控领域衡量特征预测能力的经典指标0.02 以下基本没有区分度0.1 以上算中等0.3 以上算强。# 特征筛选先算相关性矩阵去冗余再算 IV 排序 corr X.corr().abs() upper corr.where(np.triu(np.ones(corr.shape), k1).astype(bool)) high_corr_cols [col for col in upper.columns if any(upper[col] 0.8)] print(high correlation cols:, high_corr_cols) # IV 计算按分箱统计好样本与坏样本分布 def calc_iv(df, feature, target): df df[[feature, target]].dropna() df[bin] pd.qcut(df[feature], q10, duplicatesdrop) grouped df.groupby(bin, observedTrue)[target].agg([sum, count]) grouped[good] grouped[count] - grouped[sum] grouped[bad_rate] grouped[sum] / grouped[count] grouped[woe] np.log((grouped[bad_rate] 1e-6) / (1 - grouped[bad_rate] 1e-6)) iv ((grouped[bad_rate] - (1 - grouped[bad_rate])) * grouped[woe]).sum() return iv相关性矩阵那块代码用了np.triu取上三角避免重复计算 A-B 和 B-A 两组。IV 计算里woe的全称是 Weight of Evidence表示每个分箱里坏样本占比相对于好样本占比的对数差异IV 就是所有分箱 woe 的加权和。注意qcut分箱时如果特征有大量重复值会报错duplicatesdrop就是处理这个的。实际跑的时候IV 排序结果和features_importance.png里 LightGBM 的特征重要性排序通常高度一致如果出现不一致优先怀疑有没有数据泄漏或者特征被重复计算。3. 三份模型脚本读透LightGBM、XGBoost、CatBoost 的调参与差异3.1 lgb.py 的参数面板objective、num_leaves 与早停的配合lgb.py是这套项目的建模主力。LightGBM 在金融风控里是默认首选原因很实在训练速度快、对缺失值天然友好、AUC 表现稳。文件里的参数设置是一个标准的竞赛级配置核心逻辑是给足n_estimators然后用early_stopping_rounds在验证集 AUC 不再提升时自动截断这样既不会欠拟合也不用手动试轮数。# lgb.py 核心训练流程 import lightgbm as lgb from sklearn.model_selection import train_test_split X_tr, X_val, y_tr, y_val train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) model lgb.LGBMClassifier( objectivebinary, metricauc, learning_rate0.05, n_estimators2000, # 给足轮数靠早停收住 num_leaves31, colsample_bytree0.8, subsample0.8, subsample_freq1, reg_alpha0.1, reg_lambda1.0, random_state42, ) model.fit( X_tr, y_tr, eval_set[(X_val, y_val)], eval_metricauc, early_stopping_rounds50, categorical_feature[employment_status], )参数逻辑说明objectivebinary指定二分类任务metricauc让训练过程的评估指标用 AUC 而不是默认的 logloss这是因为贷款违约场景下正负样本通常不平衡AUC 对阈值不敏感更能反映排序能力。learning_rate0.05配合 2000 棵树是常见组合学习率越低需要的树越多但精度不一定线性提升。num_leaves31是 LightGBM 的核心参数控制单棵树的叶子数数值太大容易过拟合太小欠拟合一般从 31 起步调。reg_alpha和reg_lambda分别是 L1 和 L2 正则项目里两个都加了说明作者被过拟合坑过。categorical_feature显式声明类别列这个参数特别关键——如果不告诉 LightGBM 哪些是类别特征它会把类别编码当成连续值切分效果会差一截。3.2 xgb.py 与 catboot.py两套框架的写法差异与效果对比xgb.py和catboot.py是三模型对比的另外两块拼图。XGBoost 是经典方案参数习惯和 LightGBM 不同用max_depth控制树深而不是num_leaves学习率叫eta而不是learning_rate。CatBoost 在文件里拼写成了catboot但实质就是 CatBoost它最大的卖点是能自动处理类别特征不需要手动做 one-hot 编码对高基数类别特征特别友好。参数维度lgb.pyxgb.pycatboot.py树生长方式leaf-wise按损失增益分裂level-wise按层分裂对称树每层所有节点同规则类别特征处理需显式指定 categorical_feature需手动编码或指定自动识别并处理主要调参对象num_leaves, learning_ratemax_depth, etadepth, learning_rate默认缺失值处理自动跳过并按分布归边自动学习缺失方向自动处理训练速度最快大数据集优势明显中等最慢但精度稳定三个模型的写法差异不算大都是 sklearn 风格的Classifier接口但底层逻辑差别明显。XGBoost 按层生长每层所有叶子一起分裂对参数不敏感但树更深LightGBM 按叶子生长只分裂损失增益最大的叶子收敛快但也更容易过拟合所以需要有num_leaves和正则兜底。CatBoost 的对称树每层用完全相同的分裂规则配合 ordered boosting 减少预测偏移在小数据集上往往是最稳的。实操中我一般会让三个模型各自跑一遍看验证集 AUC 谁高就用谁做主要提交另外两个留着做融合素材。3.3 三个模型跑完怎么横向对比AUC 与 KS 一把尺子三个模型脚本都跑完之后不能只看 Accuracy要拿同一把尺子量AUC 和 KS。AUC 衡量的是模型把违约用户排在正常用户前面的概率KS 衡量的是好坏样本累计分布的最大差距。风控报告里这两个指标是标配答辩时老师一定会问。# 模型对比计算 AUC 与 KS from sklearn.metrics import roc_auc_score, roc_curve for name, pred in [(lgb, pred_lgb), (xgb, pred_xgb), (cat, pred_cat)]: auc roc_auc_score(y_val, pred) fpr, tpr, _ roc_curve(y_val, pred) ks max(tpr - fpr) print(f{name}: AUC{auc:.4f}, KS{ks:.4f})KS 的计算方式是用roc_curve返回的 TPR 减去 FPR 取最大值代表模型区分好坏样本的最大能力。金融风控里 KS 大于 0.3 就算可用大于 0.5 已经相当强了。跑对比的时候有个细节三个模型的预测概率分布不完全一样CatBoost 的概率普遍偏保守直接对比绝对值没有意义重点看 AUC 排序。如果某个模型 AUC 明显低于其他两个先检查是不是特征处理不一致比如 XGBoost 没有做类别特征编码、或者categorical_feature没传对。4. ensemble.py 的融合逻辑加权平均与 Blending 的落地代码4.1 为什么单模型到顶之后要融合偏差方差与排名差异单模型调参调到一定程度AUC 基本就卡住了这时候再花时间调参收益很低真正能往上顶一步的是模型融合。背后的原因不玄学三个模型用不同的方式学习数据误差来源不一样LightGBM 偏向局部最优CatBoost 处理类别特征能力强XGBoost 在正则上更稳健把它们的结果加权平均相当于把各自的误差互相抵消一部分。前提是模型之间的相关性不能太高如果两个模型的预测结果完全相同融合就没有意义。实操中我会先看三个模型预测值的相关系数矩阵相关系数低于 0.9 才有融合价值。4.2 ensemble.py 的加权融合代码用验证集权重把 0.89 顶到 0.91ensemble.py做的事情就是在验证集上搜索一组最优权重。常见做法是网格搜索或者用 scipy 的优化器权重范围和步长按模型数量决定。三个模型时权重在 0 到 1 之间穷举步长设 0.1一共 66 组组合跑起来很快。权重调完还有一招是 Blending不直接加权概率而是把三个模型的预测概率拼成新特征再训练一个逻辑回归当第二层模型效果通常比固定权重好一点但更吃数据和调参。# ensemble.py 加权融合在验证集上找最优权重 import numpy as np from sklearn.metrics import roc_auc_score # 三个模型在验证集上的预测概率 pred_lgb model_lgb.predict_proba(X_val)[:, 1] pred_xgb model_xgb.predict_proba(X_val)[:, 1] pred_cat model_cat.predict_proba(X_val)[:, 1] best_auc, best_w 0, None for w1 in np.arange(0, 1.05, 0.1): for w2 in np.arange(0, 1.05 - w1, 0.1): w3 1 - w1 - w2 blend w1 * pred_lgb w2 * pred_xgb w3 * pred_cat auc roc_auc_score(y_val, blend) if auc best_auc: best_auc, best_w auc, (round(w1, 1), round(w2, 1), round(w3, 1)) print(fbest AUC: {best_auc:.4f}, weights: {best_w})权重搜索的逻辑是两层循环枚举 w1 和 w2w3 由1 - w1 - w2算出保证三个权重之和为 1。步长 0.1 对三个模型够了追求更细可以改成 0.05但收益递减。这里必须强调权重只能在验证集上搜不能拿测试集调权重否则就是对测试集过拟合交上去分数会很难看。跑完之后把最优权重存下来对测试集预测时用同一组权重融合。实际经验是融合后 AUC 大概能涨 0.005 到 0.01不要期待翻天覆地的变化但期末作业里这 0.01 可能就是 90 分和 97 分的差距。4.3 融合的边界什么时候融合反而掉分融合不是万能的有三个典型场景会越融越差。第一个是某个单模型特别弱AUC 比其他两个低 0.02 以上此时它提供的更多是噪音权重再小也会拖后腿直接去掉反而更好。第二个是模型之间相关性太高比如 LightGBM 和 XGBoost 用同一份特征同一套参数预测结果相似度 0.95融合等于重复投票。第三个是验证集和测试集分布差异大在验证集上搜出的最优权重在测试集上可能完全失效。判断融合是否有效有一个简单方法在验证集上分别算单模型 AUC 和融合后 AUC融合后明显更高才值得保留否则就是过度工程。5. 期末大作业避坑指南5 个把分数从 80 拉到 97 的关键检查点5.1 坑 1fillna 用全局统计量填充验证集跟着遭殃现象线下验证 AUC 高达 0.93换到测试集直接跌到 0.85百思不得其解。原因在特征工程阶段用了全量数据的均值或中位数填充缺失值验证集和测试集的信息提前混进了训练集这就是典型的数据泄漏。解决先切分数据再填充或者把填充统计量严格限定在训练集上计算。gen_feas.py里我一般会先做训练集和验证集的切分再分别写填充逻辑# 正确的做法填充统计量只从训练集计算 train, val train_test_split(df, test_size0.2, stratifydf[is_default], random_state42) fill_values train[[monthly_income, credit_score]].median() train train.fillna(fill_values) val val.fillna(fill_values)判断有没有泄漏的土办法是抽查训练集和验证集同一特征的分布如果过于接近甚至完全一致就要怀疑了。特别是fillna用了均值这种全局统计量验证集被填进去的值等于训练集均值模型学到的是看到这个值就认为是平均用户测试集上必然翻车。5.2 坑 2预测结果提交的是概率还是 0/1 标签现象模型 AUC 0.90但提交后分数极低或者混淆矩阵怎么看都不对。原因直接用了predict()而不是predict_proba()把模型的分类标签当成概率提交了。贷款违约预测的评估标准通常看 AUC 或 KS这俩依赖预测概率的排序不是类别。解决统一用predict_proba()[:, 1]取正类概率提交文件里保持这个概率列不要转成 0/1。期末作业的说明文档里如果写了用 AUC 评估这一步错就全盘皆输。5.3 坑 3pandas 的 not in 索引错位混淆矩阵没法看现象用train[~train[id].isin(val[id])]切数据后发现行数对不上模型评估时数组长度报错。原因DataFrame 的索引没有重置isin返回的结果是按索引对齐的如果数据里存在重复索引或者索引不连续筛选结果会乱掉。解决数据处理初期先执行reset_index(dropTrue)或者切分时改用train_test_split而不是手动isin过滤。# 避免索引错位的稳妥写法 df df.reset_index(dropTrue) train_idx, val_idx train_test_split(df.index, test_size0.2, stratifydf[is_default], random_state42) train df.loc[train_idx].reset_index(dropTrue) val df.loc[val_idx].reset_index(dropTrue)这个坑低级但非常常见特别是从网上复制代码改的时候最容易踩。混用loc、iloc和布尔索引索引一旦错位后面的特征工程和模型训练全在脏数据上进行。5.4 坑 4LightGBM 报 bin 数不够早停轮次白设现象LightGBM 训练时直接报错或者跑满 2000 轮不触发早停训练时间长到离谱。原因early_stopping_rounds设置了但n_estimators也给了一个大值LightGBM 认为两者冲突或者eval_set里的验证集包含太多缺失值导致每个 bin 的样本数不足。解决n_estimators给一个足够大的上限比如 2000但不要手动指定最佳迭代轮数让早停来定min_data_in_leaf适当调大减少噪音分裂。还有一个常见配置错误是把early_stopping_rounds放在LGBMClassifier构造函数里而不是fit方法里后者才生效。这个坑在lgb.py里已经被处理好了但要理解为什么这么写。5.5 坑 5特征重要性图和手工算的对不上先查重复列现象features_importance.png里排第一的特征和自己手工算的分组差异对不上怀疑报告造假。原因LightGBM 的feature_importance()默认返回的是split次数也就是这个特征被用来分裂了多少次不是信息增益gain更不是业务上的预测能力排序。解决想看真正的贡献度要显式指定importance_typegain或者用permutation_importance算置换重要性# 用 gain 看特征重要性而不是默认的 split 次数 importance model.booster_.feature_importance(importance_typegain) feat_imp pd.Series(importance, indexX.columns).sort_values(ascendingFalse) feat_imp.to_csv(feature_importance_gain.csv)split次数高只说明这个特征频繁参与分裂gain高才说明分裂带来的信息增益大。报告里写特征分析时我会把gain排名和业务逻辑对照一遍负债收入比、信用历史长度这些排名靠前是合理的如果某个无关痛痒的特征排第一基本就是泄漏或者数据清洗不干净。6. 用特征重要性图反推业务结论把技术报告写成答辩加分项6.1 特征重要性图的三种读法features_importance.png不只是用来展示模型做了什么它是答辩时最有力的素材。拿到这张图先看三件事排名靠前的特征是还款能力类还是信用记录类、有没有明显不符合业务逻辑的异常特征、top 特征之间是否高度相关。贷款场景里dti负债收入比、credit_history_months、loan_rate排在前列都说得通说明模型学到的规律和信贷审核经验一致。如果某个特征排名异常高但业务上解释不了优先怀疑数据泄漏比如把未来信息放进特征里了。6.2 把技术结论转成答辩话术技术报告最容易犯的错是通篇写我用了 LightGBM 参数调到最优老师听到这种话只想追问到底怎么调的。正确的写法是拿特征重要性做桥把模型结论翻译成业务判断。一个可以直接抄的表格结构特征模型排名业务含义报告话术dti1每月债务占收入比例还款压力是违约的核心驱动因素dti 越高违约概率越大符合信贷审批的负债约束逻辑credit_history_months2信用记录时长信用历史越长信息越充分违约风险越低模型自动学会了信用积累这一风控信念loan_rate3贷款利率利率与违约风险正相关高风险用户被定价高利率模型捕捉到了风险定价的印记这套写法的好处是每句话都有据可查既展示了建模能力又展示了业务理解。我从那次之后每个项目都强制要求自己把 top 特征逐个往业务上靠能写出合理解释的特征才保留写不出来的宁可删掉。实践证明这个习惯帮我挡掉了至少三次数据泄漏的翻车也让报告从调参记录变成了风控分析。希望帮到你。本文还有配套的精品资源点击获取