☰
多输入单输出LSTM实战:从数据形状到滑动窗口切分与归一化
2026/10/9 1:02:04 网站建设 项目流程

简介:面向深度学习初学者和金融时序分析爱好者,这是一套多输入单输出LSTM模型的Python实现项目,聚焦股票收盘价预测:从历史开盘价、最高价、最低价中提取多维特征,输出下一交易日的收盘价。压缩包内共10个文件,包括2个.ipynb源码、2个.csv数据集、2个.yaml项目说明及4个zip打包副本,整体约1.05MB,轻量便于快速上手;由 DeepLearning_ 发布,已有618人学习。项目以Jupyter Notebook串联完整流程,读者可按步骤复现数据预处理、滑动窗口样本构造、LSTM网络搭建、模型训练与验证,并对比预测值与真实值;同时可参考验证集与早停策略来控制过拟合,深入理解LSTM细胞状态、遗忘门、输入门、输出门的工作机制,以及多输入单输出建模思想。这套方案不只适用于股价,还可拓展到天气、电力负荷等序列预测任务。

1. 多输入单输出LSTM:一个模型同时吃多个变量,预测一个目标值

多输入单输出LSTM,说白了就是让一个模型同时吃进好几条时间序列,最后只吐一个预测值。比如用一台风机的温度、转速、振动幅值、当前风速四个变量过去24小时的数据,去预测下一个小时的轴承温度,这就是典型的多输入单输出。这类模型在电力负荷预测、工业设备故障预警、气象要素预报里都很常见,但真正卡住大多数人第一次跑通的地方,不是LSTM单元本身的原理,而是训练数据怎么从一张“宽表”变成模型要求的(samples, timesteps, features)三维结构,以及多输入到底该走一条LSTM还是多路输入再合并。这篇文章直接按一个JupyterNotebook项目的路子,从数据切分写到模型训练、从高频坑的排查写到单步预测如何向多步预测延伸,代码可以直接抄走改成自己的数据。

2. 多输入LSTM的数据准备:把多列原始数据切成监督学习样本

2.1 数据形状的底层逻辑:samples、timesteps、features分别代表什么

LSTM不是像XGBoost那样把每一行当成一个独立样本。它要的是“一段连续历史”作为一条样本。这种差异在最开始就会绊住人:你把DataFrame直接model.fit进去,Keras立刻报错说维度不对。

常见的数据组织方式是这样:假设你有一张表格,每一行是一个时间点,列是温度、转速、振动、风速这几个特征,目标是轴承温度。要让LSTM用过去seq_len个时间点预测下一个时间点的轴承温度,构造出来的输入张量形状必须是:

维度含义示例取值
samples一共有多少条历史窗口1000行数据切出约990条
timesteps每条样本包含几个连续时间点seq_len=10
features每个时间点上有几个输入变量4个特征列

输出y的形状则是(samples,)或者(samples, 1),对应每个窗口之后的那一个目标值。注意Keras的惯例是把timesteps放在第二维,不是第三维。你把维度顺序反了,模型不会直接报错,但训练出来的loss是乱的,这种错误比ShapeError更难查。

多输入单输出的“多输入”,在绝大多数实际项目里就是指多个特征列共享一个时间轴。少数情况下,几个输入序列的时间轴或采样率不一致,才需要真正拆成多个输入分支。先记住这个前提:先按共享时间轴来准备数据,绝大多数场景用不到复杂分支。

2.2 滑动窗口切分函数与最小可复现代码

准备数据这一步,我一般用一个通用函数,输入是DataFrame,输出是X, y两个numpy数组。直接看代码:

import numpy as np import pandas as pd def create_sequences(data: pd.DataFrame, seq_len: int, target_col: int = 0): """ 把多列DataFrame切成监督学习样本。 data: 按时间排序的原始数据,每列一个特征 seq_len: 历史窗口长度,用过去seq_len个点预测下一点 target_col: 目标列在data中的列索引,默认第0列 返回: X: shape (样本数, seq_len, 特征数) y: shape (样本数, 1) """ X, y = [], [] data_arr = data.to_numpy(dtype=np.float32) for i in range(len(data_arr) - seq_len): X.append(data_arr[i: i + seq_len]) # 前seq_len行全部特征 y.append(data_arr[i + seq_len, target_col]) # 紧接下一行的目标值 return np.array(X), np.array(y).reshape(-1, 1) # 用法示例 # df = pd.read_csv('data/processed/example.csv', parse_dates=['time']) # df = df.set_index('time').sort_index() # X, y = create_sequences(df[['wind', 'temp', 'vib', 'speed']], seq_len=10, target_col=1)

这里的逻辑要拆开说。data_arr[i: i + seq_len]取的是i到i+seq_len-1共seq_len行,y取的是i+seq_len那一行的目标值。也就是说,窗口和预测目标之间没有重叠,不会把当前时刻的信息偷给预测目标。边界上,总行数减去seq_len就是样本数,最后那几行因为凑不够完整窗口被丢弃,这符合预期,不需要特意补。

几个参数值得注意。seq_len选多大,取决于你的数据采样频率和预测周期的关系。如果数据是每5分钟一条、要预测未来半小时的状态,seq_len至少要能覆盖6个点起步,实际工程里我倾向于取预测周期的3到5倍。target_col一定要确认指向目标列,很多翻车案例是把第0列当成特征、目标列反而不在窗口里。dtype=np.float32这一步别省,LSTM在混合精度或GPU上跑,float64会白白增加显存和计算量。

2.3 归一化的正确做法:只在训练集上fit的MinMaxScaler

LSTM对输入尺度极其敏感。它内部的激活函数是tanh和sigmoid,输入数值一大,梯度直接饱和,loss像一条平线。所以归一化不是可选项,是必选项。常见做法是MinMaxScaler把数据压到0到1之间:

from sklearn.preprocessing import MinMaxScaler # 严格按时间切分:前80%训练,后20%测试 split_idx = int(len(df) * 0.8) train_df = df.iloc[:split_idx].copy() test_df = df.iloc[split_idx:].copy() # scaler只fit训练集 scaler_x = MinMaxScaler(feature_range=(0, 1)) train_scaled = scaler_x.fit_transform(train_df.drop(columns=['target'])) test_scaled = scaler_x.transform(test_df.drop(columns=['target'])) # 目标列单独做scaler,方便反归一化 scaler_y = MinMaxScaler(feature_range=(0, 1)) train_y_scaled = scaler_y.fit_transform(train_df[['target']].to_numpy()) test_y_scaled = scaler_y.transform(test_df[['target']].to_numpy())

这段代码里有两

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询