Оптимизатор и планировщик для тонкой настройки BERT - PullRequest
0 голосов
/ 07 февраля 2020

Я пытаюсь настроить модель с помощью BERT (используя библиотеку transformers), и я немного не уверен насчет оптимизатора и планировщика.

Во-первых, я понимаю, что должен использовать transformers.AdamW вместо версии Pytorch. Кроме того, мы должны использовать планировщик прогрева, как предложено в статье, поэтому планировщик создается с использованием функции get_linear_scheduler_with_warmup из пакета transformers.

Основные вопросы, которые у меня есть:

  1. get_linear_scheduler_with_warmup следует вызывать с разминкой. Можно ли использовать 2 для разминки из 10 эпох?
  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(...)

Моя модель и состав поезда (очень похоже на этот ноутбук )

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)


Ответы [ 2 ]

1 голос
/ 02 мая 2020

Здесь вы можете увидеть визуализацию изменений скорости обучения, используя get_linear_scheduler_with_warmup.

Ссылка на Комментарий : Этапы разогрева - это параметр, который используется для понижения скорости обучения, чтобы уменьшить влияние отклонения модели от обучения при внезапном воздействии нового набора данных.

По умолчанию количество шагов разогрева равно 0.

Затем вы делаете большие шаги, потому что вы, вероятно, не близки к минимумам. Но когда вы приближаетесь к минимуму, вы делаете меньшие шаги, чтобы приблизиться к нему.

Также обратите внимание, что количество тренировочных шагов составляет number of batches * number of epochs, а не просто number of epochs. Таким образом, в основном num_training_steps = N_EPOCHS+1 не является правильным, если ваш batch_size не равен размеру обучающего набора.

Вы вызываете scheduler.step() каждый пакет, сразу после optimizer.step(), чтобы обновить скорость обучения.

1 голос
/ 20 февраля 2020

Я думаю, что вряд ли возможно дать 100% совершенный ответ, но вы, несомненно, можете получить вдохновение от того, как это делают другие сценарии. Лучше всего начать с каталога examples/ самого репозитория huggingface, где, например, можно найти этот отрывок :

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. Насколько я понимаю, разминка не обязательно требуется при тонкой настройке, но я менее уверен в этом аспекте и проверю другие сценарии.

...