车辆贷款违约预测竞赛复盘:从赛题拆解到风控建模实战全记录

📅 2026/8/27 4:24:16
车辆贷款违约预测竞赛复盘:从赛题拆解到风控建模实战全记录
简介消费金融业务中车贷违约预测是一类典型的监督学习二分类问题核心是利用用户画像、历史借贷与车辆信息评估还款风险。由于真实场景中坏样本占比极低模型需同时应对样本不平衡与排序精度挑战AUC 作为对类别分布不敏感且能度量区分度的评估指标成为风控建模的首选。在工程实现上基于特征工程构造贷款成数、月供压力等业务语义变量并以 LightGBM、XGBoost 等树模型构建基线结合模型融合与缺失值策略可有效提升预测稳定性。这类方法广泛适用于汽车金融、信贷审批等场景帮助机构在控制坏账率的同时降低优质客户误杀。本文完整复盘一场车辆贷款违约预测竞赛覆盖赛题拆解、数据探索、特征构造、调参优化及线上踩坑实录为同类场景提供可落地的建模路径。 我从一开始就在关注这类金融风控方向的算法赛事车辆贷款违约预测赛题尤其典型核心就是通过用户的基础信息、历史借贷表现、车辆相关数据构建一个能尽量准确判断“这笔车贷未来会不会坏账”的模型。用 Python 做这类建模整体链路其实非常清晰从数据分析、特征工程到模型训练、评估调参再到输出预测结果。这篇博文会结合我当时的完整参赛经历把整个项目从赛题拆解、数据探索、特征构造到模型落地完整复盘一遍里面会穿插大量实际踩坑和针对金融风控场景的经验总结希望能给正在准备同类赛事或刚接触风控建模的朋友一些真实可用的参考。这篇内容适合几类人阅读正在备战各类数据挖掘竞赛的学生或转行新人想要了解汽车金融风控建模基本流程的从业者以及所有对 Python 建模全流程感兴趣、想看看真实项目长什么样的人。看完之后你至少能快速建立起一套自己的风控建模方法论并且对样本不平衡、特征构造、模型融合这些高频问题有更落地的认识。1. 赛题拆解与企业级风控思维1.1 一次典型的二分类风控任务先看赛题表面的目标给定一批用户的历史信息和车辆贷款相关信息预测用户是否会发生贷款违约。本质上这是一个监督学习中的二分类问题标签通常就是“是否违约”这样的 0/1 变量。但如果你只把它当成一个普通分类问题来做忽略它背后的金融风控场景很容易在建模过程中走偏。我记得比赛开始后的前几天很多人直接套了一些常规分类模型跑榜单效果一直不理想。原因很简单汽车贷款场景下的违约样本占比通常非常低极端情况下可能只有 1% 到 3%。这种类别极度不平衡的数据如果不做任何处理模型会倾向把所有样本都预测为“好用户”因为这样整体的准确率看起来依然很高但业务上一旦真正上线根本筛不出坏客户风控等于形同虚设。所以拿到赛题资料之后第一步不是急着调模型而是先把赛题背后对应的业务逻辑想清楚。车辆贷款属于典型的消费金融业务用户分期买车按期还款一旦中途断供资方就会面临损失。对于这类业务风控模型关注的核心指标并不完全是准确率而是能否在尽可能低的通过率损失下识别出足够多的坏客户。换句话说就是模型的区分度也就是好坏客户分数分布之间拉开的差距。这种思维决定了后面所有的技术选择。你选的评估指标会决定模型优化的方向你做的特征工程要围绕“用户还款能力和还款意愿”这两个核心维度你处理缺失值和异常值的方式也要小心不能让模型把缺失直接当成一种随机噪声。1.2 赛题数据和评估指标里的门道紧接着我翻了赛事提供的数据说明。训练集给了不少字段主要分为几个维度用户基础信息年龄、性别、学历、职业等用户历史借贷和还款行为比如历史逾期次数、信贷记录相关衍生指标以及车辆相关信息车辆价格、车辆品牌、贷款金额、贷款期限等。熟悉信贷风控的人看到这些字段基本能猜出数据源大概率来自某个助贷或融租公司很多字段和征信报告上的变量高度相似。竞赛为了保密通常会把字段名做匿名化处理比如命名成cust_xxx、veh_xxx之类的但字段含义基本可以通过取值分布和名称语义去还原。赛题通常还会明确告知评估指标这点极为关键。我当时参加的这场赛事评估标准采用 AUCArea Under the Curve也就是 ROC 曲线下的面积。AUC 衡量的是模型把随机正样本排在随机负样本前面的概率对类别不平衡相对不敏感同时又很看重排序能力和风控业务“把好用户和坏用户区分开”的目标高度一致。确定指标是 AUC 之后我整个建模策略都是围绕“排序准确性”来设计的。比如后期做阈值选择时我不需要像做精确率召回率平衡那样专门去卡一个业务阈值做样本权重调整时也无需担心权重设得过高会破坏 AUC 排序。理解了这个逻辑团队内部的沟通也会顺畅很多遇到线上分数波动时第一反应是检查特征分布是否偏移而不是盲目改阈值。2. 数据初探与业务字段里的隐藏信号2.1 先跑通 EDA 再谈建模拿到数据我一般不会直接进模型而是花至少半天时间做探索性数据分析EDA。这个环节的作用有两个一是验证自己对字段的猜测二是提前发现数据里的坑。比如我见过不少比赛数据表面上字段很多但有一些字段缺失率达到 80% 以上这种字段如果直接丢进模型不仅贡献不了信息还可能增加过拟合风险。我当时的处理方式是先做一份字段清单表记录每个字段的缺失率、唯一值个数、数据类型、均值标准差等基础统计量然后针对性地看每个字段在好坏样本上的分布差异。字段不多时直接分组对比均值就很直观字段一旦多起来就得画分布图或者计算信息价值IV来筛选。比如用户年龄字段我做过一次简单的分组统计发现在坏样本里25 岁以下和 45 岁以上的用户占比明显偏高25 到 35 岁之间相对稳健。这个现象合理年轻用户收入不稳定年龄偏大的用户可能还款能力在下降。当然单变量分析只是参考真实建模时多个字段组合起来才能产生更强的区分度但至少它给你一个搜索方向知道应该在哪些特征上多做组合尝试。2.2 警惕标签泄漏和未来函数数据初探阶段还有一个非常重要的点检查是否存在标签泄漏也就是某些特征的取值在建模时点根本不可能拿到。比如说如果一个字段叫“当前逾期天数”并且它是在贷款发放后记录的那它在预测时就是未来变量直接使用会让模型在训练集上表现极好但线上完全失效。这类坑在金融类竞赛数据里不算少见尤其是那些看起来过于“好用”的字段比如非常精准地反映用户当前还款状态的变量或者某些与目标变量高度相关的征信衍生指标。遇到这种情况我一般建议做一次“按时间切分验证”也就是把训练集按时间顺序分成前半段和后半段用前半段训练、后半段验证看 AUC 有没有显著下降。如果下降非常明显基本可以怀疑数据里有时序相关或标签泄漏问题这时候就要进一步排查具体字段。因为竞赛数据通常是离线切片好的很难 100% 还原真实的放款时间线但我还是会抱着怀疑的态度去查。宁可损失一点线上分数也不能一次性把大量未来信息带进模型否则就算比赛拿了好名次这套方案放到实际业务里也完全不可用。2.3 训练集和测试集分布一致性检查另一个让很多人翻车的点是训练集和测试集分布不一致。数据竞赛中测试集往往来自不同的时间窗口或不同的用户群体如果建模时不考虑这一点很容易出现本地验证分数很高、线上分数却掉得厉害的情况。一个简单的检查方法是用模型区分训练集和测试集也就是把“是训练集还是测试集”当成一个新的二分类标签用已有特征去训练一个分类器看分类的 AUC 有多高。如果 AUC 显著高于 0.5说明两个数据集的分布有明显差异这时候就要考虑是不是某些特征在时间推移中发生了变化例如收入水平整体上升、车辆均价整体变化等。我当时也做了这个检查结果发现测试集中的一些用户特征分布和训练集确实存在偏移主要体现在个别匿名特征上。处理方式是在特征工程阶段对这些分布偏移严重的特征做特殊处理对于名称匿名度较高、业务含义不明的字段直接删除避免模型学到的规律在测试集失效对于含义明确的字段则尝试做分箱或者标准化降低量纲变化带来的影响。这个思路不是每次都能带来提升但至少能防止模型在未知数据上“飘”得太离谱。3. 特征工程实战从原始数据到建模数据3.1 手工特征与业务语义的组合我一直认为在金融风控领域手工构造特征的价值远高于自动特征搜索工具原因是风控数据中的业务逻辑很清晰好的业务特征往往自带强解释性。举例来说贷款金额和车辆价格这两个字段单独看都有一定区分度但它们组合出来的“首付比例”或者“贷款成数”才是风控真正关心的指标。贷款成数越高说明用户首付越低一旦车辆贬值用户的还款意愿可能快速下降因为车都亏钱了为什么不直接弃贷呢所以当时我构造了贷款金额 / 车辆价格这个比例特征并且在后续特征重要性分析中它排得非常靠前。同样思路我还构造了月供收入比的替代变量虽然数据集里不一定会直接给你收入但往往会有和收入相关的匿名评分字段此时把贷款金额按期限分摊成月供再除以相关的评分或收入代理变量就能得到一个“每个月还款压力有多大”的组合特征。这个特征在业务上非常直观也从结果上证明了对模型增益很大。3.2 历史行为特征的聚合与统计用户历史借贷和还款行为数据经常以“一事一行”的形式出现也就是一个用户可能有多条历史记录。比如同一用户在不同机构借过多笔贷款或者同一辆车在多个平台被抵押过。直接拿原始明细数据丢给模型是不现实的因为每个用户的记录条数不一样。正确做法是按用户做聚合统计生成“每用户一行”的宽表特征。我常用的聚合方式包括历史贷款笔数的计数、历史逾期次数总和、逾期月份的最大值、平均借款金额、最大借款金额、最近一次借款距离现在的天数以及各种 ratio 类特征比如逾期笔数占历史总笔数的比例。聚合统计时有一个细节不是所有聚合函数都有用要结合业务含义去选。比如 max 类特征可以代表用户最坏的历史情况mean 可以反映平均信用水平而 count 则可以反映用户的借贷活跃度。把一堆 count、mean、max、min 全堆上去特征数量会爆炸而且在树模型中很多相关性极高的特征会在信息增益上相互竞争反而降低单棵树的稳定性。所以我一般会先用 IV 或者特征重要性做一轮粗筛把明显无效或高度冗余的特征剔除。3.3 缺失值、异常值与编码处理金融数据集里的缺失值通常不是随机缺失。比如一个用户没有逾期记录那和逾期天数相关的字段可能全为空又比如部分用户征信报告查询次数异常多某些字段可能出于合规原因被隐藏。所以在处理缺失值时我基本不用简单的均值填充而是倾向于把“是否缺失”本身做成一个特征同时再用一个特殊值填充原字段。以树模型为例LightGBM 和 XGBoost 都原生支持缺失值处理它们会在节点分裂时自动学习缺失值应该往哪个方向走所以很多人干脆不填充直接把原始数据丢进去。这确实是可行的但我个人还是会做一层处理对缺失率特别高的字段直接删除对缺失率适中且含义明确的字段填 -999 或使用一个远低于正常范围的常数让树模型可以捕捉到“这个用户在这个字段上是特殊人群”的信号。异常值处理方面信贷数据里的异常值往往集中在极少数高风险人群比如某个字段是身份证号或者用户 ID 的哈希值这类字段绝不能当成数值特征使用只能当成类别特征。对于真正的连续型数值特征如果出现极端大或极端小的值我基本不做硬截断因为树模型对异常值并不敏感强行截断有时反而损失高分段的信息量。4. 模型构建与调参优化从基线到进阶4.1 基线模型与快速验证框架在特征工程还没完全成形的时候我会先用一个简单的基线模型把整个训练和预测流程跑通这非常关键。所谓跑通不仅仅是代码能执行还包括数据划分、特征对齐、结果输出等环节都不出错。我通常用 Logistic Regression 作为基线一方面训练速度快另一方面可以输出概率作为排序分数方便快速观察 AUC。逻辑回归本身对特征缩放比较敏感所以跑基线之前需要做标准化或者归一化但如果后续主力模型是树模型这一步就不用太纠结。数据划分上竞赛场景里为了快速验证我经常采用 5 折交叉验证把训练集分成 5 份轮流用 4 份训练、1 份验证得到平均 AUC。这样做能比较稳定地评估特征变化带来的效果。交叉验证的代码模板我基本每次比赛都会复用改一改数据路径和特征列就行省去大量重复劳动。4.2 树模型三件套与参数策略进入正式建模阶段我常用的是 XGBoost、LightGBM 和 CatBoost 三个树模型它们对表格数据的表现非常稳定也是各类竞赛里最主流的选择。三个模型各有特点XGBoost 的正则化做得比较完备对噪声数据相对稳健LightGBM 训练速度快内存占用少而且对类别特征有原生支持CatBoost 则在处理高基数类别特征和防止过拟合方面有独到优势。团队早期的策略是三个模型各跑一版分别调参然后做加权融合。但这次我想强调的是调参并不需要像网上教程那样做大规模网格搜索。网格搜索在某些场景下确实是万金油但计算成本太高。我的做法是先固定学习率一般用 0.02 到 0.05然后手动调整树的数量和树的深度。比如对 LightGBM我习惯先设num_leaves31、max_depth-1、learning_rate0.05然后训练 3000 轮同时开启 early stopping。这样既能看到模型在验证集上的表现曲线又能自动确定最佳迭代次数。之后根据验证集表现逐步调整num_leaves、min_data_in_leaf、feature_fraction和bagging_fraction。这些参数看起来复杂但实际核心就两个目的控制模型复杂度和增加随机性防止单棵树学得过细导致过拟合。4.3 样本不平衡优化与自定义目标函数关于样本不平衡竞赛里最常见的处理方式有三类第一类是采样对多数类样本进行降采样或者对少数类样本做 SMOTE 过采样第二类是调整样本权重让少数类样本在损失函数中占更大比重第三类是修改模型的目标函数比如用 Focal Loss 替代普通的二分类对数损失。但在这里我想分享一个实际经验对于以 AUC 为评估指标的任务完整的样本不处理往往也能取得不错的效果。原因在于 AUC 只看排序类别不平衡本身并不直接影响排序质量反而是在阈值选择或精确率召回率优化时不平衡的影响才明显。因此我当时先跑了一版不做任何采样的模型发现本地 AUC 已经接近 0.76然后尝试对少数类样本做 5 倍权重放大结果 AUC 反而有小幅下降因为权重变化改变了树分裂时对少数类的偏置导致整体排序受到干扰。当然这不代表样本权重完全没用。如果你后续要做概率校准或者需要输出一个更贴近业务含义的违约概率值那确实需要调整先验分布。不过竞赛场景里排名就是看排序指标所以我的结论是先跑通建模流程观察 AUC 变化再决定要不要处理样本不平衡而不是一上来就盲目采样。4.4 模型融合的正确姿势等到三个树模型都训练完毕并且各自在交叉验证上都达到了相对稳定的分数下一步就是模型融合。模型融合能提升分数的原理在于不同模型在特征空间中的决策边界不同彼此的误差存在互补性融合之后可以抵消一部分单一模型的偏差。最简单的融合方式是概率平均值就是把三个模型预测出的违约概率做加权平均权重可以按各自验证集的 AUC 做归一化。这种方法的实现非常简单一般能让线上分数微涨 0.001 到 0.003。更进阶一点的是 Stacking也就是用第一层模型的输出作为第二层模型的输入特征让第二层模型自动学习如何组合它们。但 Stacking 在竞赛中容易出现局部过拟合需要配合更严格的时间序列切分或者 K 折训练逻辑来避免信息泄漏。我个人的建议是如果你的时间和精力有限优先做概率加权融合如果还有余力再尝试 Stacking并且一定要确保第二层模型的训练数据来自第一层模型在验证集上的预测结果而不是训练集上的预测结果。这一条我在刚开始学融合时栽过跟头后来才彻底搞清楚为什么会过拟合。5. 风控业务里常常踩的坑与排查实录5.1 分数分布偏移与线上不稳定竞赛进入后期相信很多人都遇到过这种情况本地交叉验证分数稳定提升但提交到线上之后分数不升反降。遇到这种情况不要急着怀疑模型代码有 bug大概率是线上数据和本地验证数据的分布出现了偏差。我遇到过一次非常典型的例子。当时我加了一个基于用户历史逾期天数的 max 类特征本地验证 AUC 提升了 0.005原本挺高兴结果线上分数掉了一截。事后排查发现这个特征在训练集中缺失率非常低但在测试集中缺失率很高说明我构造特征时没有充分对齐训练集和测试集的数据覆盖范围。后来我把缺失率异常高的分支处理掉并对缺失部分单独编码线上分数才恢复回来。从那之后我养成了一个习惯每次新增特征都必须分别统计该特征在训练集和测试集中的缺失率、均值、标准差如果差异过大要么删掉要么做分箱处理来抹平差异。这个检查成本很低但能帮你省去大量反复提交浪费的次数。5.2 过拟合信号本地分数和线上分数差距拉大另一个高频问题是过拟合。树模型在特征数量达到几百甚至上千时很容易在训练集上做到近乎完美的成绩但验证集和线上效果却跟不上。这其实是高维特征加上模型复杂度过高共同导致的。我初期也掉进过这个坑当时为了提高 AUC我把所有能想到的交叉特征都生成了一遍特征数量从 100 个猛增到 1800 个结果本地 5 折交叉验证的分数确实涨了 0.004但线上提交之后不但没涨反而下降了一点点。经验告诉我这不是特征本身的问题而是太多无效特征引入了噪声让模型在训练集上过度拟合了。解决办法有两步。第一在特征工程完成后跑一遍 LightGBM 的特征重要性把重要度为 0 的特征删掉通常能砍掉一半以上的无关特征。第二在模型层面增加正则化参数比如调大lambda_l2或者调大min_data_in_leaf限制每片叶子上最少的数据量让模型没那么容易学到极端模式。做完这两步线上分数基本能够恢复甚至微涨。5.3 类别特征编码细节与脏数据处理表格数据时类别特征往往是容易出错的地方。竞赛数据里常见的坑包括类别编码不连续字段值里有隐藏的空格以及测试集中出现训练集没见过的类别。针对类别编码不连续的问题我一般会先统一转成字符串再做标签编码避免把编码当数值用。对于测试集中出现新类别的问题树模型天然能处理只要在训练时构造一个“未知”类别或者直接让模型在分裂时把新类别分配到缺失值分支都能规避明显的报错。还有一个容易被忽视的细节直接使用 pandas 的factorize做标签编码时如果训练集和测试集分开运行两边的编码映射可能会错位。正确做法是先合并训练集和测试集统一做编码再拆分回原训练集和测试集。这一点看似基础但我就曾在多人协作的项目里见过因为编码不一致导致的线上分数暴跌。5.4 特征重要性和模型解释性的结合风控模型上线之后除了预测准确性还要考虑可解释性。银行、汽车金融公司等资方通常需要给监管方或客户一个说法为什么这笔贷款被拒绝。虽然竞赛不需要提交解释文档但过程中养成用 SHAP 分析特征的习惯对你理解数据和模型都有帮助。SHAP 可以给出每个样本中每个特征对预测结果的贡献方向。比如你会发现某个用户的违约概率高主要贡献来自他历史逾期次数多、贷款成数高、且车辆价格偏低。这种分析不仅帮你验证特征是否合理还能在竞赛方案文档里增加说服力。我一般会在模型基本稳定之后对验证集样本跑一次 SHAP 分析挑出 Top 20 个特征看它们的贡献方向是否符合业务认知。如果某个特征贡献方向完全反常识比如贷款金额越高违约概率越低这并不一定代表逻辑错误可能是因为贷款金额和用户资质有相关性高贷款金额的用户往往征信好。但这类反直觉发现需要进一步验证必要时宁可删除这个特征避免模型学到虚假相关性。6. 参赛过程中的团队协作与时间管理这类赛事通常可以组队团队协作的效率直接决定了最终能走多远。我所在的队伍是三人组一个负责特征工程和数据分析一个负责模型训练和参数调优我主要负责代码框架搭建和最终结果验证。团队分工明确之后最需要注意的就是代码风格统一和数据管道的统一。具体来说我们约定所有特征处理逻辑都写在同一个feature_engineering.py文件里每一版特征都保存一份对应的特征列表文件方便追溯。模型训练脚本也统一封装成函数输入训练集路径和参数配置输出预测结果和验证分数。这样即使某个人临时有事其他人也能快速接手他的部分。时间安排上比赛周期通常一个月左右。前一周重点做数据探索和特征工程第二周到第三周重点调模型和做特征迭代最后几天集中做模型融合、最终验证和可能的方案文档整理。千万不要一上来就花大量时间调参越到后期才越会发现特征工程带来的提升远大于参数微调。另外一个小建议是坚持记录每次实验的变更。我习惯在每天结束时简单记录一下当天尝试了哪些新特征、改了哪些参数、验证分数变化是多少。这个习惯在长周期比赛中非常重要否则过几天就会忘记上一次的分数是因为什么涨上来的又开始重复劳动。7. 从竞赛代码到真实风控系统的迁移思考竞赛结束之后如果你真的打算把这份代码用到实际项目中有几个差距必须正视。第一是数据量级。竞赛往往只给你几十万条或者几百万条历史样本而真实业务的样本量可能是千万级别甚至每天还在增长。这会直接影响训练时间、内存占用以及特征落地的实时性。第二是数据分布漂移。真实业务中用户群体、宏观经济环境、产品政策都在变化模型不可能永远有效所以真实风控系统需要建立完善的监控体系定期看特征分布和分数分布当分布发生明显偏移时要触发重新训练或者模型下线的流程。第三是业务约束。真实风控系统不仅要追求区分度还要满足通过率、坏账率、客诉率等多项业务指标模型的分数通常还要经过人工规则、额度策略、反欺诈策略的多重叠加不可能像竞赛里那样一个模型打天下。不过话说回来竞赛中锻炼的数据敏感度和建模思路放到真实项目中依然适用。你能从一堆匿名特征里还原业务含义能判断哪些特征可能是未来变量能快速定位线上和线下分数差异这些都是真金白银买不来的经验。我甚至觉得自己第一次参与真实风控模型项目时很多思路和方法就是那段时间在竞赛里磨练出来的。如果你也想走这条路我的建议很直接不要光看别人的方案一定要自己完整跑通一遍。从数据读入、清洗、特征加工、模型训练到结果输出每一步都亲手写一遍代码。中途遇到不懂的优先去翻官方文档和靠谱的社区帖子。等你独立跑完一版再回来看别人分享的高分方案你会突然发现自己能看懂很多之前完全看不明白的操作了。那次的参赛经历对我影响挺大也让我更坚定地在风控算法这条路上走下去。后来我又陆续参加了几场同类赛事慢慢沉淀出了一套适合自己的建模模板也在真实项目里反复验证了这套方法的可靠性。如果这篇文章能帮你少走几个弯路或者让你对车辆贷款违约预测这件事有了更完整的认识那今天写下的这些内容就值了。本文还有配套的精品资源点击获取