news 2026/10/2 15:33:41

车贷违约预测:随机森林与AdaBoost的完整建模实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
车贷违约预测:随机森林与AdaBoost的完整建模实践

简介:车贷违约预测是金融风控领域常见的二分类任务,面向Python机器学习入门及进阶学习者,资源基于一份含199717条记录的车辆贷款数据集,覆盖从数据加载、预处理、特征值与目标值分离、数据集划分,到随机森林与AdaBoost两类集成模型构建、保存与评估的完整流程。资源共2个文件,压缩包7.34MB,内含可运行的Python脚本与CSV训练数据,输出精度、召回率、F1-score与准确率等指标,实测随机森林准确率0.819、AdaBoost准确率0.822。数据集包含客户编号、信用评分、资产成本、贷款与资产比例、品牌、车厂、地区等49列,字段丰富,便于理解特征工程与集成学习调优。目前已有1062人学习下载,适合希望快速上手车贷违约预测建模、参考完整代码并对比两类集成算法效果的开发者和竞赛爱好者。

1. 车贷违约预测:先定义违约,再谈模型

车贷违约预测在风控里是一个典型的二分类问题:给定一张包含历史贷款记录、客户画像、还款行为的车贷数据集,训练模型预测客户未来是否违约。随机森林和AdaBoost是这个场景里最常被拿出来对比的两套树集成方案。我见过不少团队在公开数据集上把准确率做到95%,但一放到新客群上就坏账率升高,原因往往不在算法,而在训练数据和目标定义。这篇笔记会把从数据清洗、特征工程、随机森林、AdaBoost到阈值调优的完整链路走一遍,并指出几个最容易翻车的细节。适合正在做信贷风控建模的算法工程师,也适合打算用这类数据集入门集成学习的学生。

2. 数据清洗与特征工程:把原始表变成直接能喂给树模型的格式

车贷数据集常见情况是几百个列,几十万行。模型能不能学出信号,很大程度上取决于特征矩阵干不干净。随机森林和AdaBoost虽然在一定程度上容忍缺失值和异常值,但那不代表你不需要处理。数据清洗至少包括四个动作:识别标签定义、处理缺失值、统一类别特征编码、删除目标泄漏特征。这几个动作做在前面,能给你后面的调参节省大量时间。

2.1 先看数据形态:缺失值、类型和标签分布

拿到一张car_loan.csv之后,不要急着训练,先跑下面的代码把数据摸一遍。

import pandas as pd import numpy as np df = pd.read_csv('car_loan.csv') print('shape:', df.shape) print(df.dtypes.value_counts()) target = 'is_default' print(df[target].value_counts(normalize=True))

这段代码的作用很直接:shape告诉你样本量和特征数量,如果特征超过200个,后面一定要做特征筛选;dtypes.value_counts()能一眼看出有多少列是数值型,多少列是object型,object型通常需要编码;value_counts(normalize=True)输出的是两个类别的占比,这是整个项目最重要的一个数字。

如果违约样本占比在5%~15%,还算相对乐观。假如低于5%,你要在训练时就考虑类别不平衡的影响,否则后面得到的准确率再高也都是虚的。我习惯把缺失率也统计出来:

missing_ratio = df.isnull().mean().sort_values(ascending=False) print(missing_ratio.head(10))

看前10个缺失率高的列,缺失率超过60%的列可以直接考虑丢掉,除非它有极强的业务含义。缺失率不高的列,后面用中位数填充即可。这里尤其注意像“月收入”这种字段,往往存在大量0值和空值,0值不一定代表收入为0,很可能是数据缺失被填成了0,需要和业务方确认。

2.2 类别特征编码与缺失值填充:别让对象列卡住训练

随机森林和AdaBoost本身是数值型特征驱动的模型,sklearn的实现并不接受字符串输入,所以必须先把类别特征转换成数值。这里有个容易踩的坑:直接对类别特征用pd.get_dummies()生成几百列稀疏特征,会让随机森林的训练速度变慢,而且在某些类别数量多的字段上会造成碎片化分裂。相比之下,OrdinalEncoder对违约预测这类树模型场景更实用。

from sklearn.impute import SimpleImputer from sklearn.preprocessing import OrdinalEncoder cat_cols = df.select_dtypes(include=['object']).columns.to_list() num_cols = df.select_dtypes(include=['int64', 'float64']).columns.to_list() num_cols = [c for c in num_cols if c != target] df[num_cols] = SimpleImputer(strategy='median').fit_transform(df[num_cols]) df_clean = df.copy() for col in cat_cols: df_clean[col] = OrdinalEncoder( handle_unknown='use_encoded_value', unknown_value=-1 ).fit_transform(df[col].to_frame())

这里有两个关键细节。SimpleImputer用median而不是mean,因为车贷收入、年龄这类字段往往存在长尾分布,中位数对异常值更稳健;而OrdinalEncoder的handle_unknown参数,指定了未来新用户在训练集里没出现过的类别时,编码成-1而不是报错,这一点在线上推理时非常重要。你训练时用的是历史样本,线上来的新客户难免会有新职业、新地区,编码器必须能处理这种情况。

2.3 时间特征与金额特征:不要直接丢原始值进模型

车贷数据集中通常有放款日期、首次还款日期,以及贷款金额、车辆价格、首付比例等字段。日期字段不能直接当成整数塞给树模型,比如“2024-05-17”减去“2023-05-17”得到的天数是有业务含义的,但原始日期字符串本身不是特征。我一般会把日期拆成几个有意义的衍生字段。

df_clean['loan_year'] = pd.to_datetime(df_clean['loan_date']).dt.year df_clean['loan_month'] = pd.to_datetime(df_clean['loan_date']).dt.month df_clean['down_payment_ratio'] = df_clean['down_payment'] / df_clean['car_price'] df_clean['monthly_payment_ratio'] = df_clean['monthly_payment'] / df_clean['monthly_income'] df_clean['loan_amount_log'] = np.log1p(df_clean['loan_amount'])

年份和月份能帮模型捕捉不同时期的审批政策差异;首付比例、月供收入比是车贷风控里非常经典的风险指标;对贷款金额做log变换是为了压缩极端大额贷款的数值范围。树模型对单调变换不敏感,log变换的主要价值是为了后续万一做逻辑回归或神经网络时,特征尺度更均匀。这些衍生特征在银行客户认购产品预测、个人信用预测这类金融建模任务里也是通用的思路。

2.4 特征筛选:用单棵决策树先做一次预检

当特征量大时,全扔给随机森林不是不能用,但训练时间和内存都会成问题。我的习惯是先跑一棵很浅的决策树,用它的feature_importances_做第一轮筛选。

from sklearn.tree import DecisionTreeClassifier X_selected = df_clean.drop([target, 'loan_date'], axis=1, errors='ignore') y = df_clean[target] dt = DecisionTreeClassifier(max_depth=5, random_state=42) dt.fit(X_selected, y) feat_imp = pd.Series(dt.feature_importances_, index=X_selected.columns) print(feat_imp.sort_values(ascending=False).head(30))

为什么用单棵决策树而不是随机森林?因为单棵树训练快,max_depth限制在5层,得到的重要性已经够筛掉大量无用特征。把top30到top50的特征保留下来给随机森林和AdaBoost,既减少了噪音干扰,也大幅缩短了网格搜索的时长。注意,这一步只用来粗筛,不要用它对不同模型的价值做过高判断,因为树模型的特征重要性存在随机性,换一组随机种子,排名会有波动。跑完特征筛选后,才真正进入建模环节。

3. 随机森林模型:先跑通基线,再谈调参

3.1 随机森林为什么在车贷违约场景上稳定

随机森林属于bagging类集成算法,它同时引入样本扰动和特征扰动。每一棵树从训练数据中有放回地抽样,每个节点只从随机抽取的特征子集里找最优分裂,最后通过投票输出类别。这种结构决定了它对高维稀疏数据、特征间存在非线性关系的数据都有较强的适应能力。车贷违约的影响因素很多,比如收入与月供比、历史逾期次数、工作稳定性等,它们对违约的影响往往是叠加且非线性的,随机森林的随机特征子空间恰好能挖掘这类交互效应。

随机森林和决策树的区别在于:单棵决策树很容易在训练集上长得过深、记住噪声,随机森林用多棵树投票的方式把方差摊平了,所以在中小规模的车贷数据集上不容易过拟合。它不要求特征归一化,这给数据管线省了很多事。另一个优势是能直接输出特征重要性,方便你回头做特征迭代。

3.2 训练随机森林跑通基线:stratify和AUC

代码先跑一个最简版本,把基线结果拿到,再做调参。

from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score, classification_report X = df_clean.drop(target, axis=1) y = df_clean[target] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) rf = RandomForestClassifier( n_estimators=200, max_depth=8, min_samples_leaf=30, random_state=42, n_jobs=-1 ) rf.fit(X_train, y_train) y_prob = rf.predict_proba(X_test)[:, 1] y_pred = rf.predict(X_test) print('AUC:', roc_auc_score(y_test, y_prob)) print(classification_report(y_test, y_pred))

这里有三个值得说明的参数。stratify=y意思是在切分训练测试集时,按照y的类别比例进行分层抽样,否则一旦随机切分导致测试集里违约占比特别低,你评估出来的指标会和真实效果差很多;max_depth=8限定了树深度,配合min_samples_leaf=30让每片叶子至少要有30个样本,这能抑制单棵树拟合得过细;n_jobs=-1则让所有CPU核并行训练,跑得快一些。

第一次跑完,你大概率会看到AUC在0.7~0.85之间,具体取决于数据质量。不要急着追求AUC上0.95,先对照classification_report看违约类(通常记为1)的recall和precision。如果recall很低,后面要么调class_weight,要么下调决策阈值。

3.3 三个真正值得调的参数

随机森林可以调的参数很多,但业务时间有限,经验里最值得调的是n_estimators、max_depth和min_samples_leaf。n_estimators不是越多越好,超过300以后对结果提升很有限但训练时间线性增加;max_depth决定单棵树复杂度,一般在6~12之间;min_samples_leaf是抗过拟合最有效的旋钮。

from sklearn.model_selection import GridSearchCV param_grid = { 'n_estimators': [150, 250], 'max_depth': [6, 10], 'min_samples_leaf': [20, 50] } gs_rf = GridSearchCV( RandomForestClassifier(random_state=42, n_jobs=-1), param_grid=param_grid, scoring='roc_auc', cv=5, n_jobs=-1 ) gs_rf.fit(X_train, y_train) print(gs_rf.best_params_) print(gs_rf.best_score_)

GridSearchCV会对每个参数组合做5折交叉验证,评估指标用roc_auc而不是accuracy,这在不平衡数据上更重要。为什么用交叉验证而不用单次训练测试切分?因为一次切分的AUC波动很大,交叉验证能告诉你模型在多种子样本下的稳定表现。跑这个过程时如果数据量很大,建议先缩小参数组合,否则几十万的样本跑几十个组合会等得让人心焦。调参完成后,把best estimator在测试集上重新评估一遍,并保存模型。

3.4 用特征重要性验证模型逻辑

模型训练完之后,不要只留在AUC上。把随机森林的特征重要性打印出来,和业务经验对比一遍,能发现很多隐藏问题。

import pandas as pd importance_df = pd.DataFrame({ 'feature': X_train.columns, 'importance': gs_rf.best_estimator_.feature_importances_ }).sort_values('importance', ascending=False) print(importance_df.head(20))

如果排名靠前的特征是“首付比例”“月供收入比”“历史逾期次数”,那模型的逻辑基本靠谱。如果排名第一的是某个ID类字段,比如“合同编号”“贷款申请流水号”,那极有可能是这个字段和目标之间发生了某种意外关联,应该把它从特征里去掉。这类ID列在原始数据集里很常见,它们对违约预测没有泛化价值,只会让树模型钻空子。

4. AdaBoost模型:与随机森林的差异和融合

4.1 AdaBoost算法在车贷数据上适合吗

AdaBoost是boosting家族的代表性算法。它逐轮训练弱学习器,每一轮都会放大上一个轮被分错的样本权重,让新的学习器更关注这些难样本。在车贷违约预测中,违约样本往往分布在特征空间的边缘地带,比如收入不稳定但贷款金额又高的群体,AdaBoost通过不断聚焦这些被漏掉的样本,有时能拿到比随机森林更高的AUC。

但它也有明显短板:对噪声样本和错误标签极度敏感。如果某个样本的特征极端但标签其实是人工录错,AdaBoost会把它的权重越推越高,不仅拖慢训练,还会让最终模型偏离正确边界。因此在用AdaBoost前,至少要做一次简单的离群值审计,把那些特征和标签明显互斥的样本删掉。你可以看特征分布,收入几乎为0却有极高贷款金额且正常还款的样本,多半是异常点。

4.2 训练AdaBoost:基学习器选深度2的决策树

sklearn里的AdaBoostClassifier默认基学习器是max_depth=1的决策树桩,但车贷数据中特征交叉很重要,深度1可能欠拟合。我一般会把深度设成2或3,这样既保留了boosting的串行纠错能力,又不会让单个弱学习器过拟合。

from sklearn.ensemble import AdaBoostClassifier from sklearn.tree import DecisionTreeClassifier ada = AdaBoostClassifier( estimator=DecisionTreeClassifier( max_depth=2, class_weight='balanced', random_state=42 ), n_estimators=100, learning_rate=0.6, random_state=42 ) ada.fit(X_train, y_train) y_prob_ada = ada.predict_proba(X_test)[:, 1] y_pred_ada = ada.predict(X_test) print('AdaBoost AUC:', roc_auc_score(y_test, y_prob_ada)) print(classification_report(y_test, y_pred_ada))

这里有个需要注意的地方:在较老版本的scikit-learn中,基学习器参数名是base_estimator,新版本改成了estimator,如果你的代码报错,先检查一下版本。learning_rate代表每棵新树的贡献在整体预测中被缩小的比例,0.6~0.8是车贷场景下相对稳健的区间。设得太小,需要更多轮树才能收敛;设得太大,模型容易变得激进。class_weight='balanced'告诉基学习器按样本比例给类别加权,这对违约率只有10%左右的数据集很有用。

4.3 概率校准:软投票之前别跳过

随机森林和AdaBoost输出的predict_proba并不是标准概率,而是树叶子节点上的经验频率或加权频率。如果两个模型的概率尺度不一致,软投票会被某个概率偏高或偏低的模型带偏。我一般会在融合前做一个概率校准。

from sklearn.calibration import CalibratedClassifierCV ada_calibrated = CalibratedClassifierCV( estimator=ada, method='isotonic', cv=3 ) ada_calibrated.fit(X_train, y_train) y_prob_cal = ada_calibrated.predict_proba(X_test)[:, 1] print('Calibrated AUC:', roc_auc_score(y_test, y_prob_cal))

这里用isotonic方法做非参校准,在样本量足够时比sigmoid更灵活。cv=3表示在训练集内部分三折来生成校准用的概率,避免直接用训练集校准导致过拟合。校准后AUC通常不会大幅变化,但概率变得更可解释:0.7就真的代表大约70%的违约可能。这个校准后的概率在审批策略中可以直接用来设定额度或利率。

4.4 融合模型:软投票与几何平均

随机森林和AdaBoost在特征采样和样本加权机制上差异很大,预测错误的样本往往并不完全重叠,所以把它们集成起来经常能提高稳定性。先看两个模型在测试集上的AUC,如果差距不大,就做一次soft voting。

from sklearn.ensemble import VotingClassifier voting = VotingClassifier( estimators=[('rf', gs_rf.best_estimator_), ('ada', ada_calibrated)], voting='soft' ) voting.fit(X_train, y_train) print('Voting AUC:', roc_auc_score(y_test, voting.predict_proba(X_test)[:, 1]))

soft voting会先得到两个模型各自的违约概率,然后按平均概率决定最终结果。融合时有一点值得注意:如果某个模型的AUC明显高于另一个,简单的等权平均反而会拉低成绩。我一般会用网格搜索给两个模型分配权重,或者在验证集上比较simple average、几何平均和AUC加权平均三种方式。模型融合不是必选项,但它能让你在业务上多一个可解释的容错机制。

5. 避坑:从数据泄漏到样本不平衡,五个高频翻车点

如果模型在测试集表现不错,放到真实业务却失灵,通常不是算法问题,而是下面的工程细节出了问题。

5.1 先标准化再切分带来的信息泄漏

现象:无论是随机森林还是AdaBoost,AUC在测试集上高得不正常,上线后明显下降。

原因:有些人习惯在pandas里先把所有特征做标准化,然后才做train_test_split。标准化用的是全量数据的mean和std,这意味着测试集的信息已经进入了训练环节。虽然树模型对尺度不敏感,但这在后续尝试逻辑回归或神经网络时,泄漏会直接拉低离线评估的可靠性,给业务埋雷。

解决:先切分再对训练集fit,再用同一个标准器transform测试集。更通用的做法是把整个流程放进sklearn Pipeline里,让交叉验证替你把每一折都重算统计量。

5.2 只看准确率,模型把违约客户全过滤掉了

现象:accuracy达到93%,但违约类的recall只有不到0.1。

原因:当违约样本只占5%时,模型只要全部预测为不违约,准确率就有95%。大多数分类模型默认决策阈值是0.5,但在严重不平衡的数据上,0.5根本不是最优阈值,模型在阈值附近给出的概率大多数都小于0.5,于是少数类被吞掉了。

解决:用AUC、recall、precision和confusion matrix一起来评估。训练时给随机森林或AdaBoost设置class_weight='balanced'。更关键的是阈值后调,这一点在最后一章会具体讲。不要把注意力放在模型的默认预测类别上,要看概率分布和曲线。

5.3 随机切分样本,忽略了车贷数据的放款时间

现象:用随机切分得到的测试集表现很好,但按放款时间点切分一下,模型效果突然下降。

原因:车贷数据集通常按放款时间顺序累积,客户在不同时间的还款行为受宏观环境、审批策略影响。如果直接在全部样本里随机抽20%做测试集,同一个用户的不同分期记录可能同时出现在训练和测试中,而且时间靠后的样本被提前泄露给了训练集,造成时序性泄漏。

解决:至少做一次按时间排序的验证,例如用前80%的放款月份做训练,后20%做测试。如果模型在时序切分下表现依然稳定,才说明它不是靠捕捉历史数据漏洞取得的高分。按时间切分后的AUC一般会比随机切分低一些,这很正常。

5.4 特征里混入未来信息

现象:特征重要性排序中最高的几个特征看起来和违约定义高度重合,比如“历史还款次数”“当前逾期天数”。

原因:给定数据集里可能存在和目标直接相关的衍生变量。比如“近12个月实际还款比例”这个特征,如果它是在贷款结束之后才统计的,那建模时就等于把答案灌了进去。这类特征叫目标泄漏,会让模型在离线评估中几乎满分,上线后完全没有同名特征可用。

解决:训练前逐列检查含义,把任何带有还款结果、逾期结果、催收结果语义的字段从特征中去掉,只保留申请时点或放款时点之前就已知的信息。如果数据集没有字段说明,只能靠业务经验来人工审核每个特征。没有业务解释的特征宁可先删掉。

5.5 同一用户的多期记录没有聚合

现象:模型在单个样本上的预测看起来合理,但按客户ID汇总时,同一个人的多笔贷款预测结果互相矛盾。

原因:车贷数据集里可能同一个客户有多笔历史贷款记录,如果直接把每一笔贷款当作独立样本,模型会学到“这个人以前借过,所以信用好”这类假象,而且同一客户的样本被同时分进训练集和测试集,造成客户层面泄漏。

解决:如果数据集存在客户ID字段,训练前先按客户ID聚合特征,比如取最近一次贷款记录,或者把多笔记录聚合成“历史贷款笔数”“历史逾期次数”等统计特征。如果业务上要求对每一笔贷款单独预测,也要保证同一个客户的所有记录只能出现在训练集或测试集中的一边。

6. 用阈值调优和时序验证提升落地价值

6.1 用P-R曲线找到风控需要的阈值

很多项目到AUC就停了,但如果你的任务是“预测是否违约”,决策阈值才是最直接影响审批结果的东西。默认0.5的阈值意味着只有模型认为违约概率大于50%才拒绝,在违约率只有10%的数据上,这通常过于宽松。需要用精确率和召回率的权衡来寻找阈值:

from sklearn.metrics import precision_recall_curve precision, recall, thresholds = precision_recall_curve(y_test, y_prob) f1_scores = 2 * precision * recall / (precision + recall + 1e-6) best_idx = f1_scores.argmax() print('最优阈值:', thresholds[best_idx]) print('最大F1:', f1_scores[best_idx])

如果风控策略要求违约召回率优先,可以挑一个recall不低于0.8的阈值,再比较此时的precision能否被业务接受。阈值本质上是业务参数,不是模型参数,别把它交给GridSearchCV自动决定。调完阈值后,记得在干净的测试集上重新跑一次test_status,否则阈值是在测试集上调的,等于又泄漏了一次。

6.2 用三次切分验证模型的时效性

最后一个建议是不要只做一次train_test_split,而是按放款时间或订单序号切成三段:前60%训练,中间20%验证,最后20%测试。训练时用前60%调参,用中间20%选阈值,最后20%模拟未来数据来测试。这样的验证曲线更接近真实上线后的表现。我第一次做车贷违约预测时就是靠随机切分拿到了AUC 0.85,然后按时间切分掉到0.78,才意识到自己前面一直在自欺欺人。后来养成的习惯是每次建模都必须同时跑随机切分和时序切分两组指标,数字更难看的那组才是你向风控团队汇报时该用的数。希望这个习惯也能帮到你。

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

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

WorkBuddy+LLM+Python:季度销售复盘报告与PPT自动化生成实战

每到季度末,销售团队最头疼的往往不是冲业绩,而是那堆躺在共享盘里的Excel——十几个大区、几十个产品线、上百个销售代表的明细数据,字段命名五花八门,合并单元格满天飞。老板一句"下周一经营分析会上讲一下"&#xff…

作者头像 李华
网站建设 2026/10/2 15:33:05

WorkBuddy 实战指南:从安装配置到 Skill 开发与多 Agent 协作

1. 为什么我要认真写这篇 WorkBuddy 实战指南我第一次接触 WorkBuddy 是在一个周五的晚上,当时手头堆着三个项目的收尾工作,脑子里全是“能不能让 AI 真的帮我干点活,而不是只会在对话框里说漂亮话”。试了一圈市面上的 AI 工作台之后&#x…

作者头像 李华
网站建设 2026/10/2 15:31:18

货拉拉营销广告大模型落地实践:从文案生成到合规校验

货拉拉的同城货运、搬家、拉货业务,每天要面对的是几十万甚至上百万次的用户触达窗口——App弹窗、短信、站内信、朋友圈广告、短视频投放、司机端招募物料,每一个触点背后都是一条广告物料。过去这些物料靠人工写、人工排、人工审,碰上大促节…

作者头像 李华
网站建设 2026/10/2 15:29:43

展会数据抓取实战:并发线程安全与电话验证全解析

前阵子接了个法国展会项目,客户要把法国FIP展官网上的参展商数据全部整理下来,包括展位号、企业简介、官网链接和联系方式。刚开始我觉得这不就是个爬虫嘛,requests一拉,正则一匹配就完事了,真正上手才发现这站点把爬虫…

作者头像 李华
网站建设 2026/10/2 15:29:19

数据安全产品目录2025解读:五类核心产品与流程规范实战

“做数据安全这一行,最怕的不是技术不会,而是客户问‘你们的产品有没有进目录’。今天打开行业群看到美创5款产品进了《数据安全产品目录(2025年版)》的喜报,第一反应是替老朋友高兴,第二反应是觉得这事值得…

作者头像 李华