ARTICLE DETAIL

资讯详情

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

基于深度学习LSTM的蔬菜价格预测:数据清洗、调参与避坑实战

基于深度学习LSTM的蔬菜价格预测:数据清洗、调参与避坑实战 简介这是一份围绕蔬菜价格预测的完整项目资源包适合具备Python基础、希望入门LSTM时间序列建模或完成课程设计的数据学习者。项目覆盖数据爬取、清洗、模型训练与Web展示全流程包含基于Scrapy与BeautifulSoup的爬虫脚本、基于Pandas与NumPy的数据处理代码以及利用LSTM等神经网络进行价格预测的实验方案。压缩包内共181个文件以142个csv数据文件、25个Python源码、2个docx项目说明及1个md文档为主整体体积约1.78MB兼顾可读性与轻量使用。内容预览中可见菜心、西红柿、青皮冬瓜等常见蔬菜的历史价格数据便于直接用于模型验证与分析。资源已有251人学习浏览适合作为毕业设计、竞赛备赛或个人项目复现的参考能够帮助理解从数据采集到预测落地的完整工程思路。1. 基于深度学习LSTM实现蔬菜价格预测这个题目到底要解决什么搞过农产品批发的人都知道蔬菜价格预报最难的不是算法而是数据本身又脏又没规律。这个标题里的“基于深度学习LSTM实现蔬菜价格预测”本质上是一个用 LSTM 神经网络做时间序列回归的落地项目拿过去 N 天某种蔬菜的批发价预测未来一天或者未来一周的价格区间。它对得上三类需求给采购决策做辅助分析的开发人员、想从调包进阶到能改模型的 Python 从业者、拿它当课程设计或毕业设计题目的学生。标题里打包的 python 源码、项目说明和数据集正好构成一个“数据清洗—滑窗建集—LSTM 训练—预测回测”的完整闭环。下面我按自己跑这类项目的习惯把从解压到出数、再到调参避坑的整条链路拆开讲。2. 为什么用LSTM而不是ARIMA/Prophet先摸清蔬菜价格序列的脾气很多人拿到价格数据第一反应是用 ARIMA 或者 Facebook Prophet这两个工具在宏观经济和电商销量预测里确实常见但放在蔬菜批发价上往往水土不服。原因不是模型不够好而是蔬菜价格序列的生成方式太特殊它有缓慢的季节趋势也有暴雨、寒潮、节假日需求带来的突发尖峰有七天一周的周期但每个周期的幅度还不太一样。ARIMA 这类统计模型对平稳性要求高做差分、定阶、外生变量一圈下来可能还没 LSTM 一个滑窗来得直接。我一般会先做一个简单实验把原始价格序列按 7 天窗口做成监督学习样本让单层 LSTM 去拟合如果验证集 MAPE 能压到 10% 以内这个方向就值得继续投入。下面先讲清楚为什么是这个模型合适以及它到底在吃什么样的数据。2.1 蔬菜价格序列的三个“反常规”特征第一价格不是一个干净的周期函数。台风天叶菜价格在两天内翻倍、节后迅速回落的情况很常见这种脉冲式尖峰对差分模型来说是个灾难因为差分会把尖峰放大成更大的异常值。LSTM 的门控机制对这类尖峰相对从容它可以选择性记住尖峰的影响也可以选择遗忘而不是把尖峰当作永久趋势。第二单序列里混杂了多个隐性因素。同样是“黄瓜”这个品种不同批发市场、不同产地、不同品质等级对应的是完全不一样的价格带。如果数据集里只给了一列均价那模型是在用一个模糊值做预测如果给了分市场的价格按品种分组反而是第一个预处理步骤。第三节假日效应会漂移。春节、五一、中秋这些日期在公历上每年不同用简单的滞后特征很难对齐。LSTM 训练时只要窗口足够长理论上能从历史样本里自己归纳出“临近大节价格会拉高”这类关系不需要我们手写节日哑变量但前提是数据里连续年份样本别太少。2.2 LSTM 接收的数据从一列价格到三维张量时间序列预测进入 LSTM 之前必须先转换成监督学习格式。最常见的做法是滑窗用过去n_in天的价格预测未来n_out天的价格。这个转换不依赖深度学习框架用纯 Python 就能实现。import numpy as np def make_sequences(data, n_in7, n_out1): X, y [], [] for i in range(len(data) - n_in - n_out 1): X.append(data[i:i n_in]) y.append(data[i n_in:i n_in n_out]) return np.array(X, dtypenp.float32), np.array(y, dtypenp.float32)这段代码的逻辑很简单data是一维价格数组n_in7表示取最近一周作为输入n_out1表示只预测明天返回的X形状是(样本数, 7)y形状是(样本数, 1)。如果有 1000 天数据大约能生成 993 个样本因为最后 7 天不够凑一个完整输入。送入 LSTM 前必须再补一维。LSTM 要求输入形状是(样本数, 时间步数, 特征数)所以单变量价格要变成X.reshape(-1, n_in, 1)。这里的末尾维度是特征数如果后面想把天气、节假日、成交量都加进来这一维会变成 3 或 4滑窗函数本身不用改。选择 LSTM 而不是其他模型的理由也可以对比如下模型对非线性尖峰周周期/节假日多特征扩展调参成本ARIMA弱需人工干预需手动设置季节项需要额外处理外生变量中Prophet能处理尖峰但依赖先验内置周期项支持附加回归量低LSTM自动学习滑窗内自行归纳直接拼特征列偏高这个对比不是要否定统计模型而是说明在“一压再压、价格突变”的农产品场景里LSTM 的试错成本往往更低。特别是当你手里已经有现成源码和数据集时第一个能跑出结果的方向就值得继续投入。3. 从解压到跑通用最小可复现流程做出第一次预测拿到 zip 之后第一件事不是看算法而是先让自己能跑出一个数字。项目说明文件里通常会写运行环境、数据集字段含义和启动命令但我见过的多数项目说明都不够细所以下面给一套不依赖具体数据结构的通用流程。3.1 解压后先看什么目录结构、环境与依赖解压完成后我习惯先用一条命令看目录结构find . -maxdepth 2 -type f | sort正常情况下一个规范的 Python 预测项目会包含 README、requirements、数据和源码目录大致是这个形态. ├── README.md ├── requirements.txt ├── data │ └── vegetable_price.csv ├── src │ ├── preprocess.py │ ├── lstm_model.py │ ├── train.py │ └── predict.py └── notebooks └── EDA.ipynbREADME.md就是标题里说的“项目说明”先读它重点看三块数据集字段解释、训练入口命令、预测结果输出到哪里。不要一上来就改代码先按原样跑通再做迭代。环境安装这一步最常见的坑是 TensorFlow 和 Python 版本打架稳妥做法是新建虚拟环境。python -m venv .venv source .venv/bin/activate pip install -r requirements.txtWindows 环境把第二条换成.venv\Scripts\activate即可。装完依赖以后先做一次冒烟测试python -c import tensorflow as tf; print(tf.__version__)能打印版本再继续。蔬菜价格这类项目的数据量通常只有几千行普通 CPU 也完全能训练不用一开始就纠结 GPU 环境我自己第一次跑通这类代码就是在笔记本 CPU 上完成的慢一点但足够判断模型方向对不对。3.2 数据预处理脚本日期补全、归一化与滑窗数据预处理是整套项目里决定成败的一步。常见的数据集格式是长表至少包含date、variety、price三列。下面这段以单品种黄瓜为例import pandas as pd from sklearn.preprocessing import MinMaxScaler df pd.read_csv(data/vegetable_price.csv, parse_dates[date]) df df.sort_values(date) # 如果有多个品种先筛一条序列出来 df df[df[variety] 黄瓜].set_index(date)[price] price df.resample(D).ffill().dropna() # 按时间顺序分割留出最后 20% 做验证 split_idx int(len(price) * 0.8) - 7 train, test price.iloc[:split_idx], price.iloc[split_idx:] scaler MinMaxScaler(feature_range(0, 1)) train_scaled scaler.fit_transform(train.values.reshape(-1, 1)).flatten() test_scaled scaler.transform(test.values.reshape(-1, 1)).flatten() X_train, y_train make_sequences(train_scaled, n_in7) X_test, y_test make_sequences(test_scaled, n_in7) X_train X_train.reshape(-1, 7, 1) X_test X_test.reshape(-1, 7, 1)这段代码有四个关键参数。resample(D)是把原始日期重采样成自然日周末或节假日没有报价时用ffill()把上一个有效价填进去这一步能保证时间间隔均匀。0.8是切分比例价格预测任务里时间顺序是最重要的不能用随机抽样。MinMaxScaler把价格映射到 0 到 1 之间LSTM 用 MSE 损失训练时对归一化区间很敏感。最后reshape(-1, 7, 1)中的1代表特征数。注意一个很容易忽略的问题split_idx要预留至少 7 天否则make_sequences在测试集上可能生成空数组代码不报错但后续训练会崩溃。如果数据集里已经有缺失日期resample(D)会自动补出这些缺失索引非常有用。3.3 训练与预测代码一个可以跑出数值的最小 LSTM预处理完成后训练代码可以精简到一个 15 行的 Keras 模型from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout model Sequential([ LSTM(64, return_sequencesTrue, input_shape(7, 1)), Dropout(0.2), LSTM(32, return_sequencesFalse), Dense(1) ]) model.compile(optimizeradam, lossmse) model.fit(X_train, y_train, epochs60, batch_size32, validation_data(X_test, y_test), verbose1) pred_scaled model.predict(X_test) pred scaler.inverse_transform(pred_scaled.reshape(-1, 1))这里第一层LSTM(64, return_sequencesTrue)输出仍然是时间步序列好让第二层LSTM(32)继续提取更高层的时间依赖。Dropout(0.2)在两层之间随机丢弃 20% 的神经元输出防止小数据集上过拟合。Dense(1)是输出层预测归一化后的明日价格最后用scaler.inverse_transform还原成真实的“元/公斤”数值。epochs60和batch_size32是起步值。蔬菜价格数据量通常只有几百到几千个样本60 轮足够看到收敛趋势如果验证集 loss 还在持续下降就加轮数如果验证集 loss 已经反弹就减少轮次。lossmse对价格回归任务比较直接它的梯度对偏离较大的样本更敏感适合把尖峰价格作为重要信号来拟合。如果项目说明里写的是 PyTorch 版本核心逻辑也是完全一样的用nn.LSTM(input_size1, hidden_size64, batch_firstTrue)然后在输出接一个nn.Linear(64, 1)。Keras 和 PyTorch 的差异主要在 API 风格而不是模型结构。4. 把虚高精度打回原形units、滑窗、学习率和评估指标怎么设模型能跑通之后下一件事是搞清楚它到底行不行。这个阶段最容易出现“训练 loss 很低但在真实预测里一塌糊涂”的翻车现场。原因是很多人只看训练曲线不看回测指标或者用了全数据集的统计量做归一化导致验证集结果虚高。下面从参数和指标两个维度把问题拆开。4.1 四个必须调的参数units、timesteps、batch_size、learning_rate调参这事多少带点玄学但四个参数是有章法的参数常见范围影响我的一般调法units32 / 64 / 128模型容量过大过拟合从 64 起步验证集不降再调大timesteps7 / 14 / 30滑窗长度至少覆盖一个自然周有月周期用 30batch_size16 / 32 / 64梯度更新稳定性样本不足一万用 32learning_rate1e-3 / 5e-4 / 1e-4收敛速度与稳定性Adam 默认 1e-3不收敛再减半timesteps是这里面最值得花时间调的。蔬菜价格有很强的七天周期所以 7 是最小合理值。如果只取 7 天模型看到的是“上周今天到昨天”能捕捉周内波动如果要预测节前涨价这类更慢的变化可以把窗口加到 14 或 30代价是样本数量减少。以三年日数据为例约 1000 天数据窗口从 7 加到 30样本大概减少 23 个影响不算大。units决定了 LSTM 的记忆容量。单层units32适合几百条样本的小数据集两层堆叠时第一层 64、第二层 32 是我用得比较稳的组合。再往上加到 128 或 256 时对小数据集几乎必然过拟合除非同时加大 Dropout。batch_size很容易被忽略。小数据集上batch_size16会让每个 batch 的梯度波动太大训练曲线抖得像心电图batch_size64又可能让收敛变慢。我一般固定 32先跑通再用早停兜底。learning_rate如果代码里没有特别设置就是 Adam 默认的 0.001。这个值在多数价格预测任务里能工作但如果你发现训练 loss 在某个值附近来回震荡不降也不升把学习率改成0.0005或0.0001往往立竿见影。最后提醒一点多步预测时n_out会影响滑窗函数的分割逻辑。预测未来 3 天时make_sequences里n_out3输出层要从Dense(1)改成Dense(3)否则维度对不上。4.2 用 MAPE/RMSE 判断能不能用别只看拟合曲线训练完成后模型自带的loss是归一化空间里的数值不能直接拿去跟业务方说“误差 0.01”。必须把预测值和真实值都还原到原始价格区间再算业务指标。from sklearn.metrics import mean_squared_error, mean_absolute_error # y_test 来自滑窗函数pred 是模型预测还原后的价格 rmse mean_squared_error(y_test.flatten(), pred.flatten(), squaredFalse) mae mean_absolute_error(y_test.flatten(), pred.flatten()) mape float(np.mean(np.abs((y_test.flatten() - pred.flatten()) / y_test.flatten()) * 100)) print(fRMSE{rmse:.2f} 元/公斤) print(fMAE{mae:.2f} 元/公斤) print(fMAPE{mape:.2f}%)三个指标各有用途。RMSE 对异常大的误差惩罚更重适合你关心“会不会有一天突然预测错得很离谱”的场景MAE 是最直观的平均偏差比如 MAE0.3 就代表平均每天预测值差三毛钱MAPE 是百分比误差方便跟非技术同事沟通。但 MAPE 有一个坑当真实价格接近 0 时除法会把单个样本的误差放大到百分之几千。蔬菜里出现促销价、清仓价、记录异常价时尤其明显。所以对外报告 MAPE 前我会先过滤掉y_test 0.5的样本或者干脆用对称 MAPE也就是分子除以真实值和预测值的平均值。否则你手上那个 3% 的“漂亮指标”可能只是幸存者偏差。4.3 早停和学习率衰减给训练上两道保险训练曲线下降一段时间后会进入平台期这时候再硬跑 200 个 epoch 不仅浪费时间还可能把验证集 loss 重新拉高。我的做法是挂上早停和学习率衰减两个回调。from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau early_stop EarlyStopping( monitorval_loss, patience20, restore_best_weightsTrue ) lr_schedule ReduceLROnPlateau( monitorval_loss, factor0.5, patience5, min_lr1e-5 ) model.fit(X_train, y_train, epochs200, batch_size32, validation_data(X_test, y_test), callbacks[early_stop, lr_schedule], verbose1)patience20表示验证集 loss 连续 20 个 epoch 没有下降就停止训练这比肉眼盯曲线靠谱得多。restore_best_weightsTrue会在停止后自动回滚到验证集表现最好的那一轮权重相当于给训练吃了后悔药。ReduceLROnPlateau的patience5则是说 5 轮没下降就把学习率乘 0.5最低降到 1e-5这样模型有机会从平台期里跳出来。如果跑多次训练结果差异很大还有一个小技巧在代码开头固定随机种子。import numpy as np import tensorflow as tf np.random.seed(42) tf.random.set_seed(42)这样可以保证每次运行出来的预测曲线基本一致方便对比参数调整的效果。否则你很难判断某一轮精度提升到底是参数起作用还是运气好。5. 避坑蔬菜价格预测里最常见的 5 个翻车现场下面几条全是用项目里的血泪经验换来的。每条我都按“现象 → 原因 → 解决”写方便你直接对照排查。5.1 坑1拿全数据集做归一化模型在“偷看未来”现象回测时 RMSE 很漂亮预测曲线跟真实值重叠度极高但一旦把模型放到真实环境里用它预测未来一周结果明显漂移。原因很多人习惯把整列价格一次性丢进MinMaxScaler.fit_transform这会让min和max包含未来价格信息。模型在训练阶段已经间接看到了未来价格的取值范围这叫数据泄漏回测成绩自然虚高。蔬菜价格三年里可能有极端高价出现过训练集根本没遇到那么高的值但归一化时已经知道它的存在。解决严格按时间切分先切训练集和验证集再在训练集上fit缩放器验证集和测试集只做transform。如果用滚动预测做在线更新每次只用截至当前时刻的窗口重新fit缩放器心里才踏实。5.2 坑2预测曲线总是滞后一天现象把预测值和真实值画在一张图里预测线明显比真实线晚一天真实价格拐头向下时预测还在往上涨好像一直在追昨天的行情。原因LSTM 在均方误差的驱动下发现“复制上一个值”比学习真实趋势更省力尤其是在价格序列本身平滑、日间变化小的数据集上模型的最优策略变成了惯性外推。这不是模型坏了而是任务设置有问题。解决把预测目标从原始价格改成一阶差分让模型预测“今天比昨天涨跌多少”训练结束后再把差分还原成价格。改造方法是对序列做差分后再归一化、滑窗预测出的差分值累加到最近一天真实价格上。另一个辅助手段是把滑窗从 7 加到 14 或 30让模型看到更长趋势降低学习率有时也能打破模型的惰性。5.3 坑3日期缺失导致周周期被切碎现象预测结果在周一或周五的误差明显大于其他日子而且误差方向不固定。原因批发市场在周末或法定假日不更新价格CSV 里的日期不是连续的。直接按行读数据做滑窗时“间隔三天的两个价格”被当成了相邻样本7 天窗口实际跨了 10 个自然日周一和周五错位后模型学到的周期就被打乱了。解决读取数据后立刻把日期解析成datetime并执行set_index(date).resample(D).ffill()。这样窗口里的第 1 天和最后 1 天严格相隔 6 个自然日。如果担心ffill让连续多日填同一个值导致信息重复可以在特征里加一列“距离最近有效价格的天数”让模型知道哪些值是补出来的。5.4 坑40元价格把 MAPE 打爆现象模型训练一切正常但一算 MAPE 就出现 300%、500% 这种不可解释的数字。原因数据里存在 0 元或极低价记录可能是清仓、赠品、录入错误。MAPE 的分母是真实值除数为 0 时单条样本的误差就是无穷大即使真实值是 0.1 元预测值 1 元也会产生 900% 的误差把整体指标搅浑。解决预处理阶段加一条清洗规则过滤掉price 0的记录如果担心连续日期断裂先过滤再补全补全后的价格不可能为 0。评估指标上要么用 MAE 和 RMSE 代替 MAPE要么在计算 MAPE 前把真实值小于 0.5 元的样本剔除并注明统计口径。5.5 坑5shuffle 打乱时间顺序LSTM 学了个寂寞现象训练 loss 快速下降验证集 loss 却忽高忽低曲线完全没有规律偶尔验证集特别好但换一段日期后又完全失灵。原因滑窗生成的样本之间存在大量时间重叠相邻样本的输入和输出只差一天。如果model.fit里保持默认的shuffleTrue训练样本会被全局打乱模型在一个 batch 里先学第 300 天的样本再学第 100 天的样本时间连续性被彻底破坏。更糟的是随机抽样会让部分验证样本出现在训练样本的滑窗范围内形成另一种形式的泄漏。解决训练时显式设置shuffleFalse。更严格的做法是在滑窗之前就先按时间切出训练段和验证段两段不重叠验证集永远取时间上靠后的那一段而不是随机抽样。这样评估的才是“用过去预测未来”的真实能力。6. 让预测能对外汇报滚动预测与多品种并行6.1 滚动预测让模型输出未来七天训练好的模型一次只能预测明天。要预测未来一周常见做法是滚动预测把预测出的明天价格拼回输入序列尾部再预测后天如此往复。下面是一个通用函数def forecast_future(model, scaler, recent_scaled, n_in, steps): inputs recent_scaled[-n_in:].reshape(1, n_in, 1) preds [] for _ in range(steps): yhat model.predict(inputs, verbose0)[0, 0] preds.append(yhat) inputs np.roll(inputs, -1, axis1) inputs[0, -1, 0] yhat return scaler.inverse_transform(np.array(preds).reshape(-1, 1))np.roll把输入窗口整体左移一位再把新预测值放到最后一位下一次预测就基于包含预测值的窗口继续往后推。这个方法的优点是模型不用改缺点是误差会累积预测到第 5 天以后基本只有趋势参考价值。对外汇报时我会把滚动预测结果写成“未来 3 天参考价”而不是精确到几毛钱。6.2 多品种并行一个模型还是多个模型如果数据集里不止黄瓜一种菜两个方向都能走按品种各训一个模型或者把所有品种拼成多维特征交给一个模型。品种少时我一般选前者代码不用动循环每个品种跑一遍就行品种超过 10 个时共享模型更省事把价格矩阵直接做成(样本数, n_in, 品种数)输入即可。多变量 LSTM 能学到品种之间的联动比如叶菜普涨时根茎类往往跟涨这类关系在单品种模型里是学不到的。最后讲一个我自己的习惯预测做完不急着画漂亮曲线先把最近 30 天的预测值和真实值放在同一张表里按日期逐行核对。曾经有一次我感觉模型效果不错后来发现预测结果整体比真实值早了一天原因是滑窗前没有把日期索引停留在一个自然日上。多造几个这种对照表比任何调参技巧都更能避免返工。希望这些踩坑记录能让你少走几段弯路也祝你的蔬菜价格预测项目能顺利跑出可信的结果。本文还有配套的精品资源点击获取
返回列表