简介面向金融风控与机器学习初学者这份车贷违约预测实战资源提供了基于Python的完整建模流程。针对约20万条、49个字段的车贷数据集实现了从数据加载、清洗、特征与目标分离到数据集划分、模型训练与保存的全链路代码包含随机森林与AdaBoost两种集成学习模型并输出准确率、召回率、F1-score等评估指标实测两者准确率分别为0.819和0.822。压缩包内共2个文件一个csv格式的训练数据集和一个py格式的模型脚本整体大小7.34MB轻量易用便于直接运行与二次修改。目前已有1062人学习下载适合正在学习分类算法、想要参考完整赛题方案或快速搭建车贷违约预测模型的读者可据此对比不同集成方法的实际效果。1. 车贷违约预测到底在预测什么先读懂“是否违约”这个标签车贷违约预测是一个听起来直白、做起来容易翻车的任务。给定一份包含客户信息和贷款记录的数据集要训练模型输出“这个客户未来会不会违约”随机森林和AdaBoost恰好是两类最常用、最适合作为基线的集成分类模型。它们都基于决策树但学习方式完全不同拿同一个数据集跑一遍你会同时得到“稳定基线”和“提纯冲刺”两种体验。这篇文章面向刚入门信贷风控、手里有数据集不知道怎么下手的人也面向已经跑通裸模型、想弄清楚特征和踩坑点的从业者。我会把特征加工、模型训练、参数调整和业务落地串在一起让你照着能复现遇到坑也知道回看哪里。2. 车贷数据清洗与特征构造把“能不能还钱”翻译成数值模型不认汉字也不认“是否逾期过”这种口语描述它只认数值。所以拿到车贷数据集的第一步不是急着调模型而是把原始字段改造成结构化特征。这一步做不好再强的随机森林和AdaBoost也白搭。2.1 原始字段到模型特征先做这三类变量一份典型车贷数据集通常包含客户年龄、性别、婚姻状况、学历、收入、工作年限、负债收入比、贷款金额、贷款期限、首付比例、车辆品牌、车辆价格、历史逾期次数、征信查询次数等字段。我一般会先把它们分成三类分别处理。第一类是连续数值比如年龄、收入、贷款金额、车辆价格。这类字段直接保留数值即可但要注意量纲。收入从两三千到几万贷款金额从几万到几十万如果不做标准化树模型本身不受量纲影响但如果后面要对比特征重要性或者做线性降维量纲会干扰判断。推荐的做法是只做缺失值填充量纲问题交给树模型自己处理不强行归一化。第二类是类别字段比如婚姻状况、学历、车辆品牌、所在城市。这些字段必须编码成数值。常见的做法是one-hot编码把“已婚”“未婚”“离异”拆成三个0/1列。但车贷数据集里有一些类别层级特别多比如车辆品牌可能有几十种城市有上百种全部one-hot会让特征维度爆炸。我建议对这类高频类别做频数编码统计每个品牌出现的次数把品牌替换成频数能保留“常见品牌”的整体风险信号又不会增加维度。如果品牌本身与违约率强相关可以在后续用随机森林的特征重要性去验证。第三类是时序和交互字段比如贷款发放日期、最近一次征信查询日期。日期不能直接当数值喂给模型要加工成“距离今天几个月”“距贷款发放时长”这类相对时间。交互特征可以手工加一两个比如收入与贷款金额的比值也就是负债压力指数这个特征在车贷场景里比单独的收入和金额都更有区分度。处理完这三类之后还要做一次去重和异常值筛查。我遇到过同一客户在数据集里出现两遍原因是渠道表格合并时没去重这会直接让模型记住这条样本训练集上飘高测试集上回归现实。下面是一个简单的特征加工示例用pandas完成基本清洗import pandas as pd from sklearn.model_selection import train_test_split # 假设数据已读入df关键字段包含 # age, income, loan_amount, car_price, marrige, education, loan_term df pd.read_csv(car_loan.csv) # 1. 缺失值收入用中位数填贷款金额用均值填 df[income] df[income].fillna(df[income].median()) df[loan_amount] df[loan_amount].fillna(df[loan_amount].mean()) # 2. 异常值收入小于0的记录剔除 df df[df[income] 0] # 3. 交互特征贷款金额占收入的比例即负债压力 df[debt_pressure] df[loan_amount] / df[income] # 4. 类别频数编码比如车辆品牌brand统计每个brand的出现次数 brand_freq df[brand].value_counts().to_dict() df[brand_freq] df[brand].map(brand_freq) # 5. 选择特征列 feature_cols [age, income, loan_amount, car_price, debt_pressure, brand_freq, loan_term] X df[feature_cols] y df[is_default] # 标签是0/11代表违约这段代码先做了缺失值和异常值处理。收入用中位数填充是因为收入分布偏态明显中位数比均值更稳健贷款金额用均值填充则是因为金额相对正态。debt_pressure这个特征是我在多个车贷项目里验证过的它能直接反映出客户每个月要还的钱占收入的比重比单独输入贷款金额更接近风控认知。品牌频数编码用value_counts生成映射字典实际操作时记得把训练集的映射存下来测试集用同一个映射避免信息泄漏。2.2 标签定义与数据集划分别让“已还清”和“未逾期”混在一起给定数据集里通常有一个字段表示当前还款状态常见值有“正常”“逾期”“已结清”“核销”等。做违约预测前必须先把这些状态翻译成二分类标签1表示违约0表示未违约。这个翻译动作看着简单实际上最容易埋坑。我见过一份数据把“已还清”和“当前正常”都归为0看起来没毛病但“已还清”的客户已经走完了整个生命周期中间可能发生过逾期又还上了。如果数据集里记录了历史逾期次数那么这些客户的历史逾期行为已经体现在特征里再用“最终还清”当作未违约等于让模型学“过程中逾期没关系只要最终还清就不算违约”。对风控来说这显然不对。一个相对靠谱的标签定义是观察期内在贷款发放后固定期限内比如M3即逾期超过90天发生逾期或最终被核销标记为1其余标记为0。如果数据集只有最终状态那至少要把“核销”“呆账”“法催”这类明确损失状态定义为1把“正常结清”“当前正常”定义为0。数据集划分同样需要小心。车贷数据是带时间戳的如果直接用train_test_split随机打乱很可能会把早期和晚期的客户混在一起导致模型学到时间趋势而不是真实的风险规律。我一般按贷款发放时间排序前70%作为训练集后30%作为测试集这样能模拟“用历史预测未来”的真实场景。# 按贷款发放日期排序再划分而不是随机打乱 df df.sort_values(loan_date).reset_index(dropTrue) cut_point int(len(df) * 0.7) train_df df.iloc[:cut_point] test_df df.iloc[cut_point:] X_train train_df[feature_cols] y_train train_df[is_default] X_test test_df[feature_cols] y_test test_df[is_default] print(f训练集样本数: {len(train_df)}, 违约率: {y_train.mean():.3f}) print(f测试集样本数: {len(test_df)}, 违约率: {y_test.mean():.3f})这段代码先按日期排序再切分避免了随机划分带来的时间泄漏。打印违约率的意义在于检查训练集和测试集的分布是否一致——如果测试集违约率明显低于训练集说明市场环境在变化模型在测试集上的表现会虚高。后续调参时也要以这个按时间划分的测试集为准不要原地再用随机划分。3. 随机森林模型用集成树先跑通一个能用的基线随机森林是车贷违约预测里最稳的起点模型。它训练快、对特征尺度不敏感、不容易过拟合而且能直接输出特征重要性。对于给定数据集先用随机森林跑出一个可达90% AUC的基线再决定要不要上AdaBoost或者其他复杂模型。3.1 随机森林的投票机制与车贷场景的契合点随机森林的核心理念是bagging加随机特征选择。它训练很多棵决策树每棵树用训练集的随机子样本bootstrap采样每个节点分裂时只考虑随机抽出的部分特征最后把全部树的预测结果投票或平均。这样做的结果是单棵树可能过拟合但上百棵树平均之后方差被压下来整体泛化能力很强。车贷数据集通常的特点是特征维度不高、但噪声很多——客户的收入可能是填的车辆价格可能是经销商调的征信记录里也可能有错误。随机森林对这类噪声的容忍度比较高因为单棵树的错误会被其他树的投票“稀释”掉。另外车贷数据里连续特征和类别特征混杂随机森林不需要像逻辑回归那样手动做多项式扩展它天然能捕捉非线性关系。随机森林还有一个对风控从业者特别有用的产物特征重要性。它能告诉我们“征信查询次数”比“车辆颜色”重要多少这个结论可以帮助业务方优化进件策略。不过要注意特征重要性不等于因果重要性它只能告诉我们哪些特征对预测有统计贡献不能告诉我们删掉某个特征后业务风险真的会变化。3.2 最小可复现代码随机森林训练与参数说明我用sklearn的RandomForestClassifier跑车贷数据的流程基本固定三步实例化、训练、评估。from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import roc_auc_score, classification_report # 随机森林模型训练 rf_model RandomForestClassifier( n_estimators300, # 树的数量300棵足够覆盖大多数车贷数据量 max_depth10, # 限制树深度防止单棵树过拟合 min_samples_split20, # 节点分裂最小样本数削弱噪声影响 min_samples_leaf40, # 叶子节点最少样本数让预测更平滑 class_weightbalanced, # 对违约样本加权缓解样本不平衡 random_state42, n_jobs-1 # 使用全部CPU并行训练 ) rf_model.fit(X_train, y_train) y_pred_prob rf_model.predict_proba(X_test)[:, 1] y_pred rf_model.predict(X_test) auc roc_auc_score(y_test, y_pred_prob) print(f随机森林测试集AUC: {auc:.4f}) print(classification_report(y_test, y_pred))这段代码里最值得解释的是四个参数。n_estimators决定森林规模车贷数据量在几千到几十万之间300棵是一个性价比很高的值超过500收益很小max_depth10是控制过拟合的关键很多初稿直接用默认的无限深度结果训练集AUC接近1测试集却掉下来min_samples_leaf40相当于给树加了一层“平滑”让叶子至少包含40个样本避免模型为了两个极端样本单独划片class_weightbalanced是按违约占比自动加权比如违约样本占5%则违约类权重约为正常类的约20倍这一步能在不增加代码的情况下显著提升召回。跑完之后我的习惯是画出特征重要性确认模型没有依赖到奇怪的特征。import matplotlib.pyplot as plt import numpy as np feat_imp pd.Series(rf_model.feature_importances_, indexfeature_cols).sort_values() feat_imp.plot.barh(figsize(8, 6)) plt.xlabel(Feature Importance) plt.title(RF Feature Importance) plt.tight_layout() plt.show()从这张图里你能快速看出哪些字段是“凑数”的。比如前两次我跑数据车辆颜色被one-hot成了十几个特征每个重要性都低得接近0最后直接删掉。反过来debt_pressure和income通常排在前三说明交互特征确实有效。参数调整的思路也基于此如果brand_freq重要性异常高需要检查是不是频数编码时用了测试集信息这种泄漏会让特征重要性虚高。3.3 随机森林的参数边界调参时先动哪个随机森林的可调参数很多但车贷场景下值得优先动的只有三个max_depth、min_samples_leaf、n_estimators。顺序应该是先固定n_estimators到300然后扫描max_depth和min_samples_leaf最后回到n_estimators确认是否饱和。我常用一段简单的网格搜索来确定最佳区间from sklearn.model_selection import GridSearchCV param_grid { max_depth: [6, 10, 15], min_samples_leaf: [20, 40, 60] } gs GridSearchCV( RandomForestClassifier(n_estimators300, class_weightbalanced, random_state42, n_jobs-1), param_grid, cv3, scoringroc_auc, n_jobs-1 ) gs.fit(X_train, y_train) print(f最佳参数: {gs.best_params_}, AUC: {gs.best_score_:.4f})这段网格搜索只用3折交叉验证因为车贷数据通常有几万条样本3折已经能给出稳定的结果。如果数据量小于5000建议改成5折。scoringroc_auc的原因是我们重点关注排序能力而不是特定的阈值准确率。这里有一个血泪经验网格搜索之前一定要先按时间划分否则搜索过程本身会把时间泄漏信号当成最优参数导致最终模型在真实验证集上翻车。4. AdaBoost模型在车贷违约预测里怎么调才不踩坑随机森林是“平均思维”AdaBoost是“纠错思维”。如果说随机森林输出的是脏数据下的稳定基线AdaBoost则是把基线的错误挑出来反复学习的提升算法。但它在车贷数据上有几个特有的坑调不好容易过拟合到噪声。4.1 AdaBoost的加权机制与随机森林的差异AdaBoost的训练逻辑是先训练一棵弱决策树然后提高被它分错的样本权重再训练下一棵树让新树更关注上一轮犯错的人。最后把所有树的加权投票作为输出。它和随机森林的本质区别在于随机森林的各棵树是独立并行的AdaBoost的每棵树都在前一轮的“错误地图”上生长前后有强依赖。这个机制对车贷场景意味着什么如果数据集里有一群“老赖”的特征高度相似AdaBoost会不断加大这些样本的权重最终把它们完全记住。在测试集上如果新客户的违约模式与那批老赖不完全一致模型就会误判。所以AdaBoost在车贷数据上的表现往往不如随机森林稳定除非数据集足够大、且违约样本的模式足够有代表性。另一个实际差异是参数空间。随机森林主要调深度和叶子节点数AdaBoost的核心是learning_rate和n_estimators。learning_rate控制每棵树对最终模型的贡献调小一点能防止后期树被一两个异常样本带偏但需要增加树的数量训练时间会变长。在车贷数据集上我一般把learning_rate设成0.1n_estimators从100开始扫描。4.2 AdaBoost代码实现从默认参数到逐步调优sklearn的AdaBoostClassifier默认基学习器是决策树桩也就是深度为1的树。这个默认配置在车贷数据上通常表现不好因为单个特征的分割能力太弱。我一般把基学习器换成深度为3的决策树效果提升明显。from sklearn.ensemble import AdaBoostClassifier from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import roc_auc_score # AdaBoost模型训练基学习器用深度3的决策树 ada_model AdaBoostClassifier( estimatorDecisionTreeClassifier(max_depth3, min_samples_leaf20), n_estimators200, learning_rate0.1, random_state42 ) ada_model.fit(X_train, y_train) ada_prob ada_model.predict_proba(X_test)[:, 1] ada_auc roc_auc_score(y_test, ada_prob) print(fAdaBoost测试集AUC: {ada_auc:.4f})注意新版本sklearn里base_estimator改名为estimator如果你的环境是老版本用base_estimator替代。max_depth3意味着每棵决策树最多分裂三次这样能捕捉一些交互关系又不至于过拟合。learning_rate0.1意味着每棵树对最终预测的贡献被缩小到10%这要求后面有足够多的树来“补课”。200棵树在这个学习率下通常能收敛但建议画一条AUC vs n_estimators曲线观察AUC是否还在上升。如果还在上升就继续加大n_estimators如果AUC先升后降说明已经过拟合要回头调小learning_rate或增加min_samples_leaf。实际项目中AdaBoost对特征噪声比较敏感所以它适合在特征工程做得比较干净的数据集上接棒随机森林。我通常的做法是先用随机森林做特征筛选把重要性排在后30%的特征剔除再用清洗后的特征跑AdaBoost。这样可以规避AdaBoost对无关特征的“纠结”。# 用随机森林特征重要性筛选特征 imp rf_model.feature_importances_ keep [feature_cols[i] for i in np.argsort(imp)[- int(len(imp) * 0.7):]] print(保留特征:, keep) X_train_sel X_train[keep] X_test_sel X_test[keep] # 重新训练AdaBoost ada_model.fit(X_train_sel, y_train) ada_prob_sel ada_model.predict_proba(X_test_sel)[:, 1] print(f筛选特征后AdaBoost AUC: {roc_auc_score(y_test, ada_prob_sel):.4f})这个两段式做法在多数车贷数据集上能把AUC再提升0.01到0.03。但要注意特征筛选必须在训练集内部做不要用测试集参与筛选否则属于特征泄漏。我见过有人直接用全量数据的特征重要性去筛选然后再划分训练测试这样测试集的AUC会虚高大约0.05。5. 避坑指南车贷违约预测里常见的3个翻车现场做完随机森林和AdaBoost你以为模型能上线了其实还早。车贷数据里藏着几个固定的坑几乎每个做过信贷风控的人都会踩一遍。我把最常见的三条写在这里每条都按“现象—原因—解决”来说遇到同样情况直接对上号。5.1 样本不平衡违约样本只有5%时准确率是骗人的现象训练完模型测试集准确率97%但业务方说模型几乎找不到新违约客户。一查混淆矩阵原来模型把所有客户都预测为“正常”准确率高是因为正常客户本来就占95%。这不算模型这是概率复读机。原因车贷数据集的违约率通常在2%到5%之间模型在默认损失函数下发现“全部预测为正常”的损失最低所以不愿意冒险把高违约概率的客户判成违约。准确率这个指标在极度不平衡的数据下完全失真。解决至少做三件事。第一不用准确率改用AUC、召回率、精确率、F1。第二给模型加class_weightbalanced让少数类的错误付出更高代价。第三在阈值选择上不要固定0.5而是根据业务成本去寻找最优阈值这一点我在第6章展开。如果数据量足够也可以对违约样本做过采样比如SMOTE但车贷数据里违约客户的特征往往重复度低合成样本容易伪造出不存在的人群我更倾向于用class_weight而不是SMOTE。5.2 时间泄漏用未来数据训练过去AUC再高也没用现象模型训练时AUC 0.98测试集AUC 0.88看起来不错。但上线之后发现模型对存量客户的预测准确对新申请客户的预测几乎失灵。再一查训练集和测试集是随机划分的两边的客户来自同一时间段模型学会了“那段时间整体经济环境”的信号而不是客户自身的风险。原因车贷行为与宏观经济、公司信贷政策强相关。比如宽松放款的年份整体违约率升高紧缩年份整体违约率降低。随机打乱划分会把这些时间信号混入训练集和测试集模型在测试集上看到的“未来”其实已经被训练集泄漏了。解决严格按贷款发放日期切分训练集和测试集前70%的订单训练后30%的订单测试。交叉验证也要用时间序列分割不能用普通K折。如果数据集没有日期字段那就要特别注意特征里有没有“当前时间”相关的变量比如“距今天数”“年龄”这类会随时间漂移的字段它们也会带来间接的时间泄漏。最简单的验证方法是看AUC的绝对值有没有高得不正常比如超过0.96就要怀疑是不是泄漏了。5.3 特征穿越把“是否已还款”当成特征直接泄题现象有人在特征列表里放了一个字段叫current_overdue_days当前逾期天数。模型AUC直接飙到0.99。业务方很开心但产品经理指出上线时根本没有这个数据因为你要预测的就是“未来会不会逾期”而current_overdue_days是发生逾期之后才有的结果。这就是典型的特征穿越。原因原始数据集是贷款某时点的快照表里面记录了当前状态这个状态与标签高度相关甚至就是标签的一部分。特征和标签是同一件事的两种描述模型学到的不是风险规律而是“逾期的人就是逾期”这种循环逻辑。解决特征构建只能使用客户申请时点的信息、征信报告里历史还款的信息、贷款审批时点已知的信息。任何“当前状态”字段比如当前逾期期数、当前应还金额、当前已还期数都必须从特征里剔除。如果不确定某个字段是不是穿越特征问自己一句在贷款发放的那一刻这个值我能知道吗不能知道就删掉。6. 把概率变成决策校准与阈值选择的最后一步模型输出的概率分不是真实的违约几率。随机森林和AdaBoost给出的分数是树投票的加权比例最大最小归一化不等于概率。业务要拿这个分数做决策比如分数大于某个值就拒贷就必须先做校准再定阈值。校准的常规做法是看校准曲线。横轴是模型预测概率区间纵轴是该区间样本的真实违约率。如果曲线在对角线上说明概率可信如果曲线偏离严重说明分数只能用于排序不能用于绝对风险判断。sklearn里可以直接对比校准前后的表现from sklearn.calibration import CalibratedClassifierCV # 用等温回归校准概率避免像Platt缩放那样强制sigmoid分布 rf_calib CalibratedClassifierCV(rf_model, methodisotonic, cv3) rf_calib.fit(X_train, y_train) calib_prob rf_calib.predict_proba(X_test)[:, 1] # 校准前后的AUC一致但校准后概率更接近真实违约率 print(f校准前AUC: {roc_auc_score(y_test, y_pred_prob):.4f}) print(f校准后AUC: {roc_auc_score(y_test, calib_prob):.4f})AUC不会因为校准而提升因为校准是保序变换但校准后的概率更有业务意义。比如校准前分数0.7对应的真实违约率可能只有0.3校准后就变成0.68这样业务才敢设置阈值。阈值选择不能拍脑袋用0.5。车贷业务里拒绝一个正常客户的成本失去利息收入远低于放给一个违约客户的损失本金损失。这两类代价不对等最优阈值取决于两者的成本比。假设一笔贷款金额为A预期收益率为r违约损失率为l那么最优阈值大致满足预测违约概率高于某个临界点就拒贷。实际项目中我会用验证集跑出不同阈值下的精确率和召回率再套入业务成本公式阈值精确率召回率每万笔拒绝的误杀成本每万笔放款的违约损失总成本0.30.450.78高低中0.50.620.61中中低0.70.800.41低高高这张表是示意真正的成本数字要结合资金成本、逾期回收率来算。我自己的习惯是先算“拒绝误伤”的成本再算“放款违约”的成本两者相加最小的那个阈值就是业务最优解。校准和阈值这两步做完前面的随机森林和AdaBoost才真正变成能上线决策的工具。最后说一句我的教训第一次做车贷违约预测时我把随机森林调到了AUC 0.94业务方验收也很满意结果上线一个月后新贷款的坏账率反而比旧策略高。复盘发现是时间泄漏和阈值拍脑袋两个问题叠加。后来我把“先做时间切分、再做概率校准、最后用成本定阈值”变成固定流程翻车概率才降下来。希望这份流程对你也有用祝你在自己的数据集上早日跑通。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?