
简介本资源是一套面向深度学习初学者与时间序列建模实践者的LSTM多输入多输出预测完整实现方案聚焦空气质量、污染指数等多变量时序预测任务适用于人工智能、机器学习课程设计及工业场景短期趋势预估。压缩包共126个文件含75个Python训练/推理脚本含数据加载、模型构建、滑动窗口处理、多步预测逻辑、26个CSV实测数据集如pollution.csv、air_pollution_new.csv等、15个说明与配置文本以及h5模型权重、checkpoint检查点等训练产物整体仅5.12MB轻量易部署。目前已有3856人学习下载覆盖高校学生、算法工程师及自学开发者。读者可直接复现端到端流程从多源时序数据读取、标准化与多步滑动窗口构造到LSTM多输入门控结构搭建、多输出头设计再到结果可视化与误差分析代码模块清晰、注释充分具备强教学性与工程参考价值。1. 这不是单变量预测DemoLSTM多输入输出时间序列项目能同时吃进温度、湿度、风速、气压4路传感器数据直接吐出未来3小时的逐小时负荷预测值——适合做能源调度、IoT边缘预测、工业设备健康度推演的开发者别再用单列df[value]硬喂LSTM了你手头有一套部署在变电站边缘网关上的传感器集群每5分钟上报一次温度、湿度、风速、气压、历史负荷共5维时序数据业务方要的不是“明天整体负荷涨还是跌”而是“未来3小时每小时整点的有功功率精确到0.1kW的数值”。这时候拿PyTorch写个nn.LSTM(input_size1, hidden_size64)跑单变量预测模型会当场给你上一课它根本不知道湿度突变对空调集群启停的影响权重比温度高2.3倍。这个LSTM多输入输出时间序列项目就是为这种真实工业场景写的——它把多源异构传感器数据当第一等公民处理输入层强制对齐时间戳、标准化分通道、支持缺失值掩码输出层不拼接也不reshape而是原生支持[batch, seq_len, output_features]三维张量直出比如[32, 3, 1]32个样本各预测未来3小时每小时1个负荷值或[32, 3, 2]同时预测负荷电压波动。代码里没有np.expand_dims()硬拉维度的玄学操作所有shape变换都发生在DataLoader的collate_fn里且附带断言校验。我去年在某高校智能微电网实验室落地时直接替换掉他们原来用sklearn.svm.SVR做的单变量滚动预测RMSE从1.87kW压到0.63kW关键是——上线后运维同事再也不用半夜改shift(1)的偏移量了。2. 项目结构与技术栈基于PyTorch 2.0的端到端流程含数据预处理管道、多输入LSTM核心、动态输出头三大部分2.1 目录结构解析每个文件名都在说“我负责什么”拒绝“utils.py”式黑匣子项目解压后共12个文件核心逻辑全部收敛在src/目录下无隐藏配置或动态加载模块src/ ├── data/ # 数据处理专属包非通用工具 │ ├── __init__.py │ ├── dataset.py # 核心MultiInputTimeSeriesDataset类重载__getitem__实现多源对齐 │ └── preprocessing.py # 通道级标准化fit_transform按列独立计算mean/std非全局归一化 ├── models/ # 模型定义区无训练逻辑 │ ├── __init__.py │ ├── lstm_multiio.py # 主模型MultiInputLSTM类含input_embedding和output_head双模块 │ └── layers.py # 自定义层MaskedLSTMCell跳过NaN时间步、TemporalLinear时序感知全连接 ├── train.py # 训练入口支持--input-features temp,humid,wind,pressure,load --output-features load ├── predict.py # 预测脚本接受CSV含timestamp列自动补全缺失时间戳并返回DataFrame ├── config.yaml # 关键参数seq_len: 24输入24个历史点pred_len: 3预测3步feature_dims: [1,1,1,1,1] └── requirements.txt # 锁定torch2.0.1cu118pandas1.5.3scikit-learn1.2.2提示config.yaml中feature_dims字段是关键设计——它明确定义每个输入特征的维度数如temp是标量→1但若你接入红外热成像序列可设为[1,64,64]表示64×64像素帧。这避免了传统做法中用np.concatenate()粗暴拼接导致的通道混淆。2.2 技术选型依据为什么不用TensorFlow/KerasPyTorch的PackedSequence是多输入对齐的后悔药项目放弃Keras的Functional API核心原因有三第一时间步对齐不可妥协。工业传感器采样存在天然抖动如A通道5min±3sB通道5min±8sKerasTimeSeriesGenerator只能按固定步长切片而PyTorchpack_padded_sequence允许我们先按原始时间戳排序再用torch.nn.utils.rnn.pad_sequence补零最后用pack_padded_sequence压缩有效长度——模型内部LSTM只计算非零部分避免无效计算污染梯度。第二缺失值处理必须前移至数据层。项目中dataset.py的__getitem__方法在返回前执行# src/data/dataset.py 第89行 def __getitem__(self, idx): # ... 原始数据读取 ... # 对每个特征列单独插值线性前向填充 for i, feat in enumerate(self.features): if np.isnan(data_slice[:, i]).any(): data_slice[:, i] pd.Series(data_slice[:, i]).interpolate( methodlinear, limit_directionboth ).fillna(methodffill).values return torch.tensor(data_slice, dtypetorch.float32)第三输出头需支持动态形状。业务方可能今天要预测3小时负荷明天要预测未来24小时的逐15分钟电压波动即pred_len96。Keras需重新编译模型而本项目lstm_multiio.py中output_head是nn.Sequential包裹的TemporalLinear其forward方法接收pred_len参数动态生成输出层# src/models/layers.py 第42行 class TemporalLinear(nn.Module): def forward(self, x: torch.Tensor, pred_len: int) - torch.Tensor: # x: [batch, seq_len, hidden_size] # 输出: [batch, pred_len, output_features] out self.linear(x) # 先映射到统一隐层 # 用repeat_interleave扩展时间维度非简单repeat避免梯度坍缩 return out.repeat_interleave(pred_len, dim1).view(x.size(0), pred_len, -1)2.3 多输入LSTM核心MultiInputLSTM类如何让5路传感器数据“各走各的通道”模型主体MultiInputLSTM不采用torch.nn.LSTM原生接口而是封装MaskedLSTMCell实现细粒度控制。关键设计在于输入嵌入层分离# src/models/lstm_multiio.py 第56行 class MultiInputLSTM(nn.Module): def __init__(self, input_dims, hidden_size, num_layers, dropout0.2): super().__init__() self.input_dims input_dims # e.g., [1,1,1,1,1] for 5 features self.hidden_size hidden_size self.num_layers num_layers # 为每个输入特征创建独立嵌入层非共享权重 self.embeddings nn.ModuleList([ nn.Linear(dim, hidden_size // len(input_dims)) for dim in input_dims ]) # 主LSTM输入尺寸为各嵌入层输出之和 total_embed_size sum(hidden_size // len(input_dims) for _ in input_dims) self.lstm nn.LSTM( input_sizetotal_embed_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0 )这段代码解决了一个致命问题传统多变量LSTM常将5列数据concat后喂入单个nn.Linear导致模型无法区分“温度变化1℃”和“湿度变化1%”的物理意义差异。而此处为每个特征分配独立nn.Linear强制网络学习各通道的特异性映射关系。实测中当我们将input_dims改为[1,1,1,1,2]最后一维是二维坐标模型在加入地理信息后对区域负荷峰谷的捕捉精度提升17.3%。3. 快速启动从零开始跑通预测流程含数据准备、训练、验证三阶段命令3.1 数据准备用generate_sample_data.py生成符合工业规范的模拟数据集项目未提供真实数据但附带scripts/generate_sample_data.py生成严格遵循IEC 61850时序规范的CSVcd scripts/ python generate_sample_data.py \ --output-dir ../data/simulated/ \ --n-samples 10000 \ --freq 5T \ --features temp,humid,wind,pressure,load \ --noise-level 0.05该脚本生成的train.csv包含timestamp: ISO格式时间戳如2023-01-01T00:00:00Z已按升序排列temp,humid,wind,pressure: 模拟传感器数据含5%随机缺失值load: 负荷真值由温度-湿度耦合公式生成load 100 2*temp - 0.5*humid 0.3*wind^2叠加高斯噪声注意生成的数据自动满足config.yaml中seq_len24要求——即每行代表一个5分钟点24行覆盖2小时历史窗口。3.2 训练命令一行启动关键参数必须显式声明进入项目根目录执行# 创建虚拟环境推荐conda conda create -n lstm-multiio python3.9 conda activate lstm-multiio pip install -r requirements.txt # 启动训练GPU加速 python src/train.py \ --data-path data/simulated/train.csv \ --config-path src/config.yaml \ --input-features temp,humid,wind,pressure \ --output-features load \ --seq-len 24 \ --pred-len 3 \ --epochs 50 \ --batch-size 32 \ --lr 0.001 \ --device cuda:0训练过程实时输出Epoch 1/50 | Loss: 0.8423 | Val Loss: 0.7912 | LR: 0.0010 Epoch 2/50 | Loss: 0.7215 | Val Loss: 0.6894 | LR: 0.0010 ... Best model saved to checkpoints/best_model.pth (val_loss0.4127)模型检查点保存在checkpoints/含完整state_dict和config快照确保可复现。3.3 预测验证用predict.py生成未来3小时负荷曲线并导出为业务系统可读格式训练完成后对data/simulated/test.csv含1000个测试样本执行批量预测python src/predict.py \ --model-path checkpoints/best_model.pth \ --data-path data/simulated/test.csv \ --output-path results/predictions.csv \ --input-features temp,humid,wind,pressure \ --output-features load \ --seq-len 24 \ --pred-len 3 \ --device cuda:0输出results/predictions.csv结构为timestampload_pred_t0load_pred_t1load_pred_t2load_true_t0load_true_t1load_true_t22023-01-01T00:00:00Z124.3125.7126.1124.1125.9126.0其中t0/t1/t2对应未来第1/2/3小时预测值与业务系统要求的“整点负荷预报”完全对齐。4. 避坑指南5个血泪经验总结解决90%新手首次运行失败的问题4.1 现象训练时Loss恒为nan且Val Loss显示inf原因config.yaml中seq_len设置大于数据实际长度。例如数据只有20个历史点但配置seq_len: 24dataset.py在__getitem__中尝试切片data[idx:idx24]时触发IndexError后续torch.tensor()将空数组转为nan。解决运行前先检查数据长度head -n 1 data/simulated/train.csv # 查看列名 wc -l data/simulated/train.csv # 确保行数 seq_len pred_len若数据不足在generate_sample_data.py中增大--n-samples参数。4.2 现象predict.py报错KeyError: temp但CSV明明有该列原因CSV中列名含不可见空格或BOM头。Windows记事本保存的CSV常在首行开头插入\ufeff导致pd.read_csv()读出列为\ufefftemp。解决用vim或xxd检查文件头xxd -l 10 data/simulated/test.csv # 若显示00000000: efbb bf74 656d 702c... 则存在BOM # 修复命令 sed -i 1s/^\xef\xbb\xbf// data/simulated/test.csv4.3 现象GPU显存溢出CUDA out of memory即使batch-size1原因config.yaml中feature_dims未与--input-features匹配。例如配置feature_dims: [1,1,1,1,1]5维但命令行传入--input-features temp,humid仅2维模型仍按5维初始化嵌入层导致self.embeddings占用冗余显存。解决严格保持三者一致config.yaml中feature_dims长度 --input-features逗号分隔数CSV文件中实际列数 --input-features指定列数--output-features必须是CSV中存在的列名用于真值对比4.4 现象预测结果全为0且predictions.csv中load_pred_t0列全0原因predict.py未加载训练时的标准化参数。preprocessing.py中StandardScaler对象在训练时保存为scaler.pkl但预测脚本默认从data/simulated/下查找而训练时可能指定了其他路径。解决手动指定scaler路径python src/predict.py \ --scaler-path data/simulated/scaler.pkl \ # 显式传入 # ... 其他参数4.5 现象train.py报错AttributeError: NoneType object has no attribute size原因dataset.py中插值失败。当某特征列连续缺失超过limit_directionboth可处理范围如连续100个NaNinterpolate()返回None。解决在generate_sample_data.py中降低--noise-level或修改dataset.py插值逻辑# 替换原插值代码src/data/dataset.py 第92行 # 改为强约束插值 data_slice[:, i] pd.Series(data_slice[:, i]).interpolate( methodlinear, limit10, limit_directionboth ).fillna(data_slice[:, i].mean()).values # 最终用均值兜底5. 二次开发实战如何接入真实传感器数据流并支持在线学习5.1 接入MQTT实时数据用RealTimeInference类替代predict.py工业现场数据常通过MQTT发布而非静态CSV。我们在src/inference/realtime.py中实现轻量级实时推理器# src/inference/realtime.py class RealTimeInference: def __init__(self, model_path: str, scaler_path: str, window_size: int 24): self.model torch.load(model_path).eval() self.scaler joblib.load(scaler_path) self.window_size window_size self.buffer deque(maxlenwindow_size) # 环形缓冲区存储最近24个点 def on_message(self, client, userdata, msg): # MQTT消息格式: {timestamp: 2023-01-01T00:00:00Z, temp: 25.3, humid: 65.2, ...} payload json.loads(msg.payload.decode()) # 按config.yaml中feature顺序提取数值 features [payload[f] for f in self.config[input_features]] self.buffer.append(features) if len(self.buffer) self.window_size: # 构造输入tensor X torch.tensor(list(self.buffer), dtypetorch.float32) X_scaled torch.tensor( self.scaler.transform(X.numpy()), dtypetorch.float32 ) with torch.no_grad(): pred self.model(X_scaled.unsqueeze(0)) # [1, 3, 1] # 发布预测结果到MQTT topic forecast/load client.publish(forecast/load, json.dumps({ t0: pred[0,0,0].item(), t1: pred[0,1,0].item(), t2: pred[0,2,0].item() }))部署时只需# app.py from src.inference.realtime import RealTimeInference import paho.mqtt.client as mqtt client mqtt.Client() infer RealTimeInference(checkpoints/best_model.pth, data/scaler.pkl) client.on_message infer.on_message client.connect(mqtt-broker.local, 1883) client.subscribe(sensors/#) client.loop_forever()5.2 在线学习Online Learning用update_weights()增量更新模型真实场景中负荷规律随季节漂移需定期用新数据微调。项目预留model.update_weights()接口# src/models/lstm_multiio.py 第188行 def update_weights(self, X_new: torch.Tensor, y_new: torch.Tensor, lr: float 1e-5): X_new: [batch, seq_len, n_features] 新输入数据 y_new: [batch, pred_len, n_outputs] 新真值标签 self.train() optimizer torch.optim.Adam(self.parameters(), lrlr) # 单步梯度更新避免过拟合 optimizer.zero_grad() pred self(X_new) loss F.mse_loss(pred, y_new) loss.backward() optimizer.step() return loss.item() # 使用示例在inference脚本中 if new_data_available: loss model.update_weights(X_batch, y_batch) print(fOnline update loss: {loss:.4f})提示线上更新建议用lr1e-5比初始训练小100倍且每24小时仅执行1次避免模型被短期噪声带偏。5.3 扩展输出维度从单目标预测升级为多目标联合预测当前项目默认预测load但实际需同步预测voltage_deviation和frequency_drift。只需三步改造修改配置config.yaml中output-features: load,voltage_deviation,frequency_drift调整数据确保train.csv包含这三列且feature_dims末尾增加[1,1,1]重训模型train.py自动识别输出维度output_head将生成[batch, 3, 3]张量验证时predictions.csv新增列load_pred_t0voltage_pred_t0freq_pred_t0...从那以后我每次接入新传感器都强制走一遍generate_sample_data.py生成同分布模拟数据再用train.py --epochs 5快速验证通道连通性——这5分钟省下的调试时间够我喝三杯咖啡。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。