ARTICLE DETAIL

资讯详情

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

基于深度学习与TCN的京东商品销量预测:从数据管道到补货决策

基于深度学习与TCN的京东商品销量预测:从数据管道到补货决策 简介一套基于深度学习的京东商品销量预测完整源码面向电商数据分析人员、机器学习开发者及参赛者可用于学习销量预测建模、数据清洗、特征生成与模型评估全流程。包体共51个文件、约6.96MB以Python源码、SQL脚本、CSV数据与PNG可视化图片为主另含YAML配置、TXT说明与Git忽略文件Python模块覆盖RNN、GBDT等多种模型训练与预测SQL脚本负责建表及用户、商品、行为信号的生成CSV存储数据集与提交结果PNG保存ROC曲线、损失变化等评估图表便于直观理解建模效果。已有342人学习下载适合具备Python和机器学习基础、希望参与电商销量预测赛题或落地实际项目的开发者。通过该源码可掌握时间序列建模思路、多文件工程组织方式与特征工程细节节省从零搭建项目的时间是一份完整的工程级参考。1. 基于深度学习的京东商品销量预测这个标题背后的真实工程量做电商供应链或商品运营的人对“销量预测”的感受通常很复杂。基于深度学习的京东商品销量预测设计源码听起来是给一个神经网络喂历史销量、让模型预测明天卖多少件真正落地才发现它把订单聚合、促销事件、商品生命周期和补货决策全部串在了一起。京东这类平台 SKU 基数大、大促节奏密集历史序列又长又碎深度学习模型在这里能发挥的空间不在单条序列的拟合精度而在对促销脉冲和周期模式的自动提取能力上。很多团队早期用 XGBoost 加手工特征做到 85% 准确率一到 618 和双 11 就被重挫转投 LSTM、TCN 这类深度学习算法不是因为它更高级而是手工特征工程实在维护不动了。这套标题里最容易被低估的是“设计源码”四个字。算法模型只是其中一层真正能跑进供应链系统的源码至少要覆盖数据管道、训练样本构造、模型选型、评估回测和预测结果落库电商销量预测与图像分类、情感分析这类任务不同它的样本是时间序列切分方式、归一化方式和促销标记都会直接影响最终效果。文章后面给到的代码都是可运行的最小实现拿真实订单表改改字段名就能复现出第一版。适合三类人一类是被大促预测折磨的电商运营和供应链从业者一类是正在做深度学习和时间序列相关课题的学生另一类是打算把销量预测做成内部工具、想先评估投入产出比的技术负责人。2. 从订单表到训练样本京东销量预测数据管道怎么搭2.1 先看数据长什么样订单、商品和促销表常见做法是数据仓库里至少有三张相关表订单表记录每个 SKU 每天的成交明细商品表描述价格、类目、上架时间促销表记录活动名称、开始时间和结束时间。销量预测的主角是订单表但只聚合订单远远不够商品的生命周期状态和促销事件才是深度模型能学出“为什么突然涨”的关键。订单表聚合逻辑相对固定按 sku_id 和支付日期做求和。这里有一个容易翻车的地方——订单表里既有提交时间又有支付时间还有取消和退款状态。供应链补货关注的是真实成交和库存消耗应该以支付时间作为日期字段同时要把取消单和退款单过滤掉否则它们会制造出“看起来卖了、实际没卖”的幽灵销量。京东店铺的订单数据里还有一个常见现象是合并支付和拆单聚合层要做去重否则同一笔订单在跨天对账时会被重复计算。促销表与订单表的关联也有默认口径。一个促销活动的开始时间往往不是销量上涨的起点预售和预热会让销量提前 1 到 3 天开始爬坡活动结束之后还会有几天的惯性余热。所以关联促销表时不要只标记活动当天要给前后各留出一个缓冲窗口具体留几天取决于店铺的促销节奏一般会在特征工程里做成可配置参数。2.2 用 SQL 聚出日销量再用 Pandas 补全时间轴先把订单明细聚合成一个长表每一行代表某个 SKU 在某一天的销量。这个步骤只要一条 SQL 就能完成。SELECT sku_id, DATE(pay_time) AS sale_date, SUM(qty) AS sales_qty FROM order_info WHERE store_id JD001 AND status NOT IN (canceled, refunded) AND DATE(pay_time) BETWEEN 2024-01-01 AND 2024-12-31 GROUP BY sku_id, DATE(pay_time) ORDER BY sku_id, sale_date;逻辑说明这段 SQL 按 sku_id 和支付日期分组对订单数量求和。过滤条件里的 store_id 用于圈定店铺范围status 过滤掉取消和退款单时间区间则限定了建模周期。需要注意日期字段用的是支付时间而不是下单时间因为库存消耗和财务确认都以支付为基准如果业务考核的是发货口径也可以换成出库时间但不要混用否则特征和标签会错位。聚合完成后还有一个必做步骤补零。很多 SKU 在某一天没有成交订单表里就不会有这一行但深度学习模型输入的序列必须是连续等间隔的。用 Pandas 生成完整的日期索引再做一次 reindex缺失销量用 0 填充import pandas as pd date_range pd.date_range(2024-01-01, 2024-12-31, freqD) full_index pd.MultiIndex.from_product( [df[sku_id].unique(), date_range], names[sku_id, sale_date] ) df df.set_index([sku_id, sale_date]).reindex(full_index, fill_value0).reset_index()逻辑说明先把 sku_id 和 sale_date 设为索引然后用全量笛卡尔索引去重排缺失销量填 0这保证了每一个 SKU 都有 365 天连续序列。补零之后再加入价格、促销标记等外部特征。填充操作要放在滑窗切分之前否则窗口内会缺日期卷积和循环网络的时序假设都会被破坏。2.3 训练样本的三种滑窗切法单序列、多序列和滚动预测数据管道搭好之后进入监督学习改造。销量预测本质上是把时间序列转成监督学习样本用过去 T 天的特征预测未来 H 天的销量。滑窗是这个转换过程的核心操作切法不同模型看到的世界也不同。最常见的是滑动窗口法对每个 SKU从序列第 1 天开始依次切出 (X, y) 对前 30 天作输入后 7 天作标签然后窗口整体往后滑 1 天。这种切法样本量最大适合训练序列模式稳定的成熟商品缺点是前后样本强相关模型容易在验证集上表现虚高。第二种是按事件切分窗口不重叠每次滑动步长等于预测长度 H。这种切法样本更独立更贴近真实的使用场景——每周预测一次未来 7 天销量。它的缺点是样本量骤减对数据的利用不够充分。第三种是滚动多步预测把未来 H 天作为多步标签而不是一个整体。比如预测未来 7 天就构造 7 个输出训练时用序列前段预测第 1 天再用预测结果拼进输入预测第 2 天。这种切法更贴近线上推理逻辑但误差会随步长累积。实际工程里我更推荐以第二种为主、第一种为辅先用滑动窗口法做大规模预训练再用事件切分法做精调和验证。下面的函数实现了基础的滑窗转换请注意它对多 SKU 的处理方式。def make_samples(seq, window30, horizon7, step1): X, y [], [] for i in range(len(seq) - window - horizon 1): X.append(seq[i : i window]) y.append(seq[i window : i window horizon]) return X, y逻辑说明seq 是按日期排序好的销量序列window 是回看窗口horizon 是预测长度step 是滑动步长。step 设为 1 时生成滑动窗口样本step 设为 horizon 时生成不重叠样本。参数说明window 至少要覆盖一个完整的业务周期比如预测 7 天销量窗口不要小于 28 天horizon 要和业务盘点节奏一致补货周期是 7 天就设 7不是越大越好。3. 模型选型与基础建模为什么 TCN 比 LSTM 更适合销量序列3.1 深度学习模型在销量预测里的定位自动特征提取传统机器学习做销量预测重点全在手工特征上上周同期销量、上月同期销量、折扣力度、是否节假日、搜索热度、价格弹性。特征工程能做的上限很高一个经验丰富的算法工程师可以花两周时间把特征做到极致但问题在于特征需要持续维护店铺改一次促销策略所有特征都要跟着调整。深度学习模型的定位是自动提取局部模式和长期依赖。销量序列里最有价值的结构是“周期性”和“脉冲性”周期性指周一到周日、月初与月末、季度末的规律波动脉冲性指促销、活动、节假日带来的突然拉升。卷积网络擅长捕捉局部脉冲时序模型擅长捕捉依赖关系。CNN 加 LSTM 的组合是经典做法而我实际更常用 TCN也就是时间卷积网络它是卷积网络在时序任务上的变体用因果卷积和空洞卷积扩大感受野训练比 LSTM 稳定对长序列的拟合也更可控。选用 TCN 还有三个工程上的现实理由第一它没有循环结构可以并行计算训练速度比 LSTM 快一个量级第二它的感受野由卷积核大小、层数和空洞率决定对“过去几天”的依赖范围完全可控不会像 LSTM 那样出现长期记忆衰减第三它在小数据量下比 Transformer 更容易收敛销量预测的样本量通常不足以支撑大规模注意力模型的训练。3.2 用 PyTorch 搭一个最小 TCN 销量预测模型模型输入设计成两路一路是序列特征包括最近 N 天的销量、折扣力度和促销标记形状为 [batch, features, sequence_length]另一路是静态特征包括商品价格带、生命周期阶段和类目编号形状为 [batch, static_features]。序列特征经过 TCN 编码拼上静态特征后再经过全连接层输出未来 H 天的销量。下面是一个可以直接运行的 TCN 核心模块完整模型结构在注释中展开说明。import torch import torch.nn as nn class TCNBlock(nn.Module): def __init__(self, in_channels, out_channels, kernel_size3, dilation1, dropout0.1): super().__init__() self.padding (kernel_size - 1) * dilation self.conv1 nn.Conv1d( in_channels, out_channels, kernel_size, dilationdilation, paddingself.padding ) self.bn1 nn.BatchNorm1d(out_channels) self.conv2 nn.Conv1d( out_channels, out_channels, kernel_size, dilationdilation, paddingself.padding ) self.bn2 nn.BatchNorm1d(out_channels) self.dropout nn.Dropout(dropout) self.relu nn.ReLU() self.resample None if in_channels ! out_channels: self.resample nn.Conv1d(in_channels, out_channels, 1) def forward(self, x): residual x out self.relu(self.bn1(self.conv1(x))) out self.dropout(out) out self.bn2(self.conv2(out)) out self.dropout(out) if self.resample is not None: residual self.resample(residual) return self.relu(out residual) class SalesTCN(nn.Module): def __init__(self, seq_len30, horizon7, static_dim8, base_dim64): super().__init__() self.blocks nn.ModuleList([ TCNBlock(2, base_dim, kernel_size3, dilation1), # 2: 销量 促销标记 TCNBlock(base_dim, base_dim, kernel_size3, dilation2), TCNBlock(base_dim, base_dim, kernel_size3, dilation4), ]) self.head nn.Sequential( nn.Linear(base_dim static_dim, 128), nn.ReLU(), nn.Dropout(0.2), nn.Linear(128, horizon), ) def forward(self, seq_feat, static_feat): # seq_feat: [batch, features, seq_len] h self.blocks[0](seq_feat) for block in self.blocks[1:]: h block(h) # 取最后一个时间步 h h[:, :, -1] out torch.cat([h, static_feat], dim1) return self.head(out)逻辑说明TCNBlock 使用因果卷积padding 只加在序列左侧保证当前时刻的输出只依赖当前和过去的信息。dilation 逐层翻倍分别是 1、2、4配合 kernel_size3三层堆叠后感受野约为 1 2 4 乘以3 减 1也就是能覆盖过去十几天的信息足以捕捉一周周期和短期促销影响。残差连接让网络堆深时梯度不会消失。Forward 里最后只取最后一个时间步的输出这和因果卷积的性质是一致的最后一个时间步已经汇总了全部历史信息。静态特征在序列编码后拼接让模型同时看到“这个商品是谁”和“它最近卖得怎么样”。3.3 损失函数与评估指标RMSE、MAE 和分位数损失怎么选销量预测的损失函数不是随便选一个都能用的。MAE 是绝对值误差对离群点不敏感RMSE 是平方误差对大偏差非常敏感。做过销量预测的血泪经验是大促当天销量是平日的 5 到 10 倍RMSE 会把大促样本的权重无限放大模型为了压低这些样本的误差会把日常预测调得很激进反而牺牲了正常日期的精度。我的默认配置是日常训练用 MAE另外单独监控 RMSE因为库存决策更关心缺货风险RMSE 过高说明某些 SKU 出现了不可忽略的大偏差。如果业务里还要计算安全库存就需要预测分位数而不是期望值。分位数损失的代码非常简单但作用很大def quantile_loss(pred, target, q0.5): error target - pred loss torch.max(q * error, (q - 1) * error) return torch.mean(loss)逻辑说明当 q0.5 时上式等价于 MAE当 q0.9 时模型更倾向于预测偏高因为低估会被施加 0.9 的权重高估只承担 0.1 的权重。库存场景里常用 q0.9 或 q0.95 的输出作为补货建议宁可多备一点库存也不要等到断货了再补。4. 训练与调参让模型在促销季不再乱飙4.1 特征归一化与时间序列划分这一步错了等于白训销量预测里一个最常见的低级错误是用随机打乱的方式切分训练集和验证集。销量序列天然按时间排序随机切分相当于让模型“看到未来”验证集误差会低得离谱上线后立刻原形毕露。正确做法是时间序列切分按日期排序后前 80% 做训练、后 20% 做验证。特征归一化也需要单独注意。销量序列里偶尔有大促造成的极端值用 StandardScaler 做标准化会被极端值拉偏均值效果不如 RobustScaler。RobustScaler 使用中位数和四分位距对大促峰值免疫并且要先在训练集上 fit再用同一组参数去 transform 验证集和测试集不能把全部数据合并后再 fit否则又引入了未来信息。from sklearn.preprocessing import RobustScaler scaler RobustScaler() train_len int(len(seq_data) * 0.8) scaler.fit(seq_data[:train_len]) train_scaled scaler.transform(seq_data[:train_len]) val_scaled scaler.transform(seq_data[train_len:])逻辑说明seq_data 是所有 SKU 按时间排序拼接后的特征矩阵。先切分后归一化保证验证集不会泄漏到 scaler 的统计量里。RobustScaler 的默认分位数范围是 25% 到 75%对尖峰的处理比 StandardScaler 稳健如果序列里存在长时间为 0 的断货区间数据里会有大量零值可以考虑先把零值单独标记为一列再对非零销量做缩放避免零值主导中位数。4.2 按 SKU 分批训练长短序列混在一起会让模型学成“和事佬”京东的商品结构非常不均匀爆款 SKU 一天卖几千件长尾 SKU 一天卖三五件新品可能只有几十天的历史数据。如果把这些序列直接拼在一起训练模型会倾向于预测一个“中间值”因为这样在所有 SKU 上的平均损失最低但对每个 SKU 都没有实际参考价值。我一般会在 PyTorch 的 DataLoader 里按 SKU 分组让同一个 batch 尽量来自同一类长度的序列。实现方式不复杂给 Dataset 多返回一个 sku_id采样时按 SKU 分层或者直接把序列按长度排序再分批。这个处理在代码层面只需要很小的改动但对训练稳定性的提升非常明显。class SkuDataset(torch.utils.data.Dataset): def __init__(self, seq_feat, static_feat, target): self.seq_feat seq_feat self.static_feat static_feat self.target target def __getitem__(self, idx): return ( self.seq_feat[idx], self.static_feat[idx], self.target[idx], ) def __len__(self): return len(self.target)逻辑说明这是一个标准的 PyTorch Dataset 实现每个样本包含序列特征、静态特征和目标值。按 SKU 分组的逻辑不在 Dataset 里而在 DataLoader 的 sampler 上实际操作中可以用 torch.utils.data.WeightedRandomSampler给样本少的 SKU 更高采样权重让模型把容量分配给长尾商品而不是被爆款带节奏。4.3 训练参数参考配置先跑通再调优网络结构和优化器的初始配置直接决定第一个版本能不能在一晚上的时间内跑完。下面是我验证过的一套参数适合 5000 个 SKU、每天销量记录、序列长度 365 天的场景。参数推荐值说明回看窗口 window28 到 45 天覆盖 4 到 6 个完整周预测长度 horizon7 天与每周补货周期对齐批量大小 batch_size128 到 256显存受限时降到 64学习率 learning_rate3e-4AdamW 优化器最大训练轮数 epochs30 到 50配合早停监控验证集 MAE梯度裁剪 clip_norm1.0防止促销峰值样本导致梯度爆炸学习率衰减ReduceLROnPlateau验证集 loss 连续 3 个 epoch 不降则衰减 0.5训练循环里加梯度裁剪和早停是两个性价比极高的操作。销量序列的标签波动很大偶尔一个异常样本的梯度会让模型参数猛烈更新梯度裁剪把梯度的模限制在 1.0 以内相当于给每步更新上了保险。早停可以在验证集上连续多轮没有改善时提前收工省掉无谓的算力。optimizer torch.optim.AdamW(model.parameters(), lr3e-4) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor0.5, patience3) for epoch in range(50): model.train() for seq_feat, static_feat, target in train_loader: optimizer.zero_grad() pred model(seq_feat, static_feat) loss torch.mean(torch.abs(pred - target)) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() val_loss evaluate(model, val_loader) scheduler.step(val_loss)逻辑说明训练循环里的核心动作是前向传播、计算 MAE 损失、反向传播、梯度裁剪和参数更新。scheduler.step(val_loss) 会根据验证集损失决定是否把学习率乘以 0.5。train_loader 里最好配置 shuffleFalse保证时间序列顺序不被破坏静态特征在送入模型前也要做归一化否则 embedding 层的梯度会被量纲大的特征主导。5. 销量预测避坑指南这五处最容易被坑5.1 促销标记和销量高峰对不上时间轴现象模型训练集 MAE 很低验证集一到活动日预测值就整体偏移预测高峰比真实高峰晚一天或者早一天。我排查时看到折线图上促销标记是方波而销量曲线是圆润的山丘峰值位置总是对不齐。原因促销表和订单表关联时活动开始时间被直接当成了特征跳变点但用户实际从预热期就开始下单活动结束后也还有返场销量。解决把促销表的时间轴做膨胀活动开始前 2 天和结束后 1 天的销量计算成软标记比如膨胀期内填 0.5活动内填 1.0让模型学到的是平滑过渡而不是阶跃跳变。5.2 长尾商品预测集体失灵序列太短、数据稀疏还硬套大窗口现象爆款 SKU 的预测曲线像模像样长尾 SKU 的预测几乎是一条平线和一些没有训练的随机序列无差别。原因长尾 SKU 日销量大部分为零有效信息稀疏模型在全量数据上训练时会放弃这些难学样本走向全局最优的平均解。另一个诱因是窗口太长比如强制用 45 天窗口而某些新品上架才 20 天样本根本构不出来。解决按 SKU 的上架天数动态缩小窗口对日销量低于阈值的长尾商品单独训练一个浅层模型再把商品类目作为静态特征输入让模型借助同类目其他商品的模式做迁移。5.3 用随机 K 折交叉验证评估模型结果虚高一截现象验证集上的 MAE 是 12业务方觉得模型已经能上线结果在上线后的两周里真实 MAE 是 22几乎是成倍的差距。原因随机 K 折把时间序列打散了训练集中包含验证集时间之后的数据模型相当于偷看了未来。销量预测里未来的数据是拿不到的时间泄漏和特征泄漏一样致命。解决改用时间序列交叉验证常用的做法是把数据按时间切分成 5 段先用前 1 段训练并验证第 2 段再用前 2 段训练并验证第 3 段依次滚动最终报告的误差取最后一次预测缺失的验证段。5.4 断货期被当成“正常低销量”模型越训越悲观现象某 SKU 在 3 月断货了 12 天销量为 0恢复供货后销量反弹到断货前的两倍。模型把断货期的零值当成了平滑序列的一部分导致恢复期预测被系统性拉低。原因缺货导致的销量为零并不代表需求为零它是供给约束下的观测值和真实需求有本质差异。解决在订单数据里找到断货标记断货期间不参与损失计算或者把目标值填充为该 SKU 断货前后的滚动均值更简单的做法是构造一个 is_out_of_stock 特征让模型学会识别这种结构性中断。5.5 TensorFlow 和 PyTorch 的环境切换把时间全耗在装库上现象两台机器上分别跑训练和推理一个用 PyTorch 1.13另一个用 2.1序列模型推理结果出现微小差异排查半天发现是 CUDA 算子版本不同导致的浮点数精度差异。原因深度学习环境配置在 GPU 驱动、CUDA Toolkit 和深度学习框架三者之间版本敏感PyTorch 编译时的算子实现会随版本变化。解决用 conda 环境锁定完整依赖记录关键包的版本号训练环境和推理环境必须一致尤其是一条 CUDA 版本差异、一条 cuDNN 版本差异都会改变卷积操作的数值结果。6. 从预测到补货把模型输出变成业务真正能用的决策6.1 用分位数输出替代单点预测库存决策不再靠猜单点预测只能回答“销量大概是多少”但补货决策真正关心的是“有多少概率会超过这个值”。训练时把分位数损失里的 q 分别设成 0.5、0.9、0.95得到三组输出。补货建议用 q0.95 对应的预测值采购计划用 q0.5这样既不会让库存过度堆积也不会因为预测偏低而频繁缺货。前文的分位数损失函数在这里直接复用只是需要把单目标训练改成多目标训练。6.2 回测时不要只看整体误差要把预测结果按 SKU 和价值分层看整体 MAE 下降 5% 对决策者来说感知很弱但把 SKU 按日均销量分成头部、中部、尾部三档之后差距会立刻暴露头部 SKU 的误差可能已经收敛得很好尾部 SKU 的预测几乎不可用。更合理的做法是把误差换算成补货金额统计每个 SKU 的缺货天数和库存周转天数这两个指标跟业务方的对话成本比 MAE 低得多也更容易推动模型迭代。最后从我自己的经验里补一句刚做销量预测时我沉迷调模型结构把 TCN 的层数和空洞率调了一个星期验证集效果纹丝不动后来认真复查数据管道才发现缺货周期的处理逻辑写错了。模型再强也补不了数据的结构性漏洞。从那以后我每改动一版模型都会先跑一遍回测脚本确认误差没有随样本范围漂移再聊天调整网络结构——这套习惯让我少翻了很多次车。希望帮到你。本文还有配套的精品资源点击获取
返回列表