十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用户复购预测实战:从特征工程到LightGBM调优完整指南

用户复购预测实战:从特征工程到LightGBM调优完整指南 简介在电商场景中用户行为日志是典型的结构化数据通过点击、加购、收藏、购买等行为序列可以构建二分类模型来预测用户是否会复购。此类任务的核心在于特征工程与模型选择统计用户活跃度、转化漏斗、时间规律等特征并利用LightGBM等梯度提升树模型进行训练。AUC作为排序指标能有效评估不平衡样本下的预测能力。该技术广泛应用于用户增长、营销转化、交易风控等领域。本文以天猫复购预测项目为例系统梳理了从数据理解、标签构造、特征工程到模型训练与评估的完整流程并总结了特征泄漏、样本不平衡等实战中的常见问题与解决技巧。 去年整理阿里天池学习赛的“天猫复购预测”时我把自己写的完整案例源代码和文档说明分享了出去结果不少初学者私信问我数据从哪下、特征怎么设计、LightGBM参数怎么调。这个赛题虽然叫“学习赛”但做起来并不简单数据里全是用户在天猫上的行为日志要从这些看似杂乱的操作记录里整理出有预测力的特征再判断一个用户未来会不会再次购买。整个过程对特征工程、数据划分、模型调优和结果评估都覆盖得很完整非常适合作为入门数据挖掘和结构化数据的练手项目。这篇文章就基于这个项目把我自己动手实现时可以复用的思路、代码细节和踩坑记录完整梳理一遍。内容包括赛题拆解、数据理解、标签构造、特征工程、模型训练、评估方式以及我实际复现时遇到的典型问题。无论你是刚开始接触天池这类比赛还是想系统学一下用户行为预测的完整流程这套“源代码文档说明”都可以直接参考。1. 项目整体设计与思路拆解1.1 赛题本质先搞清楚我们要预测什么很多新手拿到这个赛题后的第一反应是“用户复购预测”听起来很简单无非就是判断一个用户买过之后还会不会再买。但等打开数据才发现问题远不止这么粗暴。你要处理的是一条一条的用户行为日志里面有用户ID、商品ID、品类ID、商家ID、品牌ID、行为时间、行为类型。行为类型又分成点击、加购、收藏、购买四种。核心任务是根据一个新用户在某段时间内的历史行为预测他未来是否会对同款或同类商品再次购买。放在二分类框架下标签就是“会不会复购”1表示会0表示不会。我最初思考这个赛题时容易忽略的一点是它不是一个经典的推荐系统问题不是一个需要你给用户推荐商品的排序模型而是纯粹的用户行为二分类。所以你的建模目标不是“预测用户可能买什么”而是“预测用户会不会再买”。这也决定了接下来的特征构造方向重点围绕用户的历史活跃程度、购物深度、商品偏好稳定性这些角度展开而不是去计算商品之间的相似度。另一个容易忽略的点是这个赛题给你的是“新用户”的历史数据。也就是说我要判断的这群用户并不是平台上的老客他们在观察窗口内可能只是刚完成第一单或者还在浏览和比较阶段。这种用户的行为数据往往比较稀疏很多用户只有寥寥几条点击记录。如何从稀疏行为中提炼出稳定、有区分度的特征是整场比赛的核心难点。1.2 技术选型为什么是LightGBM而不是深度学习关于建模方案我的第一版baseline直接用了LightGBM。很多新手会问我为什么不用神经网络比如DeepFM、widedeep之类的我的回答是看数据形态和应用场景。这个赛题的数据本质上是用户行为日志聚合出来的表格数据每一行是一个用户每一列是一个统计特征。结构化表格数据上树模型至今仍然是非常稳妥的选择。LightGBM相比XGBoost有更快的训练速度、更低的内存占用并且对类别特征有原生支持。它在处理大量离散特征和稀疏特征时效果比传统逻辑回归好一个量级调参门槛又比深度学习低很多非常适合作为学习赛的起点模型。深度学习模型不是不可以用但这个任务没有现成的用户或商品的Embedding初始化需要从行为序列里自己学表示训练成本和调参难度都会明显上升。学有余力的人可以在树模型baseline之后再用图神经网络或序列模型做融合尝试。但我始终认为第一步先用LightGBM跑通整个流程、把特征工程和验证体系建立起来才是性价比最高的路线。而且从实际效果看LightGBM配合一套高质量的特征成绩就已经能排到学习赛比较靠前的位置。我自己的经验是先有一个稳定可复现的baseline再谈进阶模型否则你会陷入在模型调参里反复折腾、却不知道问题究竟出在数据还是模型上的困境。2. 数据理解与标签体系2.1 行为日志怎么读整个项目最核心的数据是一张用户行为日志表官方提供的字段通常包含几条关键信息用户ID、商品ID、品类ID、商家ID、品牌ID、行为时间戳、行为类型。有些版本还会附带用户的基本属性和商品属性表但不同场次的数据格式会有差异标准做法是先拿head()和describe()快速确认字段类型和取值范围。我在写文档说明时第一件事就是把数据字典列清楚。行为类型里数值0、1、2、3通常分别对应点击、加购、收藏、购买。注意不是所有比赛数据的行为编码都一致有的数据可能是1到4的编码顺序需要以官方说明为准。这个看似不起眼的小细节如果一开始错位后面所有特征统计都是错的。读取数据时还有几个实际注意点。Log数据通常很大动辄几千万行直接用Pandas原始读取非常吃内存。我习惯在读入时就把范围明确好——先只读取训练集需要的字段和用户范围数据类型上把用户ID、商品ID这类ID列设置为uint32或int32把行为类型设置为uint8。别小看这个操作内存占用可以直接减少60%以上。还有时间字段的处理。行为时间戳如果是一串Unix时间戳要第一时间转换成datetime类型并且按用户维度计算小时、星期几等衍生特征。天池这个赛题的时间信息非常关键用户几点活跃、周几活跃往往能反映用户的购物习惯和购买意愿。2.2 样本与标签的构造逻辑标签构造是整个项目里最容易出错、也最需要讲清楚的地方。这个赛题的官方数据往往已经划分好了训练集和测试集训练集里会直接给标签。但在学习赛中官方给出的训练集内部其实隐藏着一条时间线前一段时间的用户行为用于构造特征后一段时间的购买行为决定标签。我的理解是整个数据集的观察窗口被划分成了两个部分。特征窗口是用于统计用户历史行为的时间段标签窗口是用于判断用户是否复购的时间段。如果用户在这个时间段内有购买行为标签为1否则为0。这种基于时间窗口的构造方式至关重要因为一旦特征和标签之间存在时间重叠就会引入标签泄漏模型在离线验证时表现非常好但实际预测效果一塌糊涂。我在写源代码时专门加了一个标签构造的函数逻辑是用官方训练集行为数据按用户聚合出“标签窗口内是否购买”的标记。同时在文档里反复强调禁止用标签窗口之后的数据构造特征。因为测试集是没有标签的你只能用用户已知的历史行为来预测其未来的购买行为。有的团队会尝试自己造训练集负样本比如把没有购买行为的用户全部归为负样本把购买过的用户归为正样本。需要注意这个赛题通常正负样本比例是失衡的负样本远多于正样本所以早期不要直接套用准确率这种指标后面我会展开说。3. 特征工程实战特征工程是这类用户行为预测项目的灵魂也是我当时花时间最多、收获最大的部分。源码里我把特征分成了四大类用户基础行为特征、行为时间特征、用户对商品/品类的偏好特征、以及转化漏斗特征。下面我逐个讲清楚每一类特征的构造逻辑和代码思路。3.1 用户基础行为特征第一步肯定是对每个用户做行为统计这也是最朴素但通常最有效的特征组。核心思路是把用户的点击次数、加购次数、收藏次数、购买次数、总行为次数分别统计出来再算一些衍生比例比如点击转加购率、加购转购买率、收藏转购买率。有人可能会问这些简单的统计真的有用吗非常有用。原因很直白一个用户如果历史行为总量很大、购买次数很多说明他对这个平台的粘性很高复购的概率自然更大。反过来如果只有一两次点击连加购都没有那他大概率只是随便看看复购概率很低。我给的源码示例是这么写的def gen_user_base_features(df): user_df df.groupby(user_id).agg( total_actions(action_type, count), total_clicks(action_type, lambda x: (x 0).sum()), total_carts(action_type, lambda x: (x 1).sum()), total_favs(action_type, lambda x: (x 2).sum()), total_buys(action_type, lambda x: (x 3).sum()), ).reset_index() user_df[click_to_buy_rate] user_df[total_buys] / (user_df[total_clicks] 1) user_df[cart_to_buy_rate] user_df[total_buys] / (user_df[total_carts] 1) user_df[fav_to_buy_rate] user_df[total_buys] / (user_df[total_favs] 1) return user_df这里有个细节分母为什么要加1因为很多用户可能没有点击或者没有加购行为直接相除会出现无穷大或空值。加1这个操作叫拉普拉斯平滑虽然朴素但能在不改变数据含义的情况下避免除零错误这是代码里非常实用的小技巧。除了比例特征还可以统计用户行为类型的多样性。比如一个用户点击了多少个不同的商品、多少个不同的品类、多少个不同的品牌。如果用户的行为集中在某几个商品上说明他有明确的购买目标如果行为分散在几十个甚至上百个商品上说明他还在大量浏览比较短期内下单意愿可能不高。3.2 行为时间特征与活跃度刻画时间特征是我做这类型比赛时非常看重的一环因为时间隐含了用户的购物习惯和当前关注度。常用的时间特征包括第一次行为时间、最后一次行为时间、行为持续天数从第一天到最后一天的长度、行为密集程度平均每天多少次操作等。从源码的实现角度可以先取每个用户行为时间的最小值和最大值然后算出时间跨度。一个用户如果在一个月内持续活跃比一个只在一个下午集中操作的用户复购意图通常更强烈。我通常还会统计“最后行为时间距离数据截至日期的天数”这个特征能刻画用户是否近期还在活跃。如果一个用户的最后一次操作发生在很久以前可能已经流失了如果发生在最近说明他最近仍有购物兴趣复购概率更高。代码大致是这样的df[time_stamp] pd.to_datetime(df[time_stamp], units) user_time df.groupby(user_id)[time_stamp].agg( first_timemin, last_timemax, total_spanlambda x: (x.max() - x.min()).days ).reset_index()另外还可以提取小时维度的活跃模式。比如计算用户行为最常发生在白天还是晚上或者统计不同小时段的行为占比。这些特征在业务上解释性很强晚上活跃的用户多为下班后网购周末活跃的用户可能是空闲时间更多购买意愿度会略有不同。我个人的经验是行为时间这类特征在线上预测中往往能排到特征重要性的前列因为“近期是否有行为”本身就是复购概率极强的信号。如果你在构造特征时发现某类特征增益很大先不要急着高兴重点核查一下这个特征是不是利用了未来信息。比如“最后行为时间距离截止日期的天数”在训练集能用但预测测试集时要确保时间基准一致否则会造成时间穿越。3.3 商品偏好与转化漏斗特征用户对商品的偏好可以从用户对商品、品类、品牌、商家四个纬度的行为信息来刻画。最简单有效的做法是统计用户在不同品类下的购买次数、加购次数然后计算每个用户购买品类的集中度。比如一个用户只在一个品类上有购买行为说明他购物目的明确如果分散在多个品类可能是家庭采购型用户复购的概率也相对较高。我在源码里提供了一个计算用户品类熵的特征示例。熵越大说明用户行为越分散熵越小说明用户注意力越集中形成了某种购买惯性。from scipy.stats import entropy cat_group df.groupby([user_id, cat_id]).size().reset_index(namecnt) cat_entropy cat_group.groupby(user_id)[cnt].apply(lambda x: entropy(x)).reset_index(namecat_entropy)转化漏斗特征是我这一版代码里收获特别大的一组特征。所谓转化漏斗就是用户在购买前经历“点击 → 加购 → 收藏 → 购买”这个链路每一步的转化率能反映用户的购买意愿深度。如果一个用户加购了很多商品但迟迟没有下单说明他还在筛选中但意愿强如果点击多但加购极少可能只是随便浏览。实际构造时可以为每个用户计算每个行为步骤的累计占比比如加购行为数除以点击行为数、购买行为数除以加购行为数。还可以构建一个高阶特征用户是否对同一样商品反复查看。如果一个商品被用户点击了五六次很可能说明用户反复确认、非常想买而复购概率也会上升。4. 模型训练与评估的完整实现4.1 数据划分与验证策略分类任务里我们通常用随机划分训练集和验证集但在这个赛题里随机划分是错误的做法。原因在于用户行为数据存在时间相关性同一个用户的不同行为之间不是独立的如果随机打乱相当于把时间顺序完全破坏会给模型传递一种“未来信息”已经可见的错觉。更好的做法是使用时间切分验证。如果官方只给了训练集和测试集我建议在训练集内部按时间或者按用户ID划分验证集。比如取20%的用户的特征作为验证集其余作为训练集。这样做的好处是验证集与训练集的用户没有重叠能更好地模拟测试集上遇到新用户的情况。我自己的实践是先在训练集上用train_test_split(..., stratifyy)按标签比例分层划分保证训练集和验证集的复购用户比例一致。如果数据量足够大、时间跨度够长也可以按时间后段作为验证集这样更能模拟对未来行为的预测。4.2 LightGBM核心参数配置我的所有模型都是基于LightGBM实现的原因前面已经说过。在源码里我给的是一套经过多轮实验后比较稳定的参数组合import lightgbm as lgb params { objective: binary, metric: auc, learning_rate: 0.05, num_leaves: 31, max_depth: -1, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 1, lambda_l1: 0.1, lambda_l2: 0.1, verbose: -1, seed: 42, }关于这些参数我再补充几句。learning_rate设置得不要太高0.05左右比较稳妥配合较多的树数量几百到上千能获得更好的精度和泛化性。num_leaves控制每棵树的复杂度如果数据量大可以适当调大一点但太大会过拟合。feature_fraction和bagging_fraction是防过拟合的关键参数通常在0.7到0.9之间调整能有效提升模型稳定性。训练时我建议开启早停early stopping监控验证集上的AUC当连续50轮没有提升就停止训练。这样既能避免过拟合又能充分利用训练数据。dtrain lgb.Dataset(X_train, y_train) dvalid lgb.Dataset(X_valid, y_valid, referencedtrain) model lgb.train( params, dtrain, num_boost_round5000, valid_sets[dvalid], callbacks[lgb.early_stopping(50), lgb.log_evaluation(100)] )4.3 评估指标与结果解读这个赛题的官方评估指标是AUC。AUC的含义是任取一个正样本和负样本正样本的预测分数大于负样本预测分数的概率。它是一个排序指标不依赖具体的阈值所以非常适合处理正负样本不平衡的数据。很多人会习惯性去看准确率但在正负样本比例悬殊时准确率会非常具有欺骗性。比如复购率只有5%模型把所有样本都预测成0准确率也能达到95%但显然这个模型没有用。所以我全程用AUC评估模型效果重点关注验证集AUC和线上AUC的稳定性。在我自己复现这套方案时验证集AUC大概能到0.70到0.75之间这是这类复购预测任务的正常水平区间。不要看到有些线上的高分模型有0.8以上的AUC就焦虑那些往往融合了多种复杂模型、大量外部特征和后期精调。对于学习赛来说先把baseline跑通、把验证AUC稳定住再逐步优化特征和模型才是最务实的路径。如果想再看看模型的预测概率是否校准得好可以增加一个LogLoss的监控。AUC高只说明排序好但如果有业务方关心预测概率本身LogLoss就显得重要了。不过在这个赛题里我们只需要关注排序能力也就是AUC。5. 常见问题与排查技巧实录5.1 样本不平衡要不要处理这个赛题的正样本比例通常比较低大概在10%到20%之间很多人一上来就想用SMOTE过采样或者对负样本降采样。我个人的建议是先不用太激进地处理不平衡因为AUC评估指标本身对不平衡有一定容忍度。但如果你发现模型预测出来的概率普遍偏低正样本的召回率严重不够那可以用两种方式缓解。一是调整LightGBM中的scale_pos_weight参数让模型对少数类的惩罚加大。另一种是训练完成后根据验证集重新找一个阈值而不是用默认的0.5。这两种方式我都试过在不改变特征的前提下对最终AUC的影响通常不大所以不要把它当成救命稻草核心还是特征。5.2 AUC高但线上效果差是怎么回事这是实战中很让人头疼的问题。一个可能的原因是特征泄漏也就是你构造的特征里混入了标签信息或者时间穿越的变量。常见的情况包括把标签窗口内的行为统计进了特征或者直接用了官方测试集的数据做特征归一化。另一个常见原因是训练集和测试集的数据分布不一致。官方数据往往来源于不同时间段或不同渠道如果训练集用户整体活跃度远超测试集模型在测试集上的表现就会有所下降。缓解方法包括在特征工程时尽量做稳健的统计数据不要过度依赖极端值在验证阶段分别看不同用户群体的AUC找出模型失效的细分人群。5.3 特征泄漏的几个经典坑我把特征泄漏单独拎出来说因为这是新人最容易犯、却最难察觉的错误。第一个坑是用了用户购买后的行为去统计特征。比如你统计用户对某商品的浏览次数这个次数里可能包含了用户复购后的浏览行为这就等于提前把标签的信息喂给了模型。第二个坑是全局归一化时使用了测试集信息。例如你先整体计算了所有用户行为次数的平均值然后用来做标准化如果没有仔细区分训练集和测试集就可能导致测试集的分布信息无意中泄露进训练过程。对这种行为日志而言建议尽量多使用计数特征和比例特征少做需要全局统计的标准化操作这样能在很大程度上天然规避时间泄漏。第三个坑是时间窗口不一致。训练集和测试集如果来自不同的时间段而你在构造“最后行为距离截止日期”这类特征时用了不同的截止日期那么训练集和测试集的特征分布会不一致导致模型泛化能力变差。解决办法是确保所有时间类特征都以同一截断时间为基准。5.4 代码性能优化与Debug建议最后一个常见问题是代码跑得太慢。几千万行日志做groupby聚合如果没有处理好跑一个特征就要好几个小时。我建议做三件小事第一读取数据时指定数据类型用pd.read_csv(..., dtype{user_id: uint32, action_type: uint8})第二groupby之后尽量用聚合函数一次计算多个特征减少重复扫描数据的次数第三如果数据量实在太大可以先用一部分用户做特征探索确认特征有效后再全量跑。另外拿到代码后不要一次性跑完整流程先把行为日志做一个100万行的小样本子集跑通代码、确认特征不是空表、模型能正常训练再上全量数据。这样能极大缩短调试周期也更容易定位代码里哪个环节报错。我在整理这份源代码和文档说明时刻意没有把代码写得非常复杂而是保证结构清晰、注释完整、可复现性强。任何一个有一定Python基础的人拿到项目代码后按顺序执行都能得到一个完整的baseline结果。之后想优化可以往特征工程的方向继续扩展比如加入更细粒度的品类偏好特征、构造用户对品牌忠诚度特征或者尝试用XGBoost和CatBoost做模型融合再配合权重搜索效果通常还能再上一个台阶。做这类学习赛的真实收获不在于最后排名多高而在于你亲手把一个模糊的业务问题翻译成数据问题、再翻译成代码实现最后用科学的评估方式验证自己的方案。这套能力是所有涉及用户增长、营销转化、交易风控的日常业务问题中都会反复用到的。如果你也准备拿这个项目练手建议先不看我的完整代码自己动手写一版特征工程和baseline遇到瓶颈了再对照源码查漏补缺收益会明显更好。本文还有配套的精品资源点击获取
返回列表