BERT 微调的优化器和调度器

Posted

技术标签:

【中文标题】BERT 微调的优化器和调度器【英文标题】:Optimizer and scheduler for BERT fine-tuning 【发布时间】:2020-05-23 23:30:27 【问题描述】:

我正在尝试使用 BERT 微调模型(使用 transformers 库),但我对优化器和调度器有点不确定。

首先,我知道我应该使用transformers.AdamW 而不是 Pytorch 的版本。此外,我们应该使用论文中建议的预热调度程序,因此调度程序是使用 transformers 包中的 get_linear_scheduler_with_warmup 函数创建的。

我的主要问题是:

    get_linear_scheduler_with_warmup 应该在热身时调用。可以在 10 个 epoch 中使用 2 进行热身吗? 什么时候应该打电话给scheduler.step()?如果我在train 之后执行此操作,则第一个时期的学习率为零。我应该为每批调用它吗?

我做错了吗?

from transformers import AdamW
from transformers.optimization import get_linear_scheduler_with_warmup

N_EPOCHS = 10

model = BertGRUModel(finetune_bert=True,...)
num_training_steps = N_EPOCHS+1
num_warmup_steps = 2
warmup_proportion = float(num_warmup_steps) / float(num_training_steps)  # 0.1

optimizer = AdamW(model.parameters())
criterion = nn.BCEWithLogitsLoss(pos_weight=torch.Tensor([class_weights[1]]))


scheduler = get_linear_schedule_with_warmup(
    optimizer, num_warmup_steps=num_warmup_steps, 
    num_training_steps=num_training_steps
)

for epoch in range(N_EPOCHS):
    scheduler.step() #If I do after train, LR = 0 for the first epoch
    print(optimizer.param_groups[0]["lr"])

    train(...) # here we call optimizer.step()
    evaluate(...)

我的模型和训练例程(很像this notebook)

class BERTGRUSentiment(nn.Module):
    def __init__(self,
                 bert,
                 hidden_dim,
                 output_dim,
                 n_layers=1, 
                 bidirectional=False,
                 finetune_bert=False,
                 dropout=0.2):

        super().__init__()

        self.bert = bert

        embedding_dim = bert.config.to_dict()['hidden_size']

        self.finetune_bert = finetune_bert

        self.rnn = nn.GRU(embedding_dim,
                          hidden_dim,
                          num_layers = n_layers,
                          bidirectional = bidirectional,
                          batch_first = True,
                          dropout = 0 if n_layers < 2 else dropout)

        self.out = nn.Linear(hidden_dim * 2 if bidirectional else hidden_dim, output_dim)        
        self.dropout = nn.Dropout(dropout)

    def forward(self, text):    
        #text = [batch size, sent len]

        if not self.finetune_bert:
            with torch.no_grad():
                embedded = self.bert(text)[0]
        else:
            embedded = self.bert(text)[0]
        #embedded = [batch size, sent len, emb dim]
        _, hidden = self.rnn(embedded)

        #hidden = [n layers * n directions, batch size, emb dim]

        if self.rnn.bidirectional:
            hidden = self.dropout(torch.cat((hidden[-2,:,:], hidden[-1,:,:]), dim = 1))
        else:
            hidden = self.dropout(hidden[-1,:,:])

        #hidden = [batch size, hid dim]

        output = self.out(hidden)

        #output = [batch size, out dim]

        return output


import torch
from sklearn.metrics import accuracy_score, f1_score


def train(model, iterator, optimizer, criterion, max_grad_norm=None):
    """
    Trains the model for one full epoch
    """
    epoch_loss = 0
    epoch_acc = 0

    model.train()

    for i, batch in enumerate(iterator):
        optimizer.zero_grad()
        text, lens = batch.text

        predictions = model(text)

        target = batch.target

        loss = criterion(predictions.squeeze(1), target)

        prob_predictions = torch.sigmoid(predictions)

        preds = torch.round(prob_predictions).detach().cpu()
        acc = accuracy_score(preds, target.cpu())

        loss.backward()
        # Gradient clipping
        if max_grad_norm:
            torch.nn.utils.clip_grad_norm_(model.parameters(), max_grad_norm)

        optimizer.step()

        epoch_loss += loss.item()
        epoch_acc += acc.item()

    return epoch_loss / len(iterator), epoch_acc / len(iterator)


【问题讨论】:

相关:forums.pytorchlightning.ai/t/… 推荐使用调度器。 tldr;也许当验证损失停止减少时它是一个好主意。 【参考方案1】:

Here 你可以使用get_linear_scheduler_with_warmup 看到学习率变化的可视化。

参考this评论:预热步骤是一个用于降低学习率的参数,以减少模型偏离学习对突然暴露新数据集的影响。

默认情况下,预热步骤数为 0。

然后你迈出更大的步伐,因为你可能还没有接近最小值。但是当你接近最小值时,你会采取更小的步骤来收敛到它。

另外,请注意训练步数是number of batches * number of epochs,但不仅仅是number of epochs。所以,基本上num_training_steps = N_EPOCHS+1 是不正确的,除非你的batch_size 等于训练集大小。

您在每个批次调用scheduler.step(),就在optimizer.step() 之后,以更新学习率。

【讨论】:

未来读者注意:训练步骤是number of batches * number of epochs,但不仅仅是number of epochs,这很重要,因为这有助于理解schedulers,例如torch中的StepLR【参考方案2】:

我认为几乎不可能给出 100% 完美的答案,但您当然可以从其他脚本的做法中获得灵感。最好的起点是 huggingface 存储库本身的 examples/ 目录,例如,您可以在其中找到 this excerpt:

if (step + 1) % args.gradient_accumulation_steps == 0:
    if args.fp16:
        torch.nn.utils.clip_grad_norm_(amp.master_params(optimizer), args.max_grad_norm)
    else:
        torch.nn.utils.clip_grad_norm_(model.parameters(), args.max_grad_norm)

    optimizer.step()
    scheduler.step()  # Update learning rate schedule
    model.zero_grad()
    global_step += 1

如果我们看看周围的部分,这基本上是在更新 LR 时间表每次你做一个向后传递。在同一个示例中,您还可以查看warmup_steps 的默认值,即0。据我了解,微调时不一定需要预热,但我对这方面不太确定,也会检查其他脚本。

【讨论】:

谢谢。它让我感到困惑,因为它与 PyTorch 的其他 LR 调度程序完全不同。我不知道在拥抱脸的图书馆中究竟在哪里提到了这些热身的东西(我认为原始论文没有谈论它)

以上是关于BERT 微调的优化器和调度器的主要内容,如果未能解决你的问题,请参考以下文章

公共大数据集群中如何配置 YARN 的公平调度器和容量调度器

[QNX 自适应分区用户指南]8 自适应分区线程调度器和其他线程调度器

进程管理-第二节23:进程调度的时机切换与过程方式调度器和闲逛进程

[QNX 自适应分区用户指南]9 同时使用线程调度器和多核

yarn的学习之2-容量调度器和预订系统

Hadoop MapReduce优化和资源调度器