天猫复购预测实战:特征工程与LightGBM调优全攻略
简介在电商数据挖掘任务中用户行为分析与复购预测是常见的业务场景。通过历史行为日志构造特征结合机器学习模型识别高复购倾向用户能帮助企业精准营销。特征工程是此类任务的核心包括用户维度统计特征、品牌交叉特征以及时间衰减序列特征同时需警惕数据泄漏与长尾分布问题。模型方面LightGBM凭借高效训练与良好精度成为首选配合AUC评估、按用户切分验证集和阈值后处理能有效应对类别不平衡挑战。本文以天猫复购预测赛题为例系统拆解从原始日志到训练样本的完整流程并分享模型融合与可复现项目实践的实用经验为数据挖掘初学者和竞赛选手提供可落地的方法论。1. 赛题拆解先搞清楚预测什么再动手写代码做比赛最忌讳一上来就翻数据、写特征我在早期参加天池的学习赛时吃过这个亏。拿到“天猫复购预测”这个题目我第一件事不是看代码而是把赛题说明读了五遍把预测目标和评估方式彻底搞明白才开始动手。1.1 赛题背景与核心任务这个赛题的业务场景很典型天猫平台上有海量用户和品牌用户会浏览、加购、收藏、购买各种品牌的商品。平台关心的问题是一个用户在某次购买某个品牌的商品之后后续还会不会再次购买同一个品牌的商品。如果能提前预测出哪些用户有复购倾向平台就可以针对性地做召回、发券、推送把营销成本花在刀刃上。落到赛题上任务被简化成了一个二分类问题对每一个样本用户、品牌、时间窗口的组合预测该用户在该品牌上是否会发生复购。数据集给出的不是直接标好的训练样本而是一堆用户行为日志需要参赛者自己从日志里构造训练集和测试集这也是这类赛题比较有代表性的地方。这个赛题适合什么人去刷我的建议是正在入门数据挖掘、想熟悉特征工程和树模型的同学或者做过一两场简单比赛、想进阶体验复杂日志数据处理的选手都可以拿这个题来练手。它不像图像、NLP那样依赖深度模型和算力单机就能跑完核心考验的是特征思路和数据处理功底。1.2 数据说明与评估方式赛题数据一般分为三张表用户表、品牌表、用户行为日志表另外加上训练集和测试集的用户购买记录。用户表包含用户的基础属性比如性别、年龄段、地域、注册时长等品牌表包含品牌ID和品牌类别信息行为日志表是核心数据记录了每个用户对每个品牌的每一次行为行为类型通常分为浏览、收藏、加购、购买四类同时带有行为发生的时间。这里需要特别注意训练集和测试集的构成方式。训练集中给出的是一个时间范围内用户的行为日志其中最后一次购买行为会被单独标记出来参赛者要基于最后一次购买行为之前的数据做特征预测这次购买之后该用户是否还会再次购买同一品牌。测试集的结构类似只是不需要输出标签而是提交预测概率。评估指标这块这类任务一般用的是AUCROC曲线下面积。AUC对类别不平衡不那么敏感比准确率合理得多。实际复购用户占比通常很低可能只有百分之几如果拿准确率评估模型只要全预测0准确率也在95%以上但那没有任何业务意义。所以后文所有调优工作我都是以AUC作为主要衡量标准这一点建议新手也一上来就明确。1.3 整体方案路线我最终跑通的方案分四步走第一步是数据清洗和预处理把三张表读进来处理缺失值把时间字段转成可计算的格式第二步是特征工程围绕用户、品牌、用户-品牌对三个粒度构造统计特征、时间特征和交叉特征第三步是模型训练用LightGBM作为主力模型通过交叉验证调参第四步是后处理调整阈值和融合多个模型的结果把线上分数稳下来。这个路线比较常规但很有效。我第一次参加这种带日志数据的赛题时总觉得要用些花哨的模型才能拿高分后来发现复购预测这种结构化数据任务特征工程带来的提升远远大于模型复杂度带来的提升。LightGBM在这个场景下已经足够强没必要一上来就上深度学习。2. 特征工程复购预测的胜负手特征工程是这场比赛的决胜点。我第一版方案只用了几个基本统计量AUC大概在0.66左右后来在特征上花了大量时间之后线上分数直接提升了六七个点。下面把我认为最核心的几类特征逐个拆开讲。2.1 用户维度行为统计特征用户维度的特征回答的是“这个用户本身是不是一个容易复购的人”。指标包括用户的行为总量、行为天数、购买次数、浏览/收藏/加购次数以及各行为之间的比例关系。具体来说我统计了每个用户在所有品牌上的总行为数、总购买数、总浏览数、总收藏数、总加购数还有用户的首尾行为时间跨度、活跃天数。这些特征的构造逻辑很简单就是按用户做groupby加聚合。但有几个细节值得注意时间跨度要除以天数归一化。用户行为跨度是30天和300天的行为总量没有可比性我统一转换成“平均每天行为数”“平均每天购买数”这样特征更稳定。购买占比是个强特征。复购用户往往在历史行为中表现出更高的购买转化所以我把“购买次数 / 总行为次数”“购买次数 / 浏览次数”这类比例特征单独拎出来实测对AUC的提升非常明显。行为多样性和用户活跃度分开建。只看行为总量会忽略用户是盯着一个品牌买还是多个品牌都买的行为差异我加了用户交互过的品牌数、用户购买过的品牌数、平均每个品牌的行为数。2.2 品牌维度的交叉特征品牌维度的特征是很多新手容易漏掉的。同样一个用户在一个小众品牌和一个大众品牌上的复购概率完全不同所以必须把品牌侧的信息也带进来。品牌维度我重点做了几类品牌的被购买总数、被购买用户数、复购用户数、品牌复购率复购用户数/购买用户数、品牌被浏览总数、品牌转化率购买数/浏览数。这些特征的价值在于它们刻画了品牌的“忠诚度生态”有些品牌天然复购率高比如日用品、消耗品有些品牌复购率低比如大家电这种差异必须让模型学到。另外我会做“品牌-用户”的交叉特征例如该用户在该品牌上的购买次数占该用户总购买次数的比例该用户在该品牌上的行为占该品牌总行为的比例。这类交叉特征能把用户和品牌的关系强度直接量化出来。数据量方面品牌表里的品牌分类信息也要利用起来。类别ID可以做成类别维度的聚合特征比如该类别下的平均复购率、该类别下用户平均交互品牌数等这样能把同品类品牌的共性规律迁移到交互较少的品牌上。2.3 时间衰减与行为序列特征行为日志是带时间的这就意味着信息有先后顺序。时间特征如果只是简单统计总量会丢掉“最近是否活跃”“购买之后是否立刻有动作”这些强烈信号。我构造的第一类时间特征是“距预测日各行为的最近时间”。比如用户最近一次购买距预测日多少天、最近一次浏览距预测日多少天、最后一次加购距预测日多少天。这类特征在复购预测里非常关键如果用户最近还在活跃复购概率通常更高如果最后一次行为已经是很久以前基本意味着流失了。第二类时间特征是“行为时间分布”。我把行为按天展开统计活跃天数、平均每天行为数、行为天数的标准差、峰值行为发生在第几天。这里有个小技巧时间字段先转成“距预测日的天数”这种相对时间而不是直接用日期因为训练集和测试集的时间范围可能不完全一致绝对日期会引入分布漂移。我试过直接用日期做特征验证集表现尚可线上分数反而下降换成相对天数之后问题解决了。第三类是行为转移序列特征。把浏览、收藏、加购、购买这四个行为当成一个序列统计“浏览后多久购买”“加购后多久购买”“购买后是否在N天内再次出现行为”这类转化信息。这里注意训练集构造时只能使用预测点之前的数据不能把未来的行为拿来算当前特征。2.4 特征工程中的两个关键注意点关于特征工程有两条经验我觉得值得单独拿出来说。第一是注意数据泄漏。统计特征如果在构造时不小心混入了标签信息验证集上的分数会虚高但线上分数一落千丈。复购预测最常见的泄漏有两种一种是用全量日志统计用户行为把预测点之后的行为也算进去了另一种是直接统计“是否复购”相关的目标数据。我每加一个特征都会反复检查这个特征在预测时间点是否已经可知如果答案是否就不能用。第二是处理长尾特征时需要谨慎。天猫的数据集用户量很大但大多数用户的交互行为很少。对交互次数很少的用户统计特征的可信度比较低我会做平滑处理。具体做法是用全局均值或类别均值作为先验用户自身统计量作为观测值二者加权得到平滑后的特征值。例如用户购买率可以写成平滑购买率 (用户购买数 alpha * 全局购买率) / (用户行为数 alpha)其中alpha是平滑强度通常在0.5到2之间我用验证集试了几组1左右效果最好。这个技巧对数据稀疏的用户提升明显尤其能避免冷启动用户被模型误判成低复购人群。3. 模型训练与调优细节特征工程做完之后模型部分其实相对省心但省心不等于随便跑。我在模型训练阶段踩过不少坑比如验证集切分方式不合理、参数没有针对性调优、阈值选择只看概率不看业务目标这些都会让分数打折扣。3.1 训练集和验证集的切分方式很多初学者会用随机切分的方式把训练集分成80%训练、20%验证这个做法在这个赛题里有问题。因为同一个用户可能对应多个品牌样本随机切分会把同一个用户的样本同时分到训练集和验证集里导致验证集分数虚高。我在实际项目中采用的是按用户切分。把用户按照ID哈希分桶80%的用户进训练集20%的用户进验证集确保验证集里的用户在训练阶段完全没见过。这样验证集的AUC更接近线上真实表现。另外如果数据集是按时间划分的最好再按时间做一次验证切分即用前80%时间段的样本训练后20%时间段的样本验证这样能检测特征的时间稳定性。我最终的方案是五折交叉验证每折都用按用户切分的方式训练五个LightGBM模型预测结果取平均。这个做法既稳定又能防止单折数据分布异常带来的波动。3.2 LightGBM参数配置与调参顺序单模型我选的是LightGBM理由很简单训练快、内存省、对类别特征友好而且和XGBoost相比在百万级样本上表现基本持平但训练时间能节省一半以上。我的初始参数配置大概是这样参数名取值说明objectivebinary二分类任务metricauc评估指标learning_rate0.05基础学习率配合早停使用num_leaves63叶子节点数控制模型复杂度max_depth7最大深度防止过拟合min_data_in_leaf100叶子节点最少样本数feature_fraction0.8建树时随机选择特征比例bagging_fraction0.8样本采样比例bagging_freq1每轮都采样lambda_l10.1L1正则化lambda_l20.1L2正则化调参顺序上我先固定learning_rate为0.1先用较少迭代次数粗调num_leaves和max_depth再调min_data_in_leaf和feature_fraction最后把learning_rate降下来配合early stopping跑完整训练。一个小经验是num_leaves不能单独开太大至少要保证 2^(max_depth) num_leaves否则会引入不必要的分裂。我在调参过程中发现num_leaves63、max_depth7这个组合在这个数据量级上表现很稳定再往上加复杂度AUC的提升很有限反而有轻微过拟合。3.3 阈值选择、样本权重与后处理模型输出的是概率值但业务上最终需要的是一个判断“是否复购”的结论所以阈值的选取不能简单用0.5。在类别不平衡严重的数据集上0.5这个默认阈值几乎一定是次优的。我按验证集的AUC和召回情况实际用了两个思路做后处理。一个是在验证集上搜索最优阈值标准是最大化F1分数或者最大化业务定义的收益函数另一个是不设硬阈值直接输出概率由下游业务方根据资源情况决策。竞赛提交一般用后者但理解阈值逻辑对做项目很有帮助。样本权重方面由于正样本占比太低直接训练的话模型会偏向预测负样本。我实验过两种方案一种是对正样本进行过采样或者对负样本欠采样另一种是在LightGBM里设置is_unbalance或者scale_pos_weight参数。实践下来设置scale_pos_weight的效果更好因为它不需要重采样避免了信息损失。scale_pos_weight的取值我按“负样本数/正样本数”的比值做了缩放大概取10到30之间的值用验证集微调。注意这个参数只在训练时用预测概率时不需要做额外校正因为LightGBM内部已经做了处理你只需要关注排序指标AUC即可。3.4 多模型融合的思路虽然单模型LightGBM已经在验证集上能跑到不错的AUC但我还是尝试了模型融合最终线上分数也因此小涨了一个点。融合的方式不是粗暴地对所有模型输出取平均而是先看模型之间的相关性相关性太高的模型融合几乎没有收益。我实际融合了三个模型LightGBM主力、XGBoost增加多样性、逻辑回归对线性关系敏感。前两个模型对相同特征做训练但算法细节不同输出概率有一定差异逻辑回归则直接喂标准化后的特征捕捉线性信号。融合权重我用验证集上的网格搜索确定最终LightGBM占0.6XGBoost占0.3逻辑回归占0.1。这个比例根据数据集特征可以调整核心思路是让强模型主导、弱模型提供多样性。需要提醒的是模型融合是在单模型调优到位之后才做的事。单模型没调好之前做融合等于把多个不靠谱的结果揉在一起效果只会更差。4. 数据处理实战从原始日志到训练样本特征该怎么设计模型该怎么调前面已经讲清楚了但还有一个非常关键的环节是很多攻略里很少细说的怎么把原始日志清洗成可以直接喂给模型的训练样本。这一步没做对后面再好的特征也白搭。4.1 原始日志的读取与压缩天猫这个赛题的行为日志数据量不小如果直接读入内存很容易把16G内存的机器跑爆。我第一次处理时就是因为没注意数据类型读几百万行数据就卡死了。解决办法有两个方向。第一个方向是读入时指定列类型把数值型字段从int64降成int32甚至int16把分类字段从字符串转成category类型。比如用户ID、品牌ID这种高基数的ID字段用int32存储性别、年龄段这种低基数字段用category存储内存能省下一大半。第二个方向是分块处理。用pandas的chunksize参数分批读取日志每批做聚合后再合并结果。比如要统计“用户每天的行为数”不需要全量日志进内存可以按天分批读每天算一个用户行为数最后再汇总。这里插一句如果你用pandas做大数据量的groupby建议尽量把groupby的key列放在前面并且对key列先做sort这个操作在某些后端下能明显加快速度。如果数据量再大一个量级就直接上polars或者DuckDB处理体验会好很多。4.2 训练样本的构造逻辑构造训练样本是这个赛题最绕的一步我一开始也搞混过。简单来说赛题给了两个关键时间点训练样本的预测时间点和测试样本的预测时间点。训练集中的每个用户会有一条或多条购买记录处于预测点附近这条购买记录是“当前购买”我们根据它之前的用户行为构造特征然后看这个用户之后是否在预测点前再次购买该品牌有则标签为1没有则标签为0。实现的时候我建议按以下步骤走第一步把日志表按时间排序给每个用户-品牌对找到预测点之前最近的一次购买行为记为anchor购买行为。第二步以anchor购买行为发生时间为界把该行为之前的所有用户行为作为特征窗口。第三步查看anchor购买行为之后、预测点之前该用户是否再次购买该品牌如果有则label为1否则为0。测试集同理只是不需要计算label。这个构造过程有一个很隐蔽的坑同一个用户可能在训练集里对应多条anchor行为。比如用户A在预测点前买过三次某品牌每次购买都能构造一个训练样本。这种情况下样本之间不独立如果直接随机切分会导致数据泄漏。我的处理方式是只取最后一次购买行为作为anchor也就是“用户当前状态下的复购预测”这也是赛题要求的目标预测方式。4.3 脚本代码的组织方式一个好的项目应该让代码有清晰的分层。我最终交付的源代码文件夹是这么组织的project/ ├─ config.py # 全局配置路径、参数、时间节点 ├─ data_preprocess.py # 原始日志读取、类型转换、清洗 ├─ feature_engineering.py # 特征构造用户特征、品牌特征、交叉特征 ├─ build_samples.py # 训练样本和测试样本构造 ├─ train_model.py # 模型训练、交叉验证、调参 ├─ predict.py # 测试集预测、概率输出和阈值处理 ├─ utils.py # 公共工具函数 └─ README.md # 项目说明文档config.py单独拎出来很重要。数据路径、预测时间点、特征开关、模型参数都放在这里改配置不用去翻代码复现的时候也方便别人快速跑通。我自己写项目时最怕看到所有参数散落在各个脚本里改一个路径要全局搜索。还有一个细节是保存中间结果。特征工程这一步计算量比较大每次跑全量特征要花不少时间我会把构造好的特征保存成parquet格式后面调模型时直接读特征文件即可不用重新计算。这既是给自己节省时间也是让别人复现时能快速跳过耗时步骤。4.4 文档说明怎么写才有用赛题对应的“文档说明”质量直接影响这个项目能不能被别人复现。我给源代码配的文档主要包括几部分赛题背景和数据说明、环境依赖和运行步骤、特征列表和含义、模型参数和训练过程、实验结果和关键结论。写特征列表时我用了统一的表格特征名、特征含义、特征类型、构造方式。这样别人查起来一目了然。运行步骤部分我按从上到下的顺序写清楚每个脚本怎么跑需要什么输入、生成什么输出。最后还把验证集AUC、线上AUC、单模型和融合模型的对比都记录在文档里。很多人写项目文档喜欢只写怎么运行不写为什么这么做这个习惯在做竞赛项目时特别吃亏。文档里应该把关键决策记录清楚比如为什么选这个特征、为什么用这个切分方式、为什么设置这个平滑参数这样项目才能被后续的人理解和改进。5. 常见问题与排查技巧实录代码写完、文档配好并不意味着项目结束。我在调优过程中积累了一些常见问题和排查经验拿出来分享大家可以按图索骥。5.1 特征构造后维度爆炸特征工程做完之后特征数量可能会增加到几千维。这不是好事。特征过多会带来三个问题训练变慢、模型过拟合、可解释性变差。我的处理方式是多轮筛选。第一轮剔除缺失率超过80%的特征第二轮用LightGBM的特征重要性排序保留top100第三轮做相关性分析对相关系数超过0.9的特征对只保留其中一个。经过这三轮最终上模型的特征控制在150到200个左右训练速度快了一个量级AUC也没有明显下降。还有一个容易被忽略的问题是构造特征时使用了未来的信息。排查方法很简单随机抽取几个用户手动检查某个特征在预测时间点是否已经可以计算出来如果要用到预测点之后的行为数据就是泄漏。我在第一次做时间衰减特征时经常犯这个错后来写了个专门的检查脚本把每个特征的计算窗口和预测点时间做比对才把泄漏问题彻底堵上。5.2 训练集和测试集特征分布不一致训练集和测试集来自不同时间段特征分布会有自然漂移这是正常现象。但如果漂移过大模型在测试集上的预测效果会大打折扣。我的排查方法是分特征做分布对比。把训练集和测试集的特征值分别统计均值、方差、分位数看哪些特征差异明显。比如训练集用户平均活跃天数比测试集长说明训练集时间范围更长这时要检查特征是否用的是相对时间而不是绝对时间。另一个经验是少用绝对数值类的时间特征比如“第几天发生购买”这种改用“距预测日最近一次行为的天数”这种相对特征会更稳定。5.3 分数卡住不动先回归到基础版我遇到过很多次类似情况连续加了二十几个特征AUC纹丝不动。这时候我的习惯不是继续堆特征而是先把模型恢复到一个最简单的版本只用5到10个核心特征跑通后再逐个把特征加回去看每个特征的边际贡献。这种方法虽然麻烦但能非常清晰地判断哪些特征真正有效哪些特征是冗余的。我在复购预测中就发现一个看起来很有道理的行为序列特征加入后AUC反而下降了0.002原因是它和已有的时间衰减特征高度共线。删掉冗余特征后模型更轻量分数也更稳。5.4 常见问题速查表问题现象可能原因处理方案训练时内存溢出数据类型过大、日志未分块指定列类型、分块读取验证集AUC高但线上很低特征泄漏或随机切分导致同用户样本跨集合按用户切分检查特征计算窗口模型全预测为0正负样本比例悬殊、阈值不合理设置scale_pos_weight搜索最优阈值特征重要性都很低特征构造期用了全局统计信息重复增加交互特征减少冗余统计训练速度很慢特征数量过多按重要性筛选特征降低num_leaves测试集特征分布偏移使用了绝对时间特征转成相对时间特征检查时间窗口6. 代码落地细节可复现项目的几个实用经验最后这部分我想聊一下怎么让这份“源代码文档说明”真正变成一个别人拿到就能跑起来的项目而不只是自己机器上的一堆脚本。6.1 环境依赖的固定最让人头疼的复现问题就是环境不一致。不同版本的pandas、sklearn、LightGBM同一个代码跑出来的结果可能天差地别。我在文档里专门写了一个requirements.txt把主要依赖库的版本都固定下来包括python版本、numpy、pandas、scikit-learn、lightgbm、xgboost。这样即使别人换了一台机器也能尽量还原运行环境。另外代码里我尽量避免使用一些冷门库减少环境配置成本。能用pandas和numpy完成的事就不额外引入其他依赖。即使是LightGBM和XGBoost也是竞赛场景里的标配库安装没有障碍。6.2 随机种子的统一树模型本身有一定随机性如果不固定随机种子同一个代码多次运行结果都可能不一样这会让人很难判断分数的变化到底是模型改进带来的还是随机波动。我在代码入口处统一设置了随机种子包括numpy、random、LightGBM和XGBoost的seed参数。这样每次运行结果可复现调优过程也更可控。6.3 中间结果的版本管理特征文件保存之后后续每次修改特征工程最好都生成一个新版本文件名带上日期或者版本号比如features_v1.parquet、features_v2.parquet。这样如果发现某个版本的特征导致模型分数下降可以随时回退到之前的特征文件不需要重新计算。我自己的习惯是每次改完特征先在本地记录三件事改了什么、验证集AUC是多少、线上分数是多少。这个记录看起来麻烦但长期复盘时价值很大能帮你快速定位到底是哪个改动带来了提升或回退。我在实际做这个项目过程中的体会是天猫复购预测这类赛题表面上看是在比谁的模型更先进、谁的调参更精细但真正拉开差距的其实是特征工程和数据处理这些相对“脏活累活”的部分。模型选型上LightGBM在这个数据规模下已经是足够强的武器多花时间在特征上远比反复调整模型超参数更有性价比。如果你准备自己动手复现建议用两天时间先把基础版本跑通再花三到五天迭代特征和调参。第一次跑通不要追求高分关键是确保数据处理的链路没有bug切分方式正确然后每改动一个地方就记录一次分数变化。按照这个节奏走下来即使没有拿到榜单最前面整个流程带给你的收获也足够扎实。本文还有配套的精品资源点击获取