M5销量预测Baseline:基于LightGBM的时序建模与特征工程实战

发布时间:2026/9/15 17:53:14

M5销量预测Baseline:基于LightGBM的时序建模与特征工程实战 做销量预测绕不开 Kaggle 上的 M5 Forecasting - Accuracy。这个比赛是 2020 年结束的老竞赛但放到现在依然是入门时序建模最靠谱的教材之一尤其适合那些想把“会调库”升级成“能独立建模”的人。它要解决的问题很直接用沃尔玛 10 家门店里 30490 条商品-门店序列的历史日销数据预测未来 28 天。难的不是套一个模型而是数据规模、层级聚合、评价指标和提交细节都往真实工业场景上靠。我见过不少新手一上来就上深度模型卡在数据预处理和特征构造里好几天最后实分数反而不如一个干净的 baseline。我自己的经验是先花一晚上把 baseline 跑通把整个流程理顺后面无论换模型还是加特征都有了一个可以对照的坐标系。下面就把我整理出来的 M5 baseline 思路完整写一遍包含数据处理、特征构造、LightGBM 训练、时序预测和提交文件组装也会把我在实际复现里踩过的坑一起交代清楚。1. 一场比“预测”更考验工程能力的竞赛1.1 M5 到底让你做什么M5 是 Kaggle 上沃尔玛出的销量预测赛全称是 M5 Forecasting - Accuracy。数据的时间范围从 2011 年 1 月 29 日开始训练集一共有 1913 天的日粒度销售记录你需要预测紧接着的 28 天。竞赛分两个阶段第一阶段预测 2016-04-25 到 2016-05-22 这 28 天第二阶段再往后推 28 天每一阶段都是独立评分。很多初学者以为这是“给你一个表用模型预测一列数”但实际不是。M5 的核心难点在于序列数量极大底层维度是 30490 条独立的产品-门店组合序列而且这些序列之间不是平级关系它们可以汇总到部门、类别、门店、州等多个层级。另一个容易忽略的点是这里的数据不是严格平稳的。日销量有很强的周季节性周末和节假日的起伏明显同时大量商品属于“慢动销”甚至接近零销很多序列里有很长一段时间的连续零值。所以你会发现单纯套一个 ARIMA 或者 LSTM效果不一定比一个做了滞后特征和滚动统计的树模型好。这也是我为什么建议从 LGBM 这类梯度提升树模型开始它对表格型时间特征的处理能力很强迭代速度也快。对求职的人来说做这个比赛还有一个额外价值M5 的评分方法 WRMSSE 是带层级权重的你在简历上写“熟悉层次时间序列预测、用加权指标评估模型”会非常加分。这也是为什么到现在仍有不少人拿 M5 当面试项目拆解的原因。1.2 评价指标RMSSE 和 WRMSSEM5 并不是简单用 MAE 或 RMSE 评分它用的是 RMSSE全称是 Root Mean Squared Scaled Error中文可以叫“均方根比例误差”。这个指标的思路是把你的预测误差和“季节性朴素预测”的误差作比较。所谓季节性朴素预测就是直接用 7 天前的值当作今天的预测因为日销数据有明显的周周期性。如果你的 RMSSE 小于 1说明你的模型比“照抄上周”要强如果大于 1那还不如直接抄上周。WRMSSE 是多了一个权重项的 RMSSE。权重来自每个层级里在训练集最后一天所有商品销售额的占比。这套设计的意义在于销量大的商品、销量大的门店和部门在最终分数里占的比重大预测它们更值钱。很多人在做 baseline 时只顾着把底层 30490 条序列都预测出来却忘了越往上聚合误差会被累计放大。所以只要底层误差分布不均高权重序列一旦预测偏了分数会非常难看。理解了这一点你就不难明白为什么官方给出的分数计算里你提交的文件只有 30490 行底层序列但最终评分却会向层级结构上做加权聚合。2. 先把数据摊开看清五张表2.1 数据表之间的关联M5 的原始数据不是一张大宽表而是几张关系表拼起来的。核心表是 sales_train_validation.csv每行代表一个商品-门店组合前面几列存的是 item_id、dept_id、cat_id、store_id 这些元数据后面跟着 d_1 到 d_1913 共 1913 天的销量。看到这种结构第一个念头应该是把它转成长表因为机器学习模型需要“日期ID特征目标”这样的格式。calendar.csv 是日历表记录每一天对应的 wm_yr_wk 周编号、星期几、月份、事件名、事件类型以及三个州的 SNAP 补贴标记。sell_prices.csv 是每一周每个商品在每家门店的售价注意它是周粒度价格不是每天的。这种“价格变化频率低于销量频率”的情况很常见你合并的时候要用 wm_yr_wk 去关联不能用日期直接关联。这三张表的关系是sales_train_validation 里的每一天 d_n对应 calendar 表里 d_n 那一行的日期和周编号而 sales 表和 prices 表之间则要通过 item_id、store_id、wm_yr_wk 三个字段关联。我一开始曾经直接拿日期去左连接价格表结果发现价格全是空值后来才反应过来价格表没有 date 字段只有周编号。这是一个很容易卡住的坑也最能体现数据工程的基本功。2.2 层级结构对建模策略的影响M5 的层级结构从上到下大概是全部门店总销量、州、门店、部门、类别最后落到商品编号。你可以随时把底层序列向上求和得到上层的总量序列。这种层次关系直接决定了你该用什么策略建模因为底层序列预测完毕后直接向上加总得到的层次预测往往不是最优的最后可能需要做层次调和。不过做 baseline 的时候我建议先不用管调和老老实实预测底层 30490 条序列。先把提交文件跑通再去考虑“底层预测完了之后要不要根据上层约束修正一下”这件事。理由很简单层次调和是一个独立的优化方向如果底层模型效果已经不稳调和再多也白搭。baseline 的意义是把整个 pipeline 立住给后面所有迭代提供一个稳定的起点。3. baseline 为什么从 LGBM 开始3.1 先用最笨的办法摸底线很多人打开比赛就直接建模这是错误的第一步。拿到数据后应该先做一个“不用任何机器学习”的基准预测目的有两个第一验证自己对数据格式、提交格式的理解是否正确第二给后面所有复杂模型一个参照系。最简单的做法是取每一条序列在训练集最后 7 天的平均值作为未来 28 天每一天的预测值。更朴素一点直接用 d_1913 当天的销量复制 28 天。这样提交一次的分数大概在 RMSSE 1.0 附近因为相当于在用季节性朴素误差的尺度做预测稍有改进就能低于 1。你在做任何花哨特征之前先跑通这个提交确保自己的文件不会因为格式问题得零分。格式问题在 Kaggle 上非常常见尤其是 F1 到 F28 的列顺序、id 的顺序稍有不慎就会让整个文件被判成无效提交。等这个“笨办法”能稳定出分了再上 LGBM。你会发现LGBM 只要加上滞后 7 天、滞后 28 天和最近 7 天均值这几个基础特征WRMSSE 就能降到 0.7 上下效果立竿见影。原因很简单日销数据中最近一周的销量水平、去年同期或上月同期的水平几乎是信息量最高的信号树模型只要能看到这些数字就能自己找出周季节性的规律。3.2 LGBM baseline 的整体思路LGBM 在这个场景下的优势有三个一是能够直接处理高维稀疏类别变量比如商品 ID、门店 ID二是对特征的非线性组合建模能力强三是训练速度快方便快速试错。M5 的数据量不算小如果拿深度学习模型来跑 30490 条序列一台普通的显卡机器可能要跑很久而 LGBM 在 CPU 上就能在几分钟内完成一轮训练。我的 baseline 设计成单模型递归预测先构造一个“长表”每行是一个特定日期、特定序列的销量样本特征是这一天的滞后销量、滚动统计量、日历特征、商品价格等然后用历史数据训练一个回归模型最后在预测阶段每预测完一天就把结果写回历史窗口更新滞后和滚动特征再预测下一天。这样连续预测 28 天。整个过程看着简单但里面有两个关键点一是验证集划分二是避免数据泄漏。这两点做不好分数会给你虚假的乐观或者直接的难堪。4. 实操特征构造与模型训练4.1 从宽表到特征工程先不要直接对原始宽表做 shift那样是按行做操作每个商品序列是同一行滞后特征根本做不出来。我习惯先把表转置让时间轴变成行商品 ID 变成列再做 shift 和 rolling。这样实际上是用整个矩阵的向量化操作比在长表里 groupby 再逐组计算要快非常多。import pandas as pd import numpy as np import lightgbm as lgb sales pd.read_csv(../input/m5-forecasting-accuracy/sales_train_validation.csv) calendar pd.read_csv(../input/m5-forecasting-accuracy/calendar.csv) prices pd.read_csv(../input/m5-forecasting-accuracy/sell_prices.csv) # 保留元数据 meta sales[[id, item_id, dept_id, cat_id, store_id, state_id]] # 只取销量列转置成 时间×商品 wide sales.set_index(id).loc[:, d_1:d_1913] wide_t wide.T # 行是 d_1~d_1913列是 id # 在时间方向上生成特征 wide_t[lag_7] wide_t.shift(7) wide_t[lag_28] wide_t.shift(28) wide_t[roll_mean_7] wide_t.rolling(7).mean() wide_t[roll_std_7] wide_t.rolling(7).std() wide_t[roll_mean_28] wide_t.rolling(28).mean() # 转成长表 wide_t[d] wide_t.index long wide_t.melt(id_varsd, var_nameid, value_namesales) long long.merge(meta, onid, howleft)转成长表之后再合并日历和价格。注意价格要先用日历表把日期映射到 wm_yr_wk再和 sell_prices 关联。一个容易忽略的细节是日历表中的事件列不全是有值的直接填充为“无事件”即可但在建模时如果把它当成普通类别变量树模型会自己处理这个信息所以不用太担心缺失值。cal calendar[[d, wm_yr_wk, wday, month, year, event_type_1, snap_CA, snap_TX, snap_WI]] long long.merge(cal, ond, howleft) price_map prices.merge(calendar[[d, wm_yr_wk]], onwm_yr_wk, howleft) long long.merge(price_map, on[store_id, item_id, d], howleft)4.2 划分验证集与训练时间序列建模最忌讳的验证方式就是随机切分。这里要把最后 28 天留出来做验证模型只训练 d_1 到 d_1885 这部分验证集是 d_1886 到 d_1913。这样切和比赛真正要做的预测任务保持一致分数才有参考价值。train long[long[d] d_1885].dropna() valid long[(long[d] d_1886) (long[d] d_1913)].dropna() feature_cols [lag_7, lag_28, roll_mean_7, roll_std_7, roll_mean_28, item_id, dept_id, cat_id, store_id, state_id, wday, month, year, event_type_1, snap_CA, snap_TX, snap_WI, sell_price] X_train, y_train train[feature_cols].copy(), np.log1p(train[sales].values) X_valid, y_valid valid[feature_cols].copy(), valid[sales].values我在这里把目标变量做了 log1p 变换原因是销量数据长尾严重绝大多数是 0 或者很小值少数爆款商品数值能到几百上千。直接回归原始销量模型会被少数大数值带偏log1p 之后再回归模型会更关注相对误差。这个做法在 WRMSSE 指标下尤其重要因为该指标本身就是比例式误差定义。预测出来之后再用 expm1 还原最后把负数裁剪成 0。LightGBM 的参数不用一上来就调优先给一组合理的默认偏强参数。num_leaves 设 128 在数据量大的时候不会太差learning_rate 0.05 配合 300 到 500 棵树训练成本也不高。关键是 feature_fraction 和 bagging_fraction 都留一点随机性防止树模型在大量类别特征上过拟合。params { objective: regression, metric: rmse, learning_rate: 0.05, num_leaves: 128, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 1, verbosity: -1, seed: 42, } d_train lgb.Dataset(X_train, y_train) model lgb.train(params, d_train, num_boost_round400) pred_valid np.expm1(model.predict(X_valid)) pred_valid np.clip(pred_valid, 0, None)验证集上可以自己算一版 RMSSE不需要等官方评分的最终分。这个自算分数的意义在于快速反馈每次加特征或改参数都能在几秒钟内知道是变好还是变差不需要反复提交。官方每天提交次数有限如果你每次都靠提交看分数既浪费时间也容易过拟合公共榜。4.3 生成 28 天预测并组装提交文件预测阶段稍微特殊一点。因为在预测第一天 F1 的时候滞后 7 天还可以直接用真实历史值但到了预测第 8 天滞后 7 天对应的 F1 已经是你自己预测出来的值了。所以必须做“递归预测”也就是把每一条序列的预测值当成历史逐日更新滞后和滚动特征再预测下一天。实际操作里我会维护一个形状为 (28, 30490) 的窗口初始值用训练集最后 28 天填充。每预测完一天就把整列预测值写回窗口末尾同时把窗口往前滚一天。这样无论滞后 7 还是滚动均值 7下一次计算时使用的都是最近的真实值或模型预测值。# 窗口初始化为训练集最后28天 window wide_t.loc[d_1886:d_1913].values # 未来28天对应的日历特征需要提前准备 future_cal calendar[calendar[d].isin([fd_{i} for i in range(1914, 1942)])] preds np.zeros((28, wide.shape[0])) for h in range(28): feats np.column_stack([ window[-7], # lag_7 window[-28], # lag_28 window[-7:].mean(axis0), # roll_mean_7 window[-7:].std(axis0), # roll_std_7 window[-28:].mean(axis0), # roll_mean_28 np.tile(meta[item_id].values, (1, 1)).flatten(), # 这里还需要把类别特征、日历特征、价格特征按相同顺序拼进去 ]) f future_cal.iloc[h] # 实际上需要构造完整 feature_cols上面只展示了核心数值特征 pred np.expm1(model.predict(feats_complete)) pred np.clip(pred, 0, None) preds[h] pred window np.roll(window, -1, axis0) window[-1] pred上面代码里我为了可读性省略掉了一部分特征拼接实际编码时一定要保证预测阶段的特征列顺序和训练阶段完全一致否则模型预测结果会完全错乱。组装提交文件时直接把 sample_submission.csv 里的 id 列和 F1 到 F28 列替换掉就行。需要注意 sample_submission 的 id 顺序和 sales_train_validation.csv 是一致的所以你不会遇到行匹配的问题只要按行写入即可。sub pd.read_csv(../input/m5-forecasting-accuracy/sample_submission.csv) for h in range(28): sub[fF{h1}] preds[h] sub.to_csv(m5_lgb_baseline.csv, indexFalse)到这里一个完整的 baseline 就结束了。第一次跑这个流程效率高的大概两三个小时就能出分后面每一次调整都只要改一小段代码就能重新提交。5. 你会踩到的坑和真正的提分点5.1 三条血泪经验这个 baseline 我复现过好几次每次都会在不同位置卡一下。最常遇到的问题有三个我单独列出来给你当排查手册。第一是数据泄漏。不要在特征里用到验证集之后的信息更不要在用全部训练数据训练完之后又用同一份数据去“验证”模型。我见过有人把价格表的未来价格也合并进来了价格是在训练期之后才发布的信息这会直接让你的验证分数虚高。判断准则很简单构造任何一个特征时都问一句当前这个日期下我是否能合法知道这个值。第二是长表 groupby 太慢。如果把原始宽表转成“每一日 30490 行的长表”再对每个 id 做 shift 和 rolling 操作慢到会让你怀疑人生。正确做法是先转置到宽表在日期轴上做矩阵的向量化操作最后再一次熔成长表。这一步可以节省掉大量时间。第三是零销量和 log1p 还原问题。log1p 模型输出的均值在还原之后会和真实均值有偏差尤其是在预测那些长期零销量的序列时模型输出一个很小的数expm1 之后可能是 0.1再被 clip 成 0倒没什么问题但遇到一个序列本来就是偶发一次性卖 100 件的情况模型会把均值压得很低导致还原后严重偏低。对于 baseline 来说这不是致命问题但如果你后面想做精细化优化可以考虑两阶段模型先分类预测“是否售出”再对“售出”的序列回归具体销量。5.2 从 baseline 往上走的几个方向baseline 跑通之后很多人会急着换模型但我更建议先照着下面这几个方向挨个试一遍每改一个就记一次分数因为最后很可能不是某一个点让你大幅提升而是几个改进叠加起来的效果。第一层次调和。前面说过直接预测底层序列然后向上求和通常不是最优的。比较轻量级的做法是预测完成后再计算底层预测求和与真实历史总量的偏差再把偏差按权重分摊回底层。M5 的前排方案里几乎所有人都做了某种形式的调和或加权。第二特征扩展。当前 baseline 只用了滞后和滚动统计还不够。可以考虑价格相对上期涨跌幅、节假日和 SNAP 补贴的临近天数、商品 ID 的类别编码、按部门或门店聚合的销量均值等这些特征都能帮树模型理解序列之间的共性和差异。第三模型集成。LGBM 之外再训练一个 XGBoost 或者 CatBoost对两个模型的预测结果做简单加权平均通常能稳定降低误差。本质上是因为不同树的归纳偏置不同平均掉一部分方差之后预测会更稳健。第四训练策略。可以尝试多输出模型也就是一次预测 28 天而不是递归式地一天一天预测。这样虽然模型复杂度高一点但避免了误差累积。另外也可以多切几个验证期比如把最后 56 天分成两个 28 天看看模型在不同阶段的稳定性避免一次调参只对某一个验证窗口有效。我个人实际跑这个比赛最大的体会是baseline 的价值不在于分数有多高而在于它把一个复杂问题拆解成“数据管理、特征工程、模型训练、结果组装”四个清晰环节你之后所有尝试都可以在一个稳定流程上快速试错。如果你刚接触 Kaggle想拿一个比赛完整练手M5 的销量预测是少有的数据真实、评价指标讲究、可挖掘空间又大的题目。这套流程走完再去碰其他时间序列比赛或者工作里的销量项目你会发现自己已经不再是刚拿到数据就摸不着北的状态了。
延伸阅读

更多相关文章

2026/9/15 17:53:14

YOLOv8实例分割实战指南:从数据标注到模型部署全流程解析

1. 实例分割项目概述与环境认知做YOLOv8实例分割训练,和做目标检测完全是两个世界的玩法。检测任务只要给目标画个框,告诉模型“这个区域有东西”就结束了;实例分割要把每个目标的轮廓精准抠出来,模型除了知道“哪里有什么”&…

2026/9/15 17:48:13

HTML打包成EXE:Electron免安装便携版实战指南

我先说一下我的结论:把 HTML 打包成 EXE 这件事,做前端的人和做内部工具的人迟早都会遇到。需求说起来特别朴素——我手上有一个做好的 HTML 网页,可能是单文件,也可能带 js/css 资源,想把它发给我妈、发给同事、发给客…

2026/9/15 17:48:13

从源码解析企业网盘设计:Java开源文档协作平台拆解

简介:基于Java的开源文档管理平台/企业网盘设计源码,对应瀚为云文档协作平台,面向需要搭建内部文档库的企业、团队以及希望学习企业级Java项目结构的开发者。平台支持企业文件与个人文件分库管理,提供收藏夹、最近打开、回收站等分…

2026/9/15 18:03:20

硬RAID崩盘抢救指南:从PERC故障到软RAID重建实战

1. 这不是故障报告,是一份用血泪写成的数据抢救手记十年前我亲手把那台戴尔PowerEdge R710推进机柜时,它锃亮的银灰色机箱在机房灯光下泛着冷光,双路X5650处理器、32GB ECC内存、6块300GB 15K SAS盘组成的PERC H700硬RAID 10阵列——当时看着…

2026/9/15 17:58:17

承压含水层二维渗漏流的MATLAB有限差分模拟与迭代求解

简介:二维渗漏承压含水层流动方程的数值求解是地下水动力学教学与科研中的常见问题,这份资源基于MATLAB 2019a实现,采用有限差分法(FDM)结合高斯-赛德尔迭代解算器,对描述承压含水层渗漏的泊松方程进行离散…

2026/9/15 4:54:30

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/15 0:01:16

AI英语单词APP开发:自适应学习算法与移动端优化实践

1. 项目概述 作为一名在移动应用开发领域摸爬滚打多年的老手,我最近完成了一个AI英语单词APP的开发项目。这个项目将传统单词记忆方法与现代AI技术相结合,打造了一款能够智能适应不同用户学习习惯的英语学习工具。 市面上大多数单词APP都存在一个通病&a…

2026/9/15 0:01:16

Flutter与OpenHarmony结合开发手语学习APP实战

1. 项目背景与核心价值作为一名同时接触过Flutter和OpenHarmony的开发者,最近我完成了一个基于Flutter for OpenHarmony的手语学习APP实战项目。这个项目最大的特点在于实现了跨平台框架与国产操作系统深度结合的创新实践——用Flutter开发的应用能完美运行在OpenHa…

2026/9/15 0:01:16

六个月成为机器人工程师:从ROS2到SLAM的实战路径

1. 六个月的紧迫感从哪来:先搞清楚你要成为哪种机器人工程师说实话,六个月的期限并不是一个宽松的时间线。市面上任何一本正经的机器人学教材都超过五百页,ROS2的官方文档可以翻到你怀疑人生,再加上ABB、KUKA这些工业机器人厂家动…

2026/9/15 14:22:53

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/14 13:53:59

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/15 11:42:23

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码