LSTM时间序列DataLoader报错:张量尺寸不一致问题排查求助
LSTM时间序列预测DataLoader报错排查与修复
问题描述
运行LSTM时间序列预测代码时,DataLoader抛出错误:'stack expects each tensor to be equal size, but got [72, 4] at entry 0 and [68, 4] at entry 56',设置输入窗口长度72、预测步长12,多次调试未解决,以下是完整代码及修复方案。
原代码模块
Dataset类
class TimeSeriesDataset(Dataset): def __init__(self, csv_file, input_seq_length=72, output_seq_length=12, train=True): self.data = pd.read_csv(csv_file) # Load CSV file self.input_seq_length = input_seq_length self.output_seq_length = output_seq_length self.train = train # Normalize data self.scaler = MinMaxScaler() self.data[['column4']] = self.scaler.fit_transform(self.data[['column4']]) def __len__(self): return len(self.data) - self.input_seq_length - self.output_seq_length + 1 # Adjusted length to exclude incomplete sequences def __getitem__(self, idx): if self.train: idx += np.random.randint(0, self.input_seq_length) # Randomize training data input_data = self.data.iloc[idx:idx+self.input_seq_length].values target = self.data.iloc[idx+self.input_seq_length:idx+self.input_seq_length+self.output_seq_length]['column4'].values # Pad sequences input_data = [torch.tensor(sequence, dtype=torch.float) for sequence in input_data] input_data = pad_sequence(input_data, batch_first=True) return input_data, torch.tensor(target, dtype=torch.float)
LSTM模型
# Define LSTM model class LSTMModel(nn.Module): def __init__(self, input_size, hidden_size, output_size, num_layers=1): super(LSTMModel, self).__init__() self.hidden_size = hidden_size self.num_layers = num_layers self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device) c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device) out, _ = self.lstm(x, (h0, c0)) out = self.fc(out[:, -1, :]) return out
训练循环
# Define training function def train_model(model, train_loader, val_loader, criterion, optimizer, num_epochs=100): train_losses = [] val_losses = [] for epoch in range(num_epochs): model.train() train_loss = 0.0 for inputs, targets in train_loader: print(epoch, inputs.shape, targets.shape) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, targets) loss.backward() optimizer.step() train_loss += loss.item() train_losses.append(train_loss / len(train_loader)) model.eval() val_loss = 0.0 with torch.no_grad(): for inputs, targets in val_loader: outputs = model(inputs) loss = criterion(outputs, targets) val_loss += loss.item() val_losses.append(val_loss / len(val_loader)) print(f'Epoch [{epoch+1}/{num_epochs}], Train Loss: {train_losses[-1]}, Val Loss: {val_losses[-1]}') return train_losses, val_losses # Define function to plot losses def plot_losses(train_losses, val_losses): fig = go.Figure() fig.add_trace(go.Scatter(x=list(range(len(train_losses))), y=train_losses, mode='lines', name='Train Loss')) fig.add_trace(go.Scatter(x=list(range(len(val_losses))), y=val_losses, mode='lines', name='Val Loss')) fig.update_layout(title='Training and Validation Losses', xaxis_title='Epoch', yaxis_title='Loss') fig.show()
主函数
# Main function def main(): # Load data dataset = TimeSeriesDataset('sample_data.csv') # Split data into train, validation, and test sets train_size = int(0.6 * len(dataset)) val_size = int(0.2 * len(dataset)) test_size = len(dataset) - train_size - val_size train_data, val_data, test_data = torch.utils.data.random_split(dataset, [train_size, val_size, test_size]) # Create data loaders train_loader = DataLoader(train_data, batch_size=64, shuffle=True) val_loader = DataLoader(val_data, batch_size=64) test_loader = DataLoader(test_data, batch_size=64) # Initialize model, loss function, and optimizer model = LSTMModel(input_size=dataset.data.shape[1], hidden_size=64, output_size=1) criterion = nn.MSELoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # Train model train_losses, val_losses = train_model(model, train_loader, val_loader, criterion, optimizer) # Plot losses plot_losses(train_losses, val_losses) # Evaluate model on test data model.eval() test_loss = 0.0 with torch.no_grad(): for inputs, targets in test_loader: outputs = model(inputs) loss = criterion(outputs, targets) test_loss += loss.item() print(f'Test Loss: {test_loss / len(test_loader)}') # Run main function if __name__ == "__main__": main()
生成样本数据
import pandas as pd import numpy as np import datetime # Generate sample data num_rows = 1200 start_date = datetime.datetime(2024, 1, 1) time_index = [start_date + datetime.timedelta(minutes=5*i) for i in range(num_rows)] column1 = np.random.randn(num_rows) * 10 # Sample values for column 1 column2 = np.random.randn(num_rows) * 100 # Sample values for column 2 column3 = np.random.randn(num_rows) * 1000 # Sample values for column 3 column4 = np.random.randn(num_rows) * 10000 # Sample values for column 4 # Create DataFrame data = { # 'datetime': time_index, 'column1': column1.astype(float), 'column2': column2.astype(float), 'column3': column3.astype(float), 'column4': column4.astype(float) } df = pd.DataFrame(data) # Save to CSV df.to_csv('sample_data.csv', index=False)
问题根源分析
- 随机偏移导致索引越界:训练模式下对
idx添加随机偏移时,未限制偏移范围,导致idx+input_seq_length超出数据总长度,生成的输入序列长度不足72,引发张量尺寸不匹配。 - 错误的序列填充操作:输入序列本身是固定长度(72步×4特征),无需拆分为单个样本再填充,该操作完全多余且可能引入异常。
- 模型输出与目标不匹配:设置预测步长为12,但模型仅输出单个值,无法对应长度为12的目标序列,后续会引发损失计算错误。
修复后的代码
修正后的Dataset类
class TimeSeriesDataset(Dataset): def __init__(self, csv_file, input_seq_length=72, output_seq_length=12, train=True): self.data = pd.read_csv(csv_file) self.input_seq_length = input_seq_length self.output_seq_length = output_seq_length self.train = train # 归一化所有特征(建议统一归一化,而非仅column4) self.scaler = MinMaxScaler() self.data = pd.DataFrame(self.scaler.fit_transform(self.data), columns=self.data.columns) def __len__(self): return len(self.data) - self.input_seq_length - self.output_seq_length + 1 def __getitem__(self, idx): if self.train: # 生成合法的起始索引,确保序列不越界 max_start = len(self.data) - self.input_seq_length - self.output_seq_length start_idx = np.random.randint(0, max_start + 1) else: start_idx = idx # 提取固定长度的输入和目标序列 input_data = self.data.iloc[start_idx:start_idx+self.input_seq_length].values target = self.data.iloc[start_idx+self.input_seq_length:start_idx+self.input_seq_length+self.output_seq_length]['column4'].values # 直接转换为tensor,无需额外填充 return torch.tensor(input_data, dtype=torch.float), torch.tensor(target, dtype=torch.float)
修正后的LSTM模型(适配12步预测)
class LSTMModel(nn.Module): def __init__(self, input_size, hidden_size, output_seq_len, num_layers=1): super(LSTMModel, self).__init__() self.hidden_size = hidden_size self.num_layers = num_layers self.output_seq_len = output_seq_len self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) # 全连接层映射到预测步长的维度 self.fc = nn.Linear(hidden_size, output_seq_len) def forward(self, x): batch_size = x.size(0) # 初始化隐状态和细胞状态 h0 = torch.zeros(self.num_layers, batch_size, self.hidden_size).to(x.device) c0 = torch.zeros(self.num_layers, batch_size, self.hidden_size).to(x.device) # LSTM前向传播 out, _ = self.lstm(x, (h0, c0)) # 取最后一个时间步的输出映射到12步预测结果 out = self.fc(out[:, -1, :]) # 调整形状匹配目标:(batch_size, output_seq_len) return out
修正后的主函数(适配模型参数)
def main(): # Load data dataset = TimeSeriesDataset('sample_data.csv') # Split data into train, validation, and test sets train_size = int(0.6 * len(dataset)) val_size = int(0.2 * len(dataset)) test_size = len(dataset) - train_size - val_size train_data, val_data, test_data = torch.utils.data.random_split(dataset, [train_size, val_size, test_size]) # Create data loaders train_loader = DataLoader(train_data, batch_size=64, shuffle=True) val_loader = DataLoader(val_data, batch_size=64) test_loader = DataLoader(test_data, batch_size=64) # 初始化模型:output_seq_len设置为预测步长12 model = LSTMModel(input_size=dataset.data.shape[1], hidden_size=64, output_seq_len=12) criterion = nn.MSELoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # Train model train_losses, val_losses = train_model(model, train_loader, val_loader, criterion, optimizer) # Plot losses plot_losses(train_losses, val_losses) # Evaluate model on test data model.eval() test_loss = 0.0 with torch.no_grad(): for inputs, targets in test_loader: outputs = model(inputs) loss = criterion(outputs, targets) test_loss += loss.item() print(f'Test Loss: {test_loss / len(test_loader)}') if __name__ == "__main__": main()
额外优化建议
- 归一化所有特征:原代码仅归一化
column4,建议对所有输入特征进行归一化,提升模型收敛效果。 - 验证集/测试集禁用随机偏移:确保验证和测试时使用固定的序列切片,保证评估结果稳定。
- 添加设备无关性:可将模型和数据移至GPU(如果可用),加快训练速度。
内容的提问来源于stack exchange,提问作者risk9283
相关产品推荐
相关产品推荐

