尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

LSTM蔬菜价格预测实战:数据预处理、模型训练与避坑指南

发布时间:2026/9/28 16:50:43

资讯中心
01
ARTICLE

LSTM蔬菜价格预测实战:数据预处理、模型训练与避坑指南

LSTM蔬菜价格预测实战:数据预处理、模型训练与避坑指南
简介基于深度学习LSTM的蔬菜价格预测Python源码项目面向数据挖掘、时间序列预测方向的开发者与学生旨在解决农产品价格波动预测问题。资源完整覆盖爬虫采集、数据清洗、模型训练与Web展示全流程代码中应用了scrapy、beautifulsoup、pandas、numpy、sklearn、statsmodels、pyflux及flask等主流库并集成微信公众号查询模块用户可便捷查看预测结果。压缩包共181个文件以142个CSV数据文件如本地菜心、西红柿等蔬菜价格及天气数据、25个Python脚本爬虫、数据处理、LSTM预测、Web渲染等为主另有10个pyc编译文件、2个docx项目说明和2个md文档包体仅1.66MB结构清晰便于按需查阅。目前已有247人学习下载非常适合具备一定Python基础、希望实战深度学习与时间序列预测的读者。通过本资源可获得一整套可运行的工程源码快速复现从数据抓取、清洗到LSTM建模、结果可视化及Web/公众号部署的完整链路同时领会多种预测方法对比与项目文档撰写要点为农业数据智能化应用提供直接参考。1. 蔬菜价格预测到底在预测什么从一条菜价曲线到能用的LSTM模型把过去30天的菠菜价格喂给LSTM让它说出明天菠菜大概多少钱——这件事听着有点玄学但只要数据干净、窗口合理、评估指标对它确实能抓住短期涨跌的节奏尤其在季节交替和节假日前后。价格预测的难点不在模型本身而在数据泄露、滞后和误差累积这些看不见的坑里。这篇笔记按“数据预处理→模型搭建→训练评估→避坑排查”的顺序把一条能完整跑通的Python实现路线写清楚适合想用深度学习做时间序列预测的从业者也适合课程设计、量化交易研究之前先搭一套靠谱的baseline。2. 把价格数据变成LSTM能吃的样本归一化、滑动窗口与切分2.1 原始数据长什么样先做三件“笨”事拿到的蔬菜价格数据不管是自己爬的还是课题组分发的通常就是一张csv表至少包含三个字段日期、品种、价格单位一般是元/公斤。有的还会带批发市场名称、最低价和最高价。做预测前先别急着上模型有三件“笨”事必须做确认日期连续、补齐缺失值、把价格列转成float。我一般会先打印表头和describe()看看数据的时间跨度。常见的问题是周末没记录、节假日重复记录、同一品种在不同市场有两套价格。处理原则是先按品种分组再对每个品种单独排序日期。缺失的日期用前向填充ffill补上——蔬菜价格有连续性用前一天的价格顶替比插值更符合实际。import pandas as pd df pd.read_csv(vegetable_price.csv) df[date] pd.to_datetime(df[date]) df df.sort_values([variety, date]).reset_index(dropTrue) # 按品种前向填充缺失值并删除价格异常小于0或大于100元/公斤 df[price] pd.to_numeric(df[price], errorscoerce) df[price] df.groupby(variety)[price].ffill() df df[(df[price] 0) (df[price] 100)]这里用了两个关键操作sort_values保证时序顺序ffill把空值用前一条记录填上。注意先groupby再ffill避免不同品种之间串数据。价格过滤虽然粗暴但能挡掉单位写错、小数点漏打这类脏数据。做完这三件事再去看每个品种有多少条记录少于200条的品种不建议单练模型样本太少LSTM学不出规律。2.2 归一化为什么价格序列必须先压缩到0-1LSTM用的是tanh和sigmoid激活输入数值如果直接是5到15的菜价激活函数很容易进入饱和区梯度消失会非常快。更实际的原因是归一化之后的MSE loss数值大小才有解释意义训练曲线也更容易诊断。常见做法是用sklearn的MinMaxScaler把价格压到[0,1]区间。这里有一个新手很容易踩的坑fit scaler只能用训练集不能拿全量数据fit。我见过有人先对整列价格做归一化再切分数据验证集和测试集的信息早就混进了scaler里评估出来的误差全是虚低的这在业内叫“归一化泄露”。from sklearn.preprocessing import MinMaxScaler # 只fit训练集再transform验证集和测试集 scaler MinMaxScaler() train_price train_df[price].values.reshape(-1, 1) scaler.fit(train_price) train_df[price_norm] scaler.transform(train_df[price].values.reshape(-1, 1)) val_df[price_norm] scaler.transform(val_df[price].values.reshape(-1, 1)) test_df[price_norm] scaler.transform(test_df[price].values.reshape(-1, 1))MinMaxScaler对异常值很敏感如果训练集里有一个极端高价的记录大部分正常价格会被压缩到0到0.3之间模型很难分辨细微差异。所以我一般先做分位数裁剪把1%以下和99%以上的价格替换成边界值再喂给scaler。这在小样本的蔬菜价格数据里尤其重要一个台风天暴涨的菜价就能毁掉整条序列的分布。2.3 滑动窗口构造样本用前7天预测下一天LSTM处理的是序列不能把一整年的价格一次性塞进去而是要切成固定长度的窗口。窗口长度seq_len代表“回头看多少天”这是整个项目里最需要手动试的参数。做日度价格预测7天和15天最常见如果数据是小时级的窗口要相应拉长到24或48。import numpy as np def create_sequences(data, seq_len7): xs, ys [], [] for i in range(len(data) - seq_len): x data[i:i seq_len] y data[i seq_len] xs.append(x) ys.append(y) return np.array(xs), np.array(ys) seq_len 7 x_train, y_train create_sequences(train_df[price_norm].values, seq_len) x_val, y_val create_sequences(val_df[price_norm].values, seq_len) x_test, y_test create_sequences(test_df[price_norm].values, seq_len)每次取连续7天的价格作为输入x第8天的价格作为预测目标y窗口每滑动一天生成一个样本。这样原本1000天的价格序列能生成993个样本数据量一下子多了两个数量级。注意这里返回的x是(样本数, 7, 1)的三维结构LSTM要求输入形状是(batch, seq_len, input_size)所以最后一个维度是特征数目前只有价格这一个特征。再用PyTorch的Dataset和DataLoader包装一下训练时按batch取数据from torch.utils.data import Dataset, DataLoader import torch class PriceDataset(Dataset): def __init__(self, xs, ys): self.xs torch.tensor(xs, dtypetorch.float32) self.ys torch.tensor(ys, dtypetorch.float32) def __len__(self): return len(self.xs) def __getitem__(self, idx): return self.xs[idx], self.ys[idx] train_loader DataLoader(PriceDataset(x_train, y_train), batch_size32, shuffleTrue) val_loader DataLoader(PriceDataset(x_val, y_val), batch_size32, shuffleFalse) test_loader DataLoader(PriceDataset(x_test, y_test), batch_size32, shuffleFalse)DataLoader里shuffleTrue只用在训练集验证集和测试集必须保持原始时间顺序。这关系到后面评估时能不能画出正确的预测曲线如果shuffle了画出来的预测值和真实值会像两条对不上的乱线。train/val/test的切分比例我一般按7:1.5:1.5按时间顺序切绝不随机打乱。3. 搭建LSTM价格预测模型PyTorch实现与参数设定3.1 LSTM为什么能记住“菠菜上周涨过价”普通RNN在反向传播时梯度要连乘整个时间步的权重矩阵序列一长梯度就指数级消失模型只能记住最近两三天。LSTM引入了细胞状态和三个门遗忘门、输入门、输出门让信息可以沿着时间步线性传递梯度衰减问题被大幅缓解。放到菜价场景里菠菜价格在一个月前因为下雨涨过一波LSTM的细胞状态会把“涨过”这个信息保留下来等下次出现类似天气信号时再唤醒。这不是玄学而是门结构带来的长程记忆能力。用PyTorch实现一个最简版本并不复杂整体结构就是LSTM层 全连接输出层。核心在forward里LSTM返回的out是每个时间步的输出我们只取最后一个时间步的hidden state因为它汇总了整个窗口的信息。import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size1, hidden_size32, num_layers1, dropout0.0): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0.0, ) self.regressor nn.Linear(hidden_size, 1) def forward(self, x): out, _ self.lstm(x) # out: (batch, seq_len, hidden_size) return self.regressor(out[:, -1, :]) # 取最后一个时间步batch_firstTrue表示输入形状是(batch, seq_len, input_size)这和create_sequences生成的张量形状正好对得上。nn.LSTM的num_layers如果大于1默认层之间没有dropout必须显式传入dropout参数才会在层间加。input_size是输入特征数目前只有价格一列所以是1后面加多特征时改成对应数值即可。3.2 训练最小循环损失函数、优化器与学习率模型建好之后训练循环本身很套路。损失函数选MSELoss因为价格预测是回归任务MSE对大误差惩罚重能逼着模型避开极端预测。优化器用Adam初始学习率取0.001这是LSTM训练里最不容易翻车的组合。model LSTMPredictor(input_size1, hidden_size32, num_layers1) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr0.001) for epoch in range(100): model.train() train_loss 0.0 for xb, yb in train_loader: optimizer.zero_grad() pred model(xb) loss criterion(pred.squeeze(), yb.squeeze()) loss.backward() optimizer.step() train_loss loss.item() train_loss / len(train_loader) model.eval() val_loss 0.0 with torch.no_grad(): for xb, yb in val_loader: pred model(xb) val_loss criterion(pred.squeeze(), yb.squeeze()).item() val_loss / len(val_loader) if (epoch 1) % 20 0: print(fepoch {epoch1:3d} train {train_loss:.5f} val {val_loss:.5f})这里有个细节训练时model.train()启用dropout验证时model.eval()关掉dropout。很多人写完训练循环发现验证loss比训练loss还高一大截先检查是不是忘了切eval()模式。另外一个常见问题是验证loss波动大得像心电图多半是batch_size太小8以下梯度每个batch的方向差异太大一般调大到32或64能缓解。3.3 五个需要手工调的超参数LSTM的参数敏感性比CNN高调参是逃不掉的。我从项目经验里挑出影响最大的五个参数按优先级排列参数常用取值调参依据seq_len窗口长度7 / 15 / 30价格周期长度蔬菜日度数据7天覆盖一周周期hidden_size隐层维度16 / 32 / 64隐层越大记忆容量越大但小样本容易过拟合num_layers层数1 / 2数据量少于5000条用1层就够2层训练更难收敛dropout0.0 / 0.2过拟合明显时再加小模型默认0learning_rate0.001 / 0.0005学习率太大loss震荡太小收敛极慢hidden_size不用一味加大32在大多数蔬菜价格数据上都够用。判断依据很简单如果验证loss一路下降但测试集MAPE反而变高就是容量过剩了把hidden_size减半试一次。seq_len配合业务规律选比如菠菜从播种到上市大约30天但价格数据里的记忆效应一般只有7到10天窗口太长反而把噪声也学进去了。4. 评估与模型保存预测结果到底准不准4.1 早停机制怎么判断“再训下去要过拟合”固定训100轮不是好习惯。价格数据量小LSTM跑到后面训练loss还在降、验证loss已经开始反弹这就是过拟合信号。常见做法是加早停Early Stopping记录验证loss最低的那一轮连续patience轮没有新低就停止训练恢复最佳模型参数。best_val_loss float(inf) patience 15 counter 0 for epoch in range(200): # ... 训练和验证代码 ... if val_loss best_val_loss: best_val_loss val_loss counter 0 torch.save(model.state_dict(), best_model.pt) else: counter 1 if counter patience: print(fearly stop at epoch {epoch1}) breakpatience取值15到20比较稳妥因为验证loss本身有波动patience太小会在正常波动时误停。torch.save保存的是模型参数不是整个模型加载时先构造相同结构的LSTMPredictor再load_state_dict。训练结束后用best_model.pt做测试集评估而不是用最后一轮的模型。4.2 评估指标RMSE、MAPE与滞后检查模型训完两个指标必须看RMSE反映绝对误差单位是元/公斤MAPE反映相对误差直接对应“预测偏差百分之多少”。MAPE对价格预测更直观因为不同蔬菜价格基数差距大西红柿3块钱和生姜12块钱同样0.5元的RMSE含义完全不同。from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error preds, y_true [], [] model.eval() with torch.no_grad(): for xb, yb in test_loader: preds.append(model(xb).squeeze().numpy()) y_true.append(yb.squeeze().numpy()) preds np.concatenate(preds) y_true np.concatenate(y_true) # 反归一化回原始价格单位 preds_raw scaler.inverse_transform(preds.reshape(-1, 1)).ravel() y_true_raw scaler.inverse_transform(y_true.reshape(-1, 1)).ravel() rmse np.sqrt(mean_squared_error(y_true_raw, preds_raw)) mape mean_absolute_percentage_error(y_true_raw, preds_raw) * 100 print(fRMSE: {rmse:.3f} 元/公斤, MAPE: {mape:.2f}%)评估完指标别急着高兴还差一步把预测值和真实值按时间顺序画在一张图上肉眼看滞后。如果预测曲线整体比真实曲线“晚上移”一天到两天说明模型学到的主要是“今天价格≈昨天价格”这样的模型在价格平稳时表现很好一到涨跌转折点就废了。滞后问题光看MAPE看不出来必须画图。4.3 保存与加载让模型变成可复用的文件训练好的模型要落盘方便下次直接加载预测不用重训。保存整个模型和只保存参数各有利弊整个模型用torch.save(model, path)加载简便但PyTorch版本升级后可能不兼容只存参数更干净但加载时必须有相同的模型结构代码。推荐后一种结构代码本来就在项目里。# 保存 torch.save(model.state_dict(), vegetable_price_lstm.pt) # 加载 model LSTMPredictor(input_size1, hidden_size32, num_layers1) model.load_state_dict(torch.load(vegetable_price_lstm.pt, map_locationcpu)) model.eval()实际预测新一天价格时输入的是最近seq_len天的归一化价格经过模型前向得到归一化预测值再inverse_transform回真实价格。这个流程可以作为独立脚本封装输入一个品种的近期价格列表输出明天的预测价。5. 避坑排查五个让价格预测模型翻车的细节5.1 归一化泄露验证集效果虚高的最常见原因现象训练loss正常下降验证集和测试集的MAPE低到离谱但模型部署到真实场景后误差暴涨两倍以上。原因全量数据fit了MinMaxScaler再切分train/val/test。scaler的min和max是从整段数据里算出来的等于让模型在训练时“偷看”了未来价格的范围。测试集的分布信息提前暴露给模型评估结果自然虚高。解决严格按时间顺序先切分数据再对训练集单独scaler.fit()验证集和测试集只transform()。这个顺序错一步后面所有评估都不可信。5.2 随机打乱数据时序数据切分的大忌现象验证集和测试集表现尚可但画出来的预测曲线乱成一团完全对不上时间轴。原因写代码时用了sklearn的train_test_split(shuffleTrue)把时间顺序打乱了。LSTM的窗口样本之间存在强时间依赖乱序切分会让训练集里混入未来的信息模型学到的时间模式全乱了。解决时序数据一律按比例顺序切分可以在代码里写死split_idx int(len(df) * 0.7)这样的切分点。如果要验证模型的泛化能力用TimeSeriesSplit做时间交叉验证不要用普通K折。5.3 预测值总是滞后一天模型学成了“复制昨天的价格”现象预测曲线形状和真实曲线几乎一致但整体右移一天转折点处误差最大。原因单步预测的baseline本质是“明天价格≈今天价格”LSTM很容易学到这个捷径因为菜价短期确实平稳这样MSE已经很小模型没有动力去学更复杂的涨跌逻辑。解决在特征里加入差分项即“今天价格减去昨天价格”让模型看到价格变化率而不是只看绝对值。或者把预测目标改成未来3天的价格均值迫使模型跳出“复制昨天”的舒适区。5.4 多步预测发散误差累积的“滚雪球”效应现象单步预测MAPE只有5%但用模型滚动预测未来7天时第3天之后曲线开始飘第7天预测价直接离谱。原因多步预测是“用预测值当作下一步的输入”上一步的误差会传进下一步。菜价有均值回归的特性但LSTM在迭代输入时没有机制纠偏误差一累积就发散。解决三个止损方案。一是改用直接多步输出一次性输出未来7天的预测值不搞迭代输入二是限制滚动步数最多滚动3步就重新用真实数据校准三是在训练时加入噪声扰动让模型见过“带偏差的输入”增强鲁棒性。5.5 节假日与极端天气样本模型不是不知道是样本太少学不会现象平时MAPE很好但每逢国庆、春节前后预测价格明显偏低台风天更是一塌糊涂。原因节假日涨价和极端天气涨价是典型的低频事件一年就几次在几千条样本里占比太小LSTM的遗忘门很容易把这些“异常记忆”洗掉。解决把节假日标记做成一个0/1特征拼进输入让模型至少知道“明天是节气/节假日”。更实用的做法是单独训练一个“节假日修正器”用节假日前后各5天的数据拟合一个简单回归模型在主模型预测值上叠加修正量。数据量不够模型学就让规则来补。6. 进阶多步预测与多特征融合的两个实用技巧先讲多步预测的“直接输出法”。把输出维度从1改成HH是预测天数训练时标签取未来H天的真实价格序列。这样做最大的好处是避免迭代输入的误差累积模型一次把未来7天的价格都吐出来各天的误差互相独立不会滚雪球。代价是模型容量要稍微加大hidden_size从32调到48左右训练损失可能比单步略高但多步预测的可靠性提升明显。多特征融合是提精度的另一条路。价格不只是价格自身的延续天气、节假日、同类蔬菜价格都会影响明天菜价。常见的做法是把多个特征拼成input_size维向量喂给LSTM温度、降雨量、节假日标记、同市场其他品种均价。注意所有特征都要参与同一套归一化流程节假日标记这种0/1变量不用归一化和数值特征分开处理再concat。# 将价格、气温、节假日三个特征拼接为 (seq_len, 3) data_seq np.stack([price_norm, temp_norm, holiday_flag], axis1) model LSTMPredictor(input_size3, hidden_size32, num_layers1)加特征时切忌一次加太多我现在的习惯是一次只加一个特征看验证loss有没有下降再决定留不留。加了一堆特征验证loss反而上涨说明那些特征是噪声而不是信号。我现在每次调整完窗口或特征一定会先画一次滚动预测曲线再谈指标指标会骗人曲线不会。从csv台账到能用的LSTM模型这条路大坑就集中在这五个地方绕开了剩下的就是调参的体力活。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。