Fine-Tuning DistilBertForSequenceClassification:不是在学习,为啥loss没有变化?权重没有更新?

Posted

技术标签:

【中文标题】Fine-Tuning DistilBertForSequenceClassification:不是在学习,为啥loss没有变化?权重没有更新?【英文标题】:Fine-Tuning DistilBertForSequenceClassification: Is not learning, why is loss not changing? Weights not updated?Fine-Tuning DistilBertForSequenceClassification:不是在学习,为什么loss没有变化?权重没有更新? 【发布时间】:2020-11-22 21:22:56 【问题描述】:

我对 PyTorch 和 Huggingface-transformers 比较陌生,并在 Kaggle-Dataset 上尝试了 DistillBertForSequenceClassification。

from transformers import DistilBertForSequenceClassification
import torch.optim as optim
import torch.nn as nn
from transformers import get_linear_schedule_with_warmup

n_epochs = 5 # or whatever
batch_size = 32 # or whatever

bert_distil = DistilBertForSequenceClassification.from_pretrained('distilbert-base-uncased')
#bert_distil.classifier = nn.Sequential(nn.Linear(in_features=768, out_features=1), nn.Sigmoid())
tokenizer = DistilBertTokenizer.from_pretrained('distilbert-base-uncased')
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(bert_distil.parameters(), lr=0.1)

X_train = []
Y_train = []

for row in train_df.iterrows():
    seq = tokenizer.encode(preprocess_text(row[1]['text']),  add_special_tokens=True, pad_to_max_length=True)
    X_train.append(torch.tensor(seq).unsqueeze(0))
    Y_train.append(torch.tensor([row[1]['target']]).unsqueeze(0))
X_train = torch.cat(X_train)
Y_train = torch.cat(Y_train)

running_loss = 0.0
bert_distil.cuda()
bert_distil.train(True)
for epoch in range(n_epochs):
    permutation = torch.randperm(len(X_train))
    j = 0
    for i in range(0,len(X_train), batch_size):
        optimizer.zero_grad()
        indices = permutation[i:i+batch_size]
        batch_x, batch_y = X_train[indices], Y_train[indices]
        batch_x.cuda()
        batch_y.cuda()
        outputs = bert_distil.forward(batch_x.cuda())
        loss = criterion(outputs[0],batch_y.squeeze().cuda())
        loss.requires_grad = True
   
        loss.backward()
        optimizer.step()
   
        running_loss += loss.item()  
        j+=1
        if j == 20:   
            #print(outputs[0])
            print('[%d, %5d] running loss: %.3f loss: %.3f ' %
              (epoch + 1, i*1, running_loss / 20, loss.item()))
            running_loss = 0.0
            j = 0

[1, 608] 运行损失:0.689 损失:0.687 [1, 1248] 运行损耗:0.693 损耗:0.694 [1, 1888] 运行损耗:0.693 损耗:0.683 [1, 2528] 运行损耗:0.689 损耗:0.701 [1, 3168] 运行损耗:0.690 损耗:0.684 [1, 3808] 运行损耗:0.689 损耗:0.688 [1, 4448] 运行损失:0.689 损失:0.692 等...

无论我尝试了什么,损失都没有减少,甚至没有增加,预测也没有变得更好。在我看来,我忘记了一些东西,因此实际上没有更新权重。有人有想法吗? 哦

我尝试了什么

不同的损失函数 公元前 交叉熵 甚至 MSE 损失 One-Hot 编码与单个神经元输出 不同的学习率和优化器 我什至将所有目标都更改为只有一个标签,但即便如此,网络也没有收敛。

【问题讨论】:

你的学习率太高了。试试较小的,比如 1e-4 或 1e-5。 是的,我试过了。我只是在玩学习率,看看是否有任何变化……但即使数据集中只有一个类,它似乎也没有学到任何东西…… 尝试过拟合单个批次。也与您的问题无关,但是:您不需要显式调用 model.forward() - 只需执行 model(X);并且不需要做 loss.requires_grad = True. 即使在单个批次上过拟合也不会改变任何东西,损失保持在 0.65 左右,但变化和以前一样......很奇怪 【参考方案1】:

我会强调您的“稳定”结果的两个可能原因:

    我同意学习率肯定太,这会阻止模型进行任何重大更新。 但重要的是要知道,基于最先进的论文微调对 Transformers 的核心 NLP 能力有非常小的影响。例如,paper 表示微调仅适用于非常小的重量变化。引用它:“微调几乎不会影响 NEL、COREF 和 REL 的准确性,表明这些任务已经被预训练充分覆盖”。几篇论文表明,对分类任务进行微调基本上是浪费时间。因此,考虑到 DistilBert 实际上是 BERT 的学生模型,也许你不会得到更好的结果。 先尝试使用您的数据进行预训练。一般来说,预训练的影响更大。

【讨论】:

感谢您的回答。如上所述,我尝试了不同的学习率,所以这不是这里的问题。我没有尝试过预训练,但我想还有一个更实际的问题:即使数据集中只有一个标签,模型也不会收敛......【参考方案2】:

查看运行损失和小批量损失很容易产生误导。您应该查看 epoch loss,因为每个损失的输入都是相同的。

此外,您的代码中存在一些问题,将其全部修复,并且行为符合预期:每个 epoch 后损失会缓慢减少,并且它也可能会过度拟合到小型 minibatch。请看代码,变化包括:用model(x)代替model.forward(x)cuda()只调用一次、更小的学习率等。

调整和微调 ML 模型是一项艰巨的工作。

n_epochs = 5
batch_size = 1

bert_distil = DistilBertForSequenceClassification.from_pretrained('distilbert-base-uncased')
tokenizer = DistilBertTokenizer.from_pretrained('distilbert-base-uncased')
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(bert_distil.parameters(), lr=1e-3)

X_train = []
Y_train = []
for row in train_df.iterrows():
    seq = tokenizer.encode(row[1]['text'],  add_special_tokens=True, pad_to_max_length=True)[:100]
    X_train.append(torch.tensor(seq).unsqueeze(0))
    Y_train.append(torch.tensor([row[1]['target']]))
X_train = torch.cat(X_train)
Y_train = torch.cat(Y_train)

running_loss = 0.0
bert_distil.cuda()
bert_distil.train(True)
for epoch in range(n_epochs):
    permutation = torch.randperm(len(X_train))
    for i in range(0,len(X_train), batch_size):
        optimizer.zero_grad()
        indices = permutation[i:i+batch_size]
        batch_x, batch_y = X_train[indices].cuda(), Y_train[indices].cuda()
        outputs = bert_distil(batch_x)
        loss = criterion(outputs[0], batch_y)
        loss.backward()
        optimizer.step()
   
        running_loss += loss.item()  

    print('[%d] epoch loss: %.3f' %
      (epoch + 1, running_loss / len(X_train) * batch_size))
    running_loss = 0.0

输出:

[1] epoch loss: 0.695
[2] epoch loss: 0.690
[3] epoch loss: 0.687
[4] epoch loss: 0.685
[5] epoch loss: 0.684

【讨论】:

感谢您的回答。这会因错误而中断:RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn.知道这需要在哪里解决吗? 如果我不查看您的代码,我不知道在哪里修复。您是否完全复制了上面的代码?该错误表明某些张量的 autograd 已关闭。【参考方案3】:

当我尝试使用xxxForSequenceClassification 来微调我的下游任务时,我遇到了类似的问题。

最后,我把xxxForSequenceClassification改成了xxxModel,并添加了Dropout - FC - Softmax。神奇地解决了,损失按预期减少了。

我仍在尝试找出原因。

希望对你有帮助。

仅供参考,变形金刚版本:3.5.0

【讨论】:

以上是关于Fine-Tuning DistilBertForSequenceClassification:不是在学习,为啥loss没有变化?权重没有更新?的主要内容,如果未能解决你的问题,请参考以下文章

keras系列︱迁移学习:利用InceptionV3进行fine-tuning及预测完美案例

fine-tuning

Pytorch Note56 Fine-tuning 通过微调进行迁移学习

LLMs Fine-tuning 学习笔记:trl+peft

深度学习前沿应用图像分类Fine-Tuning

深度学习前沿应用图像分类Fine-Tuning