PyTorch LSTM测试预测值恒定、训练预测值递增问题排查
针对你遇到的LSTM模型异常,主要原因可归纳为以下几点:
输出层设计错误
若模型仅对LSTM的最后一个时间步输出做全连接变换,再重复填充到4个时间步,会导致每个样本的4个时间步预测值完全相同。这种错误常见于对序列输出逻辑的理解偏差,未对LSTM每个时间步的隐藏状态单独映射。数据未做归一化处理
特征或标签的数值范围过大时,模型难以学习到序列依赖关系,会趋向输出训练集标签的均值(23.9606大概率是Y_train的均值)——这是模型在无法捕捉有效模式时,最小化MSE损失的“惰性”选择。同时未归一化的数据也可能导致训练时梯度爆炸/消失,使预测值持续偏离。损失函数计算逻辑错误
若训练时未对每个时间步的预测值与真实值计算损失(例如仅计算最后一个时间步的损失),模型会忽略序列时间维度的依赖,最终输出恒定值。此外损失函数的方向错误(如误将损失设为负向)也会导致预测值持续升高。模型容量不足或梯度问题
LSTM的隐藏层维度(hidden_size)过小、层数不足,会导致模型无法拟合序列的复杂模式;优化器学习率设置不当(过大导致震荡,过小导致无法收敛)、梯度未正确回传,也会使模型停留在无效的恒定输出状态。
1. 修正模型输出层结构
确保LSTM每个时间步的隐藏状态都经过独立的全连接层映射,输出与标签匹配的(batch_size, 4, 1)维度:
import torch import torch.nn as nn class SeqLSTM(nn.Module): def __init__(self, input_size=7, hidden_size=64, num_layers=2): super().__init__() self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) self.fc = nn.Linear(hidden_size, 1) # 对每个时间步的隐藏状态做映射 def forward(self, x): # x shape: (batch_size, seq_len, input_size) lstm_out, _ = self.lstm(x) # lstm_out shape: (batch_size, 4, hidden_size) output = self.fc(lstm_out) # output shape: (batch_size, 4, 1) return output
2. 对数据进行归一化
将特征和标签缩放到[0,1]或[-1,1]范围,消除数值差异对模型的影响,训练完成后再反归一化还原真实值:
from sklearn.preprocessing import MinMaxScaler # 处理训练集特征 scaler_x = MinMaxScaler(feature_range=(0,1)) # 先reshape为(样本数*时间步, 特征数),再归一化 x_train_reshaped = X_train.reshape(-1, 7) x_train_scaled = scaler_x.fit_transform(x_train_reshaped).reshape(24433, 4, 7) # 处理训练集标签 scaler_y = MinMaxScaler(feature_range=(0,1)) y_train_reshaped = Y_train.reshape(-1, 1) y_train_scaled = scaler_y.fit_transform(y_train_reshaped).reshape(24433, 4, 1) # 测试集使用训练集的归一化器,避免数据泄露 x_test_reshaped = X_test.reshape(-1, 7) x_test_scaled = scaler_x.transform(x_test_reshaped).reshape(6109, 4, 7)
3. 修正损失计算与训练逻辑
确保损失函数对每个时间步的预测值和真实值计算,同时规范训练流程:
model = SeqLSTM() criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) # 转换为PyTorch张量 x_train_tensor = torch.tensor(x_train_scaled, dtype=torch.float32) y_train_tensor = torch.tensor(y_train_scaled, dtype=torch.float32) epochs = 50 for epoch in range(epochs): model.train() optimizer.zero_grad() outputs = model(x_train_tensor) loss = criterion(outputs, y_train_tensor) loss.backward() optimizer.step() if (epoch+1) % 5 == 0: print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')
4. 调整模型与优化器参数
- 增大LSTM的
hidden_size(如从32调整到64或128),或增加层数(如num_layers=2)提升模型容量; - 调整学习率:若训练损失震荡,可将学习率降至
1e-4;若损失下降缓慢,可尝试5e-3; - 训练时打乱数据集:使用
DataLoader加载数据并设置shuffle=True,避免模型学习到数据的顺序偏差。
测试阶段的正确处理
测试时关闭梯度计算,反归一化得到真实预测值:
model.eval() x_test_tensor = torch.tensor(x_test_scaled, dtype=torch.float32) with torch.no_grad(): test_outputs = model(x_test_tensor) # 反归一化还原真实值 test_outputs_reshaped = test_outputs.numpy().reshape(-1, 1) test_preds = scaler_y.inverse_transform(test_outputs_reshaped).reshape(6109, 4, 1)
内容的提问来源于stack exchange,提问作者user17515752

