做毕设选题目的时候,很多人第一反应是往"热门"里扎:目标检测、推荐系统、大模型微调……但真正动手之后才发现,要么数据量撑不起模型,要么业务背景讲不清楚,最后论文写得像说明书。我做的这个项目选的是"基于机器学习的日化产品销量影响因素的分析与预测",方向不算花哨,但做完之后收获非常大——数据量足够、业务逻辑清晰、算法有对比空间,而且源码和论文的完整性都很好。这篇就把它当成一个完整的毕设/项目复盘来写,从头到尾讲清楚我是怎么拆解题目、处理数据、构造特征、训练模型,到最终把论文和系统串起来的。如果你也在纠结"销量预测类"的题目怎么落地,或者手上正好有一堆零售数据不知道怎么挖出价值,这篇应该能给你一条可以直接参考的路线。
1. 为什么日化产品是销量预测类毕设的"最优解"?
先说选题。导师一开始给我的方向是"零售数据挖掘",范围很大,等于什么都没说。我自己把候选品类列了一遍:生鲜、零食、日化、服饰、数码。逐个筛下来,日化产品赢在几个硬指标上。
1.1 数据频率够高,模型才有得学
日化品类(洗发水、沐浴露、洗衣液、纸巾、牙膏这一类)属于高频消耗品,复购周期短。我做的是一个模拟某连锁便利店的日化货架销售数据,覆盖了近三年的日度销量记录。跟服饰、数码这类低频耐用品比起来,日化品的日销量序列几乎是连续的,很少出现大量零值日期。这非常重要——如果一个商品一整月才卖几单,你拿什么去预测明天的销量?模型学到的全是噪声。日化品正好避开了这个坑,日均销量稳定,季节性波动明显,适合做时序类或者回归类的建模。
1.2 SKU多,影响因素自然丰富
同一个品类下面,光是洗发水就有去屑、柔顺、控油、男士、家庭装等多种功能定位,价格从十几块到七八十块不等。SKU多意味着样本量被撑起来了,而且不同价位、不同定位的商品对促销和价格的反应差异很大,这恰恰是"影响因素分析"想要的东西。
我在数据处理阶段统计了一下,筛选出的可建模SKU一共有180多个,每个SKU都带了日常售价、促销价、促销状态、陈列位置、所在门店等级这些业务字段。有差异就有对比,有对比就能写分析,论文的"影响因素"部分就有着落了。
1.3 业务变化有迹可循,解释起来不费劲
日化产品的销量波动是有明确业务原因的:大促、节假日、季节交替、天气突变。比如夏天驱蚊液和花露水销量暴涨,冬天护手霜和滋润型沐浴露走强;再比如"满199减100"这种大促前一周销量被压制、大促当天爆量、大促后几天回落的"虹吸效应",都是能直接观察到的真实现象。
这意味着什么?意味着你做的每一个特征、模型给出来的每一个重要性排序,都可以落到业务逻辑上。答辩的时候导师最爱问的"这个结果为什么合理?"——你能讲出故事来,这比单纯展示AUC和F1分数有说服力得多。
1.4 算法空间大,传统机器学习到深度学习都能放进去
我们这个项目的标题里同时出现了"机器学习"和"深度学习算法",这其实是毕设里很典型的"靠内容撑题目"写法。我的方案是:用线性回归做baseline,用LightGBM和XGBoost做树模型主力,再用LSTM做深度学习路线。三组模型对比着写,既有核心实现,又有调参过程,论文的"实验对比"章节自然就厚起来了。
一句话总结选型逻辑:日化品销量做预测,本质上是"回归问题 + 时序问题 + 可解释性要求"的复合体,任何一个单模型都能做,但没有一个单模型能同时把这三件事讲到极致。所以最好的策略不是选一个模型,而是选一批有代表性的模型做对比,让算法的差异本身成为论文的看点。
2. 数据预处理:这步偷懒,后面全是坑
很多人拿到数据之后第一件事就是敲df.head(),然后急着上手建模。我劝你冷静。日化产品的零售数据是所有行业数据里最"脏"的一类,不认真处理,后面所有模型结果都是虚的。
2.1 商品名称归一的空难
同一个商品在不同门店、不同批次里的名称五花八门。举例来说,"某品牌洗发水柔顺型400ml"可能在另一家店叫"某品牌洗发水(柔顺)400ml",还有可能把400ml写在前面。如果你不做归一化,这些数据在模型眼里就是两个完全不同的商品,后果就是特征稀疏、样本被割裂,销量预测值忽高忽低。
我的做法是两步走:先做硬匹配(完全相同的主品牌和规格),再做规则匹配(提取品牌词、功能词、规格数值)。规则匹配当时花了好几天,用正则把商品名拆成结构化字段:品牌、品类、功能定位、规格ml、包装类型。做完之后180多个可建模的SKU才稳定下来。这类工作没有技术上的"惊天动地",但它是后面一切分析的地基。
2.2 促销价与成交价的二义性
零售数据里经常同时存在"日常售价"和"促销价"。这两个字段直接预测模型容易出问题:模型会发现促销价越低,销量越高,但这个规律到底是真实的因果,还是字段本身就看错日期?
我在原始数据里发现过一个典型的坑:部分记录中促销价字段填的其实是"促销活动开始前一天的日常价",等到活动真正开始那天,促销价反而没有更新。如果你直接拿这个字段建模,模型会认为"价格没变但销量暴涨",然后拼命用节假日特征来拟合这个异常值。所以我把价格字段清洗成了"实际成交价格"——也就是当日真实出库单价,缺了就用日常售价补,促销标志位才真正用上。
2.3 缺货造成的销量零值
日化产品也有缺货的时候,但我发现很多SKU的零销量不是"没人买",是"库里没货"。如果不区分这两者,模型就会学到一个错误的信号:节假日反而销量为零。
我的处理方式很简单:找到目标SKU,如果某天销量为零且库存也为零,就把这天标记成缺货日,预测时直接跳过这个样本,不参与训练和评估。这样做表面上是"丢了一部分数据",实际上让模型的训练集干净非常多。
2.4 防止数据泄露的切分方式
这是整个项目里最重要的一个分水岭。销量预测本质上是时间序列任务,如果用随机切分把数据分成训练集和测试集,模型就会通过"看到未来的数据"来作弊。比如你在8月训练时用了9月的标签,到了9月的预测环节模型只是把答案背了出来,指标自然好看,但毫无意义。
我采用的切分方式是:按时间顺序切分,前70%做训练集,中间15%做验证集,最后15%做测试集。为了保证测试集能覆盖到不同季节,我特意把时间窗口放到一个完整自然年以上。具体实现上,我用的是一个自定义的TimeSeriesSplit类,每次只允许模型用过去的窗口预测未来的窗口,从源头杜绝了数据泄露。这个问题我建议你在论文里单独拿出来写一节,属于答辩老师特别看重的那类"方法论"细节。
3. 特征工程:把"影响因素"变成模型能看懂的数值
题目里"影响因素分析"这几个字才是核心命题。模型怎么知道"大促"会影响销量?"季节"怎么进模型?这不靠算法自己悟,全靠特征工程。我这边把特征分成了六个维度,每个维度都在最后的影响因素分析阶段派上了用场。
3.1 基础时间特征
星期几、是否周末、月份、季度、当月的第几天。日化品的周期性强,每个月的月初和月中发薪日附近销量会有一个小尖峰,周末的家庭采购型消费也更旺盛。这些特征虽然不起眼,但它是树模型天然的强特征。
我额外做了一组"节假日前中后"特征:节前第几天、节后第几天、是否法定节假日当天。单独用"是否节假日"0/1这种标签太粗暴了,因为大促对销量的拉升是有过程感的——节前三天就开始囤货,节中回落,节后进入淡季。用一个距离变量表达这种过程,比一个二分类标签信息量大得多。
3.2 促销与价格特征
促销价、日常售价、折扣率、促销持续时间、是否跨品类满减、促销级别(单品直降/满减/买赠)。折扣率是一个特别好的特征,因为它直接把"降价力度"量化了。满减类活动对销量的影响明显强于单品直降,但模型自己不知道,所以我把促销类型也编码进去。
需要注意折扣率的计算:不是简单用促销价除以日常价,还要考虑满减的门槛。比如"满199减100",对单价十几块钱的牙膏来说,用户需要囤十几支才能享受优惠,这时折合到单品的实际折扣远低于表面上的折扣。我当时把满减按可达成门槛的组合做了一个预估折扣率,虽然不精确,但比直接用标价靠谱多了。
3.3 历史销量滑窗特征
这是时间序列预测的核心。滞后期是销量、前7天平均销量、前14天平均销量、前28天平均销量、同品类的整体销量均值、销量环比变化率。这些特征做出来之后,LightGBM的训练精度立刻上升了一个台阶,Feature Importance排名前五的基本都是滑窗特征。
这里有一个很多人会犯的错误:滑窗特征里包含了未来信息。比如你用t时刻的数据预测t+1,但滑窗特征是计算到t+7天之后的,那这个特征本身就等于给了模型一个"答案的提示"。我的规避方法是保证每个样本的特征窗口只包含样本时刻之前的数据,且验证集和测试集上的特征全部来自历史数据,绝不用未来信息回填。
3.4 天气与季节特征
日化品和天气的关系被很多人忽视,但它非常有意思。我拿到的数据里有一份模拟气象表,包含最高温、最低温、降雨量、湿度、风力。把天气数据按门店城市去关联之后,发现了几个明显的规律:降雨量大的日子,线下门店的日化品销量普遍下降10%~20%;气温超过30度时,洗护类销量明显上升;湿度大的季节,除湿袋、芳香剂这类商品的销量几乎翻倍。
这类特征在模型里可能排不到前列,但它在"影响因素分析"章节里确实是不可少的。你可以画一个散点图或者分组箱线图,把天气和销量的关系可视化成论文里的插图,直观又有说服力。
3.5 门店与陈列特征
门店等级、货架位置、陈列面数、是否放在收银台附近。虽然这些特征在预测阶段的增益不显著,但在分析"哪些因素影响最大"的时候,它们是重要的控制变量。比如某品牌纸巾在A类门店销量远超B类门店,可能不是消费者偏好问题,而是A类门店给它安排了端架陈列,曝光量大。
3.6 同品类联动特征
同类目商品的总销量、同品牌其他SKU的销量、竞品销量占比。日化品的"货架份额"是零和博弈,一个品牌的新品上市,往往吃掉的是另一个品牌的份额。把这个逻辑用"同品类其他商品销量"这个特征表达出来,模型会学得非常快。
整个特征工程做完,我共构造了63个维度特征。在论文里,我把这些特征整理成了一张表,每一列标出特征含义、类型、来源字段和处理方法。答辩时老师拿到这张表基本就不再追问"你做了哪些特征"了。
4. 算法模型与训练细节:从baseline到LSTM
这套项目我最终实现了三组主力模型,外加一组集成融合。每个模型承担的定位不一样,训练细节和坑也不太一样。
4.1 线性回归:做差值,不做上限
很多人觉得线性回归放在毕设里太"没技术含量",但它是最好的baseline,能帮你快速确认特征管道是不是通的。我用岭回归做了一版,把所有数值特征做标准化之后跑了一遍,测试集RMSE大概在17.6左右(单位:件/天),R方约0.58。这个成绩就是全项目的下限。
注意,线性回归不适合直接用销量作为目标变量,因为销量是右偏分布,直接拟合会让高销量SKU主导误差。我做了个log1p变换把目标拉成近似正态,效果立杆见影。记录一下:变换后的RMSE降低约12%,但要对预测结果做expm1回变换。
4.2 LightGBM与XGBoost:树模型是日化数据的"实战之王"
这两兄弟放在一起说,因为核心思路一样:用一堆弱决策树做集成,在前面的特征工程基础上暴力拟合。
实际操作中,LightGBM表现比XGBoost略好,主要原因是LightGBM对类别特征的天然支持更好,门店等级、促销类型这种高基数类别字段,不需要单独做独热编码就能直接喂进去。训练配置上,我调了几个关键参数:
n_estimators: 2000(配合早停)learning_rate: 0.03,目的是给足叶子数避免过拟合num_leaves: 31,经验值,往大了调训练集精度会提升但测试集容易崩feature_fraction: 0.8,相当于每次随机砍掉20%的特征,提高泛化能力early_stopping_rounds: 50,用验证集做早停,这个设置直接避免了我手动判断"到底训多少轮"
注意一个时间序列场景下的细节:交叉验证不能随便用普通的KFold,要用带时序顺序切分的验证方式,否则模型会被未来数据"剧透"。LightGBM的训练速度很快,180个SKU全量训练大概也就两三分钟一轮,这个体量做毕设刚刚好——既不会因为跑不动而卡住,也不至于因为速度太快没有调参的体验感。
最终LightGBM在测试集上的RMSE到了9.8,R方到了0.87,XGBoost略差一点但差距很小。这个精度说明什么?说明日化品销量的"可解释性方差"很大,促销、节假日、价格这些因素确实主导了销量的起伏。后面到了影响因素分析阶段,可以很有底气地讲故事。
4.3 LSTM:深度学习路线的关键实现
LSTM是标题里"深度学习算法"这几个字的主要承担者。我一开始也想用Transformer或者TCN,但仔细评估之后觉得LSTM更合适:样本量在几万级别,LSTM是最不容易过拟合的时序模型,而且训练资源要求低,答辩讲起来也经典。
数据构造上,我把单个SKU当做一个独立的时间序列,构造长度为14天的滑窗输入,目标是预测第15天的销量。做了一组min-max归一化,注意这个归一化必须是"用训练集的min和max去缩放验证集和测试集",否则又会踩数据泄露的坑。
网络结构搭了三层LSTM加一层Dense,核心代码结构大致是:
model = Sequential([ LSTM(64, return_sequences=True, input_shape=(14, feature_dim)), Dropout(0.2), LSTM(32, return_sequences=False), Dropout(0.2), Dense(16, activation='relu'), Dense(1) ]) model.compile(optimizer='adam', loss='mse', metrics=['mae'])这里的feature_dim是28,因为我把每个SKU的48维特征在滑窗里做了降维压缩,只保留最重要的价格、促销、节假日、历史销量等字段。LSTM的训练比较慢,我做了早停,大概在第80轮左右收敛,测试集RMSE在11.5左右,比LightGBM差一些,但意外地在某个"促销剧烈波动"的子集上,LSTM的预测更稳——树模型对未见过的促销形式容易外推过度,LSTM反而因为记忆了序列模式而表现更理性。
4.4 融合模型的收益
最后我把LightGBM和LSTM做了简单加权融合,权重是0.7和0.3,RMSE从9.8降到了9.2。虽然提升不大,但这是个很好的论文素材——你可以写"单一模型在局部场景各有优势,融合可以取长补短",这也是业界在销量预测场景比较成熟的做法。
融合模型的实现不要太花哨,我试过Stacking、线性回归套嵌,效果和时间成本不成比例,最后还是用固定的权重简单高效。答辩的时候讲"为什么用固定权重而不是可学习的Stacking"——因为样本量有限,可学习权重容易过拟合,固定权重在验证集上反而是最优的。
5. 影响因素量化分析:让模型"开口说话"
模型训练完成不算完,论文真正要让老师觉得有价值的是"影响因素分析"。树模型给了Feature Importance,但Feature Importance只说"哪个特征重要",没说"重要特征是怎么影响销量的"。这一步我用SHAP值来做。
5.1 特征重要性的排序结果
从LightGBM的Feature Importance和SHAP值两边看,排在前十的影响因素里,集中在三个大类上:促销相关特征(促销标志、折扣率、满减类型)、时间相关特征(节假日、星期、月份)、历史销量滑窗特征。这三类加起来占了整体重要性的八成以上。价格特征反而排得靠后,这个结果让我有点意外,但后来想通了:日化品的价格弹性没有快消食品那么强,消费者对洗发水的价格敏感度远低于对零食的敏感度;真正拉动销量的是"有没有活动",而不是"贵不贵几块钱"。
5.2 SHAP值告诉我们方向和幅度
SHAP不只是排名,它能给每个特征在每个样本上的影响方向和大小。我把所有样本的SHAP值做了汇总,得出了几个可写进论文的关键结论:
第一,促销标志位为正贡献,但存在边际递减。当折扣力度从8折变成6折时,销量平均拉升约35%;但从6折变成4折时,拉升幅度只有十几个百分点。为什么会这样?因为日化品的囤货属性决定了消费者的"库存容量"有限,便宜到一定程度后,用户不会因为更便宜就多买三倍的量,家里摆不下。
第二,节假日特征有强烈的"节前抬升、节后回落"效应。节前3天的SHAP值显著为正,节后第一周显著为负。这个结论对零售商的排产和补货非常有参考价值,论文里用一条SHAP值随节前/节后天数变化的折线图展示,就是很亮的可视化。
第三,不同品类对同一特征的敏感度差异大。家庭装洗衣液对折扣率的敏感度是旅行装洗发水的两倍多,原因很简单:大包装是计划性购买,等促销是理性选择;小包装是应急性购买,价格再低也不会囤十个。
5.3 把分析结果落成运营建议
论文不能停在模型指标上,要有落在业务上的结论。我从SHAP分析里提炼出了三条运营建议:大促资源优先倾斜给家庭装和囤货型品类;节前补货要提前5~7天,而不是促销当天;恶劣天气当日,线下门店应减少日化品堆头、把资源转移到雨具和即食食品上。虽然对于纯算法项目来说这部分不是硬性要求,但加入了之后,论文的"应用价值"章节就非常扎实,答辩口碑也会好不少。
6. 完整项目交付:源码结构、论文组织与答辩要点
做毕设和做纯项目有一点不一样:除了模型本身,你还得交付一套完整可复现的源码、一份能过盲审的论文,以及准备好一套有逻辑的答辩说辞。
6.1 源码目录的设计
我把整个项目按"数据处理-特征工程-模型训练-评估分析-可视化"五段来组织目录。顶层结构大概是这样:
project/ ├── data/ # 原始数据与清洗后数据 ├── src/ │ ├── data_preprocess.py # 数据清洗和商品名归一化 │ ├── feature_engineering.py # 63维特征构造 │ ├── train_lgb.py # LightGBM训练脚本 │ ├── train_lstm.py # LSTM训练脚本 │ └── evaluate.py # 模型评估与AUC/RMSE计算 ├── notebooks/ │ ├── EDA.ipynb # 探索性数据分析 │ └── SHAP_analysis.ipynb # 影响因素分析 ├── app/ │ └── dashboard.py # 简易可视化预测界面 └── docs/ └── results/ # 实验结果图表输出关键点是"可复现"。盲审老师和答辩老师大概率不会跑你的代码,但他们可能会看目录结构是否合理、注释是否友好、README是否能把流程讲清楚,所以在README里写清运行顺序和依赖版本,比调一堆花哨参数更能加分。我还在requirements.txt里把lightgbm、xgboost、tensorflow、pandas、shap这些核心库固定了版本,避免因为版本不对跑不起来。
6.2 论文各章的写作分配
论文我用了五章结构,和大家分享下我心里的"写作权重":
- 第一章绪论:题目背景与研究意义,重点写日化行业的库存管理痛点,大概占8%篇幅。
- 第二章相关技术理论:线性回归、决策树集成、LSTM原理,这部分不用写太深,讲清楚核心思想即可,大概占15%篇幅。
- 第三章数据处理与特征工程:这是毕设论文里最应该有干货的部分,把所有清洗步骤和特征构造逻辑配上代码级流程图展示,大概占25%篇幅。
- 第四章模型构建与实验:写清训练配置、调参过程、三组模型的对比结果和融合方案,并放上RMSE/R方的汇总表格,大概占30%篇幅。
- 第五章影响因素分析:SHAP结果、业务结论、运营建议,大概占22%篇幅。
在实验数据展示上,我最喜欢用表格做模型对比。这里罗列一下我当时的结果,供你参考对比:
| 模型 | RMSE(件/天) | MAE | R方 |
|---|---|---|---|
| 岭回归(baseline) | 17.6 | 12.3 | 0.58 |
| XGBoost | 11.2 | 8.1 | 0.83 |
| LightGBM | 9.8 | 7.2 | 0.87 |
| LSTM | 11.5 | 8.6 | 0.82 |
| 融合模型(7:3) | 9.2 | 6.7 | 0.89 |
这套结果最大的好处是"有层次":baseline很差,证明后续的努力有意义;树模型和LSTM各有所长,融合之后进一步提升——整个故事线完整且诚实。
6.3 答辩时需要提前准备好的几个问题
在最终答辩阶段,评审老师最关注的主要有三类问题:
第一类是数据泄露的防范。他们经常会看你的数据划分方式。这个问题最容易暴露的问题,就是用了随机打散切分。我的回答是:所有时序类项目统一用TimeSeriesSplit按时间先后切分,特征窗口只允许使用历史数据,并且测试集的最后一段时间窗口特意避开所有训练样本。把这个讲清楚,就已经能让人对你的工程严谨性建立信心。
第二类是"为什么选这些算法"或者"为什么不用更复杂的模型"。我的回答关键是"可解释性和任务的匹配度":日化品销量预测不只是要一个数,还要看清各因素如何影响销量,树模型天然有可解释性优势;深度学习则用来捕捉时序上的复杂模式,二者互补。没必要硬凑复杂度高的模型,适合数据规模和业务问题才最重要。
第三类是"你的结果对实际业务的应用价值"。这部分直接用上一章SHAP分析出的结论来答,一句话概括:"主要应用是找到促销和节假日的提前量,用于优化补货节奏和资源配置,可以让库存周转率提升几点。"有落点,有态度。
7. 踩坑记录与个人体会
整个项目做完,我自己踩了几个坑,写出来希望后面的人能绕开。
7.1 别迷信"更多数据",先看清数据属不属于你
一开始我尝试把180多个SKU全都丢进同一个模型去训练,结果训练集确实很大,但RMSE惨不忍睹。原因在于不同SKU的销量量级差异太大——家庭装洗衣液日均销量可能是旅行装洗发水的几十倍,模型为了让总体误差最小,基本放弃了小销量SKU。后来我改成"按SKU分组训练",每个SKU单独一套特征和模型,效果好很多。当时为了提升训练效率,对销量极低的SKU做了合并处理(归入"长尾组"做整体预测),既保住样本量又不牺牲主品精度。
7.2 节假日特征千万不能只做0/1
这是我在初版模型上吃过亏的地方。只做节假日标签时,模型的节假日效应严重滞后:它预测出的销量在节后依旧很高,跟真实数据对不上。后来我把节假日细化成"节前N天、节中N天、节后N天"三个连续特征,才把"囤货-爆发-回落"这个节奏表达出来。做特征工程的时候多想一想业务真实过程,回报比调参高得多。
7.3 正则和归一化的坑
LSTM输入之前,每个特征一定要单独做归一化,不能对所有特征做一个统一的min-max缩放到同样范围——促销标志位这样做了之后会被压缩成0或者1,这种0和1的差异在数值上可以忽略,但是梯度更新的权重大小完全不同。具体做法是按列归一化,每列的min和max单独计算,统一存在一个scaler文件里供后续预测复用。
7.4 一个关于"预测未来"的细节
我最初设计模型的时候,是用过去28天预测未来1天,看起来没有毛病。但实际部署到演示系统里发现,很多业务决策想要的是"未来3天/7天的销量总和"(比如要决定一周的备货量)。后来我又补了一个多步预测的评估模块:用过去14天预测未来7天,然后叠加成周销量。指标上误差会变大,但在业务上更实用。建议做类似项目的同学提前问清楚目标,不要埋头只做一个单日预测。
至于这个项目后续还能怎么扩展,我个人觉得比较自然的方向是"多门店联合建模"和"新品冷启动"。现在的模型对已有历史数据的品预测得很稳,但如果一个门店突然上新了一个没有历史销量的SKU,模型就傻了。这属于冷启动问题,可以采用跨品类迁移学习来处理,学"类似定位"老商品的历史模式,把新品的预测从无到有拉起来。如果想把毕设往更好的方向打磨,这是一个非常有价值的深度方向。
最后分享一个我自己的体会:这类销量预测项目,真正拉开差距的从来不是什么难懂的数学公式,而是你愿不愿意花时间把数据掰开揉碎看。促销标价、假日窗口、天气数据、陈列变动……每一个"不起眼"的细节都可能让RMSE掉几个点。等你在答辩台上把一条完整逻辑链讲出来——从脏数据到特征,从特征到模型,从模型到业务建议——你就知道,这个项目带给你的不只是几十页论文,而是一套面对陌生数据时的分析底气。