资讯动态

阿里移动推荐算法竞赛实战:从数据切分到LightGBM排序的完整流程

发布时间:2026/10/22 4:37:27 来源:尧图企业网站定制
简介阿里移动推荐算法竞赛.zip 是一份面向人工智能、计算机科学与技术等相关专业学生与算法爱好者的完整竞赛项目源码适合用作毕业设计课题、课程作业或推荐系统入门实战参考。资源包共118个文件约636KB以33个csv数据文件、25个Python脚本、21个C#源码及6个C文件为主另含少量头文件、批处理脚本与工程配置覆盖数据预处理、模型训练与样本扩展等环节。压缩包内提供处理训练样本、处理测试样本、扩展样本等批处理入口并包含BP神经网络相关的C实现与配套工程文件便于读者理解推荐算法从数据到模型的完整链路。目前已有226人浏览学习下载后可先查阅README.md了解目录结构与运行方式源码均经测试验证可正常运行遇到问题可通过私信或留言与作者沟通仅限交流学习参考请勿用于商业用途。1. 移动推荐赛题到底在考什么从一份离线数据集说起移动端推荐和 PC 端推荐最大的差别不在模型结构而在数据形态。用户拿着手机行为是碎片化的点开、滑走、加购、收藏、下单每一步都带着时间戳和位置信息而且大量行为发生在极短的会话窗口里。阿里移动推荐算法竞赛给出的就是这样一个典型场景——用某电商平台移动端 4 天的用户行为日志预测未来一天内哪些用户会对哪些商品产生购买行为。它解决的不是“推荐什么内容”而是“在稀疏、短周期、强时序的移动行为流里怎么把转化信号捞出来”。适合谁适合已经会跑协同过滤、想第一次认真处理时序特征和负采样的人也适合做特征工程练手的从业者。这个赛题的价值在于数据规模可控评价指标明确F1但坑足够多能逼你把召回、排序、样本构造这条链路走一遍。2. 数据切分与特征构造把 4 天日志变成可训练的样本2.1 先搞清楚时间窗口和标签定义赛题数据通常包含用户行为表user_id、item_id、behavior_type、time和商品表、用户表。behavior_type 一般有四种浏览、收藏、加购、购买。预测目标是在最后一天比如 12 月 19 日有购买行为的用户-商品对。训练集用前 3 天12 月 18 日之前构造验证集用第 4 天。这里第一个容易翻车的地方是很多人直接把“最后一天购买”当正样本前面所有交互当特征结果线下 F1 很高线上崩掉。原因是时间穿越——你用 12 月 18 日之后的行为去预测 12 月 18 日之前的购买特征里混入了未来信息。正确做法是以某一天为切分点切分点之前的行为做特征切分点当天的购买做标签。import pandas as pd # 假设 df 是行为日志列user_id, item_id, behavior_type, time # time 格式为 2014-12-18 10:23:45 df[date] pd.to_datetime(df[time]).dt.date # 切分点用 12-17 及之前做特征12-18 的购买做标签 split_date pd.to_datetime(2014-12-18).date() train_feat df[df[date] split_date] train_label df[(df[date] split_date) (df[behavior_type] 4)] # 构造正样本对 pos_pairs train_label[[user_id, item_id]].drop_duplicates() pos_pairs[label] 1逻辑说明split_date是人为设定的时间边界必须保证特征窗口严格早于标签窗口。behavior_type 4代表购买。参数上切分点不是固定的你可以用 12-16 做特征、12-17 做标签多切几组做交叉验证避免单次切分带来的偶然性。2.2 负采样别让模型只学会猜“不买”正样本只有几千到几万条负样本如果从全量用户-商品对里随机抽比例会到 1:1000 以上模型直接躺平预测全负。常见做法是对每个正样本抽 3 到 10 个负样本负样本从“当天有交互但没购买”的 pair 里选而不是从全量里随机选。这样负样本更接近真实分布。import numpy as np # 当天有交互但未购买的用户-商品对 interact df[df[date] split_date][[user_id, item_id]].drop_duplicates() neg_candidates interact.merge(pos_pairs[[user_id, item_id]], on[user_id, item_id], howleft, indicatorTrue) neg_candidates neg_candidates[neg_candidates[_merge] left_only][[user_id, item_id]] # 每个正样本配 5 个负样本 neg_sample neg_candidates.sample(nmin(len(pos_pairs) * 5, len(neg_candidates)), random_state42) neg_sample[label] 0 train_data pd.concat([pos_pairs, neg_sample], ignore_indexTrue)逻辑说明indicatorTrue用来标记哪些 pair 在正样本里出现过left_only就是没购买的。nlen(pos_pairs) * 5控制负正比5 是一个经验值太高会让训练变慢且正样本被淹没太低会让模型过于激进。random_state固定保证可复现。2.3 四类行为的时间衰减特征移动端行为的时间衰减比 PC 端更剧烈——用户上午浏览的商品下午可能就忘了。所以特征里必须体现“最近一次交互距切分点有多久”。常见做法是对每种行为分别算最近一次时间差、行为次数、行为在最近 1 天/3 天/7 天的计数。# 以浏览行为为例计算用户-商品对的特征 def build_features(df, split_date): feat df[df[date] split_date].copy() feat[days_before] (pd.to_datetime(split_date) - pd.to_datetime(feat[date])).dt.days # 最近一次交互时间差 last_interact feat.groupby([user_id, item_id])[days_before].min().reset_index() last_interact.columns [user_id, item_id, last_days] # 各行为计数 behavior_count feat.pivot_table(index[user_id, item_id], columnsbehavior_type, valuestime, aggfunccount).fillna(0).reset_index() behavior_count.columns [user_id, item_id, view_cnt, fav_cnt, cart_cnt, buy_cnt] return last_interact.merge(behavior_count, on[user_id, item_id], howouter).fillna(0)逻辑说明days_before越小说明交互越近last_days就是最近一次交互距切分点的天数。pivot_table把行为类型展开成列fillna(0)处理没有某类行为的 pair。参数上days_before用天数而不是小时是因为赛题数据跨度只有几天小时级噪声太大。3. 召回与排序的两段式落地从规则召回走到 LightGBM 排序3.1 召回层用规则先圈出候选集全量用户-商品对是千万级直接排序不现实。召回层的作用是把候选集压到几万条。移动推荐赛题里常见召回策略有三类热销商品召回、用户近期交互过的商品召回、同品类商品召回。热销召回负责覆盖新用户近期交互召回负责捕捉短期兴趣品类召回负责扩展。# 热销召回切分点前 1 天购买量 top 500 的商品 hot_items df[(df[date] split_date) (df[behavior_type] 4)] \ .groupby(item_id).size().sort_values(ascendingFalse).head(500).index.tolist() # 用户近期交互召回切分点前 1 天有浏览/加购的用户-商品对 recent_interact df[(df[date] split_date - pd.Timedelta(days1)) (df[behavior_type].isin([1, 3]))][[user_id, item_id]].drop_duplicates() # 合并候选集 candidates pd.concat([ recent_interact, pd.DataFrame({user_id: recent_interact[user_id].unique().tolist() * len(hot_items), item_id: hot_items * recent_interact[user_id].nunique()}) ]).drop_duplicates()逻辑说明hot_items取购买量 top 500数量根据内存调整500 到 2000 都常见。recent_interact用isin([1, 3])筛浏览和加购购买行为本身已经算正样本不放进召回。最后的笛卡尔积构造是为了让每个活跃用户都能看到热销商品但要注意内存用户数乘商品数可能爆炸实际中会按用户分块处理。3.2 排序层LightGBM 的二分类与参数设置召回后的候选集配上特征送进 LightGBM 做二分类。为什么选 LightGBM 而不是深度学习因为赛题数据量在百万级特征维度几十到几百树模型在稀疏特征和类别特征上更稳训练快调参直观。import lightgbm as lgb from sklearn.model_selection import train_test_split # 假设 train_data 已经 merge 了特征 X train_data.drop([user_id, item_id, label], axis1) y train_data[label] X_train, X_val, y_train, y_val train_test_split(X, y, test_size0.2, random_state42) params { objective: binary, metric: auc, learning_rate: 0.05, num_leaves: 64, max_depth: 8, min_data_in_leaf: 50, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 5, verbose: -1 } dtrain lgb.Dataset(X_train, labely_train) dval lgb.Dataset(X_val, labely_val, referencedtrain) model lgb.train(params, dtrain, num_boost_round500, valid_sets[dval], callbacks[lgb.early_stopping(50), lgb.log_evaluation(100)])逻辑说明num_leaves64和max_depth8控制树复杂度移动推荐数据噪声大树太深容易过拟合。min_data_in_leaf50防止叶子节点样本太少。early_stopping(50)表示验证集 AUC 50 轮不提升就停。learning_rate0.05配合 500 轮是常见组合想更快收敛可以调到 0.1但容易震荡。3.3 阈值选择F1 不是固定 0.5赛题评价指标是 F1而 F1 对阈值敏感。模型输出的是概率0.5 只是默认切分实际要在验证集上扫一遍阈值找 F1 最大的点。from sklearn.metrics import f1_score y_pred_prob model.predict(X_val) best_f1, best_thresh 0, 0.5 for thresh in np.arange(0.1, 0.9, 0.02): y_pred (y_pred_prob thresh).astype(int) f1 f1_score(y_val, y_pred) if f1 best_f1: best_f1, best_thresh f1, thresh print(fbest threshold: {best_thresh:.2f}, best F1: {best_f1:.4f})逻辑说明np.arange(0.1, 0.9, 0.02)是阈值扫描范围步长 0.02 兼顾精度和速度。best_thresh要记下来线上预测时用同一个阈值。注意如果验证集和测试集分布差异大阈值会漂移所以切分点最好多试几组取平均。4. 避坑与排查移动推荐赛题里最容易翻车的 5 个点4.1 现象线下 F1 0.3线上只有 0.05原因时间穿越。特征里用了标签窗口之后的行为比如用 12-18 的浏览去预测 12-18 的购买。解决严格按时间切分特征窗口的右边界必须小于标签窗口的左边界。检查方法把特征表按时间排序看最大时间戳是否小于标签最小时间戳。4.2 现象模型 AUC 0.9 但 F1 只有 0.1原因正负样本极度不平衡模型学会了把所有样本预测为负。解决负采样控制比例或者用scale_pos_weight参数给正样本加权。LightGBM 里设scale_pos_weight 负样本数 / 正样本数但不要超过 100否则梯度爆炸。4.3 现象召回候选集里正样本覆盖率不到 30%原因召回策略太窄只用了热销或只用了近期交互。解决多路召回合并热销、近期交互、同品类、同用户历史购买品类各出一路最后去重。覆盖率要盯住低于 50% 后面排序再强也白搭。4.4 现象LightGBM 训练到 100 轮就开始过拟合原因特征里有很多高基数类别特征比如 item_id 直接当数值树模型会记住特定 item。解决item_id 和 user_id 不要直接进模型改成统计特征该 item 的点击率、该用户的购买率。如果一定要用做 target encoding 并加平滑。4.5 现象验证集 F1 波动超过 0.05原因单次时间切分偶然性太大某一天的数据分布可能异常。解决做多组切分比如用 12-16 预测 12-17、12-17 预测 12-18取平均 F1。如果波动还是大检查是不是某天的促销活动导致行为突变这种日子要么剔除要么单独建模。5. 进阶技巧用滑动窗口和多模型融合把 F1 再抬一截走到这里单模型 F1 大概能到 0.08 到 0.12 之间赛题数据不同会有差异。想再往上两个方向最实在滑动窗口构造更多训练样本以及多模型融合。滑动窗口的思路是不只用“前 3 天预测第 4 天”这一组而是构造多组。比如用 12-15 到 12-16 预测 12-17用 12-16 到 12-17 预测 12-18。每组都生成正负样本合并后训练。这样训练数据翻几倍模型见过更多时间模式。代价是特征计算量线性增长但移动推荐赛题数据量不大值得做。# 滑动窗口构造多组训练样本 windows [ (2014-12-15, 2014-12-16, 2014-12-17), (2014-12-16, 2014-12-17, 2014-12-18), ] all_train [] for feat_start, feat_end, label_date in windows: feat df[(df[date] pd.to_datetime(feat_start).date()) (df[date] pd.to_datetime(feat_end).date())] label df[(df[date] pd.to_datetime(label_date).date()) (df[behavior_type] 4)] # 这里复用前面的 build_features 和负采样逻辑 # 注意每个窗口的特征计算要独立不能跨窗口混 window_data build_window_data(feat, label) all_train.append(window_data) final_train pd.concat(all_train, ignore_indexTrue)逻辑说明windows列表里每个元组是特征起始日、特征结束日、标签日。关键点是每个窗口独立算特征因为“最近一次交互距切分点”这个特征在不同窗口里基准日不同。build_window_data是封装好的函数内部做特征构造和负采样。合并后final_train的样本量大概是单窗口的 2 到 3 倍。多模型融合方面LightGBM 和 XGBoost 各训一个概率取平均。如果还想再进一步加一个简单的逻辑回归做 stacking但收益通常不如滑动窗口明显。我自己的习惯是先把滑动窗口跑通F1 稳定提升后再考虑融合否则调参空间太大容易迷失。最后一个技巧关于阈值滑动窗口训练出的模型验证集要用最后一个窗口的标签日不要用所有窗口的平均 F1 选阈值。因为线上预测的是未来一天和最后一个窗口的分布最接近。这个细节很多人忽略导致线下线上阈值不一致F1 掉一截。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑