LSTM股票预测算法实战:从时序原理到PyTorch完整复现

发布时间:2026/10/6 6:52:33
LSTM股票预测算法实战:从时序原理到PyTorch完整复现 简介基于LSTM的股票预测是金融时间序列分析的热点方向这份PDF论文正是围绕该主题展开的完整研究资料。文档从LSTM神经网络的基本原理出发结合最高价、最低价、收盘价、开盘价、日成交量与成交金额六个关键属性设计了以过去15天数据预测第16天最高价的实验方案并对比了真实值与预测值的拟合效果。资源包内仅含单个PDF文件大小约897KB内容涵盖数据爬取、Z-score标准化、PyTorch搭建模型、参数微调及梯度爆炸问题处理等关键技术点配有清晰的流程图、网络结构图和公式推导便于读者按图索骥复现实验。目前已有294人浏览学习对于从事机器学习、数据建模或量化交易的读者而言既能补充LSTM网络的理论认知也能获得股票预测项目的实操参考是一份兼具学术性与实用性的专业指导资料。1. 基于LSTM神经网络的股票预测算法研究一篇能直接落地复现的时序预测方案做股票价格预测的人大多绕不开一个现实问题股价数据本质上是带噪声的时间序列用普通的前馈网络去拟合结果往往差强人意。这份研究资源的核心价值在于它把 LSTM 神经网络完整地应用到了股票最高价的短期预测上——输入前 15 天的六维行情数据开盘价、收盘价、最高价、最低价、日成交量、成交金额输出第 16 天的最高价并在两支股票指数和四支国内个股上给出了实测误差数据。换句话说这不是一篇只讲概念的理论文章而是从数据采集、预处理、模型搭建到误差评估都走通了的完整技术路线。适合正在做时序预测相关毕设或课题的人也适合刚接触 PyTorch 和 LSTM、想找一个最小可复现项目入门的从业者。它不能让你精准抄底逃顶但能让你少走大量弯路。2. 为什么是 LSTM 而不是 BP 网络从梯度消失到三扇门的选型逻辑2.1 股票价格的时间序列特性决定了模型必须能「记住」历史股票价格不是独立随机事件今天的收盘价和过去几天的价格、成交量之间存在明显的依赖关系。论文里明确提到股票价格是随机的时间序列但「随机」不等于「无规律」——短期内趋势、均值回归这些现象都是可观测的。传统 BP 神经网络的问题在于它默认所有输入是独立的相邻两天的数据在它眼里没有任何顺序关系。你丢给它 15 天的数据它会把这 15 天当作 15 个互不相干的特征完全丢失了时间先后顺序这一层信息。LSTM 则天然为序列数据设计。它的隐藏状态在时间步之间传递这意味着模型在处理第 15 天的数据时理论上还记得第 1 天到第 14 天的信息。对于股票这种「历史影响未来」的数据形态这种结构上的契合度远高于 BP 网络。论文实验也验证了这一点四支个股的预测误差控制在 7.9% 到 18.9% 之间曲线走势与真实值基本吻合。2.2 RNN 的梯度困境BP 网络在时序任务上的致命短板如果只是需要「记忆」普通的循环神经网络RNN也能做那为什么论文直接跳过 RNN 选用了 LSTM这就涉及深度学习里一个经典的工程问题——梯度消失与梯度爆炸。RNN 在处理长序列时反向传播需要沿着时间步展开每一层梯度都要乘以同一个权重矩阵 W。如果 W 的特征值小于 1梯度的模会随步数指数级衰减最终消失如果大于 1梯度会指数级增长最终爆炸。梯度消失的直接后果是网络离当前时刻越远的记忆越无法被有效学习早期的关键信息比如 10 天前的放量信号在训练中根本传递不到输出层。梯度爆炸则会让 loss 在某个 step 突然跳到 NaN训练直接崩溃。论文里给出的解决思路非常直白——LSTM 靠三道「门」控制信息的增删让梯度在时间轴上有一条「高速公路」可以直接穿过而不是每一步都乘以同一个矩阵。这是在结构层面解决问题的不是靠调参调出来的所以稳定性远好于原版 RNN。2.3 LSTM 的核心机制细胞状态与遗忘门、输入门、输出门把论文第 4.1 节的三道门展开其实逻辑并不复杂。LSTM 在每个时间步维护两个东西细胞状态 C_t长期记忆和隐藏状态 h_t短期记忆也是输出。遗忘门决定上一时刻的细胞状态 C_{t-1} 有多少要被丢弃输入门决定当前时刻的新信息有多少要写入细胞状态输出门决定当前细胞状态有多少要输出到隐藏状态。数学上就是论文里的公式234f_t sigmoid(W_f · [h_{t-1}, x_t] b_f) # 遗忘门 i_t sigmoid(W_i · [h_{t-1}, x_t] b_i) # 输入门 C̃_t tanh(W_C · [h_{t-1}, x_t] b_C) # 候选细胞状态 C_t f_t * C_{t-1} i_t * C̃_t # 更新细胞状态 o_t sigmoid(W_o · [h_{t-1}, x_t] b_o) # 输出门 h_t o_t * tanh(C_t) # 更新隐藏状态三个门都是 sigmoid 激活输出 0 到 1 之间的值相当于一个可微分的「开关」。细胞状态更新是加法的形式梯度在这个路径上可以稳定传播这就是 LSTM 能避免梯度消失的结构原因。理解了这个机制你就明白了为什么论文把 LSTM 叫做「具有选择记忆性」——它确实能在训练中自主学会哪些历史信息要保留、哪些要遗忘。2.4 LSTM 与 BP 网络的横向对照论文实验给出的选型佐证论文在引言部分明确指出国内外股票预测以往多用 BP 神经网络而 LSTM 主要用于自然语言处理金融领域较少使用。从实验结果看这个选型是成立的。四支个股中浦发银行误差最低0.079平安银行和工商银行在 0.123 和 0.126 左右贵州茅台误差最高0.189整体都在工程可接受的范围内。一个值得注意的细节是不同股票的预测误差差异明显。这说明模型的效果不仅取决于模型本身还跟标的的数据特性有关。茅台股价高、波动大同样的模型误差就偏高。这在选型上是个提醒——LSTM 适合趋势性相对稳定、波动不过于剧烈的标的遇到暴涨暴跌的行情任何时间序列模型都会力不从心。3. 数据工程爬虫采集、z-score 标准化与前 15 天输入窗口的构造3.1 行情数据采集论文的做法与工程上的常见替代方案论文提到数据获取采用网络爬虫的方式通过安装相应的库函数模块快速抓取网页信息并保存到本地。具体实现上常见做法是用 requests 拉取财经网站的历史行情 JSON 接口或者用 akshare、tushare 这类现成库直接拿日线数据。下面这段是典型的爬虫写法按天循环拉取并写入 CSVimport requests import pandas as pd import time def fetch_stock_data(symbol, start_date, end_date): 从行情接口拉取日线数据返回 DataFrame url https://example.com/api/stock_daily params { symbol: symbol, start: start_date, end: end_date, fields: open,close,high,low,volume,amount } resp requests.get(url, paramsparams, timeout10) data resp.json()[data] df pd.DataFrame(data) df[date] pd.to_datetime(df[date]) df df.sort_values(date) # 必须按时间升序排列LSTM 依赖顺序 return df df fetch_stock_data(600519, 2015-01-05, 2018-12-08) df.to_csv(stock_600519.csv, indexFalse) print(df.head())两点说明一是fields参数里指定了论文用的六个字段——开盘价、收盘价、最高价、最低价、成交量、成交金额volume和amount分别对应该资源中的日成交量和成交金额二是排序这步最容易漏如果接口返回的数据不是严格按日期升序后面的时间窗口构造会全部错位。3.2 z-score 标准化为什么成交量不能和价格直接拼在一起把原始数据打印出来看量纲差异大得离谱贵州茅台的股价在几百元这个量级成交量却是以万手为单位成交金额更是动辄几十亿。如果不做处理直接把六个维度的数据丢进 LSTM模型会优先拟合数值大的特征价格信息被成交量直接淹没。论文采用的是 z-score 标准化公式是 x (x - μ) / σ其中 μ 是训练集均值σ 是训练集标准差。这一步有极其关键的细节——μ 和 σ 只能从训练集计算不能混入测试集数据。如果先用全部数据计算均值方差再标准化测试集的分布信息就提前泄露到了训练过程中模型评估会虚高这在金融时序预测里属于严重的作弊行为。代码实现如下import numpy as np def zscore_fit_transform(train_df, test_df): 只在训练集上计算均值标准差测试集用同一套参数变换 train_stats {} features [open, close, high, low, volume, amount] for col in features: mu train_df[col].mean() sigma train_df[col].std() train_stats[col] (mu, sigma) train_df[col _scaled] (train_df[col] - mu) / sigma test_df[col _scaled] (test_df[col] - mu) / sigma return train_df, test_df, train_stats3.3 构造监督学习样本前 15 天六个特征映射第 16 天最高价论文里的监督方式是前 15 天的 6 个属性作为输入层数据、第 16 天作为标签。这个窗口长度是把原始数据转成监督学习样本的核心操作。对连续的日线数据用一个滑窗切分即可def create_sequences(data, seq_len15, target_colhigh): data: 标准化后的 DataFrame按时间升序排列 seq_len: 15用前 15 天数据预测第 16 天 target_col: high预测目标是最高价 feature_cols [c for c in data.columns if c.endswith(_scaled)] feature_values data[feature_cols].values target_values data[target_col _scaled].values X, y [], [] for i in range(len(data) - seq_len): X.append(feature_values[i : i seq_len]) y.append(target_values[i seq_len]) return np.array(X), np.array(y)这里有几个参数要注意。seq_len15是论文明确给出的窗口大小不是经验参数而是实验设定理论上窗口越长模型看到的「历史」越久但也会带来更长的训练时间和更大的过拟合风险改动这个参数需要重新做多组对照实验才能确定优劣。目标变量用的是标准化后的最高价因为模型输出范围是 0-1 左右训练更稳定反归一化留到预测完了再做。3.4 样本划分时间序列预测为什么不能用随机打乱论文明确写了「前 80% 作为训练集数据后 20% 作为测试集数据」。这个划分方式与普通的分类任务完全不同——分类任务里训练集和测试集通常是随机抽样的但时间序列如果随机打乱训练集里会出现「未来数据」预测「过去数据」的荒谬场景评估结果完全失真。对于 LSTM 样本划分时不能直接对 X 和 y 做train_test_split必须按原始序列的时间顺序切片train_size int(len(X) * 0.8) X_train, X_test X[:train_size], X[train_size:] y_train, y_test y[:train_size], y[train_size:]论文实验的时间范围是 2015/1/5 到 2018/12/8约 950 个交易日前 80% 差不多是 760 天用于训练后 190 天用于测试。这个比例在时间序列预测里是比较主流的做法——训练数据太少模型学不到足够规律太多又容易过拟合最近期的走势。如果数据量更大还可以考虑滚动验证的方式即多次用不同时间段做训练测试取误差均值得到更稳健的模型评估。4. PyTorch 模型搭建与训练网络结构、参数微调与自定义误差函数4.1 定义 LSTM 网络结构输入维度、隐藏层大小与输出的衔接论文在 PyTorch 框架下搭建 LSTM 模型。网络结构的设计要点是输入维度 input_size6六个行情属性时间步数 seq_len15隐藏层维度 hidden_size 是第一个需要反复尝试的超参数。输出端由于任务是对最高价做回归预测不是分类所以最后一层是线性层而不是 softmax输出维度是 1。import torch import torch.nn as nn class LSTMStockPredictor(nn.Module): def __init__(self, input_size6, hidden_size128, num_layers1, output_size1): super(LSTMStockPredictor, self).__init__() self.hidden_size hidden_size self.num_layers num_layers self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue ) self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # x 形状: (batch_size, 15, 6) h0 torch.zeros(self.num_layers, x.size(0), self.hidden_size) c0 torch.zeros(self.num_layers, x.size(0), self.hidden_size) out, _ self.lstm(x, (h0, c0)) # out 形状: (batch_size, 15, hidden_size) last_output out[:, -1, :] # 取最后一个时间步的输出 prediction self.fc(last_output) # (batch_size, 1) return prediction几个关键点。batch_firstTrue让输入形状变成(batch, seq_len, feature)而不是 PyTorch 默认的(seq_len, batch, feature)可读性更好也省去转置的麻烦。num_layers是 LSTM 堆叠的层数论文实验没有明确说用几层工程上从单层起步如果欠拟合再加到 2 层超过 3 层在股票这种数据量级上几乎没有收益却显著增加训练时间。取最后一个时间步的输出是时序预测的标准做法——整个序列的信息都汇聚到了最后一步的隐藏状态里。4.2 训练循环与超参数微调从学习率到 epoch 的调试路径训练部分用的是均方误差MSE作为损失函数配合 Adam 优化器。论文强调「通过不断微调参数不断降低误差」这个微调过程在 PyTorch 里主要是调三个东西学习率、batch_size 和隐藏层维度。import torch.optim as optim def train_model(model, X_train, y_train, epochs100, lr0.001): criterion nn.MSELoss() optimizer optim.Adam(model.parameters(), lrlr) dataset_size X_train.shape[0] batch_size 64 for epoch in range(epochs): model.train() total_loss 0 for i in range(0, dataset_size, batch_size): x_batch torch.FloatTensor(X_train[i:ibatch_size]) y_batch torch.FloatTensor(y_train[i:ibatch_size]).view(-1, 1) optimizer.zero_grad() output model(x_batch) loss criterion(output, y_batch) loss.backward() optimizer.step() total_loss loss.item() * len(x_batch) avg_loss total_loss / dataset_size if (epoch 1) % 20 0: print(fEpoch {epoch1}/{epochs}, Loss: {avg_loss:.6f})一个值得注意的工程习惯batch 是顺序切片的不是随机抽样的。虽然 PyTorch 的DataLoader默认支持shuffleTrue但在 LSTM 股票预测里通常不开启 shuffle。原因在于按时间顺序的 batch 保留了序列的先后结构模型在每个 batch 内学到的是一段连续时间窗口内的关系更接近真实交易场景。如果数据序列本身噪声很大导致训练不稳定需要优先降低学习率而不是开 shuffle——调学习率的操作是每 20 个 epoch 观察 loss 是否下降不降就把学习率除以 10。4.3 自定义相对误差绝对误差在股票预测里的陷阱论文在误差评估上做了一个很有行业经验的决策——没有直接用预测值和真实值的差来衡量模型好坏而是定义了相对误差 error |prediction - y| / y。论文给出的理由非常实际如果股价是万元量级预测差 10 块钱算非常准但如果股价是十元量级同样差 10 块钱就是灾难性的。用绝对误差评估模型等于默认高价股和低价股的误差标准一样这显然不合理。def relative_error(y_true, y_pred): 按论文公式 (5) 计算相对误差 return torch.abs(y_pred - y_true) / y_true # 使用示例 errors relative_error(y_batch, output) mean_error errors.mean().item() print(f平均相对误差: {mean_error:.4f})这里有个容易踩的暗坑训练时候用的损失函数是 MSE绝对误差的平方评估时候用的是相对误差两者并不完全一致。实际工程中如果想让模型直接优化相对误差可以把损失函数也改成相对误差的均值但这样做梯度在优化后期容易振荡因为当真实值接近零时分母趋近于零会让梯度爆炸。稳妥的做法是训练仍用 MSE评估统一用相对误差论文的实验数据也是这样处理的。4.4 实验数据对照四支个股的误差分布说明了什么论文给出了四支个股在 2015/1/5 到 2018/12/8 期间的误差数据值得逐条分析实验对象误差平安银行0.123工商银行0.126贵州茅台0.189浦发银行0.079四支股票的误差从高到低排列贵州茅台最差、浦发银行最好。这个现象暴露了 LSTM 模型的一个真实边界股价绝对值高的股票模型预测的相对误差偏大。茅台的股价一度在 600 元以上同样的相对波动对应的绝对金额更大模型学习到的特征规律更难稳定捕捉高价股的日内波动。浦发银行的误差低到 7.9%说明在低价股上这个模型的表现是可用的。对于想复现实验的人这条数据可以作为模型是否调好的「参考系」——如果你的模型在浦发银行股票上误差高于 0.1大概率是数据预处理或超参数设置有偏差不是模型本身的问题。5. 避坑指南LSTM 股票预测最容易翻车的五个细节5.1 标准化参数混入了测试集信息现象模型在训练集上 loss 一路下探但测试集误差始终降不下来甚至出现训练误差远小于测试误差的异常差距。原因在数据标准化时直接用整个数据集包含测试集的均值和标准差做 z-score 变换测试集的分布信息提前泄露到了训练过程中。模型「见过」测试样本的统计特征训练时相当于开了外挂一旦换到真实场景预测未来数据就无法复用。解决严格按照论文第 3 节的逻辑只在X_train切片上计算mean和std测试集用同一组参数变换。写完代码后检查一下scaler.fit()是否只用了训练数据如果用了fit_transform而不是fit再transform就基本可以确认踩了这个坑。5.2 训练集和测试集划分时用了随机打乱现象预测曲线和真实曲线看起来完美贴合误差低到不可思议甚至趋近于零。原因用 sklearn 的train_test_split默认参数切分数据时会shuffleTrue样本被随机打乱。LSTM 样本之间有严重的时间重叠——第 i 个样本的标签恰好是第 i1 个样本的部分输入。随机划分之后测试集里可能存在与训练集几乎是同一时段的数据模型不需要泛化能力就能「记忆」出结果。解决永远按时间顺序切片前 80% 训练后 20% 测试。更保险的做法是用 Pandas 按日期排序后用df.iloc[:int(len(df)*0.8)]做切片切片完再确认测试集的最小日期晚于训练集的最大日期。5.3 预测结果忘记反归一化现象预测出来的「最高价」是一个 0 到 1 左右的小数跟实际股价完全对不上号画出来的曲线跟真实曲线数值相差几个量级。原因输入数据做了 z-score 标准化模型输出自然也是标准化空间的值。如果直接把这个值当作预测股价拿去用得到的结果是「标准化的最高价」不是真实股价。解决预测完成后要做反变换 y_true y_pred * σ μ。注意这里的 σ 和 μ 必须和标准化训练数据时用的完全一致。工程上标准化时把统计量存成字典返回预测时读取同一个字典做反归一化不要重新计算否则结果会偏差。5.4 多步预测被当作单步预测来评估现象单日预测误差表现不错但连续预测未来 10 天误差快速累积曲线严重偏离真实值。原因LSTM 训练时是用真实的历史 15 天去预测第 16 天属于「teacher forcing」模式。但实际用做多步预测时第 17 天的输入需要第 16 天的预测值来补位预测误差会作为输入传递到下一步随着步长增加误差指数级放大。论文的实验只做了下一日最高价的预测明确提到「长时间的预测真实值与预测值个别相差较大」这正是误差累积的表现。解决如果业务上确实需要多步预测常见做法是滚动预测——每次只预测明天把明天预测值拼到输入序列末尾同时丢弃最早一天的数据再预测后天。还有一种做法是直接训练多步输出模型让 LSTM 最后一步接多个输出头分别对应未来 1 到 5 天的预测值但数据量和模型复杂度都要相应调整。5.5 用绝对误差评估不同股价标的的模型现象在贵州茅台单价高上模型误差数值很大在浦发银行单价低上误差很小得出「模型适合便宜股票」的错误结论。原因绝对误差和股价量级直接挂钩用绝对误差横向量级差异很大的股票本质上是在拿「金额」而不是「准确率」做评估。论文明确提到了这个陷阱——以万为单位股价相差 10 块问题不大以十为单位相差 10 块就是灾难所以定义了相对误差公式。解决把评估指标统一换成相对误差。代码上直接用np.mean(np.abs(pred - true) / true)这一行计算。关注模型在四支个股上误差的相对排序论文里是浦发 0.079 平安 0.123 工商 0.126 茅台 0.189而不是各自的绝对差值。6. 从复现到验证反归一化、滚动预测与模型可用的判断标准论文实验里纵坐标用的是归一化后的数据没有反归一化就展示拟合曲线了。但实际做复现的人拿到模型最终想看到的是「预测明天的最高价是 12.5 元」这种可操作的结果所以反归一化这步必须补上。做法是拿到模型输出的标准化预测值后用训练集的最高价均值 μ_high 和标准差 σ_high 做逆变换pred_price pred_scaled * σ_high μ_high。这里的 μ_high 和 σ_high 是保存下来的不能重新用全量数据算。验证模型是否真正可用我有一个自己习惯的检查流程第一步把测试集的预测结果反归一化后和真实最高价画在同一张图上肉眼确认两条曲线的趋势是否跟随、峰值是否滞后。第二步把误差拆到「上涨日」和「下跌日」两组分别计算——如果模型在下跌日误差远大于上涨日说明它对市场情绪突变不敏感这在实际应用里风险很大。第三步在未参与训练的历史时段上做滚动预测也就是模拟真实交易环境中的「昨天只知道昨天的数据」连续预测 20 个交易日观察误差是否在可接受范围内膨胀。滚动预测的具体做法是模型训练完进入 eval 模式保持输入窗口长度为 15 天每次只预测一个时间点将预测值追加到序列末尾同时丢弃序列最前端的真实数据。核心注意点是在预测过程中禁止访问未来数据只能使用截止到当前时刻的真实观测值和之前时刻的预测值。这个验证方式和论文实验的区别在于它更接近真实使用场景也更严格。关于「模型到底靠不靠谱」这件事我从这个项目里学到的教训是评估一个股票预测模型不能用单一指标更不能只看整体平均误差要看最差情况下的误差分布。浦发银行平均误差 7.9%看起来很漂亮但如果你能画出每天误差的分布图会发现某些极端行情日的误差可能超过 30%而恰好在那些天你做了交易决策结果就是完全被市场打脸。从那以后我每次评估时序预测模型都强制把测试集的误差分成区间统计——误差小于 10% 的样本占比多少、10% 到 20% 的多少、超过 20% 的多少低于 60% 的样本落在误差 15% 以内这个模型我就不敢用于实盘只当作研究验证。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询