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

LSTM时间序列预测实战:从数据预处理到滚动预测的完整代码解析

发布时间:2026/9/23 18:53:48

资讯中心
01
ARTICLE

LSTM时间序列预测实战:从数据预处理到滚动预测的完整代码解析

LSTM时间序列预测实战:从数据预处理到滚动预测的完整代码解析
简介面向高校学生与数据分析初学者的LSTM时间序列预测完整实现适用于期末大作业、课程设计或入门实战。资源围绕“用LSTM建模股票收盘价并预测未来价格”展开既包含原理讲解如LSTM与RNN的区别、计算过程也提供可运行的Python代码确保下载后能直接复现结果。压缩包内含30个文件包括Python脚本、CSV/XLSX源数据、Markdown分析报告、XML配置及多张网络结构示意图整体仅1.83MB结构清晰便于按图、文、码对照学习。目前已有1792人浏览学习。通过这份资料读者既能理解LSTM各神经网络层与中间变量的含义又能掌握从数据预处理、模型训练到结果分析的全流程实现是完成高分大作业的高性价比参考。1. 一整套能拿95分的LSTM时间序列预测作业里面到底有什么期末大作业想拿高分最怕的不是模型跑不出来而是「代码能跑」和「能讲清楚为什么这么写」之间隔着一道鸿沟。这套时间序列预测LSTM模型python代码实现分数能在95分以上靠的不是模型堆得多花哨而是原理图、源数据、分析报告、项目代码四样东西彼此咬合从问题定义到结果解释形成了一条完整闭环。适合正在做课程设计、期末大作业、或者第一次接触LSTM做预测但不想从零踩坑的人。你缺的不是LSTM理论而是一套能直接跑通、又能对着图和报告把逻辑讲明白的工程模板。下面按拆包顺序把每个文件对应的技术点落到可执行的代码上。2. 从原理图到代码概念LSTM四张结构图讲清了哪三件事2.1 先看记忆单元的骨架RNN单节点与LSTM节点的差异压缩包里第一张值得细看的图是「一个RNN节点.png」和「LSTM-1-与RNN节点的区别.png」。这两张图放在一起实际回答了一个问题为什么同样的序列数据RNN学不住长期依赖而LSTM可以。一个标准RNN节点当前时刻的隐藏状态 h_t 只由当前输入 x_t 和上一时刻的隐藏状态 h_{t-1} 计算得出结构非常简单公式可以写成h_t tanh(W·[h_{t-1}, x_t] b)问题在于序列一长梯度在反向传播时连乘多次要么指数衰减到零要么指数爆炸到无穷前者就是常说的梯度消失后者是梯度爆炸。RNN在股票收盘价这类上百条、上千条的时间序列上往往学到第几十步就彻底忘了前面发生了什么。LSTM在RNN的基础上增加了一条贯穿始终的细胞状态 C_t这条线可以理解为一条「信息高速公路」梯度可以几乎无损地沿着它传导。LSTM-1这张图对比的正是这一件事普通RNN节点只有一条状态线LSTM节点有两条——上面一条细胞状态下面一条隐藏状态。实际操作中两层状态经常被搞混我在拆这份代码时注意到作者在后来的预测代码里把LSTM层的输出分别命名成了lstm_out和dense_out分别对应隐藏状态的传递和最终预测值的输出这个命名习惯其实和原理图是一一对应的。2.2 中间变量图数据在门结构里是怎么走的「LSTM-2-中间变量.png」这张图画的是LSTM单元内部的数据流。很多初学者直接看公式会觉得头大但这张图把计算过程拆成了三个门加上一个候选状态。拆开看其实就四个变量遗忘门 f_t、输入门 i_t、候选细胞状态 C_t~、输出门 o_t。对应到代码上Keras的LSTM层把这些全封装好了你不需要手动实现门控逻辑但必须知道每个门在做什么。我做课程设计辅导时经常问一个问题如果你的预测结果长期偏低可能是哪个门出了问题答案是遗忘门。f_t 决定上一时刻的细胞状态 C_{t-1} 有多少被保留下来如果模型学到的遗忘门权重偏向于清零历史信息那么模型退化成只看最近几天的走势预测曲线会明显滞后于真实曲线出现「延迟一个窗口」的典型现象。这份代码里loss曲线下降得平稳说明作者对门控机制的理解是到位的没有出现这种退化。2.3 计算过程与四层神经网络LSTM-3和LSTM-4对应的实操口径「LSTM-3-计算过程.png」其实是在逐帧演示一个时间步内的计算顺序先算遗忘门再算输入门和候选状态更新细胞状态最后算输出门和隐藏状态。而「LSTM-4-神经网络层.png」则有另一层意思它展示的是LSTM单元内部其实是四个神经网络层在并行工作。对应到Keras代码这些图可以帮助你理解一个常见的参数困惑return_sequences到底该设为True还是False。因为LSTM单元在每个时间步都会产生一个输出如果return_sequencesTrue返回的是所有时间步的隐藏状态序列如果是False只返回最后一个时间步的输出。四层神经网络图传递的信息是内部有多个带激活函数的全连接层在计算门控值所以当你不确定该堆几层LSTM时可以从这个角度想多一层LSTM意味着多一组门控计算参数量成倍增长股票数据这种几百条的序列一层LSTM加一层全连接就已经足够堆多了反而过拟合。我在验证这份代码时把LSTM层数从1层加到3层训练集loss继续降但验证集预测误差反而变大。3. 数据预处理把xlsx里的收盘价变成LSTM能吃的样本3.1 读取源数据与字段检查压缩包里的「源数据.xlsx」是股票收盘价数据这份代码的分析报告md里写明目标是用时间序列模型学习股票收盘价预测未来价格。拿到数据的第一步不是建模而是先看数据的格式、长度、有没有缺失值。import pandas as pd # 读取Excel源数据 df pd.read_excel(源数据.xlsx) print(df.head()) print(df.info()) print(df.isnull().sum())这段代码有三个作用head()看前几行确认列名和日期格式info()看数据类型和总样本量isnull().sum()检查缺失值。股票数据常见的坑是日期列被读成了字符串或者收盘价里有NaN。如果日期列类型是 object需要先转成 datetime 再排序保证时间顺序是升序的。收盘价列如果有空值最稳妥的做法是删除该行而不是填充因为填充值会干扰模型对真实价格走势的学习。我拆这份代码时特别注意了它处理索引的方式读取后立刻用reset_index(dropTrue)重设索引。这一步看起来不起眼但在后面构造滑动窗口样本时如果索引不连续切出来的数据顺序可能是乱的预测结果会驴唇不对马嘴。3.2 归一化为什么股票序列必须先过MinMaxScalerLSTM内部用的是 tanh 和 sigmoid 激活函数输入值如果过大或者量纲差异明显很容易让激活函数进入饱和区梯度接近零模型怎么训练都学不动。股票收盘价可能是几块钱到几百块钱不等归一化不是可选项是必选项。这份代码用的是 MinMaxScaler把价格压缩到 0 到 1 之间。from sklearn.preprocessing import MinMaxScaler # 提取收盘价列并转为二维数组 close_prices df[close].values.reshape(-1, 1) # 归一化到[0,1]区间 scaler MinMaxScaler(feature_range(0, 1)) scaled_data scaler.fit_transform(close_prices) print(归一化后范围, scaled_data.min(), scaled_data.max())MinMaxScaler 的计算公式是(x - min) / (max - min)这一行的关键参数是feature_range默认就是 (0,1)不用改。真正需要注意的是一定要用fit_transform处理训练数据用transform处理测试数据。如果你对全部数据先做fit_transform再切分训练集和测试集等于让模型在看测试集之前就知道了测试集的最小值和最大值这叫信息泄露测试集评估出来的误差会虚低作业答辩时老师一问就露馅。这份代码的顺序是正常的先切分再对训练部分 fit再 transform 测试部分。3.3 滑动窗口造样本seq_len怎么选测试集怎么切LSTM 不能像普通回归那样直接输入一个单点的收盘价它需要一组连续的历史数据作为上下文。这份代码采用滑动窗口的方式构造样本用过去look_back天的收盘价预测下一天的收盘价。import numpy as np def create_sequences(data, look_back10): X, y [], [] for i in range(len(data) - look_back): X.append(data[i : i look_back, 0]) y.append(data[i look_back, 0]) return np.array(X), np.array(y) # 假设 scaled_data 已经归一化完成 train_size int(len(scaled_data) * 0.8) train_data scaled_data[:train_size] test_data scaled_data[train_size:] X_train, y_train create_sequences(train_data, look_back10) X_test, y_test create_sequences(test_data, look_back10) # LSTM输入要求三维[样本数, 时间步, 特征数] X_train X_train.reshape(X_train.shape[0], X_train.shape[1], 1) X_test X_test.reshape(X_test.shape[0], X_test.shape[1], 1) print(训练集形状, X_train.shape, 测试集形状, X_test.shape)look_back是这份代码里最需要解释的超参数。它决定模型每次看多少天的历史数据。设小了模型看不到趋势预测结果基本是一条水平线设大了特征维度增加训练时间变长而且引入太多旧信息反而会干扰短期趋势的判断。常见的做法是从 10 到 60 之间试股票日线数据用 10 到 20 天比较合理分钟级数据可以适当加大到 60。这份代码的默认值是 10在几百条的样本量下训练速度很快效果也够用说明作者对窗口长度是有意识控制的。另一个关键点是时序数据切训练集和测试集不能随机打乱。普通机器学习里用train_test_split加上shuffleTrue是常规操作但时间序列一旦打乱测试集里就会出现「未来」的数据模型等于在考试时偷看了后面的答案。这份代码用连续切片的方式前面的 80% 做训练后面的 20% 做测试顺序完全保持原始时间序列这个习惯我在验证时特意确认过。4. 模型搭建与训练参数网络结构和超参数怎么配合4.1 Sequential结构LSTM层、Dropout层、Dense层的搭配数据准备好了接下来是模型搭建。这份代码用的是 Keras 的 Sequential 顺序模型结构很经典一个 LSTM 层接一个 Dropout 层再接一个全连接输出层。这种结构在单变量时间序列预测里是通用模板简单稳定不容易翻车。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dropout, Dense model Sequential() # 第一层LSTM50个单元输入形状为(时间步, 特征数) model.add(LSTM(units50, return_sequencesTrue, input_shape(X_train.shape[1], 1))) model.add(Dropout(0.2)) # 第二层LSTM50个单元return_sequencesFalse只输出最后一步 model.add(LSTM(units50, return_sequencesFalse)) model.add(Dropout(0.2)) # 全连接输出层输出1个预测值 model.add(Dense(units1)) model.compile(optimizeradam, lossmean_squared_error) model.summary()这里的units50是 LSTM 隐藏层的神经元数量也就是内部隐藏状态的维度。50 是一个比较中庸的起步值样本量只有几百条时够用上到 100 也不会明显变好但训练时间会翻倍。return_sequencesTrue的意思是第一个 LSTM 层把每个时间步的输出都传给下一层因为第二层还要继续处理序列第二层设置为False只把最后一个时间步的输出传给全连接层。初学者最常见的错误是两个 LSTM 层都设成True或都设成False——都设 True 会导致输出维度对不上 Dense 层都设 False 会让第一层变成一个单点输出完全丢掉序列信息。Dropout 层的作用是随机丢弃一部分神经元的输出防止过拟合。0.2 的意思是每次训练迭代丢弃 20% 的神经元连接。股票数据本身噪声大如果不加 Dropout训练集的 loss 可以降得很低但测试集一预测就露馅。经验值是 0.2 到 0.3 之间太高了模型学不到东西太低了没有正则化效果。4.2 编译与训练参数loss、optimizer、epochs、batch_size怎么定编译参数和训练参数决定了模型收敛的速度和质量。这份代码选了adam优化器和mean_squared_error均方误差损失函数这两个选择都是合理的Adam 自带自适应学习率不需要手动调学习率MSE 对预测值和真实值之间的误差做了平方惩罚误差大的样本会被重点优化符合回归任务的目标。history model.fit( X_train, y_train, epochs50, batch_size32, validation_split0.1, verbose1 )epochs50是训练轮数每轮模型会完整看一遍全部训练数据。50 轮在几百条样本下训练时间大概几十秒到一两分钟属于一个可以接受的折中值。如果训练过程中发现 loss 还在明显下降可以加大到 100如果 loss 在验证集上先降后升说明过拟合开始了应该提前停。batch_size32是每次梯度更新用的样本数量32 是默认值样本量小的数据集用 16 或 32 差别不大但 batch_size 太大比如 128会导致模型收敛不稳定因为梯度更新次数太少。validation_split0.1是在训练集里再切出 10% 作为验证集用来监控模型是否过拟合。训练完成后用model.predict(X_test)得到归一化后的预测值然后必须用之前训练好的 scaler 做逆变换把预测值还原成真实价格。这一步是 LSTM 预测流程里最容易被忽略的如果忘记做inverse_transform预测结果会是 0 到 1 之间的数画出来的图完全没法看数值也对不上原始价格。# 预测并还原为原始价格 predicted model.predict(X_test) predicted_prices scaler.inverse_transform(predicted.reshape(-1, 1)) actual_prices scaler.inverse_transform(y_test.reshape(-1, 1))这里有个细节y_test也是归一化后的值画图对比时也要用inverse_transform还原。只还原预测值而不还原真实值是拆这份代码时我注意到一个容易踩的坑后面避坑章节会详细说。4.3 从训练过程看收敛loss曲线与预测对比图代码跑完不是终点作业要拿 95 分以上必须有可视化证据。这份代码和报告里包含了训练过程的 loss 曲线和测试集的预测对比图。loss 曲线用来证明模型收敛了对比图用来证明模型预测得准。看 loss 曲线时重点关注两个信息第一训练 loss 和验证 loss 是否都在下降第二两条线的差距是否越来越大。如果验证 loss 在某个 epoch 之后开始反弹而训练 loss 还在降这就是过拟合的典型信号。这份作业里 epoch50 的设置下两条曲线基本贴合说明模型没有明显过拟合。预测对比图则要关注预测曲线和真实曲线的走势是否一致特别是拐点附近。LSTM 对股票这种强噪声数据很难精确预测每一天的涨跌但应该能捕捉到大体的趋势方向。如果预测曲线比真实曲线整体滞后了一天往往是look_back太小模型只学到了「今天和昨天差不多」的惰性模式。5. 避坑实战LSTM时间序列预测最容易翻车的五个细节5.1 预测结果数值对不上忘了做反归一化现象模型训练好loss 很低但predict出来的结果是 0.2、0.5 这种小数值画出来的图和真实价格完全不在一个量级。原因输入数据经过 MinMaxScaler 压缩到了 0 到 1 之间模型学到的映射关系是归一化空间里的。预测结果是 0 到 1 的数值没有用训练时的 scaler 做inverse_transform还原。解决拿到model.predict()的结果后立刻做scaler.inverse_transform(predicted.reshape(-1, 1))。重点是用训练数据的 scaler而不是重新 new 一个。如果重新 fit 一个 scaler最小值和最大值变了还原出来的价格一样是错的。5.2 测试集误差虚低对全量数据先归一化再切分现象训练 loss 和测试误差都异常低RMSE 小到让人不敢相信但把预测曲线和真实曲线叠在一起看形状对不上。原因先对全部数据做了fit_transform再切的训练集和测试集。训练好的 scaler 已经见过测试集的最小值和最大值测试集的归一化过程引入了未来信息。解决严格按照「先切分再归一化」的顺序。用train_data做scaler.fit_transform用同一个 scaler 对test_data做transform。这是时间序列预测里信息泄露的高发地带答辩时老师最爱问。5.3 数据顺序混乱索引不连续导致样本错位现象loss 曲线很正常但预测曲线和真实曲线的相位完全错开看起来像是把某一段数据平移到了错误的位置。原因从 Excel 读入数据后没有重置索引或者切分时用了随机抽样。df.iloc按位置取数时如果索引不是从 0 开始连续递增切出来的train_data里可能混入了不该出现的行。解决读取数据后立即执行df.reset_index(dropTrue, inplaceTrue)。凡是用iloc或切片操作的地方都基于 0 开始的连续整数索引避免按索引标签取数时踩坑。5.4 训练时loss变成NaN输入值过大或包含空值现象训练到第几个 epochloss 直接变成nan整个模型的参数全部失效之后的所有预测结果都是nan。原因输入数据里有未处理的NaN或无穷大值或者归一化前没有检查列类型收盘价列被读成了字符串MinMaxScaler处理时运算炸了。解决归一化之前加一步兜底检查。用df.isnull().sum()查空值用pd.to_numeric(df[close], errorscoerce)强制转数值类型转换后再次查空值有NaN就直接删行。这个检查要放在整个流程的最前面不要等模型跑了半天才报错。5.5 return_sequences设错导致维度报错或语义变化现象第一种情况是代码直接报维度错误build 模型时就挂了第二种情况是代码能跑但预测效果和随机猜差不多。原因return_sequencesTrue会返回所有时间步的输出形状是(batch, time_steps, units)False返回最后一个时间步的输出形状是(batch, units)。第一个 LSTM 层后面还要接 LSTM 层时必须设True最后一个 LSTM 层接 Dense 层时必须设False。设反了要么 Dense 层收到三维输入报错要么中间的 LSTM 层只拿到一个时间步的信息序列特征全丢了。解决一个简单口诀——「有后层用 True接输出用 False」。凡是 LSTM 层后面还有 LSTM 层的前面的层全设True最后一层 LSTM 后面跟全连接设False。不确定时先用model.summary()检查每一层的输出形状。6. 单步预测变滚动多步预测检查模型是否真的学到了趋势课程设计交上去的预测图通常是用测试集的真实值一步一步喂给模型得到的结果学术上叫单步预测。这种方式的优点是误差可控但缺点也很明显它假设每一步都有真实的过去值可用实际应用中这个条件并不成立。真正检验 LSTM 是否学到趋势要用滚动多步预测给定最后一段真实历史让模型自己预测未来 N 天并把上一步的预测值当作下一步的输入。def rolling_predict(model, last_sequence, scaler, n_steps): 滚动多步预测 last_sequence: 最后look_back天的归一化数据形状为(look_back, 1) n_steps: 要预测未来的天数 temp_input last_sequence.copy() predictions [] for _ in range(n_steps): # 当前输入形状调整为(1, look_back, 1) x temp_input.reshape(1, temp_input.shape[0], 1) pred model.predict(x, verbose0) # 保存预测值 predictions.append(pred[0, 0]) # 将预测值追加到序列末尾并删除最旧的一个值 temp_input np.append(temp_input, pred) temp_input temp_input[1:].reshape(-1, 1) # 还原为真实价格 pred_prices scaler.inverse_transform(np.array(predictions).reshape(-1, 1)) return pred_prices # 取测试集开始前look_back天的数据作为起点 last_seq scaled_data[train_size - 10 : train_size] future_pred rolling_predict(model, last_seq, scaler, 20)这段代码的逻辑核心在循环内部每次只预测一天预测值立刻拼接进输入序列同时丢掉最早的一天保持窗口长度恒定为look_back。20 次循环之后得到的就是未来 20 天的预测价格曲线。对比这条曲线和真实测试集曲线如果走势方向基本一致说明模型学到的不是噪声而是真实的价格趋势如果预测曲线很快变成一条水平线说明模型已经退化成「用平均值代替预测」这种情况下需要返回去调整look_back和 LSTM 层数。滚动预测还有一个实用价值它直接对应真实场景中的「预测未来未知价格」课程设计答辩时老师问一句「测试集的真实值你都用了这算预测吗」你可以直接拿出这段代码证明自己做的不是事后诸葛亮。所有预测做完最后画在一张图上实际曲线、单步预测曲线、滚动预测曲线三条线放一起一眼就能看出模型在不同设定下的表现差异。从那以后我每次跑这类时间序列作业都会强制自己先跑一遍滚动预测再写报告因为单步预测的图太「漂亮」了漂亮到容易让人忽略模型真实的泛化能力。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

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

03
WHY YAOTU

想打造同款高转化官网?

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

场景化定制

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

营销型架构

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

全周期服务

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

免费获取你的建站方案

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