资讯动态

天猫复购预测实战:从特征工程到XGBoost与LightGBM融合

发布时间:2026/8/30 2:49:44 来源:尧图企业网站定制
简介在机器学习领域二分类问题是最常见的任务之一而如何有效评估模型性能是关键。AUC作为对不平衡数据友好的评估指标被广泛应用于用户行为预测、信用评分等场景。特征工程是提升模型上限的核心环节通过用户行为日志构建统计特征、时间序列特征和交叉特征能显著提升模型表现。本文以天猫复购预测为例展示如何从原始行为数据出发设计用户维度、商品维度及用户-商品交叉特征并结合XGBoost与LightGBM双模型融合策略实现高AUC的解决方案。同时介绍了目标编码的防泄漏处理、时间特征构造陷阱以及内存优化与调参经验为处理类似电商用户复购预测问题提供完整参考。 去年带几个新人一起复现阿里天池的“天猫复购预测”学习赛从跑通 baseline 到最终调出一份能稳定拿到高分的源码和文档前前后后踩了不少坑。这个项目虽然挂了“学习赛”的名头但数据量和业务逻辑一点都不“学习”尤其是特征工程部分处理不好直接决定你能不能挤进前排。今天把整套案例的源码结构和文档思路完整拆开讲一遍包括我实际调试时记录下来的关键参数和排查笔记想拿这份代码跑通、或者想理解高分思路的朋友可以直接照着走。1. 项目整体设计与思路拆解1.1 赛题到底在预测什么先把这个任务说清楚。天猫复购预测本质上是给定用户在双11之前一段时间内的行为日志预测这些用户在未来一个月内通常指双11当天到双12之间会不会再次发生购买行为。注意这里不是预测“买什么”而是预测“买不买”是一个标准的二分类问题。标签只有0和11代表该用户在观察期内产生了复购0代表没有。评估指标用AUC而不是准确率这一点很重要。AUC对正负样本比例不敏感更适合这种复购率可能只有百分之几的不平衡场景。这个任务和普通的电商点击率预测CTR有相似之处但差异也很明显。CTR预测的是“某次曝光会不会点击”样本通常以“曝光记录”为粒度而复购预测的样本粒度是“用户”目标是把整个用户的行为序列压缩成一个特征向量再做分类。简单说前者是对“事件”建模后者是对“人”建模。1.2 数据层面我们拿到的是什么这套源码对应的公开数据集包含两个主要部分用户行为日志表和用户基本信息表复购训练集/测试集。行为日志覆盖了双11前后大约一个月的时间窗口每条记录包含用户ID、商品ID、行为类型浏览、收藏、加购、购买、行为时间等字段。训练集和测试集都给出了需要预测的用户ID列表其中训练集带标签测试集不带。从数据量来看行为日志记录数在千万级以上用户数在百万级。这个量级对单机内存来说压力不小但也没到必须上Spark的程度。源码默认用Pandas分块读取和特征聚合配合内存优化技巧一台16G内存的机器就能跑完。一个小细节日志里包含“双11当天”的数据但当天购买行为不能直接当复购标签用因为复购的定义是“双11之后再次购买”。源码里处理标签时把双11当天及之前的行为当作特征来源把双11之后的行为用来构造标签。如果这里边界切错AUC会虚高或者直接逻辑混乱训练出来的模型上线必翻车。1.3 高分方案的核心套路是什么这套方案能拿到高分核心不是模型多复杂而是特征工程做得比较扎实。整体套路可以概括为三个层次第一层用户维度的统计特征比如用户的总浏览数、总加购数、总购买数、行为天数、行为深度总行为数/活跃天数等。第二层用户对商品的偏好特征比如用户在某个商品上的行为次数占该商品总行为次数的比例、用户是否购买过该商品所属类目等。第三层时间序列特征比如用户最后一次行为距离双11的时间间隔、用户行为活跃度随时间衰减的趋势等。模型层面源码主要用XGBoost和LightGBM双模型融合。两个模型的差异主要在于对稀疏特征的采样方式和叶子节点的分裂策略融合后AUC通常能比单模型高0.002到0.004。在AUC小数点后第三位都值钱的比赛里这个提升非常可观。2. 核心细节解析与实操要点2.1 特征工程的几个关键操作先说用户行为统计特征。这部分最容易做也最容易做出“看起来很全但没用”的特征。源码里的做法是先按用户分组对行为类型做透视表生成“用户-行为类型-次数”的宽表然后和用户表合并。这里面有两个容易被忽略的点行为类型要转成列而不是堆在行里。比如“浏览次数”“收藏次数”“加购次数”“购买次数”要分别成列这样后续特征交叉才方便。计数特征最好配合“去重天数”使用。比如用户有100次浏览但如果集中在1天内完成和分布在10天里完成业务含义完全不同。源码里加了一个“活跃天数”特征然后用“总行为数/活跃天数”算出日均行为强度这个特征在后面重要性排序里排在非常靠前的位置。再说时间特征。时间特征不能只看“最后活跃时间距双11多少天”还要看行为的分布形态。源码里构造了三个时间特征最后行为距双11的天数、首次行为距双11的天数、行为跨度的天数。这三个特征组合起来可以判断用户是“临时起意型”还是“长期活跃型”。最后说商品维度交叉特征。这里有一个小技巧先算每个商品的总行为数再把用户对该商品的行为数除以商品总行为数得到一个“用户对该商品的相对热度”分数。如果用户对某个商品的相对热度很高说明这个用户可能是该商品的潜在忠诚客户复购概率会更高。2.2 类别特征的编码取舍原始数据里没有太多类别特征但商品ID、品牌ID、类目ID都是天然的类别特征。如何处理这些高基数类别特征是很多新手容易翻车的地方。源码的做法是对用户维度不直接做商品ID的OneHot因为商品ID的基数动辄几十万OneHot之后维度爆炸内存和训练时间都受不了。正确的做法是对用户最常购买的品牌、类目分别做统计生成“用户购买TOP1品牌”“用户购买TOP3类目”这类高价值特征。对商品ID做目标编码Target Encoder即用该商品的历史购买率作为特征值。但目标编码容易过拟合源码里用五折交叉验证方式做目标编码避免直接用全量标签计算。还有一个细节当类目ID的基数也比较大时几千到上万源码里会对类目ID做频次过滤只保留出现次数超过一定阈值的类目其余的归为“其他”类。这个操作能有效降低噪声。2.3 负样本定义与采样策略复购预测的正负样本比例通常严重失衡复购率可能只有5%左右。直接全量训练的话模型会偏向预测负样本AUC虽然不会太差但提升会非常吃力。源码里的处理方式是不做过多的负样本下采样而是保留全量数据依靠AUC优化目标和样本权重来缓解不平衡。具体实现上对正样本设置了1.5到2倍的权重让模型在训练时更关注正样本的区分度。这里提醒一下负样本下采样虽然能加快训练速度但会改变验证集上的正负比例导致验证AUC和线上AUC不一致。如果要做下采样务必保证验证集是原始分布。源码里最终没有下采样而是选择用LightGBM的is_unbalance参数配合自定义权重效果更稳。3. 实操过程与核心环节实现3.1 环境准备与数据预处理整套源码基于Python 3.7核心依赖是Pandas、NumPy、Scikit-learn、XGBoost和LightGBM。安装直接用pip即可重点说一下数据预处理阶段的内存优化。原始行为日志读进来之后第一时间做三件事列类型压缩、时间字段解析、排序。列类型压缩是指把int64转成int32或uint8、把float64转成float32这一步能省下将近一半内存。时间字段解析用pd.to_datetime但要注意格式统一否则后面算时间差会报错。排序有个小讲究必须先按用户ID排序再按时间排序。这样后面做“用户最近一次行为”这类特征时可以直接用groupby.last()效率远高于全表排序后取最大时间。3.2 特征代码的核心结构源码里有一个feature_engineering.py结构上分四个函数gen_user_feature生成用户统计特征和时间特征。gen_product_feature生成商品维度统计特征。gen_user_product_feature生成用户-商品交叉特征。gen_combine_feature合并所有特征并做最终清洗。每个函数内部都有详细的注释说明每个特征的业务含义和计算逻辑。这里贴一段gen_user_feature的关键代码展示用户活跃天数特征怎么算def gen_user_feature(behavior_df): # 按用户统计行为日志的行为类型数量 user_feat behavior_df.groupby(user_id).agg( total_action(behavior_type, count), total_browse(behavior_type, lambda x: (x 1).sum()), total_cart(behavior_type, lambda x: (x 2).sum()), total_fav(behavior_type, lambda x: (x 3).sum()), total_buy(behavior_type, lambda x: (x 4).sum()), active_days(date, nunique), last_time(time_stamp, max), first_time(time_stamp, min) ).reset_index() # 行为时间跨度天 user_feat[action_span_days] ( user_feat[last_time] - user_feat[first_time] ).dt.days # 日均行为强度 user_feat[avg_action_per_day] ( user_feat[total_action] / (user_feat[active_days] 1) ) # 最后行为距离数据截止日期的间隔 user_feat[last_action_gap] ( end_time - user_feat[last_time] ).dt.days return user_feat这段代码看起来简单但实际操作时有两个坑。第一个坑是behavior_type的值含义数据里1/2/3/4分别对应浏览/收藏/加购/购买不是0/1/2/3搞混了后面所有统计特征全错。第二个坑是end_time的定义源码里定义为数据集中最后一条行为日志的时间而不是双11当天。因为测试集的预测窗口不同用数据集末尾时间算间隔更贴合真实场景。3.3 模型训练与参数配置模型层面源码提供了两个训练脚本分别跑XGBoost和LightGBM最后做加权融合。下面是我实际调参后稳定拿到高分的参数组合。XGBoost关键参数xgb_params { objective: binary:logistic, eval_metric: auc, eta: 0.02, max_depth: 7, subsample: 0.85, colsample_bytree: 0.7, min_child_weight: 5, lambda: 2.0, scale_pos_weight: 1.8, nthread: 16, seed: 42 }LightGBM关键参数lgb_params { objective: binary, metric: auc, learning_rate: 0.02, num_leaves: 63, max_depth: 7, feature_fraction: 0.75, bagging_fraction: 0.85, bagging_freq: 1, min_child_samples: 20, lambda_l2: 2.0, is_unbalance: False, nthread: 16, seed: 42 }两个模型的共同点是学习率都调得比较低0.02迭代轮数靠早停控制。低学习率配合早停能有效防止过拟合AUC通常比默认参数高0.003左右。训练时用五折交叉验证每一折保存验证集的预测概率最后综合五折预测结果做融合。3.4 模型融合与后处理融合方式不用花哨简单的加权平均就够。源码里默认XGBoost权重0.4LightGBM权重0.6。这个比例不是我拍脑袋定的而是通过网格搜索找到的最优组合。实际操作时也可以观察两个模型在验证集上的表现谁高谁权重大一点。融合之后还有一个后处理技巧对预测概率做平滑裁剪。因为正样本比例很低模型输出的概率普遍偏低直接按0.5阈值判断会得到几乎全为0的结果。但在AUC评估下这个无所谓因为AUC只看排序不看绝对概率。所以后处理的目标不是调阈值而是让排序更合理。源码里做了一个非常细节的操作对双11当天有购买行为的用户预测概率统一加一个微小增量。原因是在实际业务里双11当天购买的用户复购意愿更强这个先验知识能小幅提升排序效果。4. 常见问题与排查技巧实录4.1 数据合并时的内存爆炸问题第一次跑全量特征工程时最可能遇到的就是Pandas内存溢出特别是在合并用户-商品交叉特征的时候。当用户的量级在百万级、同时每个用户对应多个商品时中间表会膨胀得非常快。我的排查思路是先看特征合并前各个DataFrame的行数和列数估算合并后的行数。如果行数超过5000万就要考虑降低维度。源码里最终的交叉特征并没有把“用户-商品对”作为一行而是把交叉特征聚合到用户维度后再合并这样合并后的行数始终等于用户数内存完全可控。如果你发现自己写的代码在这个环节直接卡死九成是中间表行数没有控制住。4.2 时间特征为什么会出现负值时间特征出现负值常见原因是测试集的时间范围比训练集晚直接用训练集的最大时间作为基准去计算测试集的时间间隔就会得到负值。源码里对这个问题做了统一处理所有时间特征都以“数据集的全局最大时间”为基准计算而不是按训练/测试分开计算。这样保证训练和测试的特征分布一致。另一个容易踩的坑是时区问题如果本地跑的时候系统时区不对解析出来的时间会有偏差算出来的间隔值也会集体偏移。建议在读入时间字段后统一用pd.to_datetime指定UTC。4.3 目标编码过拟合的典型表现如果目标编码没有用交叉验证你会看到一个非常迷惑的现象训练AUC很高比如0.9以上但验证AUC只有0.6左右甚至更低。这就是典型的目标编码过拟合。源码里对这个问题的处理是把训练集分成五折每一折编码时只用其他四折的数据计算目标均值再映射到当前折。这样每个样本的目标编码值都来自“见过”但“不完全依赖”的信息。测试集的目标编码则用全量训练集计算。如果发现融合后AUC偏低优先检查目标编码部分是否泄漏了标签信息。4.4 常见问题速查表问题现象可能原因排查方向训练AUC高验证AUC低目标编码泄漏、特征里混入未来信息检查特征构建时是否用到全部数据计算统计量时间特征出现负值训练/测试基准时间不一致统一使用全局最大时间计算间隔内存溢出中间表行数膨胀控制交叉特征聚合到用户维度LightGBM训练极慢类别特征基数过大对高基数类别做频次过滤融合后AUC反而下降模型权重分配不合理用网格搜索确定最优权重4.5 调参的一个心得很多新手喜欢一上来就疯狂调参但以这个项目的体量盲目调参等于大海捞针。我的建议是先把特征工程做扎实用默认参数跑通一遍记录基准AUC。然后只调两个参数学习率和树深度。学习率从0.1降到0.02AUC通常能涨一点树深度从6增到8如果AUC不升反降说明特征已经够用了再深就是过拟合。等到这两个参数确定后再考虑调正则项和采样比例。最后才做模型融合。这个顺序能保证每一步的改进都是可解释的而不是靠运气。5. 运行流程与结果分析5.1 从原始数据到预测结果需要几步整套项目跑完按顺序执行下面几个脚本即可data_preprocess.py读取原始行为日志和用户表做内存优化、时间解析和排序输出清洗后的中间文件。feature_engineering.py生成用户、商品、用户-商品交叉特征合并后输出特征宽表。train_xgb.py/train_lgb.py分别训练两个模型五折交叉验证输出验证集预测概率和特征重要性。blend.py读取两个模型的验证集预测概率网格搜索最优权重输出融合预测结果。submit.py生成符合比赛要求的提交文件。5.2 实测结果与提升空间在我这边的运行环境下XGBoost单模型验证AUC大约在0.708LightGBM单模型AUC在0.712加权融合后AUC能到0.716左右。这个成绩在当年的学习赛里已经能排进前列。如果还想继续提升有两个方向可以尝试。第一个是引入更多维度的外部数据比如商品价格、折扣力度但需要注意比赛数据使用规范。第二个是做更加细致的特征交叉比如“活跃天数”和“购买次数”的组合特征构造出“购买频率”这一业务指标。源码里预留了特征接口方便自由扩展。5.3 特征重要性参考LightGBM训练完成后我导出了特征重要性前10名排在最前面的是这几个last_action_gap最后行为距数据截止日期的间隔天数total_buy用户总购买次数avg_action_per_day日均行为强度total_action用户总行为数active_days活跃天数user_product_buy_ratio用户购买某商品数量占该商品总购买数量的比例这个排序符合业务直觉距离最近一次活跃时间越短、购买次数越多、行为越密集的用户复购概率越高。如果你自己跑出来的特征重要性排序和这个差异很大建议回查一下特征是否计算正确。6. 关于文档说明的整理方法这套案例除了源代码还配套了一份完整的文档说明这部分的价值不比代码低。文档的核心思路是让一个没跑过比赛的新人照着一步步也能复现结果。文档从四个维度展开数据说明、特征设计、模型选择、结果复盘。其中特征设计部分用了大量表格把每个特征的计算公式、输入列、输出列都列清楚。比如“用户总购买次数”这一行就写明了是用行为日志中behavior_type 4的记录数按user_id分组统计而来。这样做的好处是后期排查特征bug时有据可查不用靠猜。另一个值得关注的是文档里的“踩坑记录”章节。我把实际调试中遇到的每个问题都整理成了问题-原因-解决方案的条目式记录比如目标编码过拟合、内存溢出、时间特征负值等。这些内容在官方文档里找不到但对于真正动手复现的人来说帮助最大。文档还有一个“复现流程”章节精确到每个脚本大约运行多少时间、输出的中间文件是什么、预期AUC在什么范围。这样当别人运行结果和你给出的基线有出入时能快速定位是哪一步出了问题。个人体会是做这种比赛项目源码只是结果真正值钱的是你对数据流的理解和对问题的处理逻辑。代码能跑通只算完成了三分之一能把每个特征为什么这样设计、每个参数为什么这么调讲清楚才是一份能沉淀下来的完整案例。最后分享一个小技巧在处理用户行为日志时建议先把数据按用户ID采样一个小批量比如5万用户跑通全部流程再上全量数据训练。这样调试特征和模型的速度会快很多而且验证集的表现通常和全量训练很接近误差大约在0.002以内。用这个方式迭代特征一天能试几十个版本比闷头跑全量高效得多。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价