Seq2Seq LSTM学习不当

0 投票
1 回答
56 浏览
提问于 2025-04-13 12:37

我正在用Pytorch解决一个序列到序列的问题,使用的是LSTM。具体来说,我是用5个元素的序列来预测接下来的5个元素。我的困扰主要是关于数据的转换。我有一些张量,大小是[bs, seq_length, features],其中seq_length = 10features = 1。每个特征都是一个介于0到3之间的整数(包括0和3)。

我原以为输入数据需要用MinMaxScaler转换成浮点数范围[0, 1],这样可以让LSTM的学习过程更顺利。之后,我会应用一个线性层,把隐藏状态转换成对应的输出,输出的大小是features。这是我在Pytorch中定义的LSTM网络:

class LSTM(nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim, num_layers, dropout_prob):
        super(LSTM, self).__init__()
        self.lstm_layer = nn.LSTM(input_dim, hidden_dim, num_layers, dropout=dropout_prob)
        self.output_layer = nn.Linear(hidden_dim, output_dim)
    ...
    def forward(self, X):
        out, (hidden, cell) = self.lstm_layer(X)
        out = self.output_layer(out)
        return out

我用来进行训练循环的代码如下:

def train_loop(t, checkpoint_epoch, dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)
    for batch, X in enumerate(dataloader):
        X = X[0].type(torch.float).to(device)
        # X = torch.Size([batch_size, 10, input_dim])
        # Split sequences into input and target
        inputs = transform(X[:, :5, :]) # inputs = [batch_size, 5, input_dim]
        targets = transform(X[:, 5:, :]) # targets = [batch_size, 5, input_dim]
        # predictions (forward pass)
        with autocast():
            pred = model(inputs)  # pred = [batch_size, 5, input_dim]
            loss = loss_fn(pred, targets)
        # backprop
        optimizer.zero_grad()
        scaler.scale(loss).backward()
        scaler.step(optimizer)
        scaler.update()
        if batch % 100 == 0:
            loss, current = loss.item(), batch * len(X)
            #print(f"Current loss: {loss:>7f}, [{current:>5d}/{size:>5d}]")
        # Delete variables and empty cache
        del X, inputs, targets, pred
        torch.cuda.empty_cache()
    return loss

我用来预处理数据的代码是:

def main():
    num_agents = 2
    # Open the HDF5 file
    with h5py.File('dataset_' + str(num_agents) + 'UAV.hdf5', 'r') as f:
        # Access the dataset
        data = f['data'][:]
        # Convert to PyTorch tensor
        data_tensor = torch.tensor(data)
        size = data_tensor.size()
        seq_length = 10
        reshaped = data_tensor.view(-1, size[2], size[3])
        r_size = reshaped.size()
        reshaped = reshaped[:, :, 1:]
        reshaped_v2 = reshaped.view(r_size[0], -1)
        dataset = create_dataset(reshaped_v2.numpy(), seq_length)
        f.close()
    dataset = TensorDataset(dataset)
    # Split the dataset into training and validation sets
    train_size = int(0.8 * len(dataset))  # 80% for training
    val_size = len(dataset) - train_size  # 20% for validation
    train_dataset, val_dataset = random_split(dataset, [train_size, val_size])
    train_dataloader = DataLoader(train_dataset, batch_size=params['batch_size'], shuffle=True, pin_memory=True)
    val_dataloader = DataLoader(val_dataset, batch_size=params['batch_size'], shuffle=False, pin_memory=True)

尝试这样做后,模型的学习效果不太好,所以我在想,可能直接计算targets(范围在[0, 1]的浮点值)和pred(我认为是因为LSTM层的tanh激活函数而导致的范围在[-1, 1]的浮点值)之间的损失,可能会因为尺度不同而出问题。于是,我尝试在前向传播中在线性层后面加一个sigmoid激活函数,但效果也不好。我尝试了很多超参数组合,但都没有得到“正常”的训练曲线。我还附上了一张5000个训练周期的截图,以展示训练过程:

训练过程

我有以下几个问题:

  • 我的训练过程有什么问题吗?
  • 我说的有什么地方理解错了吗?

1 个回答

1

你代码中的一个大问题是你定义LSTM层的方式。

nn.LSTM 默认情况下期待输入的形状是 (sl, bs, features),而你的输入形状是 (bs, sl, features)。这就导致你的代码在处理数据时方向错了。你需要在 nn.LSTM 中加上 batch_first=True,这样才能使用以批次为首的输入(lstm 文档)。

另外,你的数据设置也有问题。LSTM是一个一个地处理序列中的元素,这意味着序列中的某个元素 i 只能看到前面的元素 0, 1, ... i-1。但是你却希望这个元素能预测输出序列中的 i+5

举个更具体的例子,你输入中的第二个元素被期望能预测输出中的第二个元素(整体的第七个元素),而它并没有看到中间的任何元素。你试图用输入序列的一部分来预测输出序列的元素。

最好的方法是使用下一步预测。这样每一步都能看到之前的所有步骤,预测时没有信息缺口。这只是一个简单的改动:

# old
inputs = transform(X[:, :5, :])
targets = transform(X[:, 5:, :])

# new
inputs = transform(X[:, :-1, :]) # all but the last step
targets = transform(X[:, 1:, :]) # all but the first step

如果你真的想用前五个步骤来预测接下来的五个步骤,你需要一个序列到序列的模型。这就需要添加一个解码器LSTM,它使用编码器LSTM产生的隐藏状态(这样就能获取到所有时间步的信息)。seq2seq模型还需要为解码器添加一个循环,这个过程会比较麻烦。

撰写回答