Я пытался оценить свою модель с помощью теста GLUE, но, к своему удивлению, обнаружил, что, несмотря на популярность теста GLUE, нет удобного инструмента/руководства, которое показало бы мне, как этого можно достичь. У меня есть один большой вопрос: могу ли я иметь единый скрипт, который «просто работает» для всех задач GLUE? Дополнение: могу ли я сгенерировать файл отправки таблицы лидеров GLUE с помощью того же скрипта?

Наконец, я написал этот скрипт и поместил его в репозиторий KerasNLP github, пожалуйста, проверьте его на https://github.com/keras-team/keras-nlp/tree/master/examples/glue_benchmark. Вы можете подключить любую пользовательскую модель к сценарию, следуя инструкциям, и она полностью совместима с GPU и TPU.

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

Обзор теста GLUE

Тест GLUE обычно используется для проверки производительности модели при понимании текста. Он состоит из 10 заданий:

  1. CoLA (Корпус лингвистической приемлемости): Предскажите, является ли предложение грамматически правильным.
  2. SST-2 (Stanford Sentiment Treebank): предсказать тональность заданного предложения.
  3. MRPC (Microsoft Research Paraphrase Corpus): Предскажите, являются ли пары предложений семантически эквивалентными.
  4. QQP (Quora Question Pairs2): предсказать, являются ли пара вопросов семантически эквивалентными.
  5. MNLI (Multi-Genre Natural Language Inference): Предскажите, влечет ли посылка гипотезу (заключение), противоречит ли она гипотезе (противоречие) или ни то, ни другое (нейтрально).
  6. QNLI (вопросно-ответный вывод на естественном языке): предскажите, содержит ли контекстное предложение ответ на вопрос.
  7. RTE (распознавание текстового продолжения): предсказать, влечет ли предложение за собой данную гипотезу или нет.
  8. WNLI (Winograd Natural Language Inference): Предскажите, связано ли предложение с замененным местоимением с исходным предложением.
  9. AX (Главная диагностика): Оцените понимание предложений с помощью задач на вывод естественного языка (NLI).
  10. STSB (тест семантического текстового сходства): предскажите оценку сходства между двумя предложениями.

Каждая задача имеет набор данных, разделенный на обучение, проверку и тестирование, за исключением того, что MNLI и AX используют один и тот же обучающий набор.

Все, кроме «STSB», можно рассматривать как задачу классификации текста, тогда как «STSB» — это задачу регрессии текста (вывод числа с плавающей запятой в диапазоне [0, 5]).

Обычный подход к использованию эталонного теста GLUE состоит в том, чтобы построить модель и обучить/настроить ее на обучающем наборе, а затем оценить локально с помощью проверочного набора. Как только вы будете удовлетворены результатами обучения/проверки, создайте прогнозы на тестовом наборе и запишите свои прогнозы в *.tsv файл (например, mrpc.tsv) в требуемом формате. Затем вы отправляете zip-файл со всеми .tsv файлами в таблицу лидеров GLUE, после чего сеть сообщит вам фактическую производительность при тестировании набора данных. Вы не можете оценить набор данных для тестирования локально, потому что метка тестирования не публикуется.

Установить/импортировать зависимости

pip install -q keras-nlp
import os
import csv

import keras_nlp
import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds

from tensorflow import keras

Получить набор данных GLUE и данные предварительной обработки

В этом разделе обсуждается, как загрузить набор данных GLUE в среде выполнения Tensorflow и предварительно обработать набор данных, чтобы он был готов к обучению.

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

task_name = "mrpc"

Затем мы определяем несколько гиперпараметров, необходимых для предварительной обработки данных.

batch_size = 32
sequence_length = 512

Скачать набор данных GLUE

Мы загружаем набор данных GLUE из наборов данных Tensorflow (TFDS). Приятно то, что загруженный датасет уже имеет тип tf.data.Dataset, который имеет хорошую поддержку параллелизма, оптимизацию акселератора и т. д. Соответствующие материалы можно найти здесь.

AX, MNLI_Matched и MNLI_Mismatched особенные, поскольку они используют один и тот же набор обучающих данных, хотя все они имеют собственный набор тестовых данных. MNLI_Matched и MNLI_Mismatched также имеют собственный набор данных проверки, в то время как AX не предоставляет набора данных проверки.

if task_name in ("ax", "mnli_matched", "mnli_mismatched"):
    train_ds, validation_ds = tfds.load(
        "glue/mnli",
        split=["train", "validation_matched"],
    )
    if task_name == "ax":
        test_ds = tfds.load(
            "glue/ax",
            split="test",
        )

    if task_name == "mnli_matched":
        test_ds = tfds.load(
            "glue/mnli_matched",
            split="test",
        )

    if task_name == "mnli_mismatched":
        validation_ds, test_ds = tfds.load(
            "glue/ax",
            split=["validation", "test",]
        )
else:
    train_ds, test_ds, validation_ds = tfds.load(
        f"glue/{task_name}",
        split=["train", "test", "validation"],
    )

Сохраните порядок индекса данных тестирования

Это необходимо для создания файла отправки таблицы лидеров. Вы можете пока не читать этот код.

idx_order = test_ds.map(lambda data: data["idx"])

Объединение данных

Наборы данных GLUE имеют формат словаря, и каждая задача имеет собственное имя функции, например «предложение1» и «предпосылка». Это несоответствие формата данных усложняет наше обучение, поэтому мы стандартизируем формат, чтобы упростить обучение.

Для всех задач после стандартизации каждая запись данных будет иметь следующий формат: (features, label), а features — это кортеж из одного элемента, если задача имеет только один признак, например, «COLA», и кортеж из 2 элементов, если задачи имеет 2 функции, например, «MRPC». Нет задач GLUE, имеющих ›2 особенности.

Получите имена функций для нашей выбранной задачи.

FEATURES = {
    "cola": ("sentence",),
    "sst2": ("sentence",),
    "mrpc": ("sentence1", "sentence2"),
    "stsb": ("sentence1", "sentence2"),
    "rte": ("sentence1", "sentence2"),
    "wnli": ("sentence1", "sentence2"),
    "mnli": ("premise", "hypothesis"),
    "mnli_matched": ("premise", "hypothesis"),
    "mnli_mismatched": ("premise", "hypothesis"),
    "ax": ("premise", "hypothesis"),
    "qnli": ("question", "question"),
    "qqp": ("question1", "question2"),
}
feature_names = FEATURES[task_name]

Определите функцию, выполняющую стандартизацию — конвертируйте словарь в формат (features, label). Затем используйте функцию map, чтобы применить стандартизацию.

def split_features(x):
    features = tuple([x[name] for name in feature_names])
    label = x["label"]
    return (features, label)
train_ds = train_ds.map(split_features, num_parallel_calls=tf.data.AUTOTUNE)
test_ds = test_ds.map(split_features, num_parallel_calls=tf.data.AUTOTUNE)
validation_ds = validation_ds.map(
    split_features, num_parallel_calls=tf.data.AUTOTUNE
)

Токенизация и упаковка

Модели НЛП не могут напрямую работать с текстовым вводом, нам нужно преобразовать текстовый ввод в плавающие векторы. Здесь мы используем keras_nlp.models.BertTokenizer для преобразования.

Помните, что наш feature может быть кортежем из двух строк, нам нужен какой-то способ объединить их вместе. Обычный подход состоит в том, чтобы иметь токен [SEP] между двумя предложениями и помещать два специальных токена отдельно в начало и конец объединенного предложения. Для унифицированного рабочего процесса, если функция имеет только одну строку, мы пропускаем токен [SEP], но по-прежнему дополняем начальный и конечный токены. К этому можно легко приблизиться с помощью keras_nlp.layers.MultiSegmentPacker, как показано в приведенном ниже коде.

tokenizer = keras_nlp.models.BertTokenizer.from_preset("bert_base_en_uncased")

packer = keras_nlp.layers.MultiSegmentPacker(
    start_value=tokenizer.cls_token_id,
    end_value=tokenizer.sep_token_id,
    pad_value=tokenizer.pad_token_id,
    sequence_length=sequence_length,
)
def preprocess_fn(feature, label):
    tokenized_data = [tokenizer(x) for x in feature]
    token_ids, _ = packer(tokenized_data)
    padding_mask = (token_ids != tokenizer.pad_token_id)
    return {"token_ids": token_ids, "padding_mask": padding_mask}, label

После применения preprocess_fn для всех задач GLUE каждая запись данных представляет собой кортеж (features, label), а features — словарь формата

train_ds_processed = train_ds.map(preprocess_fn).batch(batch_size).prefetch(tf.data.AUTOTUNE)
validation_ds_processed = validation_ds.map(preprocess_fn).batch(batch_size).prefetch(tf.data.AUTOTUNE)
test_ds_processed = test_ds.map(preprocess_fn).batch(batch_size).prefetch(tf.data.AUTOTUNE)

Определите модель и настройте обучение

Давайте определим некоторые гиперпараметры для нашей модели.

if task_name == "stsb":
    num_classes = 1
elif task_name in (
    "mnli",
    "mnli_mismatched",
    "mnli_matched",
    "ax",
):
    num_classes = 3
else:
    num_classes = 2

feature_dim = 128
transformer_intermediate_dim = 128
vocab_size = tokenizer.vocabulary_size()
learning_rate = 5e-5
num_epochs = 6

Затем мы определяем модель классификации, это простой кодировщик Transformer с одним слоем преобразования и одним плотным слоем. Мы можем построить эту модель с помощью нескольких строк с предложениями KerasNLP.

Мы строим модель с использованием функционального API Keras, на высоком уровне необходимо определить символьные входные данные и определить ваш график для вычисления символьных выходных данных, а затем сообщить keras.Model входную и выходную информацию. Для получения более подробной информации обратитесь к данному руководству.

token_id_input = keras.Input(
    shape=(None,), dtype="int32", name="token_ids"
)
padding_mask = keras.Input(
    shape=(None,), dtype="int32", name="padding_mask"
)
x = keras.layers.Embedding(
    tokenizer.vocabulary_size(),
    feature_dim
)(token_id_input)
x = keras_nlp.layers.TransformerEncoder(
    transformer_intermediate_dim,
    4,
    activation="tanh"
)(x, padding_mask=padding_mask)[:, 0, :]
x = keras.layers.Dense(num_classes, activation="tanh")(x)

inputs = {
    "token_ids": token_id_input,
    "padding_mask": padding_mask,
}
outputs = x
classification_model=keras.Model(inputs=inputs, outputs=outputs)

Определите функцию потерь и метрики для отслеживания обучения.

if task_name == "stsb":
    loss = keras.losses.MeanSquaredError()
    metrics = [keras.metrics.MeanSquaredError()]
else:
    loss = keras.losses.SparseCategoricalCrossentropy(from_logits=True)
    metrics = [keras.metrics.SparseCategoricalAccuracy()]
classification_model.compile(
    optimizer=keras.optimizers.Adam(learning_rate),
    loss=loss,
    metrics=metrics,
)
classification_model.fit(
    train_ds_processed,
    validation_data=train_ds_processed,
    epochs=num_epochs,
)

Сгенерируйте файлы отправки GLUE Leaderboard

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

Сначала мы определяем соответствующее имя файла и имена меток для каждой задачи.

filenames = {
    "cola": "CoLA.tsv",
    "sst2": "SST-2.tsv",
    "mrpc": "MRPC.tsv",
    "qqp": "QQP.tsv",
    "stsb": "STS-B.tsv",
    "mnli_matched": "MNLI-m.tsv",
    "mnli_mismatched": "MNLI-mm.tsv",
    "qnli": "QNLI.tsv",
    "rte": "RTE.tsv",
    "wnli": "WNLI.tsv",
    "ax": "AX.tsv",
}

labelnames = {
    "mnli_matched": ["entailment", "neutral", "contradiction"],
    "mnli_mismatched": ["entailment", "neutral", "contradiction"],
    "ax": ["entailment", "neutral", "contradiction"],
    "qnli": ["entailment", "not_entailment"],
    "rte": ["entailment", "not_entailment"],
}

Создайте пустой файл сейчас, мы скоро наполним его содержимым.

submission_directory = "glue_submissions"
if not os.path.exists(submission_directory):
    os.makedirs(submission_directory)
filename = submission_directory + "/" + filenames[task_name]
labelname = labelnames.get(task_name)

Используйте нашу модель для создания прогнозов, затем мы сопоставляем прогноз с правильным порядком индексов. Ранее мы создали idx_order, теперь он выходит на сцену!

predictions = classification_model.predict(test_ds_processed)
if task_name == "stsb":
    predictions = np.squeeze(predictions)
else:
    predictions = np.argmax(predictions, -1)

# Map the predictions to the right index order.
idx_order = list(idx_order.as_numpy_iterator())
contents = ["" for _ in idx_order]

Последним шагом будет правильное форматирование. Некоторые задачи имеют целые метки, а некоторые задачи имеют специальные строковые метки, такие как «entailment» и «not_entailment» в QNLI. Также пишем необходимый заголовок.

for idx, pred in zip(idx_order, predictions):
    if labelname:
        pred_value = labelname[int(pred)]
    else:
        pred_value = pred
        if task_name == "stsb":
            pred_value = min(pred_value, 5)
            pred_value = max(pred_value, 0)
            pred_value = f"{pred_value:.3f}"
    contents[idx] = pred_value

with tf.io.gfile.GFile(filename, "w") as f:
    # GLUE requires a format of index + tab + prediction.
    writer = csv.writer(f, delimiter="\t")
    # Write the required headline for GLUE.
    writer.writerow(["index", "prediction"])

    for idx, value in enumerate(contents):
        writer.writerow([idx, value])

Предположим, у вас есть task_name=mrpc, теперь вы можете проверить его содержимое с помощью приведенной ниже команды.

!head -10 glue_submissions/MRPC.tsv

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

!curl -O https://gluebenchmark.com/assets/CBOW.zip
!unzip -d sample_submissions/ CBOW.zip
!cp glue_submissions/MRPC.tsv sample_submissions/
!zip -r submission.zip . -i sample_submissions/*.tsv

Вы можете скачать сгенерированный файл submission.zip на свой локальный диск и отправить его через официальный портал. Оценка будет доступна через 30 секунд после отправки.

Поздравляю!! Вы дошли до конца руководства, надеюсь, теперь у вас есть хорошее представление о тесте GLUE и о том, как его использовать. Опять же, если вы ищете что-то просто работающее, ознакомьтесь со скриптом GLUE, доступным в KerasNLP.