ARTICLE DETAIL

资讯详情

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

LSTM股票预测Python代码全解析:从数据处理到模型训练

LSTM股票预测Python代码全解析:从数据处理到模型训练 简介基于Python实现LSTM对股票价格预测的完整项目源码包采用PyTorch框架搭建长短期记忆网络面向有深度学习基础、正在完成金融数据分析类课程设计或毕业设计的计算机相关专业学生也适合需要实战项目练习的初学者。资源共14个文件约359KB以Python源码为主含5个py脚本覆盖数据处理、LSTM模型定义、训练与评估等环节另有CSV股票数据、pkl参数缓存、环境依赖requirements.txt、说明文档及可视化结果图片目录结构清晰便于直接运行和二次开发。项目来自经导师指导并获98分的高分设计所有源码均已调试可运行目前已有89人学习下载能够帮助读者系统理解LSTM时间序列预测的完整流程是课程设计与项目实战的高质量参考。1. 这包 LSTM 股票预测代码为什么值得你直接下载这份基于 Python 的 LSTM 股票价格预测资源不是一个 200 行的教学 demo而是一套已经调通的端到端代码骨架。它把从原始行情数据解析、滑动窗口数据集构造到 PyTorch 里 LSTM 模型定义、训练、评估出图的完整链路全部拼好了目录里 parser_my.py、dataset.py、LSTMModel.py、train.py、evaluate.py 各管一段互不纠缠。你下载后要做的不是从零复现跑通而是先原样运行、再改参数、最后把它替换到自己的股票数据上。对正在做 Python 课程设计、期末大作业的在校生这是最省时间的起点对想快速搭一个 LSTM 时间序列基线的从业者它也是一个结构清爽的参考盘。前几天我拿到包后只改了数据路径和几个超参数就完整跑完了一轮训练和指标评估整个过程没有出现任何接口对不上的问题这类能一次跑通的资源在实际动手时比想象中更稀缺。2. 数据管道parser_my.py 与 dataset.py 决定的样本形状LSTM 股票预测的第一道工序不是搭模型而是把一张只有日期和价格的原始表处理成模型能吃的样本对。这个包里parser_my.py 负责把数据洗干净dataset.py 负责把一维价格序列切成 (X, y) 结构。这两份文件是你动手后第一个要读的因为后面模型训练的所有行为都受它们约束。2.1 用 parser_my.py 把原始行情转成标准化数据打开 parser_my.py它的核心职责是读取 data 目录下的原始数据文件统一列名、处理缺失值、按时间排序然后输出一个干净的 DataFrame 供下游使用。以最常见的 CSV 行情数据为例这个文件的处理逻辑通常是这样# parser_my.py 核心解析逻辑示意 import pandas as pd from sklearn.preprocessing import MinMaxScaler # 读取原始行情date 列为日期close 列为收盘价 df pd.read_csv(data/stock_data.csv) df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) # 去掉收盘价为空的行停牌日通常会留下 NaN df df[df[close].notna()].reset_index(dropTrue) # 用 MinMaxScaler 把价格归一化到 [0, 1] 区间LSTM 训练更稳定 scaler MinMaxScaler(feature_range(0, 1)) df[close_scaled] scaler.fit_transform(df[[close]].values)这里有两件事值得注意。第一件是sort_values(date)股票数据天然按时间排列但如果你从不同渠道拼接数据经常会出现乱序LSTM 对序列顺序极其敏感乱序会让模型学到完全错误的依赖关系。第二件是归一化股票价格动辄几十到几百如果不做缩放直接喂给 LSTM梯度很容易爆炸训练 loss 会像心电图一样剧烈抖动。常见做法是用 MinMaxScaler 把价格压到 [0,1] 或 [-1,1]这个 scaler 对象必须保存下来后面 evaluate.py 里做反归一化还要用到它包里的 evaluate.py 就依赖这个对象把预测结果还原成真实价格。2.2 dataset.py 的滑动窗口怎么把一列价格变成 (X, y) 样本LSTM 不能直接吃一整段价格序列来做预测它需要的是「用过去 N 天预测第 N1 天」的监督学习样本。这个切分过程在 dataset.py 里完成用的是典型的滑动窗口法。假设你设定 seq_len60那么第 0 到第 59 天的价格是 X第 60 天的价格是 y接着窗口滑动一天第 1 到第 60 天是 X第 61 天是 y以此类推。# dataset.py 滑动窗口数据集构造示意 import torch from torch.utils.data import Dataset class StockDataset(Dataset): def __init__(self, prices, seq_len60): prices: 归一化后的价格序列一维数组 seq_len: 回看窗口长度决定用多少天预测下一天 self.seq_len seq_len self.X, self.y [], [] for i in range(len(prices) - seq_len): self.X.append(prices[i:i seq_len]) # 窗口内的历史价格 self.y.append(prices[i seq_len]) # 窗口后一天的真实价格 # 每个样本形状为 (seq_len, 1)unsqueeze(-1) 补上特征维度 self.X torch.tensor(self.X, dtypetorch.float32).unsqueeze(-1) self.y torch.tensor(self.y, dtypetorch.float32).unsqueeze(-1) def __len__(self): return len(self.X) def __getitem__(self, idx): return self.X[idx], self.y[idx]这段代码里最关键的是unsqueeze(-1)。经过这步操作X 的每个样本从 (seq_len,) 变成 (seq_len, 1)也就是 60 天、每天 1 个特征。PyTorch 的 LSTM 默认要求输入是 (batch, seq_len, input_size) 的三维张量这里 input_size1 表示只用收盘价这一列特征。如果你想加入成交量、开盘价等更多特征就把每个时间步的特征数从 1 改成对应维度模型第一层的 input_size 也要同步调整。窗口长度的选择直接影响预测效果。seq_len 太短比如 5 天模型看不到中期趋势预测结果基本跟着最近几天的涨跌乱跳seq_len 太长比如 250 天训练样本数量大幅减少而且过早的历史价格对当前预测的参考价值已经很低。我一般会在 30 到 90 之间试几个值对比验证集 RMSE 再定。这个参数是这份代码里你最值得动手调的第一个旋钮。3. 模型与训练LSTMModel.py 到 train.py把 LSTM 跑起来的每个参数数据管道准备好之后接下来的重头戏是模型定义和训练流程。这两块分别对应 LSTMModel.py 和 train.py是这份资源里技术含量最高的部分。读懂这两个文件你才算真正掌握了这套代码的主动权。3.1 LSTMModel.py两层 LSTM 加回归头的结构拆解LSTMModel.py 里定义的网络结构非常标准一个两层 LSTM 接一个全连接回归层输出一个标量作为下一天的预测价格。核心结构大致如下# LSTMModel.py 模型结构示意 import torch.nn as nn class LSTMModel(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, # 特征维度只用收盘价所以是 1 hidden_sizehidden_size, # 隐层神经元数量控制模型容量 num_layersnum_layers, # LSTM 堆叠层数 batch_firstTrue, # 输入形状为 (batch, seq_len, input_size) dropoutdropout # 层间 dropout防止过拟合 ) self.fc nn.Linear(hidden_size, 1) def forward(self, x): # out 形状(batch, seq_len, hidden_size) out, _ self.lstm(x) # 取最后一个时间步的隐状态接全连接层输出预测值 last_hidden out[:, -1, :] pred self.fc(last_hidden) return pred这段代码里最容易忽略的是out[:, -1, :]。LSTM 在每个时间步都会输出一个隐状态整个序列输出形状是 (batch, seq_len, hidden_size)。做单步预测时我们需要的是序列最后一个时间步的信息它编码了前面所有历史数据的摘要所以取out[:, -1, :]。如果你误取成out[:, 0, :]模型就只能看到序列开头的信息预测结果基本没参考价值。hidden_size 和 num_layers 是这层结构里最重要的两个超参数。hidden_size 太小比如 16模型学不到足够复杂的价格模式hidden_size 太大比如 512训练变慢且极易过拟合。对股票价格这种信噪比很低的数据64 到 128 通常是个合理的区间。num_layers2 是时序预测里很常见的选择一层 LSTM 拟合能力有限三层以上在数据量不够时基本是过拟合的重灾区。dropout 只在层数大于 1 时生效我建议先设 0.2如果训练 loss 下降但验证 loss 反弹再往上调。3.2 train.py 训练流程学习率、轮数和模型保存train.py 承担了训练主循环的全部工作加载数据集、初始化模型和优化器、迭代训练、保存 checkpoint。核心训练逻辑可以概括成下面这段# train.py 核心训练逻辑示意 import torch import torch.nn as nn device torch.device(cuda if torch.cuda.is_available() else cpu) model LSTMModel(input_size1, hidden_size64, num_layers2).to(device) criterion nn.MSELoss() # 回归任务均方误差做损失 optimizer torch.optim.Adam(model.parameters(), lr0.001) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, patience5, factor0.5 ) for epoch in range(epochs): model.train() total_loss 0.0 for X_batch, y_batch in train_loader: X_batch, y_batch X_batch.to(device), y_batch.to(device) optimizer.zero_grad() pred model(X_batch) # 前向传播 loss criterion(pred, y_batch) # 计算 MSE loss.backward() # 反向传播 optimizer.step() # 更新参数 total_loss loss.item() # 每个 epoch 结束在验证集上评估一次据此调整学习率 val_loss evaluate(model, val_loader, criterion, device) scheduler.step(val_loss) # 保存模型参数预测时直接加载不用重新训练 torch.save(model.state_dict(), model/lstm.pth)训练这块有三点直接决定你能不能跑出一个像样的结果。第一是优化器选 Adam 并设置 lr0.001这是 LSTM 回归任务里被验证过无数次的起点配置新手不要一上来就换 SGD收敛速度会慢到让你怀疑人生。第二是ReduceLROnPlateau学习率调度器当验证 loss 连续 5 个 epoch 不下降时学习率自动减半这个小技巧能帮你在接近最优点时稳定下来避免在最优点附近来回震荡。第三是每个 epoch 结束就保存一次model.state_dict()这样即使训练中断你也能从最近的 checkpoint 恢复而不是从头再来。注意保存的是参数而不是整个模型对象这样加载时对模型结构的改动能灵活适配加载代码需要先实例化一个同结构的 LSTMModel再调用load_state_dict。如果你用的不是这份资源自带的训练入口而是想复用 train.py 的流程跑自己的数据要注意 train_loader 的 batch_size 建议设为 64 或 128不要超过 256。股票数据样本之间的相关性很强batch 太大容易让模型在每个 batch 内看到的信息过于单一batch 太小则梯度更新噪声太大训练曲线会一路毛刺。4. 评估与避坑evaluate.py 的正确用法与高频翻车点训练结束只是走完了一半路另一半是评估模型到底预测得准不准。evaluate.py 就是干这个的它负责在验证集上计算误差指标并把预测结果和真实价格画在一起生成 img 目录下的对比图。但这步也是最容易出问题的我见过太多人在这里翻车而且翻得莫名其妙。4.1 evaluate.py 怎么算指标反归一化和误差计算evaluate.py 的核心逻辑是加载训练好的模型在测试集上做预测然后把预测值和真实值从归一化空间还原回真实价格再计算 RMSE 和 MAE。关键点在于指标必须在反归一化之后计算# evaluate.py 评估逻辑示意 import numpy as np model.eval() # 切换为评估模式关闭 dropout with torch.no_grad(): pred_scaled model(X_test).numpy() true_scaled y_test.numpy() # 用之前保存的 scaler 把预测值和真实值还原为真实价格 pred_price scaler.inverse_transform(pred_scaled.reshape(-1, 1)) true_price scaler.inverse_transform(true_scaled.reshape(-1, 1)) # 计算误差指标此时单位是“元”有实际意义 rmse np.sqrt(np.mean((pred_price - true_price) ** 2)) mae np.mean(np.abs(pred_price - true_price)) print(fRMSE: {rmse:.2f}, MAE: {mae:.2f})这里最容易犯的错误是直接在归一化后的数值上算 RMSE。归一化后的误差是 0.03看起来很小但还原成真实价格可能是几块钱完全不是一个量级。所以我每次都会强调只要涉及误差指标展示就必须经过scaler.inverse_transform还原否则你报告里的数字对别人毫无意义甚至对你自己也起不到判断作用。另外model.eval()和with torch.no_grad():是评估阶段必须成对出现的东西。model.eval()会关闭 dropout 层让模型用全部神经元做预测保证结果确定torch.no_grad()会关闭梯度计算显著减少显存占用和推理时间。漏掉任何一个轻则预测结果有随机性重则显存溢出直接报错。4.2 五个高频翻车点按现象到解决排查这套代码我在复现和修改过程中踩过不少坑也帮别人排查过几轮这里挑五个最高频的写下来每一条都是「现象 → 原因 → 解决」的完整链路你遇到时可以直接对照排查。现象一训练 loss 降得很快但验证集预测曲线几乎是一条水平线完全跟不上真实价格的波动。原因是模型把输入序列里的时间顺序打乱了。常见错误是在构造 DataLoader 时开了shuffleTrue股票数据是强时序数据随机打乱后训练集里混入了未来信息模型学会了「偷看」但一到验证集就露馅。解决方法是训练集和验证集按时间顺序切分比如前 80% 做训练、后 20% 做验证DataLoader 里shuffleFalse只有在训练模式时才允许对训练集做随机采样。现象二预测结果比真实价格滞后一天整条预测曲线像是真实曲线向右平移了一个身位。原因是滑动窗口的构造方式错误把当天价格也包含进了 X。也就是说你用第 t 天的价格预测第 t 天模型只要记住「输出等于输入的最后一位」就能把 loss 压到很低但这种预测没有实际意义。解决方法是确认样本构造是「用第 0 到第 59 天预测第 60 天」X 和 y 之间至少要隔一天。你可以检查 dataset.py 里的循环边界如果range(len(prices) - seq_len)写成了range(len(prices) - seq_len 1)就说明 y 取了窗口最后一天。现象三训练过程中 loss 先是下降到某个 epoch 后验证 loss 突然飙升之后再也回不去。原因是学习率设置过大模型在最优点附近反复横跳甚至直接发散。之前就有过一次我把 lr 从 0.001 改成 0.01结果第 30 轮 loss 直接从 0.001 跳到 1e8整个模型参数全部打飞。解决方法是把学习率调回 0.001并确保ReduceLROnPlateau的 patience 设置合理让学习率在验证 loss 停滞时自动衰减而不是等到模型已经崩溃才介入。现象四加载保存的 checkpoint 时报错提示 key 对不上或者模型结构不匹配。原因是保存模型时用的是torch.save(model, ...)加载时model torch.load(...)依赖保存时的类定义路径一旦更换文件或者模型代码改了目录就反序列化失败。解决方法是只保存model.state_dict()加载时先手动实例化一个同结构模型再调用load_state_dict。如果你拿到的这份代码里已经有训练好的模型文件加载前务必核对 LSTMModel 的初始化参数是否与训练时一致。现象五运行 train.py 时报错 CUDA out of memory即使把 batch_size 调小到 16 仍然如此。原因是上一轮训练或评估的进程没有完全释放显存或者评估时忘了加torch.no_grad()导致显存里堆积了大量中间梯度张量。解决方法是先看任务管理器里有没有残留的 Python 进程再检查代码里所有推理路径是否都包在with torch.no_grad():里。我在自己机器上跑这个包时batch_size 设 128、两层 LSTM、hidden_size64显存占用大概只有 2GB如果你的显存占用异常高多半是泄漏了。5. 把训练的成果接回真实场景checkpoint 加载与滚动预测训练和评估都通过后下一步是把模型用起来——加载训练好的 checkpoint对最后一段已知价格做预测然后滚动外推未来几天。这个环节很多课程设计到交作业都没做但恰恰是它让这份代码从「能跑」变成「能用」。# predict.py 加载 checkpoint 并滚动预测未来 5 天示意 import torch import numpy as np from LSTMModel import LSTMModel model LSTMModel(input_size1, hidden_size64, num_layers2) model.load_state_dict(torch.load(model/lstm.pth, map_locationcpu)) model.eval() # recent_seq 是最近 60 天归一化后的价格形状为 (1, 60, 1) recent_seq torch.tensor(last_60_days, dtypetorch.float32).unsqueeze(0).unsqueeze(-1) future_days 5 with torch.no_grad(): for _ in range(future_days): next_pred model(recent_seq) # 预测下一天 next_pred next_pred.unsqueeze(-1) # 保持三维形状 recent_seq torch.cat((recent_seq, next_pred), dim1)[:, -60:, :]这段滚动预测的逻辑很直白每次用模型预测出下一天的价格就把这个预测值追加到序列末尾同时丢掉最开头的一天保持窗口长度恒为 60然后继续预测下一天。它等价于模型在「自我循环」预测得越远误差累积越明显所以未来预测天数一般不要超过窗口长度的三分之一。真实价格的还原依然要交给前面保存的 scaler最后把结果画成图对比模型预测值和真实值你会直观地看到模型在上升趋势和下降趋势切换时的滞后——这是所有 LSTM 股票模型的通病不代表代码有 bug。最后分享一个我养成的习惯从那以后我每次拿到一份时间序列预测代码都会强制走一遍「时间顺序切分确认 → 归一化对象保存确认 → 预测反归一化确认」这三步再开始跑训练。看起来琐碎但能挡掉大部分莫名其妙的翻车。希望帮到你。本文还有配套的精品资源点击获取
返回列表