基于Python的循环神经网络股票价格预测:RNN与LSTM实战指南 📅 发布时间:2026/9/8 15:19:28 👁 浏览次数: 简介一份面向Python学习者和量化投资初学者的循环神经网络股票价格预测完整项目包整合了设计报告与可运行代码覆盖从数据读取、LSTM建模到预测结果可视化的全流程。资源共10个文件压缩包大小约1.03MB主要包含Python脚本、Word设计报告、Markdown说明文档及多张图表图片其中图片直观呈现原始数据、预测曲线和网络结构报告则系统讲解实验思路与结果方便对照代码理解实现细节。已有4714人学习下载适合用于课程设计、毕业设计或个人实践参考。整体结构清晰既能帮助快速复现一个典型的RNN/LSTM股价预测实验又能为后续扩展特征工程、调参优化提供可修改的代码基础是入门时间序列预测任务时一份务实、可上手的参考资料。1. 项目动机为什么偏偏是RNN做股价预测1.1 股价数据本质上是时间序列我拿到这份“基于Python的循环神经网络股票价格预测.zip”的时候第一反应不是急着跑结果而是先看它的代码和数据。很多人一看到“股票预测”四个字就兴奋以为解压之后能直接得到一个“稳赚不赔”的模型这种预期从一开始就是错的。实际上这个项目的价值在于它完整演示了如何用循环神经网络处理时间序列数据股价只是载体。股价数据有一个非常明显的特征它是有先后顺序的。今天的收盘价和昨天的收盘价之间存在时间依赖这种依赖不能用普通的全连接网络去建模。如果你把数据打乱再丢给全连接网络它学到的只是“价格范围”而不是“价格走势”。RNN循环神经网络的设计初衷就是处理这种序列依赖关系它通过一个“隐藏状态”把前面时间步的信息保留下来传递到后面。这种结构天然适合对股票历史行情做建模所以这类项目在社区里一直很火。1.2 这个项目适合谁、能学到什么这个项目并不是一个可以直接拿去实盘的交易系统而是一个入门级深度学习应用示范。如果你是这几类人它会非常对口已经会基础Python想学RNN/LSTM怎么落地看过很多“损失函数”“反向传播”的理论但没亲手把网络跑在真实数据上想研究时间序列预测但又不想一上来就啃Transformer那种大模型。我会在下面按实际动手的顺序来讲先处理数据再讲模型原理然后给完整代码链路最后把我在实测里踩过的坑展开来说。每个坑都配了原因分析和规避方式你照着做能少走很多弯路。2. 数据准备获取行情数据与滑窗构造2.1 数据获取与清洗项目里最常见的数据来源是用yfinance拉取美股行情比如苹果AAPL。为什么要选美股而不是A股因为yfinance对美股支持稳定接口不需要额外处理复权因子拿到的数据干净程度高适合用来学习。A股数据虽然也能拿但各种复权、停牌、涨跌停标记会让预处理复杂不少新手很容易被这些细节带偏。import yfinance as yf import pandas as pd df yf.download(AAPL, period5y, interval1d) df df[[Open, High, Low, Close, Volume]].copy() df.dropna(inplaceTrue) print(df.head())这里有两个细节值得注意。第一period5y拉5年日线数据量在1200条左右对RNN训练来说足够又不会太长导致训练时间失控。第二我特意把列限定为OHLCV因为后面建模只用收盘价Close其他列先留着备用不要一开始就把维度搞太复杂。拿到数据后先看一眼收盘价的分布。很多教程跳过了这一步但实际做下来我发现如果某段时间股价从100涨到200模型很容易学到“明天价格≈今天价格”这个愚蠢的恒等映射然后预测结果看起来“还挺准”。所以在正式建模前必须确认数据的趋势形态这会影响你后续对模型效果的判断。2.2 标准化训练集和测试集必须分开fit股价数据的数值范围会随着时间变化比如5年前价格在50附近现在在180附近。如果不做标准化LSTM的输入输出量纲差距太大训练很难收敛。通常用MinMaxScaler把数据缩放到0到1之间。这里有一个新手最容易犯、而且后果非常隐蔽的错误对整个数据集统一做fit。正确的做法是先切分出训练集在训练集上fit再用同一个scaler去transform测试集。原因很简单测试集代表“未来未知的数据”如果建模时就让模型“看过”未来数据的统计信息相当于把未来的信息泄露到了训练过程中。这种操作叫数据泄露data leakage会让测试集指标虚高但一到真实场景立刻露馅。from sklearn.preprocessing import MinMaxScaler # 仅用训练集拟合 scaler scaler_close MinMaxScaler() train_close df[Close].values[:train_size].reshape(-1, 1) scaler_close.fit(train_close) df[Close_scaled] scaler_close.transform(df[Close].values.reshape(-1, 1))train_size一般取总长度的80%留20%做测试。这里我建议不要用随机切分直接按时间顺序切因为时间序列一旦随机打乱就破坏了顺序语义训练出来的模型没有意义。2.3 滑窗构造lookback到底取多长RNN一次接收的是一个固定长度的序列片段。我们需要用滑窗sliding window把长序列切成很多个“输入-输出”对每个样本是过去lookback天的收盘价标签是第lookback1天的收盘价。这个lookback就是窗口大小也叫时间步数。窗口选多少算合理我见过有人用10、20、60、90都试过。实际经验是太短比如5模型只能看到近一周的走势抓不住中短期趋势太长比如250一年前的价格对明天的影响其实已经很弱反而增加训练难度常用经验值60天约一个季度。选60还有一个计算层面的原因LSTM虽然能处理长序列但超过某个长度后梯度传播会衰减训练变得不稳定。60以内时单层LSTM的记忆能力还在可控范围内。窗口构造本身不复杂但要注意生成样本时的顺序X[i]是i到i59的序列y[i]是i60的值。实现时不要用pandas的shift直接做容易把窗口搞错建议写成显式的循环逻辑更直白。3. 标准RNN与LSTM核心公式和模型结构3.1 隐藏状态更新公式热词里提到了“标准循环神经网络(vanilla RNN)核心公式(时间步 t)”这一步如果你自己画图推导过一遍后面读代码会顺畅很多。vanilla RNN的核心是隐藏状态更新[ h_t \tanh(W_{hh} h_{t-1} W_{xh} x_t b_h) ]其中(x_t)是当前时间步的输入(h_{t-1})是上一个时间步传递过来的隐藏状态(W)是权重矩阵(b)是偏置最后用(\tanh)做非线性激活。从公式能看出来时间步(t)的输出不仅取决于当前输入(x_t)还依赖(h_{t-1})这就完成了“记忆”的传递。用一个生活化的类比RNN像一个读书的人每读一个字脑子里会保留前面所有字的语境再结合当前字理解语义。但vanilla RNN有一个致命弱点当序列很长时梯度在时间维度上反复相乘会出现梯度消失或梯度爆炸。通俗说就是“读到最后忘了开头”学不到长距离依赖。这正是它后来被LSTM取代的主要原因。3.2 为什么实战中几乎都用LSTMLSTM长短期记忆网络在vanilla RNN的基础上引入了一个“细胞状态”cell state用三个门来控制信息的读、写、遗忘遗忘门forget gate决定上一时刻的信息多少要扔掉输入门input gate决定当前时刻哪些新信息要写入细胞状态输出门output gate决定当前时刻要把哪些信息输出到隐藏状态。用最直白的话说LSTM给RNN装了一个“记事本”。它可以选择性记住长期有用的信息、忘掉无关的噪音。股票价格里既有短期波动又有长期趋势LSTM的这种选择能力天然比vanilla RNN更合适。在本项目里我建议直接使用LSTM。PyTorch的实现已经封装好了你不需要手动写门控公式但理解上面的语义对调整参数、定位问题非常有帮助。有人会问GRU行不行也行GRU是LSTM的简化版参数更少、训练更快效果通常差距不大。我做这个项目时先用LSTM跑通后续你想优化再换GRU对比。3.3 本项目的网络结构设计我用的网络结构不复杂三层而已层名类型参数输入层LSTMinput_size1只用收盘价hidden_size64num_layers2中间层Dropoutp0.2输出层Linearin64out1input_size1说明每次只输入一个特征即涨跌归一化后的收盘价。你可能会问要不要把成交量、开盘价都加进去可以但会显著增加训练难度而且对新手来说特征一多就分不清模型学得好不好到底是哪一维特征在起作用。我强烈建议第一次跑通时只用收盘价后续再加特征。网络前向传播的逻辑是输入形状为(batch_size, time_steps, input_size)的序列经过两层LSTM后取每个样本最后一个时间步的隐藏输出output[:, -1, :]再接一个线性层输出预测的下一日收盘价。这里最后那个output[:, -1, :]很多人一开始会忽略默认用整个序列的隐藏状态导致维度对不上代码直接报错。4. 核心代码链路从训练到预测4.1 模型定义与训练主流程用PyTorch实现上面这个结构代码非常紧凑import torch import torch.nn as nn class StockLSTM(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2, dropout0.2): super().__init__() self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue, dropoutdropout) self.fc nn.Linear(hidden_size, 1) def forward(self, x): out, _ self.lstm(x) out out[:, -1, :] pred self.fc(out) return pred训练主流程里有两个关键点loss用MSELossoptimizer用Adam。MSE适合回归问题能放大较大误差的梯度Adam自适应学习率对新手最友好不用手动做太多学习率调节。model StockLSTM() criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr0.001) for epoch in range(100): model.train() total_loss 0 for X_batch, y_batch in train_loader: optimizer.zero_grad() output model(X_batch) loss criterion(output.squeeze(), y_batch) loss.backward() optimizer.step() total_loss loss.item() if epoch % 10 0: print(fEpoch [{epoch}/100], Loss: {total_loss / len(train_loader):.6f})训练循环本身没有特别神秘的地方真正决定成败的是数据加载器如何构造。这里我强调一下batch_firstTrue它让输入张量的第一个维度是batch这个习惯在PyTorch里更直观别省略。4.2 超参数有哪些讲究超参数我会重点看四个lr、hidden_size、batch_size、epochs。lr0.001是Adam的默认学习率对这个规模的数据基本够用我试过0.01损失会震荡收敛不稳定hidden_size64对单变量时间序列来说已经不算小再大比如128训练时间翻倍但预测精度提升很有限batch_size64前提是你的样本数量够如果总样本只有几百个batch_size建议降到32epochs100配合下文的早停机制使用不要机械地跑满100轮。你要理解这些参数是在“数据量”和“模型容量”之间找平衡。数据就一千多条模型太复杂反而容易记住噪音。这也是为什么我一直建议先跑小模型、少特征的原因。4.3 预测、还原与可视化模型输出的是标准化后的预测值要得到实际价格必须用前面那个scaler_close做逆变换model.eval() with torch.no_grad(): test_pred model(X_test_tensor).squeeze().numpy() test_pred_inv scaler_close.inverse_transform(test_pred.reshape(-1, 1))这里还有一个容易踩的坑inverse_transform时传入的形状必须和fit时一致如果你在fit时是(-1, 1)那么inverse_transform时也必须传入二维列向量否则sklearn会报错或返回错误结果。我经常看到有人在这一步卡住以为自己模型有问题其实是形状没对齐。可视化建议用matplotlib把“真实收盘价”和“预测收盘价”画在同一张图上。画完之后你大概率会看到测试集前段拟合得还不错但到后段或者市场波动剧烈时预测曲线会出现明显的滞后——预测值总是慢半拍像是“昨天的价格被平移了一天”。这不是bug这是RNN模型在时间序列预测上的典型局限下面我会专门讲。5. 实测中的坑数据泄露、过拟合与评估误区5.1 最致命的坑数据泄露我在第2节提到过数据泄露这里是展开复盘的关键。我第一次做这个项目时图省事先用全量数据的Close做了MinMaxScaler然后才切分训练集和测试集。结果测试集上的RMSE低得惊人我把图贴出来一看测试集的预测曲线几乎贴着真实曲线走当时差点误以为模型已经可以用了。后来我做了一个“时间穿越”实验把训练集里最后60天的数据手动改成随机噪声然后重新训练。按理说训练集尾部只影响最近的模型状态不应该让测试集误差变化那么剧烈但结果发现测试集误差明显变化。这就说明scaler把未来数据的信息传进了训练过程。怎么自查你可以在训练前打印scaler的data_min_和data_max_然后和测试集的最小/最大值对比。如果scaler的最大值明显大于训练集最大值基本可以断定你用了全量数据fit。这类问题很难从最终的损失值上看出来因为指标看起来永远是“好的”。5.2 过拟合与早停股价预测是过拟合的重灾区。训练集损失下降得很漂亮测试集损失却很高。原因是股价本身带有大量噪音模型如果容量足够会把噪音也“背”下来。这个项目里我用了一个有效手段早停early stopping。每个epoch后计算验证集Loss如果连续10个epoch验证集Loss没有创新低就停止训练并恢复最佳权重。实现起来很简单维护一个全局最优验证Loss和一个计数器即可best_loss float(inf) patience 0 for epoch in range(100): train_one_epoch() val_loss evaluate() if val_loss best_loss: best_loss val_loss patience 0 torch.save(model.state_dict(), best_model.pt) else: patience 1 if patience 10: print(fEarly stop at epoch {epoch}) break这里我再提醒一点早停的验证集必须是时间顺序上靠后的数据不能用随机抽样。如果验证集是随机抽的等于间接把未来信息拿来做决策又是一种隐性泄露。5.3 不要迷信评估指标最后我想认真泼一盆冷水测试集上的RMSE、MAE再漂亮也不代表这个模型能预测明天的股价。原因在于股价预测和一般回归任务有本质区别——训练数据本身就是“非平稳”的。模型学到的规律可能在下一个季度就失效。我自己实测过一个现象拿模型去预测“未来未知”的行情前一两周觉得还行一旦遇到财报发布、突发事件预测曲线直接大幅偏离。这不是模型写错了而是市场行为里几乎所有可被历史数据稳定学习的“规律”都已经被套利掉了剩下的更多是随机游走成分。所以这个项目的价值到底在哪我的定位是学习工具你可以把完整的训练、验证、预测、回测流程跑通理解RNN、LSTM、时间序列数据处理、模型评估这些核心概念。如果你想继续往下走可以考虑两个方向一是把单步预测改成多步预测比如预测未来5天二是引入更多特征成交量、技术指标并尝试注意力机制或Transformer。但无论哪条路都只是技术研究不构成任何投资建议。最后分享一个我在实操中很受用的小技巧无论模型改了多少版都要保留第一次跑通的代码和结果基线。这个基线未必是最好的但它能帮你定位“后期改动到底提升了什么、改坏了什么”。买聚类、堆模型之前先把自己的评估流程固定下来才能避免在一堆变量里来回打转。祝你把代码跑通也记得保存好你的best_model.pt。本文还有配套的精品资源点击获取