☰
债券违约预测实战:Lasso特征提取与GBDT建模全流程解析
2026/9/27 23:35:02 网站建设 项目流程

简介:一套围绕债券违约预测的机器学习研究包,面向金融风控研究者、量化分析人员及希望将特征工程用于多模型对比的机器学习学习者。研究包基于截至2017年7月17日的债券违约事件与宏观流动性数据,先梳理归因,再借助Lasso回归筛选特征,并输入带L2惩罚项的逻辑回归、SVM、神经网络、GBDT、随机森林等模型进行违约预测,完整呈现“数据清洗—特征提取—模型比较—结论输出”的研究链路。压缩包共11个文件,以9个Python脚本为主体,覆盖描述性统计、KMV数据获取、CoVaR分位数回归、Lasso特征提取与模型选择等关键环节,另有1份研究报告PDF与1份README说明文档,整体大小3.11MB,轻量易用。目前已有175人学习。脚本结构清晰,便于复现GBDT性能最佳的研究结论,并观察特征工程对线性模型预测效果的显著影响;适合作为债券违约预测课题的代码模板或教学案例,也可在此基础上扩展更新数据、调整宏观指标和模型参数。

1. 债券违约预测:为什么Lasso和GBDT搭配起来,比单靠堆模型更值得做

债券违约预测处在信用研究和量化风控的交叉点上,特征少则几十、多则几百,违约样本却往往只有个位数百分比。这类问题上我反复验证过很多次,最后留下的不是某个“最先进”模型,而是一套朴素组合:先用Lasso把噪声特征压掉,再把剩余特征交给GBDT去拟合交互关系。标题里那句“GBDT性能最佳”不是玄学,而是表格数据里GBDT对特征尺度不敏感、能处理缺失值并自动抓交互项的自然结果。我下面把这套链路拆开,适合刚开始做违约预测、或做机器学习想在风控场景落地的读者,按步骤复现,同时避开标签泄漏和数据穿越的陷阱。

2. 建违约预测模型前,先把数据口径和特征池定死

2.1 标签定义:违约不是“财报异常”,而是“未来12个月内发生实质违约事件”

在金融风控里,标签先行。别急着取数,先把“违约”的定义固定下来。常见做法是以“未来12个月”为表现期:如果发行人在观察日之后12个月内出现债券到期未兑付、破产、债务重组、银行借款逾期等事件,就打1,否则打0。为什么是12个月而不是30天?因为债券违约的显性信号往往滞后,评级下调或负面舆情先出现,实际兑付失败多在几个月后;12个月窗口能覆盖信用恶化的传导周期,又不会把太远期、和当前特征相关性弱的事件算进来。

样本切片同样重要。我一般按“季度或年度滚动取观察日”,比如2015到2022年每年底取一次,每个主体每个观察日成为一行样本。这样同一个主体会被切片多次,但每次用的特征都只能是观察日之前已披露的财报。如果用2023年的年报数据去预测2021年的违约,就成了典型的“未来函数”,后面避坑章会专门讲。

2.2 特征池:财务比率、报表结构、审计意见、外部负面事件

债券违约预测的特征池一般围绕四个来源构建。

  • 财务比率:流动比率、速动比率、资产负债率、有息负债率、现金短债比、应收账款周转率、存货周转率、ROA、净利润现金含量。这类特征反映短期偿债和盈利质量。
  • 报表结构:长期借款占比、短期借款占比、对外担保规模、受限资产比例。这些科目直接关系到“能不能快速变现还债”。
  • 审计意见:标准无保留、带强调事项、保留意见、无法表示意见。审计意见是区分度很高的离散特征,往往比某个财务比率更早暴露问题。
  • 外部负面事件:评级下调、列入观察名单、涉及诉讼、股权质押比例异常、商誉减值公告。这部分最好通过公告关键词和舆情数据抽取,但要注意时间戳对齐。

特征池大小建议先做到50到300维。太少,模型没有足够信息;太多,样本量不够时会让线性模型很难稳定。金融数据的特征之间相关性还特别强,资产负债率和有息负债率几乎必然高相关。这正是后面需要用Lasso做特征提取的原因之一。

2.3 数据清洗与缺失值:先看缺失率再补,而不是上来就均值填充

债券发债主体多为上市公司或城投,财务数据披露相对完整,但仍有不少报表科目缺失。新手上路最常见的翻车点是直接对全表均值填充,人为制造“数据看似完整”的假象。我一般分三步处理。

import pandas as pd import numpy as np # 假设 df 是特征宽表,每行是一个样本,feature_cols 是特征列 df = df.copy() miss_rate = df[feature_cols].isnull().mean().sort_values(ascending=False) # 第一步:缺失率超过 80% 的特征直接丢弃 high_miss = miss_rate[miss_rate > 0.8].index df.drop(columns=high_miss, inplace=True) feature_cols = [c for c in feature_cols if c not in high_miss] # 第二步:分行业填充连续型特征 for col in feature_cols: if df[col].dtype in ('float64', 'int64') and col not in discrete_cols: df[col] = df.groupby('industry_code')[col].transform( lambda s: s.fillna(s.median())) # 第三步:离散特征用众数填充,并标记是否缺失 for col in discrete_cols: df[col + '_isna'] = df[col].isnull().astype(int) df[col] = df[col].fillna(df[col].mode().iloc[0])

这段代码逻辑不复杂,但有三个细节值得说明。第一,缺失率阈值放在80%,是因为违约预测里高缺失特征往往只在少数主体披露,分布严重不平衡,利用了反而引入噪声。第二,用行业分组的median而不是全局median,是为了避免不同行业之间的杠杆率、周转率差异被填充值抹平。第三,连续特征没有做缺失值插补时,我习惯保留原始缺失状态交给GBDT,因为GBDT原生支持缺失值划分;但给LR或Lasso用之前,还是要做填充或指示变量。

参数上,缺失率阈值可以按特征总量调整。特征少于50个时阈值放到50%,特征超过200个时放宽到90%也无妨。行业分组填充的组别至少要有30个样本,否则组内中位数不稳定。这些细节会在后面反复影响模型能不能稳定复现。

填充之后我还会做一次分布检查:把填充后的特征和原始非空分布放在同一张直方图里看。如果填充后出现一个过度集中的峰值,说明大量样本被中位数覆盖,这时就要考虑改成分箱变量或干脆把该特征改成“是否披露”这种0/1特征。这种“披露缺失本身就有含义”的思路,在信用风险里经常比强行插值更有效。

2.4 特征提取与Lasso的定位:先降维,而不是直接扔给GBDT

机器学习里的“特征提取”有两层意思:一是从原始日志、文本、图像里自动构造新特征,比如多模态特征提取器做的事情;二是从已有特征表里选出一个低维且稳定的子集。债券违约预测绝大多数情况是后者。Lasso的定位,不是去“创造”新特征,而是通过L1正则化把不重要的特征系数压到零,留下一组稀疏特征。

为什么不让GBDT直接吃全量特征?GBDT确实能自己筛特征,训练时也会大量忽略无用特征。但在违约样本很少的情况下,特征维度过高会让GBDT的每棵树的划分空间更稀疏,更难收敛到稳定结构。先用Lasso把噪声特征去掉,GBDT就能把有限的复杂度用在真正的结构上。这也是“特征工程的重要性”落到实操时最关键的判断:特征工程是给模型减负,而不是堆更多特征。和PCA相比,Lasso保留了原始特征的可解释性;和单变量筛选相比,Lasso考虑到了特征之间的冗余。在监管或信用评审场景里,能被解释成“资产负债率”而不是“主成分1”,这条优势是决定性的。

3. 用Lasso做特征提取:alpha、标准化与稳定性一个都不能少

3.1 特征筛选前先明确:Lasso回归还是L1逻辑回归

标题写的是Lasso,但在二分类违约预测里,严格区分Lasso回归和带L1惩罚的逻辑回归是有必要的。Lasso回归的损失函数是MSE加L1惩罚,而逻辑回归的损失是log-loss加L1惩罚。两者都能产生稀疏系数,都可做特征提取。如果直接用LassoCV去拟合0/1标签,它优化的不是分类边界;虽然系数排序通常可参考,但你最后拿到的“特征子集”并不一定对违约概率建模最优。

我的一般做法是:先用LassoCV跑一版,观察系数的大致量级和正负方向,再用L1逻辑回归作为正式的特征筛选器。下面这段代码兼顾两者。

from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LassoCV, LogisticRegression from sklearn.feature_selection import SelectFromModel # X_lasso 是已经处理过缺失值的特征矩阵,y 是 0/1 标签 scaler = StandardScaler() X_scaled = scaler.fit_transform(X_lasso) # 纯回归视角:用 LassoCV 做特征预排序 lasso = LassoCV(cv=5, max_iter=10000, random_state=42) lasso.fit(X_scaled, y) nonzero_idx = np.abs(lasso.coef_) > 1e-8 print('LassoCV 保留非零特征数:', nonzero_idx.sum()) # 分类视角:用 L1 逻辑回归做正式特征选择 lr_l1 = LogisticRegression( penalty='l1', solver='saga', C=0.5, max_iter=5000, class_weight='balanced' ) sfm = SelectFromModel(lr_l1, threshold='median') sfm.fit(X_scaled, y) selected_mask = sfm.get_support() print('L1 逻辑回归选中特征数:', selected_mask.sum())

为什么这里阈值用median而不是固定值?SelectFromModel的threshold='median'会保留系数绝对值在中位数以上的特征,比较适合特征数在几十到几百的场景。如果你希望更稀疏,可以改成threshold='0.5*mean'。如果希望保留多一些,可以改成threshold='mean'。需要配合后面的稳定性和模型效果回调。

class_weight='balanced'是必须的。债券违约样本占比通常低于10%,如果不对类别权重做任何处理,L1逻辑回归很容易把所有样本都判成不违约,系数也偏向保守。balanced会让少数的违约样本在损失函数里获得更高权重,但要注意它只是缓解不平衡,不是解决不平衡。

3.2 哑变量处理的顺序:连续特征标准化,离散特征不做

L1惩罚对特征尺度极其敏感。假如一个特征“资产负债率”取值0到100,另一个特征“总资产”取值1到1000亿,如果不做标准化,总资产的小幅变化就会主导损失,而资产负债率容易被惩罚成0。所以在进入L1逻辑回归之前,连续特征必须标准化。

哑变量是0/1,本身已经在一个可比较的尺度上,我一般不参与标准化。标准化后的哑变量会从0/1变成带负数的连续值,既破坏稀疏性,也影响后续对“是否属于某个行业”这类变量的解释。实际操作我会先分离连续特征和哑变量,分别处理后拼接。

continuous_cols = [c for c in selected_cols if df[c].dtype in ('float64', 'int64') and c not in discrete_cols] cat_cols = [c for c in selected_cols if c in discrete_cols] scaler = StandardScaler() X_cont = scaler.fit_transform(df[continuous_cols].values) X_cat = df[cat_cols].values # 已经是 0/1 或 onehot 后的稀疏矩阵 import scipy.sparse as sp if sp.issparse(X_cat): X_all = sp.hstack([sp.csr_matrix(X_cont), X_cat]).tocsr() else: X_all = np.hstack([X_cont, X_cat])

如果哑变量数量特别多,比如行业有30个门类,建议用稀疏矩阵存储,L1逻辑回归在solver='saga'时能接受scipy稀疏输入。还要注意哑变量生成时要从类别里留一个基准类别,否则会引入完全多重共线性;虽然L1不太怕共线性,但系数解释会变得很绕。

3.3 稳定性评估:重采样20次看特征被选中频次

Lasso/L1逻辑回归的系数对样本扰动非常敏感,尤其违约样本少的时候,某次交叉验证选出的特征可能只是运气。我在正式确定特征集之前,一定会做稳定性测试。

from sklearn.utils import resample selected_count = np.zeros(X_scaled.shape[1]) n_rounds = 20 for i in range(n_rounds): idx = resample(np.arange(len(y)), n_samples=len(y), random_state=i) X_bs, y_bs = X_scaled[idx], y[idx] lr_tmp = LogisticRegression( penalty='l1', solver='saga', C=0.5, max_iter=5000, class_weight='balanced' ) lr_tmp.fit(X_bs, y_bs) selected_count += (np.abs(lr_tmp.coef_[0]) > 1e-8).astype(int) stable_features = np.where(selected_count >= n_rounds * 0.7)[0] print('至少被选中14次的特征数:', len(stable_features))

这里用bootstrap重采样而不是交叉验证,是为了观察特征选择结果对样本波动的稳定性。阈值0.7是经验值:样本量小于2000时,建议提高到0.8;样本量大于5000时,0.6也能接受。关键点是每轮都先对原始X_scaled重采样,而不是对原始特征矩阵重采样后再做标准化。因为标准化参数的均值和方差在真实上线时也是在训练集上确定的,如果每轮重新计算,稳定性评估就混入了额外噪声。

3.4 C值怎么调:用小网格搜索,而不是凭感觉

在融资风控项目里,L1逻辑回归的C值(正则强度的倒数)直接决定最终特征数量。C太小,特征被全部压成0;C太大,特征选择退化成普通逻辑回归,失去特征提取意义。我不会只跑一个固定值,而是用一个很小的网格配合AUC做粗调。

from sklearn.model_selection import StratifiedKFold, GridSearchCV from sklearn.metrics import roc_auc_score, make_scorer param_grid = {'C': [0.01, 0.05, 0.1, 0.5, 1.0, 5.0]} lr_grid = LogisticRegression( penalty='l1', solver='saga', max_iter=5000, class_weight='balanced' ) cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) search = GridSearchCV( lr_grid, param_grid, scoring='roc_auc', cv=cv, n_jobs=-1 ) search.fit(X_scaled, y) best_C = search.best_params_['C'] print('最佳 C:', best_C)

注意这个网格搜索只是用来定C的合理范围,不是用来选最终模型的。如果最优C对应的特征数量不足10个,我会把C向上调一个数量级再搜一次;如果最优C选出的特征超过50个,就往下调。经验上,债券违约预测最终特征数落在10到40个之间比较合适。这个“特征数”不是绝对的,但它能帮你快速排除C设错导致的极端情况。

4. 多种模型对比:把GBDT真正跑赢其他模型的全过程

4.1 先定评估指标:别用准确率,用AUC和平均精度

违约数据里负样本占比通常超过90%。一个“全部预测不违约”的模型,准确率也能到90%以上,但在风控上毫无价值。我做对比时至少看两个指标:ROC-AUC和平均精度AP。AUC衡量排序能力,AP更关注预测违约样本的精度变化。另一个业务向指标是“在误杀前5%正常主体的前提下,召回多少违约样本”,这个要等阈值确定后再算。

模型对比之前,所有模型必须用相同的训练集和验证集。如果模型A用2018-2021年训练,模型B用2019-2021年训练,那比较就没有意义。更严格的做法是先用Lasso特征稳定性测试确定特征集,再统一划分数据,而不是每个模型各自筛一遍特征。我见过不少报告里,每个模型用了不同的特征子集,最后的“性能最佳”其实是特征工程差异,不是模型差异。

4.2 基线模型代码:LR、随机森林、GBDT、XGBoost

在特征子集确定后,先跑一组可复现的基线。下面的代码用sklearn和xgboost,没有调高级参数,目的是看每个模型在同一个数据上的自然表现。

from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier from sklearn.linear_model import LogisticRegression from sklearn.metrics import roc_auc_score, average_precision_score from sklearn.model_selection import train_test_split import xgboost as xgb # X_sel 是特征选择后保留的矩阵 X_train, X_val, y_train, y_val = train_test_split( X_sel, y, test_size=0.2, random_state=42, stratify=y ) models = { 'LR': LogisticRegression(max_iter=5000, class_weight='balanced'), 'RF': RandomForestClassifier( n_estimators=300, max_depth=6, min_samples_leaf=20, class_weight='balanced', random_state=42 ), 'GBDT': GradientBoostingClassifier( n_estimators=300, max_depth=4, learning_rate=0.05, subsample=0.9, random_state=42 ), 'XGBoost': xgb.XGBClassifier( n_estimators=300, max_depth=4, learning_rate=0.05, subsample=0.9, colsample_bytree=0.8, scale_pos_weight=10, eval_metric='auc', random_state=42 ) } for name, clf in models.items(): clf.fit(X_train, y_train) y_pred_prob = clf.predict_proba(X_val)[:, 1] auc = roc_auc_score(y_val, y_pred_prob) ap = average_precision_score(y_val, y_pred_prob) print(f'{name}: ROC-AUC={auc:.4f}, AP={ap:.4f}')

逻辑说明:所有模型使用同一个train/val切分,并且用stratify=y保证违约样本比例在两边接近。随机森林max_depth=6、min_samples_leaf=20,是防止小样本下长出完全记忆训练集的叶子。GBDT的learning_rate=0.05而不是0.1,因为违约预测样本量小,学习率太大会快速过拟合。XGBoost的scale_pos_weight=10,是负样本数除以正样本数的近似值;如果违约率只有5%,可以设19或更高。

这组基线不是终点,而是先观察各模型的差距。我遇到过很多次,LR基线AUC只有0.78,GBDT一上来就有0.85,这个差距基本不是调参来的,而是非线性交互带来的。

4.3 为什么几乎每次都是GBDT胜出

表格类特征尤其是财务比率,与违约之间主要是阈值式的单调关系。比如“现金短债比小于0.3时风险陡增”“对外担保占净资产超过50%后违约概率快速上升”。逻辑回归只能拟合线性组合,随机森林能抓非线性但划分方式比较粗,GBDT通过加法模型和分裂点逐步拟合残差,对小样本和缺失值都很友好。

更重要的是,GBDT在构建树的过程中会不断组合特征,生成类似“如果流动比率低且审计意见非标准,风险就高”的规则。这种自动特征交互能力,让它在债券违约预测里天然占优势。相比之下,Lasso只能看到线性相关性,无法发现非线性交互;这就是为什么标题里“Lasso特征提取”和“GBDT性能最佳”并不矛盾——一个负责在稀疏空间里找主线,一个负责把主线之间的交叉项拟合出来。

4.4 类别不平衡:继续调权重,而不是急着上SMOTE

在违约预测里,SMOTE这类过采样方法要谨慎。违约样本本身太少,SMOTE生成的新样本只是在特征空间里做插值,很容易把噪声放大,模型在真实数据上稳定性变差。高频次示范下,我发现调类别权重和小阈值的效果通常比SMOTE更可靠。

调整的办法有几类:

  • 逻辑回归和随机森林用class_weight='balanced',让少数的违约类获得更高权重。
  • XGBoost用scale_pos_weight,它影响正负样本的梯度比例。
  • 自己往GBDT的fit里传sample_weight,一般来说违约样本权重设为“负样本数除以正样本数”即可。

如果用了SMOTE,一定要保证只在训练集上生成合成样本,验证集保持原始分布。否则验证集的AUC会被高估,因为模型已经“见过”类似合成样本的分布。

4.5 用GBDT的特征重要性反向验证Lasso特征子集

Lasso筛出的是线性重要特征,GBDT给出的feature_importance是非线性交互后的重要特征。两边重合的特征才是更值得报告的解释变量。我在最终分析里一定会做这个交集计算。

import pandas as pd # 从前面 dict 里取已经训练好的 GBDT gbdt_model = models['GBDT'] imp = pd.Series(gbdt_model.feature_importances_, index=feature_names) lasso_selected = set(stable_feature_names) gbdt_top = set(imp[imp > imp.quantile(0.7)].index) print('Lasso 与 GBDT 顶部特征交集:', lasso_selected & gbdt_top)

如果交集很小,说明线性和非线性视角差异大,线上预测应以GBDT的top特征为主要候选,Lasso结果作为线性解释补充。如果交集覆盖率超过50%,说明特征选择非常稳健,这个信号值得写进研究报告。我通常也会把GBDT top特征重新加入模型做一轮回验,确认去掉Lasso选中的部分特征后,AUC没有明显下降。

5. 避坑:债券违约预测最容易翻车的5个数据与模型问题

5.1 标签泄漏:用未来数据预测过去,AUC虚高到0.99

现象:交叉验证AUC高得不像话,接近0.99;或回测时预测概率和真实违约几乎同步变化,像是“事后诸葛亮”。

原因:观察日和特征数据没有严格对齐。最常见的是用最新财报去配历史违约标签,或者把同一个主体的不同观察日样本放进训练集和验证集,模型通过主体ID学到记忆,而不是学到特征与违约的关系。另一个隐蔽泄漏是特征里直接放了“是否已发生债券回售”“是否已评级下调”等明显后视信息。

解决:每个样本的特征快照固定为观察日前最近一期已披露财报,不能用期末后补记的科目;训练/验证切分按主体分组,用GroupKFold或提前按主体去重。更严格的验证是按季度逐年滚动,只拿过去样本训练,预测未来样本。这条是底线,如果这里出错,后面所有模型对比都是假的。

5.2 全局标准化导致数据穿越

现象:StandardScaler在全量样本上fit后再划分训练/验证,验证集AUC偏高,但上线后概率分布偏移严重。

原因:scaler的均值和方差包含验证集未来信息。金融数据在不同年度的财务指标均值会随宏观周期漂移,全局标准化等于把未来分布泄露给训练集。违约样本又少,模型很容易学到这种看似稳定的均值结构。

解决:在Lasso选特征阶段也坚持只对训练折做标准化。代码上先train_test_split,再train_scaler.fit(X_train),再用transform得到X_test_scaled。类别权重、SMOTE等同样只能在训练集内计算。这一点在时间序列数据里比普通分类任务更重要。

5.3 Lasso的C值设太小,特征全部被压没

现象:SelectFromModel选出来0个或不到3个特征,GBDT接下来只能在极窄特征上训练,AUC反而低于不筛特征。

原因:C越小,L1惩罚越强。有人直接把C=0.01固定下去,标准化后所有特征系数都被压成0。这不算bug,而是没检查惩罚强度的信号。

解决:先用GridSearchCV在C=0.01到10之间按对数均匀搜索,以验证集AUC为评分。不要只按“选中特征数”选C,因为选特征是为了稳住模型,不是为了最小化特征数。一般建议最终保留10到40个特征;如果最优C导致特征少于10个,就把C向上调一个数量级。

5.4 GBDT在训练集上AUC=1,验证集却跌到0.65

现象:训练集AUC漂亮,换到下一个时间窗口的验证集上明显失效。

原因:违约预测样本量通常在几百到几千,GBDT树一深就开始记忆个别主体。n_estimators=300、max_depth=8时,完全可以把所有训练样本分开。再加上全量特征训练,几乎没有泛化。

解决:把max_depth压到3到5,min_samples_leaf提高到20以上,learning_rate降到0.02到0.05,并开启subsample=0.8到0.9。模型对比时统一观察train/val AUC差值,差值超过0.15就当发生过拟合,不要只看验证集那个数字。XGBoost可以开early_stopping_rounds,让它自动停在最佳迭代附近。

5.5 默认0.5阈值:违约召回率只有10%

现象:AUC达到0.85,但把预测大于0.5才判违约,验证集里的违约样本只抓住两成。

原因:违约样本占比低,模型输出的概率分布整体偏向0.1以下,0.5阈值根本不是为这种不平衡场景设计的。AUC只看排序能力,不回答概率切在哪里。

解决:用验证集计算precision-recall曲线,找出recall达到0.8时对应的概率阈值。或者以“误杀Top3%正常主体内能抓到多少违约主体”为业务指标,反向定阈值。输出概率前可以做一个Platt校准或Isotonic回归,但校准要用独立验证集,不能用训练集。实际操作里,阈值调整带来的效果提升往往比换模型更明显,这也是最容易出成果的一步。

6. 验证方法进阶:时间滚动、学习曲线与SHAP让GBDT真正可信

6.1 用时间滚动窗口替代随机切分

随机切分会高估模型效果,因为训练集和验证集的分布可能重叠。做债券违约预测,我最终会改用滚动时间窗口:用2015到2017年训练,预测2018年;用2015到2018年训练,预测2019年,以此类推。这样最接近真实上线流程。sklearn的TimeSeriesSplit可以做,但要注意它默认严格按样本顺序切分;如果数据没有按时间排序,要先用观察日排序。

6.2 用学习曲线判断是数据量不够还是特征不够

画出训练集和验证集AUC随训练样本量变化的曲线。如果训练AUC远高于验证AUC,说明模型容量过高,减少树的深度或增加min_samples_leaf;如果两者都很低,说明特征不够,要继续做特征工程而不是调参。这个诊断能帮你避免在错误的方向上花大量时间。

6.3 用SHAP值解释GBDT并和Lasso互相印证

GBDT是黑匣子,但SHAP能给出每个样本的特征贡献方向。常见做法是取GBDT的tree explainer,算验证集上每个特征的SHAP均值,再和Lasso选中的稳定特征做对比。如果某个Lasso未选中的特征SHAP值很高,说明它主要通过非线性交互起作用;这类特征应该保留。如果SHAP和Lasso都认为某个特征不重要,那我才会放心地把它从最终特征集里拿掉。

6.4 我的最后一步:把阈值回退到业务语言

我不会只交付一个模型文件。我会做一张表,列出“在不同阈值下,误杀多少正常主体、漏掉多少违约主体、最终保留多少头寸”。这张表比AUC更容易让信评或投资同事接受,也更能暴露阈值选择的业务代价。我在每个违约预测项目里都会重复这套验证:先查泄漏再谈指标,先看特征稳定性再谈调参。硬功夫都在特征工程和验证设计上,GBDT只是把功夫兑现。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询