news 2026/9/3 19:16:11

基于Python的车辆贷款违约预测实战:从特征工程到风控模型

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Python的车辆贷款违约预测实战:从特征工程到风控模型

简介:本资源是2021年科大讯飞主办的车辆贷款违约预测挑战赛的完整Python实现方案,面向金融风控方向的入门与进阶学习者,适用于课程设计、毕业设计、工程实训及算法竞赛备赛等场景。项目聚焦真实信贷业务中的逾期风险建模问题,提供从特征工程(gen_feats.py)、多模型训练(XGBoost/LightGBM)、概率融合到最终提交的端到端流程。压缩包共18个文件,含5个CSV数据集(train_final.csv/test_final.csv等)、4个核心Python脚本、3个预训练模型(.pkl)、2个Shell执行脚本(train.sh/test.sh)及README.md和requirements.txt,整体大小为59.41MB,结构清晰、模块解耦度高。已有226人下载学习,读者可直接复现Top1方案的关键技术路径,包括邻域违约概率特征构造、多模型权重融合策略及可复用的工具函数封装(utils.py),具备强实践参考价值。 去年这个时间,我正好在折腾科大讯飞办的车辆贷款违约预测挑战赛。当时看到赛题名里带着“基于Python实现”,我就知道这不是那种纯粹比模型精度的竞赛,而是更贴近业务场景的数据挖掘题。整场比赛做下来,我的感受是:它考的并不是你掌握了多少高大上的算法,而是你能不能把车辆贷款业务数据里的风险信号挖出来,并用一套可靠的Python工作流跑出稳定的结果。这篇文章就把我自己的完整思路和踩坑过程写出来,给准备参加这类风控竞赛,或者想入门信贷违约预测的朋友做个参考。

车辆贷款违约预测,本质上是一个二分类问题:根据借款人的个人信息、贷款信息、历史行为等数据,判断这个人未来会不会违约。听起来很简单,但实际处理起来,变量之间的关系、样本的不均衡、特征的构造方式,都会直接影响最终分数。我最初提交的版本只拿到一个很普通的成绩,后来反反复复调整特征和验证方式,排名才慢慢往上走。整个过程踩了不少坑,也积累了很多可以直接复用的经验。

1. 先看清比赛任务:是“预测违约”而不是“拟合分数”

1.1 业务目标与评估指标背后的信号

比赛任务一句话就能讲清楚:给定一批车辆贷款样本,训练一个模型,对每个样本输出违约概率,然后按概率排序,用AUC这类指标评估好坏。但这里有个容易忽略的点——评估指标直接决定了我们该怎么优化。如果赛题用AUC,那模型只需要给样本排出正确的相对顺序就行,不需要精确预测概率本身;如果赛题用对数损失(LogLoss),那就必须让预测概率足够“准”。科大讯飞这场挑战赛用的是AUC作为主要评估标准,这其实是在告诉我们:把精力放在特征排序能力上,比纠结概率校准更重要。

从业务角度理解,车辆贷款违约预测和一般信用卡逾期预测还有差异。车辆作为抵押物,一旦发生违约,金融机构可以通过处置车辆来追回部分损失,所以模型不只是判断“会不会还不上钱”,还得关注“违约后可能的损失”。但比赛通常不会把回收率等变量放进来,我们能做的就是尽最大努力用已有字段预测违约风险。

我在拿到数据后的第一件事,不是急着跑模型,而是反复读赛题说明,搞清楚训练集和测试集的划分方式、字段含义、正负样本比例。因为比赛数据往往是脱敏后的,很多字段名已经改成了匿名形式,比如f1f2这种。这种情况下,对字段业务含义的猜测就很重要了。

1.2 数据字段结构与量级

那次比赛提供的数据,大概有几十个特征,样本量在十几万左右。字段主要分为几类:

字段类型常见字段示例业务含义推测
用户基本信息年龄、性别、婚姻状况、学历判断申请人稳定性
收入与资产负债月收入、负债率、房产情况还款能力
贷款属性贷款金额、期限、利率、首付比例贷款风险敞口
历史行为历史逾期次数、征信查询次数、已有贷款笔数信用历史
第三方衍生指标匿名评分、额度使用率等合作方风控结果

当然,实际比赛里很多字段是脱敏后的,名字可能是x1x2,甚至直接是一些已经编码好的数值。这时就需要通过分布、相关性和建模后的特征重要性,反过来推断哪些字段可能更重要。

我习惯先做一轮快速数据体检:用pandasinfo()describe()看缺失率、唯一值数量、均值方差,再用train_test_split快速跑一个LightGBM基线,看看特征重要性排序是否合理。这一轮往往能发现很多问题,比如某个特征缺失率高达80%,那它要么是“用户没填”,要么是“只有部分用户才有”,这两种情况处理方法完全不同。

2. 数据清洗与特征工程的完整落地过程

2.1 缺失值处理:先看分布再动手

很多刚入门的朋友一看到缺失值就fillna(0)或者fillna(mean),这样不是不行,但非常粗糙。我当时在比赛里遇到过一个特征,缺失率超过60%,一开始我直接填了-1,结果模型效果一般。后来我单独看这个字段的分布,发现缺失样本和非缺失样本的违约率差异非常明显。这说明缺失本身就带有信息——某些用户可能因为资质不足没有填写某些信息,或者第三方数据源没有返回结果。

所以我的做法是:

  • 先统计每个特征的缺失率,画一个缺失率条形图,看是否存在高缺失特征。
  • 给每个高缺失特征额外生成一个“是否缺失”的0/1标志,把缺失标志加进去,再对原特征填充。
  • 分类特征缺失填充为__MISSING__,数值特征缺失填充为-1或特征中位数,但更重要的是保留缺失标志。

用代码表达就是:

for col in df.columns: if df[col].isnull().any(): if df[col].dtype == 'object': df[col + '_miss'] = df[col].isnull().astype(int) df[col] = df[col].fillna('__MISSING__') else: df[col + '_miss'] = df[col].isnull().astype(int) df[col] = df[col].fillna(-1)

这个小技巧当时帮我提升了大概千分之几的AUC,不要小看这一点,竞赛里千分之几可能就差几十个名次。

2.2 连续特征的处理:离群值、对数变换与标准化

连续特征最容易出问题的不是缺失,而是离群值。车辆贷款数据里,收入、贷款金额这些变量天然带有长尾分布。比如大多数人的月收入在几千到几万之间,但少数人可能月入几十万甚至上百万。树模型对离群值相对不敏感,因为分裂只依赖排序,但如果你后面要上神经网络或者做一些距离相关的计算,离群值就会严重影响结果。

我当时对明显长尾的连续特征做了一层np.log1p变换,把收入、贷款金额等特征压缩到近似正态分布。效果没让AUC涨太多,但对后续的特征组合有帮助。对于树模型,标准化的意义不大,所以我没有对LightGBM的特征做标准化,只做了分位数截断处理:把超过99.5%分位数的值截断到99.5%分位数的值,减少极端值对分裂点选择的影响。

另外,我强烈建议检查一下特征里有没有“重复表达”的字段。比如一个特征是“月收入”,另一个特征是“年收入”,那它们之间就是线性关系,模型虽然能处理,但会浪费分裂次数。可以用相关性矩阵快速筛查,把相关性超过0.95的特征做去重或保留其一。

2.3 类别特征与监督编码:别直接扔进模型

车辆贷款数据里,像婚姻状况、学历、职业、所在城市这类类别特征,数量可能不多。对于LightGBM等树模型,直接用LabelEncoder编码成整数也能跑,但我发现直接编码后,模型会把这些类别当成有序变量,导致某些无序类别之间被强行加上大小关系。更好的做法是:

  • 类别数量少(不超过几十个)时,建议使用独热编码,或者直接让LightGBM原生处理类别特征。
  • 类别数量多,且类别里某些等级出现频率很低时,用目标编码(target encoding)或WOE编码往往效果更好。

目标编码的做法是用该类别下样本的违约率均值替换原始类别。但要注意,目标编码很容易过拟合,尤其是低频类别。我当时的做法是做一个5折交叉目标编码:在每一折中,利用另外4折计算类别均值,再映射到当前折,这样能避免直接用全量均值导致的信息泄露。

from sklearn.model_selection import StratifiedKFold import pandas as pd import numpy as np def target_encode(df, col, target, n_folds=5): df = df.copy() skf = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=42) encoded = np.zeros(len(df)) for tr_idx, te_idx in skf.split(df, df[target]): tr_mean = df.iloc[tr_idx].groupby(col)[target].mean() encoded[te_idx] = df.iloc[te_idx][col].map(tr_mean).fillna(df[target].mean()) df[col + '_te'] = encoded return df

当然,目标编码是“双刃剑”,用不好就很危险。如果比赛数据里存在时间顺序,目标编码会导致很强的时序泄露,必须按时间切分来计算编码值,而不是随机交叉。这个我在后面验证策略部分会展开。

2.4 构造业务衍生特征:还款能力与风险敞口

如果说特征工程有一个“核心原则”,那就是围绕业务逻辑构造新的输入。车辆贷款违约,最核心的两个风险驱动因素是“有没有能力还”和“愿不愿意还”。能力端,可以构造收入负债比、月还款金额占收入比例等;意愿端,可以构造历史逾期率、查询次数等。

我当时构造了不少衍生特征,这里列几个效果比较明显的:

  • 还款负担比:每月还款金额 / 月收入。这个比值越高,违约风险越大。如果数据里没有直接的月还款金额,可以用贷款金额、期限、利率推算一个近似值。
  • 贷款金额与收入比:贷款金额 / 年收入。类似买车的“杠杆率”。
  • 历史逾期率:过去逾期次数 / 历史贷款笔数。这是一个比单一逾期次数更稳定的指标。
  • 申请频次:近期贷款审批查询次数。查询次数越多,往往代表申请人资金紧张。
  • 利率偏离度:用户贷款利率与同期限平均利率的差值。高风险客户通常对应更高利率。

这些衍生特征不一定每个都能提升AUC,但组合起来能帮树模型找到更好的分裂点。我用LightGBM做特征重要性分析时,发现“还款负担比”和“历史逾期率”排在最前面,说明构造特征的思路是对的。

如果特征列很多,还可以用feature_importance_permutation做一轮筛选,去掉那些加入后反而降低验证集AUC的冗余特征。这一步能减少过拟合风险。

3. 建模与验证:我在赛题上的模型选择过程

3.1 基线模型与统一验证协议

建模阶段,我没有一开始就上很复杂的网络,而是先用LightGBM快速跑了一个基线。LightGBM在结构化数据上的表现稳定,训练速度快,特征工程和调参迭代都很方便。选择它作为基线模型还有一个原因:它的特征重要性输出非常直观,可以快速发现哪些特征值得继续深挖。

在跑基线之前,先要确定验证协议。那场比赛数据没有明确的时间字段,但数据本身是来自某个时间段的贷款样本。为了保险起见,我先用随机划分的5折交叉验证来评估。如果后续发现某些特征有明显的时序趋势,再把验证方案改成按时间切分。统一验证协议有一个好处:每次改特征、调参数,都能用同一个指标对比,避免因为验证集不同而误判效果。

我的基线流程大致是:

import lightgbm as lgb from sklearn.model_selection import StratifiedKFold from sklearn.metrics import roc_auc_score skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) oof = np.zeros(len(train_df)) for tr_idx, te_idx in skf.split(train_df, train_df['label']): tr_data = lgb.Dataset(train_df.iloc[tr_idx][features], train_df.iloc[tr_idx]['label']) va_data = lgb.Dataset(train_df.iloc[te_idx][features], train_df.iloc[te_idx]['label']) params = { 'objective': 'binary', 'learning_rate': 0.05, 'num_leaves': 31, 'max_depth': -1, 'feature_fraction': 0.8, 'bagging_fraction': 0.8, 'bagging_freq': 1, 'verbosity': -1, 'seed': 42, } model = lgb.train(params, tr_data, num_boost_round=1000, valid_sets=[va_data], callbacks=[lgb.early_stopping(100), lgb.log_evaluation(0)]) oof[te_idx] = model.predict(train_df.iloc[te_idx][features], num_iteration=model.best_iteration) auc = roc_auc_score(train_df['label'], oof) print('OOF AUC:', auc)

当时我的第一个基线AUC大概在0.75左右,不算好也不算差。后续通过特征工程一点点提升到了0.78以上。

3.2 样本不均衡处理:不盲目SMOTE

车辆贷款违约样本比例通常不高,我那个比赛数据里正样本比例大概只有不到10%。对于AUC这类排序指标,样本不均衡的影响没有想象中那么严重,因为AUC只看正负样本的排序关系。但如果违约率过低,训练时模型会倾向于把所有样本预测为低概率,导致梯度更新不够充分。

处理不均衡有几种常见方法:过采样、欠采样、调整样本权重、修改损失函数。我的经验是:

  • 不要一上来就用SMOTE。SMOTE生成的是插值样本,对树模型来说容易引入噪声,而且让特征分布失真。我试过一次,AUC反而掉了。
  • 先试scale_pos_weightis_unbalance。LightGBM里设置scale_pos_weight = 负样本数 / 正样本数,或者直接设置is_unbalance=True,能快速缓解不均衡问题。我当时用scale_pos_weight大概提升了0.005左右。
  • 对样本进行欠采样往往也能稳定提升。如果正样本太少,可以保留全部正样本,对负样本进行多次欠采样,训练多个模型后平均。这种“bagging”的思路在风控比赛中经常有效。

我当时最后还是选了scale_pos_weight,并用5折交叉验证来确认没有过拟合。记住:所有不均衡处理方法都要在验证集上评估,不能只看训练集指标。

3.3 多模型融合与调参顺序

基线稳定之后,我加入了XGBoost和CatBoost,尝试多模型融合。它们的差异在于分裂方式、类别特征处理和对缺失值的默认策略,融合之后能降低单一模型的方差。

我的调参顺序是这样的:

  1. 先固定一个比较低的学习率(0.02~0.05),用早停确定最优迭代轮数。
  2. num_leavesmax_depth,控制模型复杂度。
  3. feature_fractionbagging_fraction,增加随机性,防止过拟合。
  4. 调正则化参数lambda_l1lambda_l2
  5. 最后再细微调整学习率和迭代轮数。

调参不是闷头搜索,而是每调一个参数就观察OOF AUC的变化。我当时用optuna做了一次自动搜索,但发现它对计算资源消耗很大,而且搜索到的参数组合有时只是运气好,在另一折上就不稳定。所以后来我改为手动+少量网格搜索,核心参数控制在几组范围内。

融合方案上,我试过加权平均和Stacking。加权平均最简单:给每个模型的预测概率乘以一个权重再相加。权重可以根据OOF AUC排个序,比如LightGBM权重0.4、XGBoost权重0.3、CatBoost权重0.3。Stacking则更复杂一些,要用第一层模型的OOF预测作为第二层模型的特征,训练一个逻辑回归或LightGBM。Stacking在小数据集上容易过拟合,我那次比赛里加权平均的效果反而更稳。

4. 最容易翻车的细节:验证、阈值与特征泄露

4.1 时间切片验证与随机验证的选择

比赛数据里如果存在时间字段,或者特征明显带有时间累积效应,那最忌讳的就是直接用随机划分交叉验证。因为金融数据几乎都有“概念漂移”问题——不同时间段的客群质量、政策环境、产品策略都会变化。

我复盘时发现,有一版特征我用随机5折验证AUC很高,但提交到线上后分数明显下降。后来排查发现,问题出在目标编码上:我用随机划分的交叉验证对高基数类别做目标编码,相当于用未来数据预测过去,验证指标虚高。改成按时间顺序切分训练集和验证集后,线上表现和验证指标才比较一致。

具体做法是:如果数据里有申请日期或放款日期字段,按时间排序后取前80%做训练,后20%做验证。如果没有明确时间字段,那就只能用随机验证,但也要谨慎使用目标编码和需要“未来信息”的特征。

4.2 阈值调整与概率校准

AUC评估的是排序能力,但比赛只给概率,不强制使用阈值。不过在实际业务里,我们需要根据违约概率的高低来决定是否放款,阈值选择非常重要。竞赛阶段,我一般不会过度纠结阈值,因为AUC分数不受阈值影响。但如果是做完整的风控项目,就必须结合业务成本和收益来定阈值。

有一点值得注意:不同模型输出的概率分布差异很大,LightGBM的概率往往偏“锐利”,集中在0和1附近,而逻辑回归的概率更“平滑”。在融合多模型时,如果直接对概率做平均,可能会被某个模型的概率分布主导。我的做法是先对每个模型的预测概率做排序转换(RankGauss或分位数归一化),再取平均,融合效果更稳定。简单说,就是不看概率绝对值,而是看相对排名。

4.3 特征重要性与过拟合检查

特征重要性是排查过拟合和信息泄露的利器。我拿到一版特征重要性后,发现排名第一的特征是一个匿名编号,它的AUC贡献高得离谱。进一步检查后发现,这个编号在训练集和测试集中的分布完全不一致,模型学到的其实是数据集的“身份信息”,而不是规律。删除这个特征后,线下AUC略微下降,但线上排名大幅提升——这说明线下指标虚高是因为模型记住了训练集的身份。

怎样发现这类问题?一个简单的方法是:把特征和标签做分组统计,看看某个特征的取值是否和标签出现明显的机械对应。比如某个特征值为0的样本违约率是5%,特征值为1的违约率是95%,且该特征在测试集里几乎没有值为1的样本,那极有可能就是泄露特征。

另一个检查方法是用“随机标签验证”:把标签打乱,训练模型,看特征重要性是否依然很高。如果打乱标签后,某些特征重要性依然很高,那说明这些特征和原本的标签之间没有因果联系,只是碰巧在训练集上能区分。

5. 从比赛到实际风控:几个值得记住的认知

5.1 竞赛分数的水分

比赛排名看着刺激,但分数本身有很多水分。比如有些参赛者会通过后处理、特殊融合、多折平均等技巧把分数提高一点点,但模型是否真的更健壮,很难说。我自己参赛后最大的一个感受是:不要迷信Public LB的分数,更不要为了Public LB不断过拟合验证集。

在线下用统一的验证协议持续迭代,比反复提交拿线上反馈更有效。因为线上提交次数有限,而且线上分数存在波动,看多了反而会扰乱判断。我一般只在特征工程和模型方案稳定后提交几次。

5.2 业务落地时的差异点

竞赛模型追求的是预测精度,而业务风控模型追求的是稳定性和可解释性。在真实场景里,模型上线后要面对的是不断变化的数据分布、被攻击的风险、以及监管对可解释性的要求。竞赛里经常用的目标编码、复杂特征组合,在业务环境里可能因为上线流程复杂、数据口径变化而变得不可维护。

所以,如果你把这段比赛经历写进简历或者拿来做项目复盘,千万别只强调AUC刷到了多少,而应该突出你如何设计验证方案、如何识别潜在泄露、如何通过特征工程提升模型鲁棒性。这些才是业务风控真正看重的能力。

5.3 一套可直接套用的项目流程清单

比赛结束之后,我总结了一套适合车辆贷款违约预测类项目的流程,这里分享给后来者:

  1. 业务理解:明确预测目标、评估指标、正负样本量、是否存在时间顺序。
  2. 数据体检:统计缺失率、唯一值、分布、相关性,画目标变量分布图。
  3. 特征工程:缺失标志、长尾变换、类别编码、业务衍生特征,所有编码都要在验证方案内完成,避免泄露。
  4. 基线建模:用LightGBM快速建立基线,确认验证协议和特征有效性。
  5. 迭代优化:基于特征重要性和错误样本分析,反复修改特征和模型。
  6. 多模型融合:在相同验证协议下训练XGBoost、CatBoost等模型,做概率归一化后加权平均。
  7. 风险排查:检查特征重要性和随机标签模型,剔除疑似泄露特征。
  8. 最终提交:用线下指标和有限次线上提交验证模型稳定性,保存最佳参数和特征列表。

最后再分享一个小技巧:每次你改了一版特征,都记录下这次的AUC和想尝试的新思路。这个记录即使不能马上带来提升,过几天回看时也能帮你理清方向。我当时就是靠一张表格,一步步把AUC从0.75拉到了0.79以上。对了,如果你拿到的数据里有类似“车辆品牌”“车型”这样的字段,一定要记得做频率编码或者目标编码,这类字段在车辆贷款场景里信息量很大,直接丢掉的损失太大。祝你在比赛里跑出好成绩。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 8:27:20

Proteus仿真STM32:uC/OS-II任务调度与BLDC电机控制及12864显示实践

简介:面向STM32与Proteus开发者的直流无刷电机联合仿真资源,工程在Proteus 8.7中搭建,主控为STM32F103R6,移植μC/OS-II操作系统,通过MOS管驱动BLDC_STAR电机,并驱动AMPIRE128X64液晶屏显示,字库…

作者头像 李华
网站建设 2026/9/1 8:25:41

游戏手机稳定性实测指南:帧率、温控与功耗的评判标准

同价位游戏机谁更稳?iQOO Neo10 和 Z10 Turbo Pro 是两千元档里经常被放在一起比较的两台直屏机型,都有高刷屏,都强调游戏调度,价格又落在同一区间。可“稳”这个字在不同人口中含义完全不同:有人说的稳是长时间不掉帧…

作者头像 李华
网站建设 2026/9/3 19:42:26

SpringBoot+Vue构建企业经济效益评价系统:动态指标与计算引擎实战

1. 先搞清楚这个系统到底要解决什么问题 企业经济效益综合评价系统,听起来名字很大,但落到实际开发上,核心就一件事: 把一堆分散的财务、运营数据,通过一套固定的计算模型,变成一个可以横向、纵向比较的分…

作者头像 李华
网站建设 2026/9/3 20:16:21

基于MCP协议与IDA Pro构建安卓SO文件自动化逆向分析环境

在移动安全研究、漏洞挖掘和恶意软件分析领域,安卓应用的 so 动态链接库是核心战场。 so 文件承载了应用的核心算法、加密逻辑和关键业务功能,直接分析其二进制代码是理解应用行为、发现安全漏洞的必经之路。IDA Pro 作为业界标准的静态反汇编和调试…

作者头像 李华
网站建设 2026/9/1 8:23:11

ROS2 + Intel RealSense:从驱动编译到点云订阅的完整指南

简介:面向在ROS2环境中使用Intel RealSense D435/D405相机的机器人开发者与视觉工程人员,这份资源梳理了从Windows端安装RealSense Viewer、Linux端配置依赖与编译RealSense-ROS2,再到启动ROS2节点并通过话题读取RGB、深度、点云数据的完整链…

作者头像 李华
网站建设 2026/9/3 6:46:51

广告牌识别数据集实战:从COCO JSON到YOLOv8训练部署

简介:本资源是面向计算机视觉算法工程师与深度学习初学者的广告牌目标检测专用数据集,聚焦商场、建筑及道路边等典型城市场景中的广告牌识别任务,可直接用于YOLO、Faster R-CNN等主流模型的训练与评估。压缩包共2000个文件,包含19…

作者头像 李华