用于 BERT 微调的优化器和调度器
Optimizer and scheduler for BERT fine-tuning
我正在尝试使用 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)
我认为很难给出 100% 完美的答案,但您肯定可以从其他脚本的做法中获得灵感。最好的起点是 examples/
directory of the huggingface repository itself, where you can for example find 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
。以我的理解,warmup在微调的时候不一定需要,但是我对这方面不太确定,也会和其他脚本核对一下。
Here 您可以使用 get_linear_scheduler_with_warmup
查看学习率变化的可视化。
参考this评论:warm up steps是一个参数,用于降低学习率,以减少模型偏离学习对突然暴露新数据集的影响。
默认情况下,预热步骤数为 0。
然后你迈出更大的步伐,因为你可能没有接近最小值。但是当你接近最小值时,你会采取更小的步骤来收敛到它。
另外,请注意训练步数是 number of batches
* number of epochs
,而不仅仅是 number of epochs
。所以,基本上 num_training_steps = N_EPOCHS+1
是不正确的,除非你的 batch_size
等于训练集的大小。
您在每个批次 optimizer.step()
之后立即调用 scheduler.step()
来更新学习率。
我正在尝试使用 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)
我认为很难给出 100% 完美的答案,但您肯定可以从其他脚本的做法中获得灵感。最好的起点是 examples/
directory of the huggingface repository itself, where you can for example find 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
。以我的理解,warmup在微调的时候不一定需要,但是我对这方面不太确定,也会和其他脚本核对一下。
Here 您可以使用 get_linear_scheduler_with_warmup
查看学习率变化的可视化。
参考this评论:warm up steps是一个参数,用于降低学习率,以减少模型偏离学习对突然暴露新数据集的影响。
默认情况下,预热步骤数为 0。
然后你迈出更大的步伐,因为你可能没有接近最小值。但是当你接近最小值时,你会采取更小的步骤来收敛到它。
另外,请注意训练步数是 number of batches
* number of epochs
,而不仅仅是 number of epochs
。所以,基本上 num_training_steps = N_EPOCHS+1
是不正确的,除非你的 batch_size
等于训练集的大小。
您在每个批次 optimizer.step()
之后立即调用 scheduler.step()
来更新学习率。