Я пытался оценить свою модель с помощью теста GLUE, но, к своему удивлению, обнаружил, что, несмотря на популярность теста GLUE, нет удобного инструмента/руководства, которое показало бы мне, как этого можно достичь. У меня есть один большой вопрос: могу ли я иметь единый скрипт, который «просто работает» для всех задач GLUE? Дополнение: могу ли я сгенерировать файл отправки таблицы лидеров GLUE с помощью того же скрипта?
Наконец, я написал этот скрипт и поместил его в репозиторий KerasNLP github, пожалуйста, проверьте его на https://github.com/keras-team/keras-nlp/tree/master/examples/glue_benchmark. Вы можете подключить любую пользовательскую модель к сценарию, следуя инструкциям, и она полностью совместима с GPU и TPU.
Хотя исполняемый скрипт хорош, он не может охватить достаточно деталей без объемных, утомительных и нечитаемых комментариев. Это причина, по которой я пишу этот пост.
Обзор теста GLUE
Тест GLUE обычно используется для проверки производительности модели при понимании текста. Он состоит из 10 заданий:
- CoLA (Корпус лингвистической приемлемости): Предскажите, является ли предложение грамматически правильным.
- SST-2 (Stanford Sentiment Treebank): предсказать тональность заданного предложения.
- MRPC (Microsoft Research Paraphrase Corpus): Предскажите, являются ли пары предложений семантически эквивалентными.
- QQP (Quora Question Pairs2): предсказать, являются ли пара вопросов семантически эквивалентными.
- MNLI (Multi-Genre Natural Language Inference): Предскажите, влечет ли посылка гипотезу (заключение), противоречит ли она гипотезе (противоречие) или ни то, ни другое (нейтрально).
- QNLI (вопросно-ответный вывод на естественном языке): предскажите, содержит ли контекстное предложение ответ на вопрос.
- RTE (распознавание текстового продолжения): предсказать, влечет ли предложение за собой данную гипотезу или нет.
- WNLI (Winograd Natural Language Inference): Предскажите, связано ли предложение с замененным местоимением с исходным предложением.
- AX (Главная диагностика): Оцените понимание предложений с помощью задач на вывод естественного языка (NLI).
- 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.