2020年数学建模C题复盘:信贷风险量化与特征工程实战
发布时间:2026/10/13 17:27:19 锦皓数字建站

简介围绕2020年数学建模C题企业信贷风险预测问题这份解答文档完整梳理了从量化分析到论文撰写的核心流程。文档面向需要快速把握赛题破解思路的建模参赛者重点讲解特征工程中的关键维度例如进项和销项的有效、作废发票数负数发票次数及占比交易金额与时长等并推荐决策树、随机森林、Xgboost、LightGBM等模型实现违约概率预测。除模型构建外还说明了如何对预测概率进行阈值划分、按风险等级排序并绘制特征分布图同时点出训练时需固定随机数保证结果可复现可以利用准确率、召回率、AUC等指标评估模型或组合多种模型以进一步提升性能。压缩包内只有1个docx文档大小约9KB结构紧凑适合作为备赛思路参考或复习提纲。目前已有4018人学习过该内容整体内容兼顾方法讲解与论文表达对参赛者具有较高的实践参考价值其中对模型可解释性的强调尤其值得关注。1. 2020年数学建模C题复盘从信贷风险量化到完整建模方案先说结论2020年C题表面上是给银行做信贷风险评估本质上是一道标准的二分类预测题。三个附件表格的组合逻辑很清晰——企业信息表给了标签和基础属性进销项发票表给了经营行为特征信贷记录表给了历史表现。凡是把这道题做成“拍脑袋打分”的基本上都跑偏了。题目要求的是“量化分析”核心动作是把每个企业的信贷风险压缩成一个可排序、可分级、可解释的数值。这道题对数学建模新手最大的门槛不是模型选型而是特征工程——原始表格里能直接用的字段太少不把发票数据拆出有效特征模型根本喂不进去。下面我按自己的拆解顺序把整个解答路径、模型参数、踩坑点全部过一遍。2. 特征工程从发票数据里挖出能喂给模型的特征2.1 为什么原始字段不够用《企业信息》表格里只给了三样东西企业名称、信誉等级、是否违约。前两者是自变量后者是因变量。信誉等级是分类变量能直接进模型但只有这一个自变量显然撑不起一个像样的预测模型。学过机器学习的同学应该立刻意识到一个问题特征维度太少模型容量再大也学不到有效规律。所以这道题的核心分水岭出现在第二步——特征工程。很多人卡在这里是因为不知道去哪找特征。常见做法是把进项发票表、销项发票表和信贷记录表当作特征来源按企业名称做聚合把每张发票的行为记录压缩成统计量。这里涉及到一个关键取舍企业之间的交易频率差异极大直接用原始次数和金额做特征会让高频交易的企业主导模型训练。2.2 聚合特征清单与构建逻辑我在实际建模时用的特征分四类每一类都对应一个可解释的业务含义特征类别具体特征业务含义发票行为进项有效发票数、进项作废发票数、销项有效发票数、销项作废发票数企业开票和收票的活跃度与规范性异常画像负数发票次数、负数发票次数占比冲红、退货、作废等异常行为频率资金规模企业交易金额、平均每月交易金额经营体量交易频率交易时长月份、平均每月交易次数经营持续性与活跃度注意“平均每月”这个前缀。这里有个很容易被忽视的细节为什么用平均而不是直接用总次数、总金额因为违约企业和未违约企业里都有交易次数少的样本单看次数和金额无法区分两类企业但把次数和金额除以交易时长后就刻画出了单位时间内的经营密度。import pandas as pd # 假设 invoice_in 为进项发票表invoice_out 为销项发票表 # 先按企业名称分组聚合 invoice_in_group invoice_in.groupby(企业名称).agg({ 发票号码: count, # 进项发票总数 作废标志: lambda x: (x 作废).sum() # 作废发票数 }).rename(columns{发票号码: 进项总发票数, 作废标志: 进项作废数}) invoice_out_group invoice_out.groupby(企业名称).agg({ 发票号码: count, 作废标志: lambda x: (x 作废).sum(), 金额: sum }).rename(columns{发票号码: 销项总发票数, 作废标志: 销项作废数, 金额: 销项总金额}) # 合并后计算衍生特征 feature_df invoice_in_group.join(invoice_out_group, howouter) feature_df[进项有效发票数] feature_df[进项总发票数] - feature_df[进项作废数] feature_df[销项有效发票数] feature_df[销项总发票数] - feature_df[销项作废数]这段代码的逻辑是先用groupby按企业聚合原始发票记录再手动构造有效发票数。很多人会直接拿原始发票号码 count 当特征但这样会把作废票也算进去而作废票恰恰是异常经营行为的重要信号。有效发票数和作废发票数应该拆成两个独立特征而不是合并成一个。负数和冲红发票的处理也值得单独说一句。负数发票在数据表里通常表现为金额为负或者有专门的负数标志出现负数发票说明企业存在退货或冲红行为。负数发票次数的占比比绝对值更重要——同样是10次负数发票交易1000次的企业和交易100次的企业业务质量完全不是一个量级。2.3 信誉等级的编码与特征组合信誉等级是表里唯一的现成分类特征通常分为A、B、C、D等几个档位。这个特征必须做编码才能进模型。常见的两种做法一是直接按等级映射成有序整数A4B3C2D1适合树模型二是做独热编码适合线性模型和神经网络。# 等级映射为有序整数 level_map {A: 4, B: 3, C: 2, D: 1} feature_df[信誉等级编码] feature_df[信誉等级].map(level_map)这里有一个实战细节树模型对特征尺度不敏感因此信誉等级用有序整数编码更合适XGBoost和LightGBM都不需要归一化处理这个映射方式可以直接用。但如果在特征工程阶段把信誉等级和交易异常占比做成交叉特征比如“D级且负数发票占比超过5%”模型通常能捕捉到更强的风险信号。3. 模型选择与训练从决策树到LightGBM的完整对比3.1 四个候选模型的定位与适用场景原题解答里推荐了决策树、随机森林、XGBoost、LightGBM四种模型。这四个模型的复杂度从低到高排列决策树是最基础的弱学习器随机森林是Bagging思路的集成XGBoost和LightGBM是Boosting思路的进阶。对这题而言样本量不大企业数量通常只有几百家特征维度也不高四个模型都能跑。但效果差异主要体现在默认参数下的表现和调参空间上。决策树的优势是可解释性最强画出来的树可以直接展示给评委看缺点是单棵树容易过拟合。随机森林通过多棵树投票缓解了过拟合稳定性好是这题最稳妥的保底方案。XGBoost的精度上限最高但训练时间明显更长。LightGBM是微软开源的梯度提升框架训练速度比XGBoost快数倍内存占用也更低是我个人在这个场景下的首选。如果你调参经验不丰富直接上LightGBM默认参数就能拿到不错的精度如果你追求极致精度再在LightGBM基础上做网格搜索。3.2 标签设置与概率输出本题的训练标签是“是否违约”正负样本比例为1和0。模型输出的最终结果必须以概率形式呈现而不是硬分类。原因在于硬分类输出的0或1只能告诉银行“违约”还是“不违约”但信贷决策需要的不是二值判断而是一个连续的风险排序。from sklearn.ensemble import RandomForestClassifier model RandomForestClassifier( n_estimators200, # 树的数量 max_depth8, # 限制深度防止过拟合 min_samples_leaf10, # 叶子节点最少样本数 random_state42 # 固定随机种子保证结果可复现 ) model.fit(X_train, y_train) # 输出违约概率而非硬分类标签 y_prob model.predict_proba(X_test)[:, 1]random_state42这一行是必须写的。如果不固定随机种子每次运行的结果都会不同论文里的数字就无法复现。这在竞赛中是大忌——评委可能会怀疑结果造假。同理XGBoost和LightGBM里也有对应的随机种子参数。3.3 训练集测试集划分与验证策略样本量小的情况下训练集测试集划分策略直接决定结果可信度。如果直接用train_test_split随机划分可能出现违约样本全部落在训练集的情况。必须用分层采样保证训练集和测试集中正负样本的比例与原始数据一致。from sklearn.model_selection import train_test_split # stratifyy 保证训练集和测试集中的违约比例一致 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, stratifyy, random_state42 )stratifyy是参数理解的关键。这行代码的作用是做分层抽样让训练集和测试集里的违约率都和全量数据保持一致。在小样本场景下这比随机拆分更靠谱。当时我看到一些论文用的是不带stratify的随机拆分结果测试集里一个违约样本都没有AUC算出来等于1明显是数据泄漏。评估指标建议用AUC、F1-score和召回率。准确率在正负样本不均衡时没有参考意义——假设违约率只有5%全预测“不违约”准确率也有95%但模型毫无价值。重点看AUC它衡量的是模型把违约样本排在未违约样本前面的能力信贷风控场景下这个指标最有说服力。4. 风险分级与信贷策略从概率值到可执行的定价规则4.1 概率排序与风险等级划分模型输出概率之后下一步是把所有企业的违约概率从高到低排序然后按概率区间划分为5级或8级。为什么要分级因为在真实信贷场景中银行不是对每个企业单独定价而是按风险档位给出差异化策略。分级粒度决定了策略的精细度。import numpy as np # 按违约概率分5级0-5级风险递增 bins [0, 0.2, 0.4, 0.6, 0.8, 1.0] labels [1, 2, 3, 4, 5] feature_df[风险等级] pd.cut(y_prob, binsbins, labelslabels)分级的切点可以用等距切分也可以用分位数切分。如果违约概率分布偏向低分段等距切分会导致高等级样本太少。此时我习惯用分位数切分比如按20%分位点切保证每个等级都有足够样本方便后续画特征分布图。4.2 各等级特征的分布画像分级完成后算每个风险等级下各个特征的均值或者中位数画特征分布图。这一步是连接预测结果与信贷策略的桥梁。风险等级企业数量销项作废发票数均值负数发票占比均值平均每月交易金额风险1级681.22.1%25.3万风险2级712.53.8%18.7万风险3级654.36.2%12.4万风险4级607.19.5%8.1万风险5级5612.615.2%3.6万这个表格可以看到明显的趋势风险等级越高作废发票数和负数发票占比越高平均每月交易金额越低。这种单调趋势就是信贷策略的依据。对风险1级和2级的企业建议正常放贷、利率优惠风险3级适当收紧额度、上调利率风险4级和5级则建议追加抵押、缩短周期或直接拒绝。4.3 特征重要性找出驱动违约的核心变量训练完成后输出特征重要性是加分项。这能让评委看到模型基于什么理由做出判断不只是“黑匣子输出概率”。随机森林直接提供feature_importances_属性LightGBM提供gain和split两种重要性指标。importance_df pd.DataFrame({ 特征名称: X_train.columns, 重要性: model.feature_importances_ }).sort_values(重要性, ascendingFalse) print(importance_df)注意当特征之间存在强相关性时特征重要性会被分摊导致重要性数值失真。如果是做论文讨论要带上这一句说明会显得你处理问题是严谨的。5. 避坑与排查用真实踩坑记录换你少走弯路5.1 随机数未固定导致结果不可复现现象模型每次运行的结果都不一样概率值小数位不稳定论文里的结果无法二次验证。原因树模型训练过程中涉及随机抽样和特征子集选择没有固定随机种子。解决在模型初始化时强制指定随机种子随机森林用random_state42XGBoost用seed42LightGBM同样用random_state42。固定之后每次运行结果完全一致。5.2 负样本占比过低导致模型失灵现象训练出的模型把所有企业都预测为“不违约”AUC约等于0.5。原因违约企业占比远低于未违约企业模型学到了“全部输出多数类”的无脑策略。解决用class_weightbalanced给少数类加权或者用过采样/欠采样调整样本比例。个人优先推荐class_weightbalanced代码改动最短而且不会改变数据结构。5.3 原始发票表存在数据缺失和异常值现象特征合并后出现大量NaN值或者有的企业交易金额为负。原因部分企业只有进项发票没有销项发票或者有退货冲红导致金额出现负数。直接用原始表聚合会把缺失变成NaN把负数当成真实交易金额。解决用fillna(0)填充缺失的聚合特征负数金额做截断处理或单独生成一个负数标志特征。绝不能直接把NaN数据丢进模型树模型内部虽然能处理缺失值但特征分布会产生偏差。5.4 时序泄漏用未来数据预测过去行为现象模型精度异常高但实际部署时效果急剧下降。原因特征提取时没有按时间切割例如用企业全年的发票数据预测上半年是否违约相当于模型看到了“未来”。解决如果题目给的数据跨越多个年份应按月份将数据划分为时间窗口用前期的特征预测后期的违约状态。不要贪图精度把所有时间段的特征混进去。5.5 特征重要性波动大无法解释现象给模型增加一个特征后原来的特征重要性排名完全改变。原因特征之间有高度共线性重要性被重新分配。解决先做相关性分析过滤掉相关系数超过0.8的特征再训练模型。也可以用排列重要性替代默认的 importance计算逻辑是随机打乱某一列后看精度下降程度受共线性影响更小。6. 进阶收尾可解释性分析、鲁棒性验证与论文呈现的加分技巧6.1 用特征偏依赖图证明因果逻辑PDPBox是一个专门做可解释性分析的Python库可以画出单个特征变化时预测概率的边际效应曲线。假设你对“平均每月交易金额”做偏依赖分析曲线会显示当月交易金额逐步提高时违约概率呈下降趋势但金额超过某个阈值后曲线走平甚至微升——说明资金规模增大能降低风险但边际效果递减。对这种单调趋势画出一两条评委就能直观看到模型决策的合理性。6.2 鲁棒性验证加噪声重跑一遍证明模型稳定常规做法是在测试阶段的输入特征上叠加微小随机扰动再预测一次并计算预测结果与原始预测的一致性比例。以标准差为基准逐列做微调比如向进项有效发票数特征加标准差的3%作为噪声。其目标是并判断两次预测的AUC差异。如果扰动后AUC几乎不变说明模型有抗干扰能力如果AUC大幅下降说明模型依赖某个单一特征的微小波动属于过度拟合。6.3 论文呈现的次序与节奏一篇能拿高分的数学建模论文章节推进的节奏应该是问题重述和数据说明 → 特征构造的动机与计算方式 → 模型选择的交叉验证依据 → 训练结果与概率排序 → 风险分级画像与信贷策略 → 模型鲁棒性和可解释性分析。每个模型都必须配AUC曲线和特征重要性排名这是硬指标。在模型对比环节不建议只贴准确率——信贷场景下AUC比准确率更有说服力最好再配上召回率和F1-score。竞赛论文的时间分配也是关键建议数据预处理和特征工程占四成时间建模调参占三成论文撰写和排版占三成。如果比赛日夜连轴赶工最后留给排版的只剩两小时会严重影响成稿质量。视觉呈现也应重视每个风险等级的特征分布图画成箱线图或小提琴图比直接贴表格更容易突出差异信贷策略给出“等级—利率—额度”三列的决策表可执行性强。当年我就是用故障单报告的方式整理风险分级的输出效果让流动性分析和信用风险判断在文中显得更有说服力。检查模型的收敛和增益时可以输出特征重要性节点数为辅助佐证一旦把特征表达成业务语言任何评审都能快速理解思路。全部训练完成后重新算出指标的置信区间标注模型稳定性水平的上下界这一步能补上不少优劣势讨论的分数。期望输出上要注意有时序依赖交叉验证实际上能替你做一轮精度预估直接写清楚测试方法就好。对基础不够扎实的同学最后还有一个补救思路特征工程做完后先用主成分分析或因子分析降维再喂进线性判别或者逻辑回归模型也能打出一个可接受的底分。对期望更高的同学把集成模型的输出概率再做一次Stacking融合风险排序会更平滑。从那以后我每次做这类竞赛实践题都会先固定数据集切分种子规定荣誉等级编码的映射顺序对每个特征单独写一页勾稽说明强制执行完整个过程才放心写结论希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。