You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

问题根源分析

  1. 随机偏移导致索引越界:训练模式下对idx添加随机偏移时,未限制偏移范围,导致idx+input_seq_length超出数据总长度,生成的输入序列长度不足72,引发张量尺寸不匹配。
  2. 错误的序列填充操作:输入序列本身是固定长度(72步×4特征),无需拆分为单个样本再填充,该操作完全多余且可能引入异常。
  3. 模型输出与目标不匹配:设置预测步长为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 12:29:53