ARTICLE DETAIL

资讯详情

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

LSTM股票预测实战:数据切分、归一化与验证避坑全解析

LSTM股票预测实战:数据切分、归一化与验证避坑全解析 简介这是一篇基于LSTM神经网络的股票预测算法研究论文PDF面向具备一定机器学习基础、希望将深度学习应用于金融时间序列的开发者与研究人员。文章围绕最高价、最低价、收盘价、开盘价、日成交量和成交金额六项属性使用PyTorch搭建LSTM模型通过z-score标准化、按前80%与后20%划分训练测试数据、微调超参数等步骤实现对股票下一日最高价的预测。论文系统阐述了LSTM的细胞状态、输入门、遗忘门与输出门机制以及梯度爆炸问题的应对思路并与BP神经网络作了对比。资源为单个PDF文件大小897KB便于离线阅读。目前已有294人学习本资源文中包含完整的算法流程、公式推导与两支指数股票的预测拟合曲线能帮助读者理解LSTM的时间序列建模过程和实验设计方法具有较好的参考价值。1. 一个“看起来能预测股价”的LSTM模型为什么论文都这么写落地却总翻车“基于LSTM神经网络的股票预测算法研究”这个标题在金融机器学习的论文和开源项目里出现频率极高但几乎每个照着复现的人都会在第二周开始怀疑人生训练集损失一路降到0.01回测曲线贴得严丝合缝换到最近三个月的数据却直接踏空预测值变成一条接近水平的直线。这不是LSTM没用而是多数教程在数据处理上漏掉了时间序列最核心的规矩——切分、归一化和评估都不能看到未来。这篇文章把LSTM股票预测这条路线从头拆到尾从模型选型、数据切分、PyTorch训练到验证指标逐个落地目标是让拿到这份研究方案的你能自己做出一套可复现、敢说边界、知道坑在哪的预测流程而不是照抄一个看不清内部的黑匣子。2. 序列建模的思路先立住为什么是LSTM以及预测目标怎么定2.1 股票预测本质是回归任务LSTM因为门控机制胜出股票预测里最常见的设定是“预测下一日收盘价”输入过去N个交易日的历史数据输出一个连续的价格数值。这个任务在数学上就是回归模型选择范围其实很宽——前馈神经网络普通多层感知机、一维卷积神经网络、RNN循环神经网络、Transformer都能做真正决定表现的是模型对“时间顺序”的利用方式。前馈神经网络把每个时间步当成独立特征展开向量化之后完全丢失了“昨天在明天之前”的顺序约束一维卷积神经网络能通过卷积核捕捉局部依赖但感受野宽度固定调kernel_size像做手工特征工程而股票序列里的自相关往往没有固定周期短则三五天、长则一两个月。循环神经网络天然为序列设计但原始RNN在长序列上会遇到梯度消失或梯度爆炸——反向传播时误差沿时间步回传超过十几步就被连乘的激活函数导数衰减得几乎看不到了。LSTM为这件事加了三条门控通路遗忘门决定丢掉多少历史信息输入门决定新信息写入多少输出门控制对外暴露多少另有一条细胞状态的直通线让梯度能“抄近道”回传。这就是为什么过去几年LSTM神经网络几乎是股票预测论文的默认起点——不是它最聪明而是它对“非线性短中期依赖”的建模最稳妥参数数量也可控训练一版只需要分钟级。2.2 预测目标怎么定单步预测与多步预测的取舍先说结论研究阶段先做单步预测实盘策略再考虑多步。单步预测的t1标签是“下一个交易日的收盘价”训练时每个样本只用未来一天的真实价格做标注优点是误差来源单一、收敛快、容易检查模型是不是真的学到了东西缺点是单步预测的方向信号太弱——今天预测明天涨1%实际只涨0.3%幅度错但方向对信号仍然可用但不能直接拿去下单。如果要给交易策略用至少得预测未来3到5日。常见做法有两种一种是直接改损失函数让模型输出未来N天的价格向量另一种是滚动预测把模型预测出的t1价格当作输入继续跑前向算出t2、t3N步就做N次推理。后者不改模型结构但误差会随步长累积所以评估时方向准确率比RMSE更关键这件事在第4章展开。我一般建议新手从“单步预测滚动延伸”的组合入手训练目标不复杂推理阶段又能模拟未来几天的走势兼顾可复现性和实用价值。3. 用PyTorch搭最小可运行流程拿数据、归一化、滑窗切分与训练代码3.1 数据获取先用日线的收盘价和成交量这类原始行情股票预测的入门实验不需要太多特征日线的收盘价和成交量已经能撑起一个最小闭环。常见做法是从tushare或akshare这类数据接口拉取行情落地成CSV存好后续每次实验都从同一份文件读入保证结果可复现。下面是读取和预处理的起点import pandas as pd import numpy as np import torch import torch.nn as nn from sklearn.preprocessing import MinMaxScaler # 日线行情按日期升序 df pd.read_csv(stock_daily.csv, parse_dates[date]) df.sort_values(date, inplaceTrue) df.reset_index(dropTrue, inplaceTrue) # 特征列收盘价、成交量后续也可以加开盘/最高/最低 feature_cols [close, volume] raw df[feature_cols].astype(np.float32).values print(样本量:, len(raw), 特征维度:, raw.shape[1])这里有个新手常忽略的点数据必须按时间升序。很多接口默认返回的顺序不固定不排序直接往后做滑窗构造出来的样本是“倒着看时间”模型学到的规律是反的后面再怎么调参都是白费力气。停牌导致的空值先用ffill()填充或直接删除不要在原始序列里留NaNLSTM的前向计算遇到NaN会把整条梯度pollute掉。3.2 归一化与滑窗切分把时序变成有监督学习样本归一化在股票预测里是重灾区。最稳妥的顺序是先按时间切出训练集和测试集的索引区间再在训练集上fit归一化器最后用同一个归一化器去transform测试集。MinMaxScaler如果先在全序列上fit等于是让模型偷看了未来数据的上下界测试集的“未知”就名存实亡了。具体代码如下# 归一化先得到训练集索引再fit train_ratio, val_ratio 0.7, 0.15 n len(raw) train_idx int(n * train_ratio) val_idx int(n * (train_ratio val_ratio)) scaler MinMaxScaler(feature_range(0, 1)) # 关键步骤只用训练集fit验证/测试集只transform train_scaled scaler.fit_transform(raw[:train_idx]) val_scaled scaler.transform(raw[train_idx:val_idx]) test_scaled scaler.transform(raw[val_idx:]) # 滑窗构造样本窗口不能跨区间 def make_sequences(data, seq_len20, step1): X, y [], [] for i in range(seq_len, len(data) - 1, step): X.append(data[i - seq_len:i]) # 过去seq_len天的特征 y.append(data[i 1, 0]) # 下一日收盘价 return np.array(X), np.array(y) X_train, y_train make_sequences(train_scaled, seq_len20, step1) X_val, y_val make_sequences(val_scaled, seq_len20, step1) X_test, y_test make_sequences(test_scaled, seq_len20, step1) print(X_train.shape, y_train.shape)滑窗输出的shape是(样本数, seq_len, 特征数)这正是PyTorch LSTM需要的三维输入。data[i 1, 0]里的i 1是在给窗口“往后挪一天”——第i天是窗口最后一天标签就应该取i 1。seq_len20的初始值对应A股一个月约20个交易日给模型看一个月的走势比较合理step1表示每天滑一格样本量最大。如果嫌样本重叠太多可以把step调到5代价是样本数直接除以5量少的时候要谨慎。3.3 模型定义与训练循环两层LSTM加一个全连接最小可用结构是两层单向LSTM接一个全连接输出层输入维度等于特征数输出维度是1。batch_firstTrue这个参数建议显式打开否则输入张量要按(seq_len, batch, features)排非常容易写错。训练时用MSELoss做回归损失Adam优化器配lr0.001是稳定起点。class LSTMPredictor(nn.Module): def __init__(self, input_dim, hidden_dim64, num_layers2, dropout0.2): super().__init__() self.lstm nn.LSTM(input_dim, hidden_dim, num_layers, batch_firstTrue, dropoutdropout) self.fc nn.Linear(hidden_dim, 1) def forward(self, x): out, _ self.lstm(x) # out: (batch, seq_len, hidden_dim) return self.fc(out[:, -1, :]) # 取最后一个时间步的输出 model LSTMPredictor(input_dimX_train.shape[2], hidden_dim64, num_layers2) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr0.001) train_dataset torch.utils.data.TensorDataset( torch.from_numpy(X_train), torch.from_numpy(y_train)) train_loader torch.utils.data.DataLoader(train_dataset, batch_size64, shuffleTrue) for epoch in range(50): model.train() total_loss 0 for xb, yb in train_loader: optimizer.zero_grad() pred model(xb).squeeze(-1) loss criterion(pred, yb) loss.backward() optimizer.step() total_loss loss.item() if epoch % 10 0: print(fepoch {epoch}, loss {total_loss / len(train_loader):.5f})out[:, -1, :]提取的是每条序列最后一个时间步的隐藏状态因为我们要预测的是“看完20天后”的下一日价格不是中间某一天。训练时打开shuffleTrue把样本顺序打乱对单步回归问题可接受因为单个样本内部的时间顺序依然是升序但验证和测试阶段绝对不能shuffle一旦打乱就等于在模拟里作弊。dropout0.2是默认值两层LSTM之间加dropout能抑制过拟合num_layers先别超过3层数一深反向传播路径变长梯度消失问题会重新出现那时候你会同时面对拟合不足和训练不稳定两个问题。4. 验证方法要“穿越时间”测试集切分、评估指标与基线对比4.1 按时间顺序切分数据训练集/验证集/测试集的三种比例股票序列切分只能按时间顺序不能像图像分类那样随机洗牌后切。随机切分会把2021年的数据和2023年的数据混进同一个训练集模型等于在“未来和过去同时学习”测试时又拿到邻近的样本指标虚高到没法看。常见的经验比例是7:1.5:1.5训练集用于学习参数验证集用于调超参数测试集只允许在最终评估时跑一次。换句话说你在测试集上反复改参数换结果测试集就慢慢变成了训练集的影子最终决策被污染。切分后先检查三段时间范围是否真的连续再检查滑窗有没有跨边界。很多教程给出的make_sequences是在全量归一化数据上直接切窗口这会造成训练样本和验证样本重叠——训练集最后一个窗口的标签可能落在验证集区间内评估结果虚高。严格做法是切完区间再各自造窗口宁可损失边界上的几条样本也不要让信息穿越。4.2 评估指标MAE、RMSE之外方向准确率才是交易关心的事训练阶段用MSE做损失函数没问题但模型选型和“这个预测能不能用”的评估必须换一套指标。下面这张表是我每次都会打印出来的四个值指标怎么算在这个场景里看什么MAE预测值与真实值绝对差的均值价格偏差的平均水平直观但会被小误差带偏RMSE均方根误差放大显著偏差用来检查有没有极端离谱的预测MAPE绝对百分比误差求平均对比不同价格区间的标定时更有意义方向准确率预测值与真实值相对前日的涨跌方向一致的比例交易里最关心的“涨了还是跌了”四个指标里方向准确率最贴近交易的实际动作预测精度差0.1元和差0.5元在交易上的意义完全不同前者可能只是噪声后者是明确信号而无论差多少只要方向判错开仓就是反向操作。模型在验证集上的MAE和RMSE都凑合方向准确率却只有52%左右那这个模型基本等于抛硬币不值得投入。4.3 先跑一个线性回归作为基线LSTM要说“我比线性模型好”才算数LSTM不是不能用但必须证明它比简单模型多带来了价值。我一般会在同一个滑窗数据上先跑一个线性回归当基线把每个窗口展平成特征向量拟合y然后在同样的测试集上算MAE和方向准确率。如果LSTM只比线性回归好一个百分点说明这个标的价格里可预测的部分主要是线性成分LSTM的复杂度和训练成本换不来实际收益。from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error X_flat X_train.reshape(X_train.shape[0], -1) # (样本数, seq_len * 特征数) lr LinearRegression().fit(X_flat, y_train) X_test_flat X_test.reshape(X_test.shape[0], -1) lr_pred lr.predict(X_test_flat) print(LR MAE:, mean_absolute_error(y_test, lr_pred))线性回归没有记忆能力只能抓到“水平”但它能告诉你数据的线性下限在哪里。LSTM的MAE如果比线性回归低不了20%以上我通常不会继续在LSTM上调参而是回去换特征、换标的或者缩短预测周期——股票预测算法研究里最该证明的是“收益可重复”不是“曲线画得漂亮”。5. LSTM股票预测避坑数据泄漏、过拟合与参数玄学4条血泪经验5.1 归一化泄漏MinMaxScaler先在全序列上fit等于看到了未来现象训练集损失正常测试集表现却好得异常或者反过来差得离谱。最常见的是训练区间股价在50到100元测试区间涨到150元归一化后的测试数据最大值跑到1.5模型从没见过超出1.0的输入预测直接崩成一条直线。原因MinMaxScaler在完整序列上先执行了fit再用全序列的min和max去缩放所有区间。测试集的上下界已经渗入特征缩放流程模型相当于间接看到了未来价格的分布范围测试不再是测试。解决先按时间切区间再对训练集单独fit验证集和测试集只调用transform代码见3.2节。已经写错了的话最直接的后悔药是把流程重跑一遍把scaler挪到切分之后。注意验证集和测试集绝对不能fit_transform否则泄漏的是同一个问题。5.2 滑窗重叠与索引错位验证集混进了训练样本现象训练损失降得很快画出来的验证集预测曲线比真实曲线“滞后一天”方向准确率高得离谱能达到90%以上。滞后一天是泄漏的典型信号——模型要预测的y其实已经在训练窗口里出现过了。原因滑窗跨越了区间边界。训练集最后一天索引是1000构造验证集滑窗时从数据点999开始取窗口里包含999和1000两天而这两天属于训练区间验证样本的实际信息早就被模型学过了。解决严格按照时间索引切区间同一份数据每个区间单独建窗口训练集最后一个窗口结束在train_idx - 1验证集第一个窗口必须从train_idx开始。边界上损失几条样本完全值得。排查方法是打印X_train[-1]和X_val[0]对应的日期确认没有重叠如果它们之间日期连续且不交叉才算切干净。5.3 参数敏感hidden_size和num_layers不是越大越好现象把hidden_dim从64调到256训练损失继续下降验证集损失反而上升epochs从50加到200训练集能拟合到接近0验证集开始发散。原因LSTM参数量大而股票日线数据一年只有约250个交易日滑窗后的有效样本也就几千条网络容量一大噪声也一并背下来了。回传梯度时层数越深路径越长梯度被连续乘法衰减训练开始不稳定——这是反向传播的天然限制不是LSTM写错了。解决先固定小模型从稳定参数起步。下面这组参数是我试过多只股票后的经验起点不要一上来就追求大模型参数建议起点可调范围seq_len20560hidden_dim6432128num_layers213dropout0.200.5lr0.0010.00010.01验证集出现“先降后升”就是过拟合的明确信号优先early stopping或者把dropout往上调。另外推理时一定要先model.eval()否则dropout还在随机丢神经元同一份输入两次预测结果都不一样回测无法复现。5.4 市场状态切换导致失效模型不是永动机现象模型在某段历史测试集上表现很好换到最近一年的行情突然预测完全偏离或者输出变成一条接近水平的线完全不跟价格波动。原因股票价格是非平稳序列。市场微观结构、波动率水平、板块风格随时在变LSTM学到的是训练区间的数据分布分布一变模型输出就失效。这不是代码bug是任务的适用边界问题。解决常见做法是滚动窗口重训——每隔20个交易日用最近250天的数据重新训练一次或者把输入特征从纯价格扩展到成交量、换手率、指数相对位置等行为因子。更重要的是调整预期明确“这个模型只在特定行情阶段有效”实盘前用最近一个季度的数据做一次滚动验证失效就停用不要硬扛。6. 让预测“能信”方向准确率、多次运行与滚动验证的收尾技巧模型训练完我建议在测试集上做最后一道质检不是看loss而是算方向准确率。下面的代码直接用预测值和真实值计算“涨跌方向是否一致”# 方向准确率预测值相对前一日是否同向变化 actual y_test # 真实下一日收盘价归一化后 actual_prev np.concatenate([[actual[0]], actual[:-1]]) pred predicted_test # 模型在测试集上的输出 same_dir np.sign(pred - actual_prev) np.sign(actual - actual_prev) acc same_dir.mean() print(fdirection accuracy: {acc:.2%})方向准确率的基线是50%。能稳定在55%以上说明模型确实抓到了一些统计规律如果只有51%到52%基本可以断定学到的是噪声不要拿去做策略。这个指标比RMSE更贴近交易的实质动作所以我把它放在最后一道质检。第二个技巧是多次运行取平均值。LSTM的权重初始化是随机的同样的数据和参数跑三次预测结果可能有明显差异。我现在习惯固定三个随机种子各跑一遍方向准确率取最低值作为模型宣布结果——最简单的模型搭配合格的时序处理比一个精调但方差巨大的LSTM更值得信任。第三个技巧是滚动验证再决定投入。把测试集按季度切成几段每段单独算MAE和方向准确率看是不是有的季度好、有的季度差。某一段突然失效时对照那段时间的行情特征——涨停密集、成交量骤降、指数横盘就能判断是模型退化还是市场状态切换。这个做法比单一整体指标诚实得多因为股票预测算法的价值不在平均分而在稳定性和可解释性。老实说我在这个方向上踩得最深的坑不是LSTM本身而是总想用更复杂的模型去救一个本身就有泄漏的训练集。数据切分、归一化和验证时序纠正好之后哪怕只靠两层LSTM加线性基线对比也能做出有说服力的结果。希望帮到你。本文还有配套的精品资源点击获取
返回列表