【问题标题】:GRU model not learningGRU 模型不学习
【发布时间】:2021-08-27 16:29:47
【问题描述】:

我正在尝试在文本数据上拟合 GRU 模型,以预测 26 个标签中的一个。问题是该模型并没有真正学习(准确率约为 4%,这只是随机机会)。由于我知道问题是“可学习的”,我怀疑我的代码中存在错误,但我无法弄清楚它是什么。

我的数据由(标记化和单词编码的)每个标签 10 万个句子组成(每个句子都映射到 26 个标签之一)。我的任务是预测一个新的看不见的句子的标签。 我尝试了几种方法,例如使用大于 1 的批处理大小和填充,但我现在坚持的方法是将每 20 个句子加入一个批处理,所以我的样本变得有点大,并且适合模型一次 1 批。

型号:

class GRU(nn.Module):
    def __init__(self, input_size, num_classes, batch_size):
        super(GRU, self).__init__()
        self.hidden_state = None
        self._batch_first = True
        self.batch_size = batch_size
        self.hidden_size = 256
        self.num_layers = 1
        embedding_dim = 256
        self.embedding = nn.Embedding(input_size, embedding_dim=embedding_dim)
        nn.init.uniform_(self.embedding.weight, -1.0, 1.0)
        self.gru = nn.GRU(embedding_dim, self.hidden_size, self.num_layers, batch_first=self._batch_first)
        self.fc = nn.Linear(self.hidden_size, num_classes)
    
    def init_hidden(self):
        self.hidden_state = torch.randn(self.num_layers, self.batch_size, self.hidden_size).to(device)

    def forward(self, x):
        embeds = self.embedding(x)
        out, self.hidden_state = self.gru(embeds, self.hidden_state)
        out = out[:, -1, :]
        out = self.fc(out)
        return out

   
# Loss and optimizer
criterion = nn.CrossEntropyLoss()
learning_rate = 0.001
optimizer = lambda mdl: torch.optim.Adam(mdl.parameters(), lr=learning_rate)

model = RNN(len(vocab), len(encoded_lbls), BATCH_SIZE).to(device)

# RNN(
#   (embedding): Embedding(19353, 256)
#   (rnn): GRU(256, 256, batch_first=True)
#   (fc): Linear(in_features=256, out_features=26, bias=True)
# )

我尝试了不同的学习率和不同的损失,例如使用 LogSoftmax 的 NLLLoss,但这没有任何区别。

由于我认为单词 ngram 是解决这个问题的一个很好的特性,我将每个批次拆分为单词三元组,并逐个 ngram 将它们提供给模型 ngram,同时在每个批次之前重置隐藏状态:

model.train(mode=True)
for epoch in range(epochs):
    for label,encoded_txt in train_loader:
        encoded_txt, label = encoded_txt.to(device), label.to(device)
        model.init_hidden()
        output, loss, _ = evaluate(model, optim, encoded_txt, label, train=True)

    # validation eval...

这是evaluate() 函数:

def evaluate(model, optim, txt, label, train=False):
    for ngram in txt.split(NGRAM_LEN):  # NGRAM_LEN = 3
        output = model(ngram)
    loss = criterion(output, label)    

    if train:
        optim.zero_grad()
        loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), 0.25)
        for p in model.parameters():
            p.data.add_(p.grad, alpha=-learning_rate)
        optim.step()
        
    accuracy = np.mean(np.array([item.item() for item in torch.argmax(output, dim=1)]) == label.cpu().numpy())

    return output, loss.item(), accuracy

这是我在 10 个 epoch 后得到的结果:

Epoch 0: Training Loss: 3.3762, Validation Loss: 3.4029, Validation Accuracy: 3.87%
Epoch 1: Training Loss: 3.3084, Validation Loss: 3.5362, Validation Accuracy: 3.89%
Epoch 2: Training Loss: 3.1202, Validation Loss: 3.8107, Validation Accuracy: 4.32%
Epoch 3: Training Loss: 2.9897, Validation Loss: 4.0599, Validation Accuracy: 4.57%
Epoch 4: Training Loss: 2.9118, Validation Loss: 4.3766, Validation Accuracy: 3.93%
Epoch 5: Training Loss: 2.9161, Validation Loss: 4.4962, Validation Accuracy: 4.23%
Epoch 6: Training Loss: 2.9117, Validation Loss: 4.7663, Validation Accuracy: 4.47%
Epoch 7: Training Loss: 2.9203, Validation Loss: 4.9078, Validation Accuracy: 4.55%
Epoch 8: Training Loss: 2.9253, Validation Loss: 5.1911, Validation Accuracy: 4.49%
Epoch 9: Training Loss: 2.9592, Validation Loss: 5.4946, Validation Accuracy: 4.23%

我希望验证集的准确率至少达到 60%,但正如您所见,这只是随机机会。 训练损失并没有真正减少,验证损失正在增加。 我不能说它过度拟合,因为训练损失非常高,所以它不是真正的学习。

谁能发现代码中的错误或提出调试方法?

【问题讨论】:

    标签: python deep-learning nlp pytorch recurrent-neural-network


    【解决方案1】:

    我认为您不应该在这样的循环中调用 nn.GRU。我认为 nn.GRU 应该接受一系列令牌。如果你想在手动循环的地方编写代码,你可能需要 nn.GRUCell (https://pytorch.org/docs/stable/generated/torch.nn.GRUCell.html)。

    如果您查看 https://pytorch.org/docs/stable/generated/torch.nn.GRU.html 底部的示例,您可以一次全部传递您的序列。 (记住要注意批处理维度与 GRU 构造函数的 batch_first arg 对齐。)

    另外,您可能不希望随机初始化隐藏状态。我可能会将其初始化为全零。

    【讨论】:

    • 我给它一个标记序列(单词三元组)[see example of looping manually]。但是,我只是尝试放弃循环和 ngram 拆分,但并没有改变结果。关于初始化,您可能是对的,但我只是尝试过,不幸的是它根本没有帮助。