BERT DataLoader: разница между shuffle=True и Sampler?

Я обучил модель DistilBERT с помощью DistilBertForTokenClassification на данных ConLL для прогнозирования NER. Обучение, похоже, прошло без проблем, но у меня есть 2 проблемы на этапе оценки.

  1. Я получаю отрицательное значение потерь

  2. Во время обучения я использовал shuffle=True для DataLoader. Но во время оценки, когда я делаю shuffle=True для DataLoader, я получаю очень плохие результаты метрик (f_1, точность, полнота и т. д.). Но если я делаю shuffle = False или использую сэмплер вместо перемешивания, я получаю довольно хорошие метрические результаты. Мне интересно, есть ли что-то не так с моим кодом.

Вот код оценки:


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()) я все еще получаю выпадения из модели в режиме оценки.

Можете ли вы показать в коде, как вы рассчитываете f_1, accuracy, recall

mujjiga 21.12.2020 18:26

Только сейчас добавил

Harun Küfrevi 21.12.2020 18:28

Я добавил еще одно обновление для значения потерь. Кажется, теперь все работает нормально, но у меня до сих пор нет ответа на проблему с DataLoader.

Harun Küfrevi 21.12.2020 19:54
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
3
443
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы не правильно считаете потери. Во-первых, мне интересно, почему вы используете среднее значение логитов как потерю, но это может быть специфическая задача, с которой я не знаком. Но вы точно не правильно суммируете убытки, а если быть точнее, то совсем не так. Потеря, которую вы распечатываете, относится только к последней партии. Это объясняет, почему результаты отличаются при использовании перемешивания. Это определенно не должно иметь место при правильной реализации.

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

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

Извините, я случайно ввел неправильный код. Обычно я складывал все значения потерь из каждой партии вместе. Я обновил фрагмент кода для этой части, и результирующие потери по-прежнему отрицательные (-0,15). Что касается метрик, я получаю одинаковое значение потерь от обоих объектов загрузчика данных. Я также добавил код метрики внизу. Я не уверен, должен ли я добавить часть, где я преобразовываю логиты в фактические значения? Мне кажется, что добавлять в пост просто не нужно. Спасибо за ваш ответ!

Harun Küfrevi 21.12.2020 18:22

Также нет особой причины, по которой я получаю среднее значение потери. Это то, что я видел из всех других ноутбуков для оценки шага.

Harun Küfrevi 21.12.2020 18:23

Я добавил еще одно обновление для значения потерь. Кажется, теперь все работает нормально, но у меня до сих пор нет ответа на проблему с DataLoader. @ValeKnappich

Harun Küfrevi 21.12.2020 19:54
Ответ принят как подходящий

Насколько я понимаю, ответ довольно прост:

«Я видел, как мой отец делал это таким образом, и его отец тоже делал это таким образом, поэтому я тоже делаю это таким образом».

Я просмотрел множество блокнотов, чтобы увидеть, как люди загружают данные для проверки, и в каждом блокноте я видел, что люди используют Sequential Sampler для проверки. Никто не использует перетасовку или случайную выборку во время проверки. Я точно не знаю, почему, но это так. Так что, если кто-то, посетивший этот пост, задавался тем же вопросом, ответ в основном тот, что я процитировал выше.

Кроме того, я отредактировал исходный пост для проблемы потери, с которой я столкнулся. Я неправильно рассчитывал. По-видимому, Берт возвращает потерю с индексом 0 вывода (выходы [0]), если вы также передаете модели исходные метки. В первом фрагменте кода, когда я получал выходные данные из модели, я не передавал модели исходные метки, поэтому она не возвращала значение потерь с индексом 0, а возвращала только логиты.

В основном, что вам нужно сделать, это:

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

Причина, по которой вам не нужно перетасовывать данные при проверке, заключается не в том, что все так делают. На самом деле есть веская причина. Во время проверки вы не выполняете обратное распространение и не обновляете веса модели, поэтому порядок, в котором вы передаете данные проверки в модель, совершенно не имеет значения. Следовательно, нет смысла их перемешивать, и мы можем просто подавать данные в исходном порядке последовательно.

andrea 01.09.2022 18:46

Другие вопросы по теме