ARTICLE DETAIL

资讯详情

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

天猫复购预测实战:从用户行为日志到LightGBM的完整特征工程方案

天猫复购预测实战:从用户行为日志到LightGBM的完整特征工程方案 简介在电商场景中复购预测是用户行为分析与数据挖掘的核心任务之一其本质并非简单的二分类问题而是对用户与店铺之间关系强度的建模。通过解析用户行为日志中的浏览、收藏、加购、购买等动作序列可以提取出反映用户粘性的关键信号。特征工程是提升模型效果的关键尤其是基于时间窗口的行为节奏特征能够显著刻画用户的复购倾向而LightGBM作为高效的梯度提升树模型在处理表格数据时兼具速度与精度。此类技术广泛应用于推荐系统、用户流失预警和精细化运营等场景。本文以天猫复购预测赛题为载体从数据粒度、时间窗口特征、用户与商家维度聚合、模型验证等环节完整展示一套可复现的机器学习工程实践方案帮助读者掌握从行为日志中挖掘高价值特征并构建稳定分类模型的系统方法。1. 赛题拆解复购预测不是一个普通的二分类1.1 先弄清官方到底在问什么阿里天池上的天猫复购预测赛题核心任务看起来很简单给定一批用户在某家店铺里的行为日志让参赛者预测这些用户之后还会不会在同一家店铺再次购买。每一个训练样本是用户-店铺二元组标签是 0 或 1评估指标是 AUC。但如果你直接把它当成一个普通的二分类问题去做大概率会翻车。原因是复购这个业务语义本身带有很强的时间属性和关系属性。它不是让你预测用户会不会买而是预测用户会不会再买一次。一个用户可能已经在这家店铺下过单也可能只是反复浏览、收藏、加购但从没成交。这两类用户在特征上的初始状态完全不同建模时不能混为一谈。我当时看的赛题说明里正样本比例并不高整体不到 10%也就是说大部分样本是没有复购行为的。这种情况下如果你拿准确率当指标随便预测全 0 也能拿到 90% 以上的准确率但这毫无意义。AUC 只关注正负样本的排序能力所以它才是这个赛题的主指标。理解了这层关系你才会明白后续所有特征工程都应该是围绕怎么把有复购倾向的样本排到前面来展开而不是纠结分类阈值怎么定。1.2 数据文件的真实结构user、user_log、train、test 之间的关系赛题通常会提供几张表核心的是以下四类文件字段粒度用途user.csvuser_id、性别、年龄、职业、城市等级等用户维度描述用户画像user_log.csvuser_id、item_id、cat_id、seller_id、brand_id、time_stamp、action_type用户-商品-行为最核心的行为序列train.csvuser_id、seller_id、label用户-店铺训练样本与标签test.csvuser_id、seller_id用户-店铺待预测样本这里有个非常容易忽略的粒度问题user_log 的最小记录单位是用户-商品-时间戳-行为类型而训练集和测试集的样本单位是用户-店铺。同一个用户可能在同一个店铺里对好几个商品有过浏览、收藏、加购、购买等不同动作如果不做聚合日志是没法直接 join 到样本上的。我第一次跑的时候没注意这一点直接把 user_log 按 user_id 和 seller_id join 到 train.csv 上结果出现了大量重复行特征维度错得离谱。正确做法是先把日志聚合到用户-店铺二元组上再做关联。这一步是所有特征工程的地基地基歪了后面全白搭。另外一个值得留意的是时间口径。行为日志里每一条记录都有 time_stamp而 train.csv 的标签是基于某一时间点之后是否产生复购来判定的。换句话说特征构造时只能用该样本在行为窗口内能看到的数据不能用窗口外的信息。很多人初赛时拼命堆特征AUC 看着很高但一提交就崩多半就是在这里出了问题。1.3 赛题的业务直觉复购行为的本质是关系强度复购预测和一般的转化率预估有个明显差异它更强调用户与店铺之间的长期关系。一个用户可能因为一次大促在某家店买了东西之后再也不来也可能因为这家店的商品质量好、客服响应快、物流稳定形成了稳定的复购习惯。这些业务层面的因素在数据里不会直接给出但可以通过行为日志的分布形态推断出来。我习惯把它理解为用户对店铺的粘性粘性越强复购概率越高。粘性的信号包括但不限于行为次数是否持续增长、两次购买之间的间隔是否在缩短、收藏和加购之后有没有真正转化为订单、用户是不是把这家店当成了固定采购来源。带着这个直觉去构造特征会比盲目堆砌统计量要高效得多。2. 特征工程从用户行为日志里挖出复购信号2.1 用户-商家维度的基础聚合特征工程的第一步是把 user_log 聚合到用户-店铺维度。这个聚合可以按不同层面去拆第一层是行为次数。把 action_type 分成浏览、收藏、加购、购买四类分别统计每个用户在每家店铺里的动作次数再统计总行为次数。这一层是兴趣浓度的直接体现一个在店铺里产生过 50 次浏览、10 次加购的用户和一个只来过 1 次的用户复购概率显然不同。第二层是多样性维度。统计用户在这家店铺里浏览过多少去重商品、多少品类、多少品牌。多样性高意味着用户可能是在逛店多样性低但购买次数高则更可能是复购熟客。这两类特征在后续模型里可以形成很好的区分。第三层是比率特征。把不同行为算比值比如购买次数除以浏览次数、加购次数除以浏览次数、收藏次数除以加购次数。这些比值本质上是转化率反映的是用户从感兴趣到真正下单的转化效率。复购用户往往有一个特点首次购买之后后续的浏览行为很大概率直接加购或下单转化率会明显高于普通用户。我贴一段当时实际使用的核心聚合代码可以直接跑通import pandas as pd import numpy as np log pd.read_csv(user_log.csv, parse_dates[time_stamp]) train pd.read_csv(train.csv) test pd.read_csv(test.csv) train[is_train] 1 test[is_train] 0 sample pd.concat([train, test], ignore_indexTrue) # 1. 行为计数action_type 0浏览 1收藏 2加购 3购买 actions log.groupby([user_id, seller_id, action_type]).size().unstack(fill_value0) actions.columns [act_0_browse, act_1_fav, act_2_cart, act_3_buy] # 2. 多样性特征 uniq_item log.groupby([user_id, seller_id])[item_id].nunique().rename(uniq_item) uniq_cat log.groupby([user_id, seller_id])[cat_id].nunique().rename(uniq_cat) uniq_brand log.groupby([user_id, seller_id])[brand_id].nunique().rename(uniq_brand) uniq_day log.groupby([user_id, seller_id])[time_stamp].nunique().rename(uniq_day) pair_feat actions.join(uniq_item).join(uniq_cat).join(uniq_brand).join(uniq_day).reset_index() # 3. 转化率特征加 1 平滑避免除零 pair_feat[buy_browse_ratio] pair_feat[act_3_buy] / (pair_feat[act_0_browse] 1) pair_feat[cart_browse_ratio] pair_feat[act_2_cart] / (pair_feat[act_0_browse] 1) pair_feat[fav_cart_ratio] pair_feat[act_1_fav] / (pair_feat[act_2_cart] 1) pair_feat[buy_cart_ratio] pair_feat[act_3_buy] / (pair_feat[act_2_cart] 1) sample sample.merge(pair_feat, on[user_id, seller_id], howleft) sample sample.fillna(0)这段代码本身不复杂但有两点必须说明。一是 join 时要用 left join不能把 test 里没有日志记录的样本丢弃因为模型要能处理冷启动用户。二是加平滑分母的 1 不是为了数学严谨而是为了防止出现除以 0 导致的无穷大值让树模型在切分特征时更稳定。2.2 时间窗口特征行为节奏才是复购的核心信号如果说基础聚合是及格线那时间窗口特征就是把分数从 0.68 拉到 0.70 以上的关键。同一组行为次数放在不同的时间分布形态下含义完全不同。举一个很简单的例子用户 A 在店铺里总共产生 10 次购买但这 10 次全集中在一个月里之后三个月完全没有动静用户 B 同样产生了 10 次购买但每个月都有 2 到 3 次。显然用户 B 的复购概率会高很多。这就是行为节奏的重要性。我常用的做法是把行为日志按时间切成前半段和后半段分别做统计。这样模型就能看到用户对这家店的兴趣是在上升还是在衰减。例如前半段有 3 次购买、后半段有 7 次购买说明用户粘性在增强反过来则说明用户可能正在流失。还可以增加一个最近 7 天窗口专门统计临近窗口末尾时的行为。这个特征能捕捉到用户在即将进入预测期时的即时状态。一个在窗口最后一周还在频繁加购的用户和一个月前就消失的用户复购概率差异非常明显。以下是时间窗口切分的参考实现基于时间排序的百分比切分# 给每个用户-店铺内的行为按时间排序并计算时间排名比例 log[time_rank] log.groupby([user_id, seller_id])[time_stamp].rank(methodfirst, pctTrue) # 前半段行为统计 first_half log[log[time_rank] 0.5].groupby( [user_id, seller_id, action_type] ).size().unstack(fill_value0) first_half.columns [first_half_act_ str(c) for c in first_half.columns] # 后半段行为统计 second_half log[log[time_rank] 0.5].groupby( [user_id, seller_id, action_type] ).size().unstack(fill_value0) second_half.columns [second_half_act_ str(c) for c in second_half.columns] # 最近 7 天行为统计 last_7d log[log[time_stamp] log[time_stamp].max() - pd.Timedelta(days7)].groupby( [user_id, seller_id, action_type] ).size().unstack(fill_value0) last_7d.columns [last_7d_act_ str(c) for c in last_7d.columns] pair_feat pair_feat.merge(first_half.reset_index(), on[user_id, seller_id], howleft) pair_feat pair_feat.merge(second_half.reset_index(), on[user_id, seller_id], howleft) pair_feat pair_feat.merge(last_7d.reset_index(), on[user_id, seller_id], howleft) pair_feat pair_feat.fillna(0)除了窗口统计时间间隔类特征也非常值钱。我常用的有这几类首次行为距日志窗口开始的天数最后一次行为距日志窗口结束的天数首次购买和最后一次购买之间的跨度相邻两次购买之间的平均间隔、最大间隔、最小间隔购买间隔的方差用来刻画用户购买的规律性购买间隔方差是我后期调参时发现的一个很稳的特征。复购用户的购买间隔往往比较有规律方差小偶尔下单的用户则可能隔很久才来一次方差大。这个特征不需要太多计算量但对 AUC 的贡献很明显。2.3 用户级、商家级特征与交叉特征pair 特征是骨架但只靠它还不够。用户本身可能是一个高活跃用户在几百家店铺都有购买记录也可能是一个只在三四家店活动的低频用户。同样是在某家店买了 3 次对前者来说这只是九牛一毛对后者来说却是高度忠诚。因此需要把用户全局特征拉进来做参照。用户级特征我习惯单独构建一张表用 user_id 做 key统计用户在全部店铺上的行为总量包括总浏览、总收藏、总加购、总购买、总活跃天数、购买品类数等。然后把它 merge 回样本。通过用户在这个店铺的购买次数 / 用户全店铺平均购买次数这样的比值模型可以知道这个店铺在用户所有消费行为中的占比有多高。商家级特征也用同样的思路。统计每个店铺的总曝光次数、总购买次数、购买转化率、有多少个用户在这里买过。如果一家店的平均复购率本来就高那么新用户在这里产生复购的概率也会更高。这个逻辑有点类似推荐系统里的物品热度和物品质量。交叉特征方面树模型确实可以在内部自动做特征组合但提前手动构造一些高价值的交叉项往往能显著降低树的深度提升泛化能力。比较有效的几个组合包括用户在店铺的购买次数除以用户所有店铺的平均购买次数用户在店铺的购买次数除以该店铺的平均每用户购买次数用户在店铺的加购次数乘以购买转化率用户在店铺的行为天数除以用户全局活跃天数注意比率特征一定要注意稀疏数据条件下的稳定性。有些用户只在店铺里有一条浏览记录算出来的转化率要么是 0 要么是 1这种极端值对模型没有太大帮助。我一般会在特征工程阶段先统计一下特征值的分布对极端值做截断或开方处理。3. 模型选择与验证方案高分和低分往往差在评估习惯上3.1 为什么 LightGBM 是这个赛题的主力模型这个赛题的样本量级大概在几十万特征维度在几十到一两百之间属于典型的中小规模表格数据问题。在这种场景下GBDT 系列模型几乎是性价比最高的选择。我对比过 XGBoost 和 LightGBM最终以 LightGBM 为主力原因有四个第一训练速度快。同样的特征集LightGBM 比 XGBoost 能快 3 倍左右这在特征迭代频繁的比赛阶段非常重要。你一天可能要做几十次实验跑得慢意味着你一天只能验证十几次差距很快就出来了。第二LightGBM 原生支持类别特征。user_id、seller_id 这类高基数列可以直接作为 categorical_feature 传入它会自动做最优切分不需要手动 one-hot既省内存又省时间。第三对缺失值的处理很自然。GBDT 系列模型在切分特征时会把缺失值自动放到增益最大的一侧不需要你提前做复杂的填充。相比之下如果用深度模型缺失值处理就要小心得多。第四参数调优空间相对友好。核心参数就那么几个num_leaves、min_child_samples、learning_rate、feature_fraction、bagging_fraction、正则化系数。调一圈基本能稳定到一个不错的水平不太会出现玄学波动。3.2 验证集必须按时间切分不能随机切这是我在这个赛题上最深刻的教训之一也是拉开高低分差距的关键环节。很多人拿到数据之后习惯性地用 sklearn 的 train_test_split 随机切分验证集这在很多竞赛里问题不大但在复购预测这类带时间序列属性的赛题上随机切分会造成严重的数据泄漏。原因是测试集的样本在时间上比训练集更靠后而日志里的行为序列是连续的。如果你随机切分训练集中会混入时间上较晚的行为样本模型在训练时就已经见过了部分未来信息。离线验证分数虚高但线上测试分布一旦变化分数就会崩塌。正确的做法是构造一个时间上晚于训练集的验证集。最简单的方案是把样本按照某个时间属性排序取最后 20% 当验证集。比如你可以用用户最后一次购买行为的时间排序也可以用用户首次出现在日志中的时间排序。还有一种做法是把日志按时间切成两段用前一段构造特征训练用后一段构造特征验证这样能最大程度模拟真实测试环境。我当时实际采用的是按时间排序后按用户切分的方式先把所有 user_id 按照其最早行为时间排序取前 80% 的用户作为训练后 20% 的用户作为验证。这个方法虽然会损失少量样本但验证结果的可靠性比随机切分高很多。之后所有特征工程和参数调优都以这个验证集为准最后公共榜上的分数和验证集基本一致几乎没有发生过大的偏差。3.3 公共榜与私有榜复购预测里最容易被忽视的过拟合天池这类比赛通常会把测试集分成公共榜和私有榜两部分。公共榜分数是你提交后立刻能看到的私有榜则是最终排名依据。很多人会陷入一个陷阱疯狂在公共榜上刷分结果私有榜分数大幅下跌。复购预测赛题里公共榜和私有榜的样本分布如果存在细微差异就容易暴露模型的过拟合问题。最常见的情况是某些高基数的用户特征被模型直接当成了记忆工具。比如 user_id 本身有几十万个取值LightGBM 如果把它当作类别特征确实可以记住很多训练样本的标签使得公共榜分数很好看但一旦遇到没有见过的新用户 ID模型就失灵了。我的应对策略有三个第一在特征工程阶段尽量避免把原始 ID 直接加入模型。如果一定要加也要先验证它在时间切分验证集上的稳定性。我最后实际使用的特征里只保留了一部分低基数的店铺 ID 聚合特征user_id 完全没有进模型。第二做多次不同的时间切分取平均 AUC 作为模型选择的依据。如果你换了三种切分方式分数都能稳定在某个水平那这个特征或者参数才值得信任如果只有一种切分下分数高其他切分下明显下跌那就是过拟合的信号。第三伪标签技术要谨慎使用。伪标签确实可以帮助模型利用无标签的测试样本但它的前提是模型对伪标签的置信度足够高。在特征还没稳定的时候就用伪标签相当于把模型的偏见放大再喂回去最终结果往往适得其反。我建议等模型在验证集上稳定之后再在最终提交前做一轮伪标签微调。4. 高分代码的关键实现可直接复现的完整流程4.1 特征构建与样本合并的工程细节在正式进入训练之前特征工程的代码组织方式也会影响整个项目的调试效率。我建议把所有特征构建函数拆成一个个独立函数每个函数接受 log 和 sample返回一份以 user_id、seller_id 为 key 的特征表最后在统一的地方进行 merge。这样做的好处是你可以单独验证每一份特征的质量。比如某个特征列全是 0说明你的聚合逻辑写错了这时候单独调试比在几千行代码里找问题轻松得多。有一点要特别注意所有特征表的 key 必须是 user_id 和 seller_id 的组合而且两列的 dtype 要保持一致否则 merge 的时候会出现大量 NaN。我遇到过好几次因为 user_id 在一个表里是 int64另一个表里被读成了 float64导致 merge 后特征全部丢失的问题。下面给出一份整合后的特征工程代码框架包括 pair 特征、用户级特征和商家级特征def build_user_features(log): # 用户全局行为特征 u log.groupby(user_id).agg( total_browse(action_type, lambda s: (s 0).sum()), total_fav(action_type, lambda s: (s 1).sum()), total_cart(action_type, lambda s: (s 2).sum()), total_buy(action_type, lambda s: (s 3).sum()), total_act(action_type, count), active_days(time_stamp, nunique), total_item(item_id, nunique), total_cat(cat_id, nunique), ).reset_index() return u def build_seller_features(log): # 商家全局行为特征 s log.groupby(seller_id).agg( seller_buy(action_type, lambda s: (s 3).sum()), seller_browse(action_type, lambda s: (s 0).sum()), seller_paid_users(user_id, nunique), seller_items(item_id, nunique), seller_cats(cat_id, nunique), ).reset_index() s[seller_buy_rate] s[seller_buy] / (s[seller_browse] 1) return s然后统一做样本合并user_feat build_user_features(log) seller_feat build_seller_features(log) sample sample.merge(user_feat, onuser_id, howleft) sample sample.merge(seller_feat, onseller_id, howleft) # 最后合并 pair 特征 sample sample.merge(pair_feat, on[user_id, seller_id], howleft) # 填充缺失值并区分无日志用户 sample sample.fillna(0) # 定义特征列 ignore_cols [user_id, seller_id, label, is_train] feat_cols [c for c in sample.columns if c not in ignore_cols]工程化的思维在这个阶段非常重要。不要把所有代码写在一个大脚本里而是按数据读取 → pair 特征 → 用户特征 → 商家特征 → 合并 → 训练的模块划分。后期你只需要改动其中一个函数重新跑一遍主脚本就能看到效果。4.2 LightGBM 训练与参数配置模型训练部分我直接给出一个可运行的 LightGBM 训练代码。注意这里的参数是我经过多轮实验后相对稳定的配置不是绝对最优但作为起点非常合适。import lightgbm as lgb from sklearn.model_selection import train_test_split X sample[sample[is_train] 1][feat_cols].values y sample[sample[is_train] 1][label].values X_test sample[sample[is_train] 0][feat_cols].values # 演示用随机切分实际请按时间切分 X_train, X_valid, y_train, y_valid train_test_split( X, y, test_size0.2, random_state42, stratifyy ) params { objective: binary, metric: auc, learning_rate: 0.02, num_leaves: 31, min_child_samples: 100, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 1, lambda_l1: 0.1, lambda_l2: 1.0, max_bin: 255, verbose: -1, } d_train lgb.Dataset(X_train, labely_train) d_valid lgb.Dataset(X_valid, labely_valid) model lgb.train( params, d_train, num_boost_round3000, valid_sets[d_valid], valid_names[valid], callbacks[lgb.early_stopping(100), lgb.log_evaluation(50)] ) pred model.predict(X_test, num_iterationmodel.best_iteration)参数里有几个值得细说的地方。learning_rate 设成 0.02 是因为样本量不大我宁愿用更多轮次去拟合也能减少单棵树的置信度过高。num_leaves 控制在 31配合 min_child_samples100能有效防止树模型在稀疏特征上过拟合。feature_fraction 和 bagging_fraction 都设成 0.8本质上是给模型加了随机性让每棵树看到的特征和样本不完全一样增强集成效果。正则化部分lambda_l1 和 lambda_l2 分别控制 L1 和 L2 正则。复购预测这个场景下L2 正则的效果比 L1 更明显所以我把 L2 设得更大一些。如果你的特征数量特别多可以适当增加 L1让模型自动做一些特征选择。还有一个容易被忽略的细节early_stopping 的轮数。100 轮是个比较中庸的值不会太早停导致欠拟合也不会太晚停导致过拟合。如果训练集特别大可以适当缩小到 50 轮。4.3 从我自己的实验记录看 AUC 提升过程为了让整套方案更直观我贴一份当时实验记录的摘要。每一轮实验都是在同样的时间切分验证集上进行的这样前后才有可比性。实验阶段验证 AUC公共榜成绩主要新增内容1. 基线0.68120.6830行为次数 去重商品/品类特征2. 时间窗口0.70150.7038前半/后半行为统计、最近 7 天特征3. 比率与交叉0.71080.7112转化率占比、用户店铺相对购买强度4. 用户与商家全局特征0.71820.7196用户活跃度、商家热度5. 模型融合与参数微调0.72100.7221LightGBM XGBoost 结果平均从表里能明显看到第二阶段时间窗口特征的提升幅度最大直接贡献了 0.02 的 AUC。这说明对于复购预测而言什么时候买比买了几次更能刻画复购倾向。第三阶段的比率特征也有接近 0.01 的提升这部分特征帮助模型区分了高活跃但低转化的用户。第四阶段加入用户和商家全局特征之后提升幅度开始放缓但也稳定贡献了 0.007 左右。第五阶段的模型融合其实是我做的最犹豫的一个操作。LightGBM 和 XGBoost 在特征完全一致的条件下结果相关性很高融合带来的理论增益有限。但实际操作中发现XGBoost 在部分样本上的排序能力确实和 LightGBM 有差异简单做概率平均之后还是稳定提升了 0.003 左右。如果你的时间允许这种轻量级融合值得一试。5. 复现过程中最容易踩的坑5.1 时间戳精度与切分泄漏user_log 里的时间戳精度需要注意。如果时间只精确到天那么同一个用户同一天内多条行为的先后顺序其实是未知的。你在做时间窗口切分时如果按行数比例去切分可能会把同一天的行为拆到训练集和验证集两边造成轻微泄漏。我的建议是凡是涉及时间切分的操作一律先统一切到天级别再排序切分。这样至少在逻辑上保证了同一天的行为不会同时出现在两侧。更进一步在构造用户最后一天特征时要明确这个最后一天是指日志窗口的截止日期而不是用户自身行为的最后日期。另外特征工程里最好不要直接用 max(time_stamp) 这样的全局统计量去做归一化因为测试集的日志窗口可能和训练集不同全局统计量会导致训练和测试特征分布不一致。如果确实需要归一化建议以行为窗口的固定日期作为参照。5.2 高基数 ID 特征与内存优化user_id 和 seller_id 的取值数量通常可以达到几十万级别。如果你把这两个列直接作为 int64 类型处理光是这两个列的内存就可能占掉几百 MB。更高效的做法是先 cast 成 int32 或直接使用 pandas 的 category 类型再传给 LightGBM。LightGBM 支持 categorical_feature 参数可以在训练时指定哪些列需要按类别特征处理。但正如前文所说user_id 这种超高基数的列直接进模型很容易过拟合我更推荐的做法是只把 seller_id 这类数量级在一万以内的列作为类别特征user_id 则完全不进模型只保留它的聚合特征。这样既能利用类别信息的切分能力又不会让模型过度记忆样本。一个重要提醒不同数据表里同一个 ID 列 dtype 必须一致。train.csv 和 user.csv 里的 user_id 可能一个被读成 int64一个被读成 object直接 merge 会出现大量 NaN 行。建议读取数据后立刻统一做 astype(int32) 或 astype(str)避免这类低级但又非常致命的错误。5.3 冷启动用户与缺失值的处理测试集里一定会有一些用户在训练日志中完全没有出现。这些用户的行为特征是纯 0 或者 NaN模型无法判断他们的复购倾向。如果你想硬生生填 0模型可能会把它们和有行为记录但特征值为 0的用户混淆。我尝试过比较有效的方法是加一个是否出现过的标记特征。也就是在构建特征时专门计算一个 indicator 特征用户是否在日志中存在或者用户-店铺组合是否在日志中存在。这个标记特征能让模型明确区分无数据和真零值实测能带来约 0.003 的 AUC 提升。缺失值填充方面除非你有很强的领域先验否则我建议把缺失直接留给 LightGBM 处理不要自己填充。LightGBM 在训练时会自动学习缺失值的最佳方向你的填充反而可能引入噪音。我见过很多人所有特征都 fillna(0)结果把有缺失和真实为 0混为一谈模型效果反而变差。5.4 实验记录的习惯高分方案的隐形护城河最后分享一个实战里特别受用的习惯每一次跑实验都记录下特征清单、参数配置、验证集 AUC、公共榜分数和备注。我之前用一张简单的 Markdown 表格维护实验日志长这样时间特征版本参数版本验证 AUC公共榜备注10-15v2lgb_0210.70150.7038加了时间窗口10-15v2lgb_0220.70090.7012调低 feature_fraction10-16v3lgb_0220.71080.7112加了转化率比例这个习惯在比赛后期价值极大。当你有几十个特征版本、十几种参数配置时如果没有记录很容易忘记哪个版本的有效结果来自哪里最终提交时全靠猜。反过来如果记录完整你能快速定位到最优组合也能在私有榜翻车时快速回溯到更早的稳定版本。我在这个赛题上的最终成绩不是靠某一个独门技巧而是靠一整轮的实验闭环先把数据粒度搞清楚再按业务直觉构造特征然后用可靠的时间切分验证最后用完整的实验记录收束整个过程。复购预测这类问题真正拉开差距的从来不是某个灵光一现的想法而是这些看起来琐碎但环环相扣的工程细节。如果你照着上面的代码和流程完整跑一遍大概率也能稳定拿到一个不错的分数。本文还有配套的精品资源点击获取
返回列表