拓冰建站拓冰建站
首页 / 资讯中心 / 正文

京东借贷预测实战:特征工程与LightGBM+Stacking融合

简介这是一份面向机器学习初学者及金融风控从业者的京东借贷需求预测实战资源聚焦利用人工智能技术分析用户购物、信用与还款行为完成借贷需求的建模与预测。全包共8个文件以5个Python脚本为核心覆盖特征生成、LightGBM建模、Stacking集成及LR与XGBoost融合等完整流程另含1个Markdown说明文档、1个Numbers特征设计表与1个Excel特征列表从特征设计到建模调参均有对应材料便于系统跟学。压缩包整体约575KB小巧实用。目前已有223人学习使用。通过本包读者可掌握从数据预处理、特征选择到模型训练评估的闭环方法理解XGBoost、LightGBM等主流模型在金融场景中的调参与应用并能迁移到其他借贷或销量预测任务中为业务决策提供有力支持。1. 京东借贷需求预测这份代码包能直接跑通一个还不错的基线前几年我在做金融类机器学习项目时碰过不少标着“需求预测”的代码包多数打开一看就是调包加随机森林跑完就扔。JDD_Loan_Forecasting 这套不太一样压缩包里只有六个 Python 脚本和一张特征清单却把特征生成、LightGBM 单模、LRXGB 融合、Stacking 串联成了一条能出分的完整流水线。它解决的核心问题是在缺少标签样本、算力有限的情况下如何快速做出一份可解释、可迭代的京东借贷需求预测基线。适合正在复现比赛方案、或者想给公司做一个轻量风控需求预测 Demo 的人。新手照着 README 能跑通全流程熟手可以直接替换特征和模型做二次开发。2. 特征工程基建从 genfeature.py 到特征注册表的全流程2.1 先看数据结构再谈特征设计京东借贷需求预测这类任务数据不是一张干净的训练表而是和用户行为强相关的连续记录。比赛原始数据里常见的是用户ID、操作时间、业务类型、金额等字段目标的借贷需求标签往往只出现在某个时间点之后。这里最容易犯的错是把所有数据一股脑灌进模型忽略了行为时序对标签的影响。拿到 JDD_Loan_Forecasting-master 后我建议先看 features_list.xlsx 和 README.md这两个文件交代了特征命名和数据格式。特征设计.numbers 里记录的是字段来源和加工思路比如某个特征是基于“近7天行为次数”还是“历史累计金额”聚合出来的。先弄清列名含义再写代码能省掉后面大量返工时间。2.2 genfeature.py 到底生成了什么特征genfeature.py 是整套代码的特征生成入口。它核心做的事情是遍历原始行为记录按用户ID和时间窗口做聚合输出一个宽表每行是一个用户每列是一个特征。# 以常见写法为例说明 genfeature.py 里的核心逻辑 import pandas as pd import numpy as np def gen_user_features(df): # 按用户聚合统计基础行为量 user_feat df.groupby(user_id).agg( order_count(action_time, count), # 总行为次数 unique_days(action_time, pd.Series.nunique),# 活跃天数 amount_mean(amount, mean), # 平均金额 amount_sum(amount, sum), # 累计金额 amount_std(amount, std) # 金额波动 ).reset_index() return user_feat这里的order_count、unique_days属于基础统计特征amount_std刻画用户消费稳定性。金融风控场景里波动过大的用户往往伴随更高风险这类特征对借贷需求预测很有区分度。genfeature.py 里通常还会加滑窗特征比如“近3天行为次数”“近7天金额占比”。滑窗窗口的选择直接决定特征时效性窗口太短特征稀疏窗口太长可能引入标签时刻之后的信息造成时间穿越。2.3 用特征注册表管理特征去向features_list.xlsx 在我眼里比特征代码本身更值钱。它记录了每列特征的名称、类型、来源、是否可用于线上相当于一份特征注册表。多人协作时大家都靠这张表对齐“哪个特征是谁生成的、含义是什么”避免建模时拿错列或重复造轮子。特征名类型聚合方式窗口是否上线order_count_all数值count全量是amount_7d_mean数值mean近7天是active_days_30d数值nunique近30天是first_action_gap数值min-max全量否我一般会把类似的表当作特征工程的“真相源”。模型效果不好时先查这张表而不是直接调参往往能更快定位问题。2.4 跑 baseline 前先做特征体检特征生成后不要急着训练。先做一个快速体检检查空值占比、无穷值、常数特征def check_features(df): for col in df.columns: if df[col].dtype object: continue nunique df[col].nunique() inf_cnt np.isinf(df[col]).sum() nan_cnt df[col].isna().sum() if nunique 1: print(f常量特征: {col}) elif inf_cnt 0: print(f含无穷值: {col}, 数量: {inf_cnt}) elif nan_cnt / len(df) 0.5: print(f空值过多: {col}, 占比: {nan_cnt / len(df):.2%})特征体检这步很多人会跳过但金融数据里经常出现除数为0导致的 inf 或全空字段LightGBM 能容忍 NaN却对 inf 敏感训练时常报 “Input contains infinity”。提前用np.clip或填充处理比训练中报错再排查省时间。3. LightGBM 单模型先把一个模型练稳再谈集成3.1 lightGBM_JDD.py 的主流程拆解lightGBM_JDD.py 是整套代码的主力脚本流程可以拆成四步加载特征宽表、划分训练集与验证集、训练 LightGBM、输出预测结果。这里最需要注意的是验证集划分方式不能随手train_test_split(random_state42)而应该按时间切分。import lightgbm as lgb from sklearn.model_selection import train_test_split # 按时间排序后切分避免随机切分造成的时间泄漏 df df.sort_values(feature_date) # 按特征日期排序 train_df df[df[feature_date] 2024-06-01] valid_df df[df[feature_date] 2024-06-01] X_train train_df.drop([label], axis1) y_train train_df[label] X_valid valid_df.drop([label], axis1) y_valid valid_df[label]上面这段代码的关键在于sort_values和按日期切分。随机切分在线下可能 AUc 很高但到了线上特征分布一变就崩溃。时间切分才是金融预测的正确做法。如果你拿到的数据只有一份静态表也要构造一个“时间截断点”把每个用户最早的一部分行为当作训练集后面当作验证集。3.2 参数初始值与调参方向lightGBM_JDD.py 里的参数大致可以归成两类一类决定模型复杂度一类决定训练速度。按比赛常见的经验值我习惯这样初始化参数推荐初始值作用objectivebinary二分类任务metricauc关注排序能力learning_rate0.02学习率越小越稳num_leaves63控制树复杂度feature_fraction0.8列采样防过拟合bagging_fraction0.8行采样bagging_freq1行采样频率lambda_l21.0L2正则min_data_in_leaf100叶子最小样本数learning_rate设到 0.02 之后需要配合较大的n_estimators才能收敛常见做法是用早停代替硬编码迭代次数params { objective: binary, metric: auc, learning_rate: 0.02, num_leaves: 63, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 1, lambda_l2: 1.0, min_data_in_leaf: 100, verbose: -1 } model lgb.train( params, lgb.Dataset(X_train, y_train), num_boost_round5000, valid_sets[lgb.Dataset(X_valid, y_valid)], callbacks[lgb.early_stopping(100), lgb.log_evaluation(100)] )num_boost_round设 5000 并不意味着一定训练 5000 轮early_stopping(100)会保证连续 100 轮验证集 AUC 不提升就自动停止最终模型停留在表现最好的迭代点这是一项很实用的设定。verbose-1是关闭日志输出比赛调参时可以改成verbose100看到每百轮的效果。3.3 单模型评估不只盯 AUC还要看分布偏移LightGBM 训练完成后脚本里会输出验证集 AUC。AUC 能反映整体排序能力但借贷需求预测更关心的是“预测概率是否随时间漂移”。我见过不少案例AUC 稳定在 0.95 以上但每日平均预测分数逐周走低最后发现是用户行为习惯发生变化模型却还停留在老特征上。# 按天检查预测分布均值判断模型是否漂移 import pandas as pd result pd.DataFrame({ date: valid_df[feature_date], pred: model.predict(X_valid) }) daily_mean result.groupby(date)[pred].mean() print(daily_mean.head())这段代码把每日预测均值打印出来如果某一天开始大幅波动就要检查特征来源是否变化或者考虑对训练数据做时效加权。lightGBM_JDD.py 里可能只写了 AUC 评估但上线前补上这一步能避免很多线上事故。4. 从单模型到 Stacking手工特征融合不是玄学是工程4.1 为什么 LRXGB 融合在金融预测里总是能涨点京东借贷需求预测本质是二分类树模型擅长捕捉特征间的非线性关系和交互而线性模型擅长对“单调关系”建模对特征尺度敏感。把两者结果做融合往往能吸收不同模型的偏好降低单一模型的偏差。lr_xgb_ensamble.py 做的就是这件事先用 XGBoost 训练一个模型再用逻辑回归对 XGB 输出做二次校准等价于在树模型的预测概率上加一层线性变换让它更贴合真实概率而不只是排序分数。4.2 Stacking 的正确姿势必须用 OOF 预测stacking.py 是整套代码里最值得反复读的脚本。它把训练集分成多折每折训一个基模型在验证折上产生预测值然后把这些 OOF 预测作为下一层模型的特征。关键点是不能用同一个模型在训练集上既训练又预测那样下一层模型拿到的特征已经“见过答案”线下指标会虚高。from sklearn.model_selection import StratifiedKFold from sklearn.linear_model import LogisticRegression import lightgbm as lgb import numpy as np import pandas as pd def get_oof_pred_lgb(X, y, n_splits5): skf StratifiedKFold(n_splitsn_splits, shuffleTrue, random_state42) oof np.zeros(len(X)) for train_idx, valid_idx in skf.split(X, y): X_tr, X_va X.iloc[train_idx], X.iloc[valid_idx] y_tr y.iloc[train_idx] d_train lgb.Dataset(X_tr, y_tr) d_valid lgb.Dataset(X_va, y.iloc[valid_idx]) model lgb.train(params, d_train, num_boost_round500, valid_sets[d_valid], callbacks[lgb.early_stopping(50)]) oof[valid_idx] model.predict(X_va) return oof这里StratifiedKFold用于保证每一折正负样本比例一致。shuffleTrue, random_state42保证实验可复现。真正执行时shuffle 要慎重如果数据是时间序列应改为按时间切分否则 OOF 仍会泄露出部分未来信息。Stacking 是在线下场景才做的实验性操作实际生产每训一折会消耗 5 倍训练时间要有心理准备。4.3 第二层融合模型的选择stacking.py 的第二层一般用简单的逻辑回归因为特征是多个模型的预测概率本身就是一维或低维向量用复杂模型反而容易过拟合。stack_feat np.column_stack([oof_lgb, oof_xgb]) stack_df pd.DataFrame(stack_feat, columns[lgb_pred, xgb_pred]) meta_model LogisticRegression(C1.0) meta_model.fit(stack_df, y)C是正则化强度的倒数默认 1.0 在大多数场景下都够用。如果两个基模型相关性很高融合收益会变小这时可以让第二层模型加上特征重要性观察哪路预测权重更高。4.4 辅助脚本usseless.py 的功能定位useless.py 这名字一看就是边角料脚本但我读下来发现它通常承担的是“过滤器”职责对特征做方差过滤、相关性去重或者打印特征重要性排序。它不参与最终预测却是防止盲目堆特征的有效工具。跑完 genfeature.py 后先跑一次 useless.py 看看哪些特征没有任何区分度再进 LightGBM比直接全部丢进模型要稳。5. 避坑清单金融预测里最容易翻车的五个细节5.1 时间穿越造特征用了未来数据现象线下 AUC 高达 0.98线上效果却远低回测时才发现某个特征在“标签所在时刻”之后才被记录。原因构造滑窗特征时没有滤掉“未来”记录比如用整段历史均值代替截至标签时刻的均值等于提前把答案喂给模型。解决所有特征必须在每个样本的观测时间点截断只允许使用该时间点之前的数据。代码里要显式传入一个asof_datedef gen_until(df, asof_date): return df[df[action_time] asof_date]从那以后我每次写特征工程都先明确一个“截止时间列”所有聚合都带着where action_time asof_date走一遍。5.2 随机切分验证集导致线下指标虚高现象train_test_split随机切分后验证集 AUC 比按时间切分高出 0.03 左右模型上线后却达不到这个水平。原因借贷需求数据里同一个用户的行为会分布在多个时间点随机切分容易把同一个人的相近行为同时分进训练和验证造成数据重合。解决改成按时间排序后切分或用GroupKFold按用户分组切分确保同一个人不会同时出现在两个集合里。5.3 训练报错 Input contains infinity现象训练到第 20 轮时直接报ValueError: Input contains infinity or a value too large for dtype(float64)。原因特征工程里做了除法分母是 0或者金额取对数时出现负数生成了 inf 或 NaN。解决在特征体检阶段就把 inf 替换掉常见做法是np.clip限制范围df df.replace([np.inf, -np.inf], np.nan) df[col] df[col].fillna(0)LightGBM 原生能处理 NaN但 inf 一定要先转 NaN 或通过裁剪处理。5.4 Stacking 的 OOF 泄漏现象Stacking 线下提升非常明显但测试集或线上达不到同样效果。原因第二层模型使用了整个训练集上预测的结果做特征相当于用全量数据交叉验证后又把验证折的结果混进了第二层训练信息泄漏了。解决严格按 K 折产出 OOF第二层只用 OOF 预测值训练测试集的预测也必须在各折模型上分别预测后再取平均。5.5 类别特征没有显式声明现象AUC 一直卡在 0.8 附近怎么调参都不涨。原因把用户ID、时段编号这类类别特征直接当作数值型树模型会按大小排序产生无意义的切分点。解决LightGBM 里明确传入categorical_featurelgb.Dataset(X_train, y_train, categorical_feature[user_id, hour])如果类别过多先做频次编码或 target encoding再考虑放进模型。6. 落地验证与后续从离线 AUC 到能上线的三步检查离线 AUC 只是第一步金融场景里更要看模型在时间维度的稳定性。我习惯在输出预测后做三个检查按日看预测均值漂移、算 KS 值、算 PSI 特征稳定性。KS 值衡量正负样本预测分布的最大区分度业界普遍认为 0.3 左右属于可用范围。可以直接从 LightGBM 的预测概率里算from scipy.stats import ks_2samp pred_pos model.predict(X_valid[y_valid 1]) pred_neg model.predict(X_valid[y_valid 0]) ks_value, _ ks_2samp(pred_pos, pred_neg) print(fKS: {ks_value:.4f})PSI 是特征稳定性指标判断训练集和上线后新数据的分布差异。当 PSI 超过 0.25 时说明该特征分布已经明显偏移需要排查是否来源变化或用户行为变化。做完这三步再把 lr_xgb_ensamble.py 的融合概率输出成按用户ID汇总的表业务侧就能直接拿着做资金调配或风险名单圈选。线上如果特征齐全可以把特征注册表里标记为“可上线”的列同步到线上存储保证线上下特征口径一致。这份代码包最大的价值是把“特征生成—单模型—融合—验证”的主线流程完整串起来了。相比自己从零搭它能让你把时间花在理解数据和调特征上而不是反复踩时间切分和 OOF 泄漏的坑。希望这轮拆解能帮你把项目跑得更顺畅也希望你带着自己的数据再走一遍拿到比 baseline 更好的结果。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门