BERT DataLoader:shuffle=True 与 Sampler 之间的区别?

BERT DataLoader: Difference between shuffle=True vs Sampler?

我在 ConLL 数据上使用 DistilBertForTokenClassification 训练了一个 DistilBERT 模型来预测 NER。培训似乎已经顺利完成,但我在评估阶段遇到了 2 个问题。

  1. 我得到负损失值

  2. 训练时,我对DataLoader使用了shuffle=True。但是在评估期间,当我为 DataLoader 执行 shuffle=True 时,我得到的指标结果非常差(f_1、准确性、召回率等)。但是,如果我执行 shuffle = False 或使用 Sampler 而不是洗牌,我会得到非常好的度量结果。我想知道我的代码是否有问题。

这里是评价码:


print('Prediction started on test data')
model.eval()

eval_loss = 0
predictions , true_labels = [], []

for batch in val_loader:
  b_input_ids = batch['input_ids'].to(device)
  b_input_mask = batch['attention_mask'].to(device)
  b_labels = batch['labels'].to(device)

  with torch.no_grad():
      outputs = model(b_input_ids, 
                      attention_mask=b_input_mask)

  logits = outputs[0]
  logits = logits.detach().cpu().numpy()
  label_ids = b_labels.detach().cpu().numpy()
  
  predictions.append(logits)
  true_labels.append(label_ids)

  eval_loss += outputs[0].mean().item()


print('Prediction completed')
eval_loss = eval_loss / len(val_loader)
print("Validation loss: {}".format(eval_loss))

输出:

Prediction started on test data
Prediction completed
Validation loss: -0.2584906197858579

我相信我在这里计算的损失是错误的。是否可以使用 BERT 获得负损失值?

对于 DataLoader,如果我使用下面的代码片段,我的指标结果没有问题。

val_sampler = SequentialSampler(val_dataset)
val_loader = DataLoader(val_dataset, sampler=val_sampler, batch_size=128)

但如果我这样做,我会得到很差的指标结果

val_loader = DataLoader(val_dataset, batch_size=128, shuffle=True)

使用 shuffle=True 与 shuffle=False 得到的结果大不相同是否正常?

指标计算代码:

metric = load_metric("seqeval")
results = metric.compute(predictions=true_predictions, references=true_labels)
results

输出:

{'LOCATION': {'f1': 0.9588207767898924,
  'number': 2134,
  'precision': 0.9574766355140187,
  'recall': 0.9601686972820993},
 'MISC': {'f1': 0.8658965344048217,
  'number': 995,
  'precision': 0.8654618473895582,
  'recall': 0.8663316582914573},
 'ORGANIZATION': {'f1': 0.9066332916145182,
  'number': 1971,
  'precision': 0.8947628458498024,
  'recall': 0.9188229325215627},
 'PERSON': {'f1': 0.9632426988922457,
  'number': 2015,
  'precision': 0.9775166070516096,
  'recall': 0.9493796526054591},
 'overall_accuracy': 0.988255561629313,
 'overall_f1': 0.9324058459808882,
 'overall_precision': 0.9322748349023465,
 'overall_recall': 0.932536893886156}

当我使用 Sampler 或 shuffle=False 时会打印上述指标。如果我使用 shuffle=True,我得到:

{'LOCATION': {'f1': 0.03902284263959391,
  'number': 2134,
  'precision': 0.029496402877697843,
  'recall': 0.057638238050609185},
 'MISC': {'f1': 0.010318142734307824,
  'number': 995,
  'precision': 0.009015777610818933,
  'recall': 0.012060301507537688},
 'ORGANIZATION': {'f1': 0.027420984269014285,
  'number': 1971,
  'precision': 0.019160951996772892,
  'recall': 0.04819888381532217},
 'PERSON': {'f1': 0.02119907254057635,
  'number': 2015,
  'precision': 0.01590852597564007,
  'recall': 0.03176178660049628},
 'overall_accuracy': 0.5651741788003777,
 'overall_f1': 0.02722600361161272,
 'overall_precision': 0.020301063389034663,
 'overall_recall': 0.041321152494729445}

更新:我修改了损失代码以进行评估。这段代码似乎没有问题。您可以在下面看到新代码:

print('Prediction started on test data')
model.eval()

eval_loss = 0
predictions , true_labels = [], []

for batch in val_loader:

  b_labels = batch['labels'].to(device)

  batch = {k:v.type(torch.long).to(device) for k,v in batch.items()}
  
  with torch.no_grad():
      outputs = model(**batch)

      loss, logits = outputs[0:2]
      logits = logits.detach().cpu().numpy()
      label_ids = b_labels.detach().cpu().numpy()
  
      predictions.append(logits)
      true_labels.append(label_ids)

      eval_loss += loss


print('Prediction completed')
eval_loss = eval_loss / len(val_loader)
print("Validation loss: {}".format(eval_loss))

虽然我仍然没有得到 DataLoader 问题的答案。 我还意识到当我这样做时 print(model.eval()) 我仍然在评估模式下从模型中退出。

您没有正确计算损失。首先,我想知道为什么您使用对数的均值作为损失,但这可能是我不熟悉的特定任务。 但是您肯定没有正确地累积损失,或者根本不准确。您打印出的损失只是来自最后一批。这解释了为什么使用 shuffle 时结果不同。如果正确实施,绝对不会出现这种情况。

你得到负损失的事实是因为你只是使用对数的均值作为损失,这当然可以是负的。

准确性等其他指标不应受此影响,但您没有提供计算这些指标的代码,因此无法发现故障点。

据我了解,答案很简单:

“我看到我爸爸是这样做的,他爸爸也是这样做的,所以我也是这样做的”。

我查看了很多笔记本,看看人们是如何加载数据进行验证的,在每个笔记本中,我都看到人们在使用顺序采样器进行验证。在验证期间没有人使用洗牌或随机抽样。我不完全知道为什么,但就是这样。因此,如果访问此 post 的任何人都想知道同样的事情,答案基本上就是我上面引用的内容。

另外,我编辑了原来的 post 以解决我遇到的丢失问题。我计算错了。如果您还为模型提供原始标签,显然 Bert 会在输出 (outputs[0]) 的索引 0 处返回损失。在第一个代码片段中,当我从模型中获取输出时,我没有为模型提供原始标签,因此它没有返回索引 0 处的损失值,而是仅返回对数。

基本上你需要做的是:

outputs = model(input_ids, mask, label=label)
loss = outputs[0]
logits = outputs[1]