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

В данном случае по обучающей статье был сделан Fine-Tuning модели T5 (text-to-text transfer transformer) по задаче машинного перевода, и в целом все получилось.

Исходная обучающая статья на HuggingFace была подсказана коллегами в чате, за что соответствующее спасибо..

Работаем в Colab.

Переводим с английского языка на французский.

Install

Установка библиотек.

Внимание: обычная установка transformers выдает ошибку, понадобилась коррекция на вариант transformers[torch]

!pip install transformers[torch] datasets evaluate sacrebleu

Dataset

Скачиваем данные и разделяем на тренировочную и тестовую выборки.

from datasets import load_dataset
books = load_dataset("opus_books", "en-fr")
books = books["train"].train_test_split(test_size=0.2)

Посмотреть пример пары возможно так:

books["train"][0]

Preprocess

В Colab корректно сработали t5-small и t5-base.

На t5-large стандартным способом не хватило памяти.

checkpoint = "t5-small"

from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(checkpoint)

from transformers import AutoModelForSeq2SeqLM, Seq2SeqTrainingArguments, Seq2SeqTrainer

model = AutoModelForSeq2SeqLM.from_pretrained(checkpoint)

source_lang = "en"
target_lang = "fr"
prefix = "translate English to French: "

def preprocess_function(examples):
    inputs = [prefix + example[source_lang] for example in examples["translation"]]
    targets = [example[target_lang] for example in examples["translation"]]
    model_inputs = tokenizer(inputs, text_target=targets, max_length=128, truncation=True)
    return model_inputs

tokenized_books = books.map(preprocess_function, batched=True)

from transformers import DataCollatorForSeq2Seq
data_collator = DataCollatorForSeq2Seq(tokenizer=tokenizer, model=checkpoint)

Evaluate

Добавляем метрику BLEU.

import evaluate
metric = evaluate.load("sacrebleu")

import numpy as np

def postprocess_text(preds, labels):
    preds = [pred.strip() for pred in preds]
    labels = [[label.strip()] for label in labels]
    return preds, labels

def compute_metrics(eval_preds):
    preds, labels = eval_preds
    if isinstance(preds, tuple):
        preds = preds[0]
    decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True)

    labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
    decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)

    decoded_preds, decoded_labels = postprocess_text(decoded_preds, decoded_labels)

    result = metric.compute(predictions=decoded_preds, references=decoded_labels)
    result = {"bleu": result["score"]}

    prediction_lens = [np.count_nonzero(pred != tokenizer.pad_token_id) for pred in preds]
    result["gen_len"] = np.mean(prediction_lens)
    result = {k: round(v, 4) for k, v in result.items()}
    return result

Login

Для данного дообучения нужно авторизоваться, введя токен с правами "wtite".

from huggingface_hub import notebook_login
notebook_login()

Train

Загружаем выбранную предобученную модель

from transformers import AutoModelForSeq2SeqLM, Seq2SeqTrainingArguments, Seq2SeqTrainer
model = AutoModelForSeq2SeqLM.from_pretrained(checkpoint)

И формируем новую.
По сравнению со статьей добавлено "overwrite_output_dir=True", на случай перезаписи при сбоях.

new_model = 'my_t5_small_test'

training_args = Seq2SeqTrainingArguments(
    output_dir=new_model,
    overwrite_output_dir=True,
    evaluation_strategy="epoch",
    learning_rate=2e-5,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=16,
    weight_decay=0.01,
    save_total_limit=3,
    num_train_epochs=2,
    predict_with_generate=True,
    fp16=True,
    push_to_hub=True
)

trainer = Seq2SeqTrainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_books["train"],
    eval_dataset=tokenized_books["test"],
    tokenizer=tokenizer,
    data_collator=data_collator,
    compute_metrics=compute_metrics,
)

trainer.train()

Дообучение прошло успешно.

TrainOutput(global_step=12710, training_loss=1.875453057578002, metrics={'train_runtime': 3081.1844, 'train_samples_per_second': 65.993, 'train_steps_per_second': 4.125, 'total_flos': 4999920540844032.0, 'train_loss': 1.875453057578002, 'epoch': 2.0})

Сделаем оценку еще раз для более удобного представления данных.

trainer.evaluate(tokenized_books["test"])

После завершения размещаем модель на HuggingFace.

trainer.push_to_hub()

Теперь модель расположена на HuggingFace.

Если тренировку повторить в том же виде, то есть trainer.train() еще раз, то показатели улучшаются.

Inference

text = "translate English to French: Legumes share resources with nitrogen-fixing bacteria."

from transformers import pipeline
translator = pipeline("translation", model=new_model)
translator(text)

>>> [{'translation_text': 'Legumes teilen Ressourcen mit Stickstoff-fixierenden Bakterien.'}]

Обработчик сообщает, что хочет "translation_XX_to_YY" вместо "translation".
Корректируем.

translator = pipeline("translation_EN_to_FR", model=new_model)
translator(text)

>>> [{'translation_text': 'Les légumes partagent les ressources avec les bactéries fixatrice'}]

Еще о собственных данных

Еще один датасет создавался для примера "вручную", не по статье.

texts = [
    {'en': 'The Wanderer', 'fr': 'Le grand Meaulnes'},
    {'en': 'Hello', 'fr': 'Bonjour'}
    ]

data_dict = {'id': [ key for key in range(len(texts)) ], 'translation': texts}
my_dataset = Dataset.from_dict(data_dict)
dataset_dict = DatasetDict({"train": my_dataset})

Так тоже все сработало.

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

Вопросы, оставшиеся неясными

В исходной обучающей статье предложено 2 эпохи.

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

Примечания

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

Комментарии (0)