PyTorch LSTM时间序列多步预测:输出连续相同值问题排查
LSTM多步预测异常:预测值完全相同的问题分析
问题背景
我搭建了一个LSTM模型,用于预测数据集第0列(c1)的未来10行数值。输入序列包含10行时间序列数据与19个特征,序列生成逻辑如下:
for i in range(sequence_length, len(data) - 10): sequences.append(data.iloc[i-sequence_length:i, 2:2+input_size].values) labels.append(data.iloc[i + 1: i + 11, 0])
样本数据示例
c1,c2,c3,c4,c5,c6,c7,c8,c9,c10,c11,c12,c13,c14,c15,c16,c17,c18,c19,c20,c21 1.084,1.08405,1.0841,1.08405,1.0841,1.084,11240,6.249999999985434e-05,-1.0164458235761842e-05,-5.1788748878102555e-05,1.0840285714285716,1.0840928571428572,1.0840280952380952,1.08405,-0.000937629492890638,0.8237791754445127,-0.009223815892633767,49.223395431868134,-3.13680151375703,0.010743580701520136,1000.2306464528247 1.084,1.08405,1.08405,1.08405,1.0841,1.08405,14158,-2.4999999999941735e-05,-9.32997172098382e-06,-6.046625792230974e-05,1.0840285714285716,1.0840857142857143,1.0840309523809522,1.084046103896104,-0.0008606520795521739,3.185291329162407,-0.009223815892633767,49.223395431868134,-2.9477598235694686,0.009208783458445832,1000.2306464528247 1.0839,1.08395,1.08405,1.08395,1.08405,1.08385,19095,-0.00015749999999981057,-1.6547055257998267e-05,-7.543797446324434e-05,1.0840142857142856,1.0840690476190478,1.0840204761904761,1.0840337662337662,-0.0015264100999568611,8.156945531675506,-0.009224666758912318,41.76004501048701,-4.958497925954123,-0.26489247132130206,1000.2306464528247 1.08395,1.084,1.08395,1.084,1.084,1.08385,12756,-0.0001474999999999671,-1.8024291017937344e-05,-9.06405060916429e-05,1.0840035714285714,1.0840547619047618,1.0840185714285715,1.084027489177489,-0.0016626858514864735,7.660743847017261,0.009225943352706798,46.15600965239905,-5.393125751532237,-0.13593640398949522,1000.2767846809004
训练异常现象
训练过程中损失大幅下降(已降至3.1…e-8),但单个输入序列对应的10个预测值始终完全相同。例如:
标签序列:
[1.084,1.0845,1.084,1.08395,1.0839,1.0838,1.0839,1.084,1.0845,1.084]
预测序列:
[1.08395,1.08395,1.08395,1.08395,1.08395,1.08395,1.08395,1.08395,1.08395,1.08395]
批量输出形式:
[ [1.08395,1.08395,..] [1.0841,1.0841,..] .. ]
完整模型代码
import torch import pandas as pd import torch.nn as nn import numpy as np from torch.utils.data import DataLoader, TensorDataset from torch.optim.lr_scheduler import StepLR import matplotlib.pyplot as plt device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') class CustomLSTM(nn.Module): def __init__(self, input_size, hidden_size, output_size, dropout, num_layers): super(CustomLSTM, self).__init__() self.hidden_size = hidden_size self.num_layers = num_layers self.lstm = nn.LSTM(input_size, hidden_size, num_layers=num_layers, batch_first=True, bidirectional=True) self.relu = nn.ReLU() # ReLU activation layer self.bn = nn.BatchNorm1d(hidden_size * 2) # Batch normalization layer self.dropout = nn.Dropout(dropout) self.fc = nn.Linear(hidden_size * 2, output_size) def forward(self, x): h0 = torch.zeros(self.num_layers * 2, x.size(0), self.hidden_size).double().to(x.device) c0 = torch.zeros(self.num_layers * 2, x.size(0), self.hidden_size).double().to(x.device) x = torch.nn.functional.normalize(x) out, _ = self.lstm(x, (h0, c0)) out = self.relu(out[:, -1, :]) # Apply ReLU activation out = self.bn(out) # Apply batch normalization out = self.dropout(out) # Apply dropout out = self.fc(out) return out input_size = 19 # Number of input features # Loss calculation for regression model criterion = nn.MSELoss() data = pd.read_csv('chapter6/a_without_normalization.csv') # Split the dataset into train and test sets train_size = int(0.9 * len(data)) test_size = len(data) - train_size train_dataset, test_dataset = data[:train_size], data[train_size:] def create_sequences(data, sequence_length): sequences = [] labels = [] for i in range(sequence_length, len(data) - 10): sequences.append(data.iloc[i-sequence_length:i, 2:2+input_size].values) labels.append(data.iloc[i + 1: i + 11, 0]) return np.array(sequences), np.array(labels) sequence_length = 10 train_sequences, train_labels = create_sequences(train_dataset, sequence_length) test_sequences, test_labels = create_sequences(test_dataset, sequence_length) # Convert to PyTorch tensors train_sequences = torch.from_numpy(train_sequences) train_labels = torch.from_numpy(train_labels) test_sequences = torch.from_numpy(test_sequences) test_labels = torch.from_numpy(test_labels) # Create a TensorDataset from sequences and labels train_dataset = TensorDataset(train_sequences, train_labels) test_dataset = TensorDataset(test_sequences, test_labels) batch_size = 32 dropout = 0.2 hidden_size = 64 weight_decay = 0.001 lstm_layers = 2 lr = 0.001 output_size = 10 # Number of output features num_epochs = 101 model_eval_every = 2 print_loss_every = 1 save_model_every = 2500 # Create a DataLoader with the current batch size train_dataloader = DataLoader(train_dataset, batch_size=batch_size) test_dataloader = DataLoader(test_dataset, batch_size=batch_size) train_dataloader_len = len(train_dataloader) # Instantiate the model model = CustomLSTM(input_size, hidden_size, output_size, dropout, lstm_layers).double().to(device) # Define the optimizer optimizer = torch.optim.Adam(model.parameters(), lr=lr) # Define the scheduler scheduler = StepLR(optimizer, step_size=30, gamma=0.6) print(f'Training with weight_decay {weight_decay}') for epoch in range(num_epochs): total_loss = 0 for batch in train_dataloader: # Unpack the batch batch_sequences, batch_labels = batch[0].to(device), batch[1].to(device) # Pass the batch through the model output = model(batch_sequences).squeeze() # Compute the loss loss = criterion(output, batch_labels) total_loss += loss.item() # Backpropagate the loss optimizer.zero_grad() loss.backward() optimizer.step() # Update the learning rate scheduler.step()
核心疑问
这种情况是否是因为目标值之间的差异过小导致的?
问题分析与解决方案
目标值差异小是诱因之一,但核心问题出在模型结构设计和训练逻辑上:
关键问题点
- 模型结构不合理:当前模型用LSTM最后一个时间步的输出直接映射到10个预测值,本质是用单时序状态预测多步结果。这种设计下,MSE损失会引导模型拟合目标序列的均值(因为均值能最小化整体平方误差),完全忽略时序变化。
- 数据归一化不一致:仅对输入做归一化,未处理目标值。目标值本身范围极小(1.083~1.0845),损失会被快速压低,但模型学到的只是全局/批次均值。
- 隐藏状态未复用:每次前向传播都重置h0和c0为0,丢弃了序列间的时序依赖,模型无法学习长期趋势。
- 模型复杂度冗余:双向LSTM+2层+BatchNorm+Dropout的组合过于复杂,对于小范围的时序预测,容易出现过拟合到均值的情况。
针对性解决方案
- 重构为多步时序预测结构:
- 方法一:让LSTM的每个时间步输出对应一个未来预测值(需调整输入序列与标签的对齐方式)。
- 方法二:采用自回归方式,用前一步的预测值作为下一个时间步的输入(需结合静态特征)。
- 统一数据归一化:对输入特征和目标值都做归一化(如
StandardScaler),训练完成后反归一化得到真实值,避免因数值范围差异导致的损失误导。 - 复用隐藏状态:在batch之间传递LSTM的隐藏状态,仅在每个epoch开始时重置,保留序列间的时序关联。
- 调整损失函数:在MSE损失基础上加入时序平滑损失(如预测值相邻步的平方差),强制模型学习时序变化趋势。
- 简化模型:先尝试单层单向LSTM,降低dropout比例或暂时关闭BatchNorm,让模型更容易捕捉时序特征。
内容的提问来源于stack exchange,提问作者Sugafree
相关产品推荐
相关产品推荐

