尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

天猫复购预测实战:特征工程与LightGBM调优全流程

天猫复购预测实战:特征工程与LightGBM调优全流程 简介在电商场景中用户复购预测是提升客户生命周期价值的关键任务其本质是一个典型的二分类问题。解决这类问题的核心不在于模型堆叠而在于如何从海量用户行为日志中提取有效的特征信号并通过合理的评估指标衡量模型排序能力。AUC作为一种不依赖分类阈值的评估标准能够有效应对正负样本不平衡的挑战。同时LightGBM凭借高效训练和对特征工程的良好支持成为表格数据建模的主流选择。通过五折交叉验证和严格的时间截断防泄漏设计可以构建稳健的预测流程。本文以天猫复购预测项目为例完整梳理了赛题拆解、特征构造、模型调优与代码组织为从事电商数据分析和机器学习实践的读者提供一套可复用的工程方法论。 几个月前我打开阿里天池大赛学习赛的“天猫复购预测”赛题页面时第一反应是这不就是一个典型的二分类任务吗给每个用户-商家对打上“是否复购”的标签套个树模型跑一跑就完事了。直到真正开始处理官方提供的用户行为日志我才意识到这个赛题的核心难点根本不在模型而在三件事怎么从千万行行为数据里提炼出复购信号怎么组织一份别人能看懂的源代码怎么把整个项目的文档说明写到“可复现、可扩展”的程度。这篇文章我就以完整跑通这个项目的视角把赛题拆解、数据清洗、特征构造、模型调优、代码结构以及文档组织的全过程一次讲清楚。无论你是刚接触数据竞赛的新手还是想把复购预测搬到实际电商业务里的从业者这篇内容应该都能帮到你。1. 赛题拆解复购预测到底在预测什么1.1 业务背景复购是电商里最值钱的信号复购简单说就是用户在一家店铺买过一次之后还会不会再来买第二次。电商行业里第一次成交往往要付出高额的用户获取成本广告投放、优惠券、活动补贴哪一个都不便宜。而老用户的二次购买转化成本低、客单价往往更高还能带来口碑传播。所以各大平台都想在用户产生“再次购买”意图之前先把这批人识别出来然后针对性地做触达和推荐。天池这个学习赛把真实业务场景简化成了标准的数据挖掘问题官方给出用户基础信息、用户行为日志以及已经标注好是否复购的训练样本参赛者需要预测测试集里每个用户-商家对发生复购的概率。任务描述起来很简洁但想拿到一个稳定的效果需要把数据理解、特征工程和模型细节都做到位。1.2 数据文件说明与字段关系这次我用到的官方数据集主要由四个文件组成user_info用户基础信息比如性别、年龄段区间字段不多但可以补充用户侧的画像特征user_log用户行为日志每一行代表某个用户在某一个时间点对某个商品产生了行为行为类型包括浏览、加购、删购、收藏、购买等train训练集的用户-商家对以及对应的复购标签labeltest测试集的用户-商家对需要预测复购概率。核心字段包括user_id用户ID、item_id商品ID、cat_id类目ID、merchant_id商家ID、brand_id品牌ID通过这些主键行为日志可以和用户信息、候选样本关联起来。特别要注意的是训练集的每一行不是用户行为日志而是“用户-商家”这个复合维度。也就是说官方提前筛选好了候选对我们只需要在这些候选对的基础上构建特征不需要自己生成全量组合。1.3 为什么评估指标选AUC这个赛题的官方评估指标是AUC。业务和算法背景不同的朋友可能有疑问为什么不用准确率原因很简单复购行为的正样本占比通常不高我拿到这份数据时标签为1的比例不到两成。如果模型把所有样本都预测为0准确率可以做到80%以上但这个模型毫无业务价值。AUC衡量的是模型把随机一个正样本排在随机一个负样本前面的概率它不依赖人为设定的阈值直接反映模型对样本的排序能力非常适合复购这类正负样本极不均衡、最终只看排序结果的场景。理解了AUC后面调参时就不会把注意力浪费在“阈值设0.5还是0.3”这种问题上而是集中在如何把正样本的预测分数整体拉高。2. 数据初探先搞清楚用户在哪一步流失2.1 行为日志的结构与脏数据拿到数据第一步我不是急着写特征脚本而是先把user_log读进来看看每一列的真实分布。这份行为日志是典型的长表结构一个用户会对应大量行为记录整体行数很快膨胀到千万级别。读入之后我做了几件事查看每列的数据类型和缺失值情况统计action_type各种取值的数量分布检查user_id、item_id、merchant_id是否有异常编码查看时间字段的范围和格式。在这个过程里发现过一些脏数据比如有同一user_id在同一个merchant下出现重复购买记录可能是因为行为日志本身存在同一订单多次上报的情况还有部分时间戳格式不统一需要统一转成datetime类型后再处理。这些看起来不影响模型的小细节一旦放到特征工程阶段就会造成统计偏差所以清洗必须前置。2.2 用聚合表建立整体直觉我习惯在写特征工程之前先基于user_log做一次快速聚合生成一张“用户行为概览表”。具体包括每个用户的行为总数、购买次数、涉及的商家数、商品数、类目数、品牌数以及首次和末次行为时间。这张表有两个作用第一帮我快速了解数据里用户的活跃度分布第二后面构造用户侧特征时可以直接引用其中的统计结果省去重复计算。分布结果多数符合预期浏览类行为占比最高购买行为占比很低。这说明用户从看到商品到完成下单中间会经历大量比较和犹豫。对于复购预测来说这种“决策链路”特征非常关键——如果用户在一家店完成了购买说明他已经突破了犹豫阶段接下来第二次购买的门槛比第一次低得多。2.3 候选样本的边界问题这里有一个容易搞错的地方训练集和测试集已经给出了user_id和merchant_id的组合我们做特征时要始终以这个候选对为主体。不要在代码里对user_log或者user_info单独做某种聚合后把行数放回到全部行为日志长度那样会破坏训练样本和测试样本的对齐关系。同时要注意训练样本和测试样本的时间范围。不同版本的赛题数据在时间切分上不完全一致有些是同一时间段内划分候选对有些是切出了未来一段时间作为预测目标。无论哪种情况特征构造的代码都必须支持“传入截止时间只使用截止时间之前的行为”。这个设计我在后面还会详细展开因为它直接关系到有没有数据泄漏。3. 特征工程把复购信号从日志里捞出来3.1 用户侧特征刻画用户的基本盘用户侧特征解决的是“这个用户整体上有多活跃、购买倾向有多强”。我构造的维度包括用户总行为数、总购买数、总加购数、总收藏数购买占浏览的比例、加购占浏览的比例用户购买的去重商品数、去重类目数、去重品牌数和去重商家数用户行为覆盖天数、首次行为到末次行为的跨度用户平均每天产生多少个行为。这些特征中我格外关注“购买/浏览”这个比值。它反映用户从产生兴趣到最终下单的转化效率。如果一个用户平时只是大量浏览但不买说明他的购买决策非常谨慎让他复购的难度更高而一个购买/浏览比例较高的用户对平台的信任度更强二次下单概率通常也更高。3.2 商家侧特征识别高复购店铺复购行为不仅取决于用户也取决于商家。有些店铺品类天然具备高频复购属性比如零食、日用品、美妆有些商家客单价高、复购周期长比如家电、家具。商家侧特征可以帮助模型捕捉这种店铺层面的整体差异。我的做法是先构造一张商家维度汇总表再关联到候选样本上。主要字段包括商家服务的用户数、购买用户数商家总行为数、总购买数、总加购数商家购买用户占服务用户比例商家平均每个购买用户的购买次数商家的浏览到购买转化率。这里要提醒一句商家侧特征计算相对轻量但对复购预测的帮助很直接。实际实验中只加入商家侧特征AUC就提升了将近1个百分点原因是复购标签在商家维度上存在明显的聚集效应——有些商家的复购率天然就高。3.3 用户-商家交叉特征复购预测的核心引擎如果说用户侧和商家侧特征是背景板那用户-商家交叉特征就是整张特征体系的主角。复购本质上是“用户和商家之间既有关系”的延续所以双方交互的强度是预测最重要的输入。我重点构造了以下交叉特征该用户在该商家的总行为数、购买数、加购数、收藏数该用户在该商家的购买次数占该用户全部购买次数的比例该用户在该商家的最后一次行为距统计截止时间的天数用户在该商家的首次购买与末次购买间隔天数用户在该商家相邻两次购买的平均间隔该用户在该商家购买过的商品数、类目数、品牌数。其中我特别关注“最后一次购买距截止时间的天数”。这个特征可以理解为用户对该商家的近期热度。如果用户上周刚在这家店买过东西那么接下来观察窗口内复购的概率会明显高于一个三个月前才来过的用户。这一点和业务直觉完全一致——模型的逻辑是能从数据里学出来的但前提是我们把信息整理好喂给它。3.4 时间窗口与行为序列特征全量统计特征容易把用户的历史行为“平均化”丢失近期信号。所以我在交叉特征之外又加了几个滑窗统计比如分别统计截止时间前7天、15天、30天内该用户在该商家产生的行为数和购买数。滑窗特征的意义在于让模型感知到“最近发生了什么”。一个用户可能半年前和这家店有大量交互但最近一个月完全沉寂全量统计下他和另一个持续活跃的用户看起来一样这显然不对。加了滑窗之后模型能区分出“过去活跃但现在冷却”和“刚刚活跃”这两种状态。我实现滑窗特征的代码结构大致是定义一个build_window_feature函数输入截止时间和窗口天数从行为日志中筛出截止时间之前的数据再进行聚合。训练集和测试集共用这套函数只是传入的截止时间不同。这样可以保证特征逻辑的一致性和防泄漏。3.5 特征构建的防泄漏原则整个特征工程里最重要的一条原则任何特征只能用预测截止时间之前的信息不能用整段观察期内的全量数据统计。我第一次跑这个赛题时图省事直接用整个user_log做了用户和商家的全量统计训练集和测试集都用同一套特征五折AUC一度冲到0.95以上当时还觉得模型效果离谱地好。后来细想才发现这种做法的本质是让模型在预测时“看到了未来”。因为测试集里的用户-商家对如果通过全量日志统计就能知道这些用户在整段观察期结束之后产生了什么行为这在真实业务场景中根本不可能实现。改成按截止时间截断后AUC回到了0.88左右但这是真实可信的水平。做学习赛项目这种“伪提升”一定要避免否则你学会的只是自欺欺人的流程。4. 模型训练与调优从baseline到AUC提升4.1 模型选型为什么是LightGBM表格类数据竞赛里LightGBM几乎是默认首选。相比传统GBDT实现它训练速度快、内存占用低、支持类别特征还自带缺失值处理。对新手来说最大的好处是迭代效率高改一次特征两分钟内就能看到AUC变化这种反馈速度对学习非常关键。我在这个项目里也试过XGBoost两者在最终效果上接近但LightGBM的训练时间大约只有XGBoost的三分之一。如果是个人练手项目我建议优先LightGBM拉通流程有余力再考虑XGBoost和CatBoost做模型融合。4.2 五折交叉验证与评估方法模型评估我没有采用单次划分验证集而是固定使用五折分层交叉验证。训练数据量不算大单次划分的AUC波动会比较明显可能只是因为运气好就虚高了0.005导致你误判某个特征有效。五折平均能大幅降低这种随机波动。代码上我用StratifiedKFold保证每一折的正负样本比例接近全量。每一折训练结束后保留验证集上的预测概率整个流程跑完会得到一个out-of-fold预测结果。这个结果既可以用来计算可靠的AUC也可以后续在做模型融合时作为下一层模型的训练输入。4.3 参数调优的实际路线我调参不太喜欢一上来就上网格搜索因为搜索空间大、耗时高。我的路线是先固定一组合理参数跑出baseline再观察训练集AUC和验证集AUC的差值判断模型处于欠拟合还是过拟合状态。如果训练集AUC很高但验证集AUC明显偏低说明过拟合优先调整减小num_leaves增大min_data_in_leaf调低feature_fraction和bagging_fraction。如果训练集和验证集AUC都不高说明欠拟合优先调整增大n_estimators增大num_leaves适当调低learning_rate让模型学得更细。参数方向确定后我再用一小轮网格搜索精调。最后一步是把learning_rate从0.05降到0.01配合early_stopping重新训练。这样能得到一个更稳的模型不至于在大学习率下提前过拟合。4.4 特征重要性与迭代节奏每完成一轮实验我都会输出LightGBM的feature_importance观察排在前30的特征是哪些。实测下来用户-商家交叉特征和滑窗特征占据主导地位用户侧的一些行为占比特征偶尔靠前。这个结果印证了之前的判断复购预测的核心信息集中在“用户和商家的既有关系”上。特征迭代的节奏我也建议控制一下。不要一次加几十个特征否则模型效果变了根本说不清是哪个特征起的作用。我习惯每次只加一组逻辑相关的特征记录AUC变化。有效就保留无效就回退这样每轮迭代都在积累可解释的经验而不是在撞大运。5. 源代码与文档怎么组织才能算“可交付”5.1 代码目录结构这个项目我最终整理成了下面这样的结构。它不是最复杂的但胜在清晰别人拿到手之后按README操作就能复现. ├── README.md ├── requirements.txt ├── config.py ├── data/ │ ├── raw/ │ └── processed/ ├── feature/ │ ├── build_user_feat.py │ ├── build_merchant_feat.py │ ├── build_cross_feat.py │ └── build_time_feat.py ├── model/ │ ├── train.py │ ├── predict.py │ └── lgb_config.py ├── utils/ │ ├── logger.py │ └── metrics.py └── run.shdata/raw放原始数据data/processed放每一层特征工程产出的中间结果feature下的脚本按特征类型拆分互不干扰model目录下train和predict分开避免train脚本里写完训练逻辑又顺手写预测逻辑的混乱utils放通用函数比如计算AUC和打印日志。5.2 核心模块的实现要点运行时建议直接执行根目录下的run.sh它会按照顺序执行特征工程脚本和模型脚本。每个feature脚本的输出都保存成parquet格式统一以user_id和merchant_id作为对齐键。这样即使中间某个脚本报错之前已经算好的特征文件可以继续复用不用从头跑。train.py里我写了几个固定动作加载所有特征文件按id列做merge形成训练矩阵从config读取模型参数采用五折交叉验证训练输出每一折的AUC和最终out-of-fold预测保存训练好的模型和特征列表。predict.py则负责加载模型文件对测试集做相同的merge和预测输出提交概率文件。这里贴一个train.py里核心训练逻辑的简化示例帮助理解整体流程import lightgbm as lgb import pandas as pd from sklearn.model_selection import StratifiedKFold from sklearn.metrics import roc_auc_score train pd.read_parquet(data/processed/train_feat.parquet) features [c for c in train.columns if c not in (user_id, merchant_id, label)] skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) oof_pred pd.Series(indextrain.index, dtypefloat) for fold, (train_idx, valid_idx) in enumerate(skf.split(train[features], train[label])): dtrain lgb.Dataset(train.loc[train_idx, features], train.loc[train_idx, label]) dvalid lgb.Dataset(train.loc[valid_idx, features], train.loc[valid_idx, label]) params { objective: binary, metric: auc, learning_rate: 0.01, num_leaves: 31, feature_fraction: 0.8, bagging_fraction: 0.8, verbose: -1, } model lgb.train( params, dtrain, num_boost_round2000, valid_sets[dvalid], callbacks[lgb.early_stopping(100), lgb.log_evaluation(0)], ) oof_pred.loc[valid_idx] model.predict(train.loc[valid_idx, features], num_iterationmodel.best_iteration) print(out-of-fold auc:, roc_auc_score(train[label], oof_pred))这段代码本身不复杂但它是整个项目的“主心骨”。所有特征、参数、数据处理逻辑最后都汇聚到这里跑通它整个流程就串起来了。5.3 文档说明怎么写才算合格一个学习赛项目的文档重要性不亚于代码本身。我在README里按这样的顺序组织内容项目概述和赛题背景数据来源、文件字段说明环境依赖Python版本、关键包版本从原始数据到最终提交的完整运行命令目录结构说明实验结果记录表。实验记录表我会用表格形式维护记录每次实验的日期、特征组合、模型参数、五折AUC、训练耗时和备注。别小看这个习惯它能让你的每次迭代都有据可查。没有实验记录的调参本质上只是在碰运气。6. 实战复盘我踩过的四个坑6.1 时间泄漏让线下AUC虚高前面提到过我第一次做这个赛题时用全量行为日志构造特征线下AUC高得离谱。后来改成按截止时间截断后分数回落到正常区间。这个坑虽然低效但我相信很多新手都会踩。学习赛虽然不涉及实时上线但如果你以后把代码迁移到真实业务场景时间泄漏会直接导致线上效果崩塌。我的建议是从第一天写特征脚本起就坚持“特征函数必须接收截止时间参数”的模式。虽然前期会多写几行代码但这个习惯能帮你避免最后一刻推倒重来的痛苦。6.2 内存溢出与日志量之间的矛盾user_log是千万行级别的数据直接groupby再merge到候选样本上内存经常爆炸。我一开始想着加大内存解决但换到配置一般的机器上依然不行。后来总结了三个有效手段把高基数ID列转成category类型内存占用能降到原本的四分之一groupby之前只保留需要的列不要拖着一堆用不到的字段参与聚合中间结果落盘到parquet分段计算、分步读取。这种做法不只是为了省内存也让代码更容易调试。每算完一类特征就落盘你可以在下一个环节直接验证特征质量不用每次都从原始日志开始跑。6.3 训练和测试的样本顺序不一致有一次我调整了train.py里merge的写法发现训练集AUC和验证集AUC都很正常但提交的预测文件几乎全是同一个值排名一落千丈。排查后发现测试脚本里对user_id做了一次reset_index训练脚本里没有导致两边特征矩阵的行顺序不一致预测概率和对应的样本ID错位了。解决方法很干脆整个项目里对候选样本id的处理只在特征工程阶段做一次后续所有脚本都通过merge结果里的id列对齐。同时在predict.py里加上断言确认预测结果行数和测试集行数一致。这种低级错误完全可以通过代码规范避免。6.4 关于“学习赛”的心态与收获这次天猫复购预测的学习赛最大的收获不是最后AUC数字而是让我把数据竞赛的完整流程走了一遍业务理解、数据清洗、特征工程、模型训练、交叉验证、代码组织、文档撰写。比赛排名是暂时的但这一套方法论是通用的。如果你也想用这个案例练手我的建议是先不看任何现成方案自己把baseline跑通然后对照源码和文档补上自己漏掉的特征和细节最后再尝试做自己的改进比如引入新的时间窗口、尝试模型融合、优化特征存储方式。这个过程走完你对复购预测和相关技术栈的理解会明显上一个台阶。本文还有配套的精品资源点击获取
返回列表