LLM 课程文档

翻译

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

翻译

Ask a Question Open In Colab Open In Studio Lab

现在让我们深入探讨翻译。这是另一个 序列到序列任务,这意味着这是一个可以被表述为从一个序列到另一个序列的问题。从这个意义上讲,这个问题与 摘要 非常接近,您可以将我们在这里看到的内容应用于其他序列到序列问题,例如

  • 风格迁移:创建一个模型,将以某种风格编写的文本翻译成另一种风格(例如,从正式到随意,或从莎士比亚英语到现代英语)
  • 生成式问答:创建一个模型,根据上下文生成问题的答案

如果您有足够大的双语(或多语)文本语料库,您可以从头开始训练一个新的翻译模型,就像我们在关于 因果语言建模 的章节中所做的那样。但是,微调现有的翻译模型会更快,无论是您想要微调到特定语言对的多语言模型(如 mT5 或 mBART),还是您想要微调到特定语料库的、专门用于从一种语言翻译到另一种语言的模型。

在本节中,我们将微调一个 Marian 模型,该模型经过预训练,可以将英语翻译成法语(因为许多 Hugging Face 员工都说这两种语言),数据集采用 KDE4 数据集,这是一个 KDE 应用程序的本地化文件数据集。我们将使用的模型已在从 Opus 数据集 中提取的大量法语和英语文本语料库上进行了预训练,该数据集实际上包含了 KDE4 数据集。但是,即使我们使用的预训练模型在预训练期间已经见过该数据,我们也会看到,经过微调后,我们可以获得更好的版本。

完成之后,我们将拥有一个能够做出如下预测的模型

One-hot encoded labels for question answering.

与之前的章节一样,您可以使用下面的代码找到我们将要训练并上传到 Hub 的实际模型,并在此处 此处 双重检查其预测结果。

准备数据

要从头开始微调或训练翻译模型,我们需要一个适合该任务的数据集。如前所述,在本节中,我们将使用 KDE4 数据集,但是您可以轻松地调整代码以使用您自己的数据,只要您拥有要翻译的两种语言的句子对即可。如果您需要回顾如何在 Dataset 中加载自定义数据,请回顾 第 5 章

KDE4 数据集

像往常一样,我们使用 load_dataset() 函数下载数据集

from datasets import load_dataset

raw_datasets = load_dataset("kde4", lang1="en", lang2="fr")

如果您想使用不同的语言对,可以使用它们的代码来指定。此数据集共有 92 种语言可用;您可以在其 数据集卡片 上展开语言标签来查看所有语言。

Language available for the KDE4 dataset.

让我们看一下数据集

raw_datasets
DatasetDict({
    train: Dataset({
        features: ['id', 'translation'],
        num_rows: 210173
    })
})

我们有 210,173 对句子,但只有一个拆分,因此我们需要创建我们自己的验证集。正如我们在 第 5 章 中看到的那样,Dataset 有一个 train_test_split() 方法可以帮助我们。我们将提供一个种子以实现可重现性

split_datasets = raw_datasets["train"].train_test_split(train_size=0.9, seed=20)
split_datasets
DatasetDict({
    train: Dataset({
        features: ['id', 'translation'],
        num_rows: 189155
    })
    test: Dataset({
        features: ['id', 'translation'],
        num_rows: 21018
    })
})

我们可以像这样将 "test" 键重命名为 "validation"

split_datasets["validation"] = split_datasets.pop("test")

现在让我们看一下数据集的一个元素

split_datasets["train"][1]["translation"]
{'en': 'Default to expanded threads',
 'fr': 'Par défaut, développer les fils de discussion'}

我们得到一个字典,其中包含我们请求的语言对中的两个句子。这个充满计算机科学术语的数据集的一个特点是,它们在法语中都被完全翻译了。然而,法国工程师在谈话时,大多数计算机科学专业词汇都保留英文。例如,在这里,“threads”这个词很可能出现在法语句子中,尤其是在技术对话中;但是在这个数据集中,它被翻译成更正确的“fils de discussion”。我们使用的预训练模型是在更大的法语和英语句子语料库上进行预训练的,它采用了更简单的选项,即保持单词不变。

from transformers import pipeline

model_checkpoint = "Helsinki-NLP/opus-mt-en-fr"
translator = pipeline("translation", model=model_checkpoint)
translator("Default to expanded threads")
[{'translation_text': 'Par défaut pour les threads élargis'}]

另一个这种行为的例子可以在“plugin”这个词中看到,它不是一个正式的法语单词,但大多数母语人士都会理解,并且不会费心去翻译。在 KDE4 数据集中,这个词在法语中被翻译成更正式的“module d’extension”

split_datasets["train"][172]["translation"]
{'en': 'Unable to import %1 using the OFX importer plugin. This file is not the correct format.',
 'fr': "Impossible d'importer %1 en utilisant le module d'extension d'importation OFX. Ce fichier n'a pas un format correct."}

然而,我们的预训练模型仍然坚持使用简洁而熟悉的英语单词

translator(
    "Unable to import %1 using the OFX importer plugin. This file is not the correct format."
)
[{'translation_text': "Impossible d'importer %1 en utilisant le plugin d'importateur OFX. Ce fichier n'est pas le bon format."}]

看看我们微调后的模型是否会采纳数据集的这些特殊性将会很有趣(剧透:它会的)。

✏️ 轮到你了! 另一个在法语中经常使用的英语单词是“email”。在训练数据集中找到第一个使用这个词的样本。它是如何翻译的?预训练模型如何翻译同一个英语句子?

处理数据

现在您应该知道流程了:所有文本都需要转换为 token ID 集合,以便模型可以理解它们。对于此任务,我们需要对输入和目标进行 token 化。我们的首要任务是创建我们的 tokenizer 对象。如前所述,我们将使用 Marian 英法预训练模型。如果您正在尝试使用另一对语言的代码,请确保调整模型检查点。Helsinki-NLP 组织提供了超过一千个多种语言的模型。

from transformers import AutoTokenizer

model_checkpoint = "Helsinki-NLP/opus-mt-en-fr"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="pt")

您也可以将 model_checkpoint 替换为您在 Hub 中喜欢的任何其他模型,或者您保存了预训练模型和分词器的本地文件夹。

💡 如果您正在使用多语言分词器(如 mBART、mBART-50 或 M2M100),您将需要在分词器中设置输入和目标的语言代码,方法是将 tokenizer.src_langtokenizer.tgt_lang 设置为正确的值。

我们数据的准备工作非常简单。只有一件事要记住;您需要确保分词器以输出语言(此处为法语)处理目标。您可以通过将目标传递给分词器的 __call__ 方法的 text_targets 参数来做到这一点。

为了了解其工作原理,让我们处理训练集中每种语言的一个样本

en_sentence = split_datasets["train"][1]["translation"]["en"]
fr_sentence = split_datasets["train"][1]["translation"]["fr"]

inputs = tokenizer(en_sentence, text_target=fr_sentence)
inputs
{'input_ids': [47591, 12, 9842, 19634, 9, 0], 'attention_mask': [1, 1, 1, 1, 1, 1], 'labels': [577, 5891, 2, 3184, 16, 2542, 5, 1710, 0]}

正如我们所看到的,输出包含与英语句子关联的输入 ID,而与法语句子关联的 ID 存储在 labels 字段中。如果您忘记指示您正在 token 化标签,它们将由输入分词器 token 化,在 Marian 模型的情况下,这根本行不通

wrong_targets = tokenizer(fr_sentence)
print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"]))
print(tokenizer.convert_ids_to_tokens(inputs["labels"]))
['▁Par', '▁dé', 'f', 'aut', ',', '▁dé', 've', 'lop', 'per', '▁les', '▁fil', 's', '▁de', '▁discussion', '</s>']
['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', '</s>']

正如我们所看到的,使用英语分词器预处理法语句子会导致更多的 token,因为分词器不知道任何法语单词(除了那些也出现在英语中的单词,如“discussion”)。

由于 inputs 是一个包含我们常用键(输入 ID、注意力掩码等)的字典,因此最后一步是定义我们将应用于数据集的预处理函数

max_length = 128


def preprocess_function(examples):
    inputs = [ex["en"] for ex in examples["translation"]]
    targets = [ex["fr"] for ex in examples["translation"]]
    model_inputs = tokenizer(
        inputs, text_target=targets, max_length=max_length, truncation=True
    )
    return model_inputs

请注意,我们为输入和输出设置了相同的最大长度。由于我们处理的文本似乎很短,因此我们使用 128。

💡 如果您正在使用 T5 模型(更具体地说,是 t5-xxx 检查点之一),则模型将期望文本输入具有指示手头任务的前缀,例如 translate: English to French:

⚠️ 我们不关注目标的注意力掩码,因为模型不会期望它。相反,与填充 token 相对应的标签应设置为 -100,以便在损失计算中忽略它们。这将稍后由我们的数据整理器完成,因为我们正在应用动态填充,但如果您在此处使用填充,则应调整预处理函数以将与填充 token 相对应的所有标签设置为 -100

我们现在可以一次性将该预处理应用于数据集的所有拆分

tokenized_datasets = split_datasets.map(
    preprocess_function,
    batched=True,
    remove_columns=split_datasets["train"].column_names,
)

现在数据已预处理完成,我们准备好微调我们的预训练模型了!

使用 Trainer API 微调模型

使用 Trainer 的实际代码将与之前相同,只有一个小小的变化:我们在这里使用 Seq2SeqTrainer,它是 Trainer 的子类,它将允许我们正确处理评估,使用 generate() 方法从输入中预测输出。当我们谈论指标计算时,我们将更详细地探讨这一点。

首先,我们需要一个实际的模型来进行微调。我们将使用常用的 AutoModel API

from transformers import AutoModelForSeq2SeqLM

model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)

请注意,这次我们使用的是在翻译任务上训练的模型,实际上已经可以使用了,因此不会出现关于缺少权重或新初始化的权重的警告。

数据整理

我们需要一个数据整理器来处理动态批处理的填充。在这种情况下,我们不能像 第 3 章 中那样只使用 DataCollatorWithPadding,因为这只会填充输入(输入 ID、注意力掩码和 token 类型 ID)。我们的标签也应该填充到标签中遇到的最大长度。而且,如前所述,用于填充标签的填充值应为 -100,而不是分词器的填充 token,以确保在损失计算中忽略这些填充值。

所有这些都由 DataCollatorForSeq2Seq 完成。与 DataCollatorWithPadding 一样,它采用用于预处理输入的 tokenizer,但也采用 model。这是因为此数据整理器还将负责准备解码器输入 ID,这些 ID 是标签的移位版本,开头带有一个特殊 token。由于对于不同的架构,这种移位略有不同,因此 DataCollatorForSeq2Seq 需要知道 model 对象

from transformers import DataCollatorForSeq2Seq

data_collator = DataCollatorForSeq2Seq(tokenizer, model=model)

为了在一些样本上测试这一点,我们只需在来自我们 token 化的训练集的一些示例列表上调用它

batch = data_collator([tokenized_datasets["train"][i] for i in range(1, 3)])
batch.keys()
dict_keys(['attention_mask', 'input_ids', 'labels', 'decoder_input_ids'])

我们可以检查我们的标签是否已使用 -100 填充到批处理的最大长度

batch["labels"]
tensor([[  577,  5891,     2,  3184,    16,  2542,     5,  1710,     0,  -100,
          -100,  -100,  -100,  -100,  -100,  -100],
        [ 1211,     3,    49,  9409,  1211,     3, 29140,   817,  3124,   817,
           550,  7032,  5821,  7907, 12649,     0]])

我们还可以查看解码器输入 ID,以查看它们是否是标签的移位版本

batch["decoder_input_ids"]
tensor([[59513,   577,  5891,     2,  3184,    16,  2542,     5,  1710,     0,
         59513, 59513, 59513, 59513, 59513, 59513],
        [59513,  1211,     3,    49,  9409,  1211,     3, 29140,   817,  3124,
           817,   550,  7032,  5821,  7907, 12649]])

以下是数据集中第一个和第二个元素的标签

for i in range(1, 3):
    print(tokenized_datasets["train"][i]["labels"])
[577, 5891, 2, 3184, 16, 2542, 5, 1710, 0]
[1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, 817, 550, 7032, 5821, 7907, 12649, 0]

我们将把这个 data_collator 传递给 Seq2SeqTrainer。接下来,让我们看一下指标。

指标

Seq2SeqTrainer 为其超类 Trainer 添加的功能是在评估或预测期间使用 generate() 方法的能力。在训练期间,模型将使用 decoder_input_ids 和注意力掩码,确保它不使用它试图预测的 token 之后的 token,以加快训练速度。在推理期间,我们将无法使用这些,因为我们没有标签,因此使用相同的设置评估我们的模型是一个好主意。

正如我们在 第 1 章 中看到的那样,解码器通过逐个预测 token 来执行推理——这在 🤗 Transformers 中通过 generate() 方法在幕后实现。如果我们设置 predict_with_generate=TrueSeq2SeqTrainer 将允许我们使用该方法进行评估。

用于翻译的传统指标是 BLEU 分数,由 Kishore Papineni 等人在 2002 年的文章 中引入。BLEU 分数评估翻译与标签的接近程度。它不衡量模型生成输出的可理解性或语法正确性,而是使用统计规则来确保生成输出中的所有单词也出现在目标中。此外,还有一些规则会惩罚同一单词的重复,如果它们在目标中也没有重复(以避免模型输出诸如 "the the the the the" 之类的句子)和输出句子比目标中的句子短(以避免模型输出诸如 "the" 之类的句子)。

BLEU 的一个弱点是它期望文本已经被 token 化,这使得比较使用不同分词器的模型之间的分数变得困难。因此,如今用于基准测试翻译模型的最常用指标是 SacreBLEU,它通过标准化 token 化步骤来解决这个弱点(和其他弱点)。要使用此指标,我们首先需要安装 SacreBLEU 库

!pip install sacrebleu

然后我们可以像在 第 3 章 中所做的那样,通过 evaluate.load() 加载它

import evaluate

metric = evaluate.load("sacrebleu")

此指标将文本作为输入和目标。它旨在接受多个可接受的目标,因为通常同一句子有多个可接受的翻译——我们使用的数据集仅提供一个,但在 NLP 中找到提供多个句子作为标签的数据集并不少见。因此,预测应该是一个句子列表,但参考应该是一个句子列表的列表。

让我们尝试一个例子

predictions = [
    "This plugin lets you translate web pages between several languages automatically."
]
references = [
    [
        "This plugin allows you to automatically translate web pages between several languages."
    ]
]
metric.compute(predictions=predictions, references=references)
{'score': 46.750469682990165,
 'counts': [11, 6, 4, 3],
 'totals': [12, 11, 10, 9],
 'precisions': [91.67, 54.54, 40.0, 33.33],
 'bp': 0.9200444146293233,
 'sys_len': 12,
 'ref_len': 13}

这得到了 46.75 的 BLEU 分数,这相当不错——作为参考,“Attention Is All You Need” 论文 中的原始 Transformer 模型在英语和法语之间类似的翻译任务中取得了 41.8 的 BLEU 分数!(有关各个指标的更多信息,如 countsbp,请参阅 SacreBLEU 存储库。)另一方面,如果我们尝试使用通常从翻译模型中产生的两种不良类型的预测(大量重复或太短),我们将获得相当差的 BLEU 分数

predictions = ["This This This This"]
references = [
    [
        "This plugin allows you to automatically translate web pages between several languages."
    ]
]
metric.compute(predictions=predictions, references=references)
{'score': 1.683602693167689,
 'counts': [1, 0, 0, 0],
 'totals': [4, 3, 2, 1],
 'precisions': [25.0, 16.67, 12.5, 12.5],
 'bp': 0.10539922456186433,
 'sys_len': 4,
 'ref_len': 13}
predictions = ["This plugin"]
references = [
    [
        "This plugin allows you to automatically translate web pages between several languages."
    ]
]
metric.compute(predictions=predictions, references=references)
{'score': 0.0,
 'counts': [2, 1, 0, 0],
 'totals': [2, 1, 0, 0],
 'precisions': [100.0, 100.0, 0.0, 0.0],
 'bp': 0.004086771438464067,
 'sys_len': 2,
 'ref_len': 13}

分数可以从 0 到 100,分数越高越好。

为了从模型输出到指标可以使用的文本,我们将使用 tokenizer.batch_decode() 方法。我们只需要清理标签中所有的 -100(分词器会自动对填充 token 执行相同的操作)

import numpy as np


def compute_metrics(eval_preds):
    preds, labels = eval_preds
    # In case the model returns more than the prediction logits
    if isinstance(preds, tuple):
        preds = preds[0]

    decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True)

    # Replace -100s in the labels as we can't decode them
    labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
    decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)

    # Some simple post-processing
    decoded_preds = [pred.strip() for pred in decoded_preds]
    decoded_labels = [[label.strip()] for label in decoded_labels]

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

现在完成此操作后,我们准备好微调我们的模型了!

微调模型

第一步是登录 Hugging Face,以便能够将您的结果上传到 Model Hub。有一个方便的函数可以帮助您在笔记本中执行此操作

from huggingface_hub import notebook_login

notebook_login()

这将显示一个小部件,您可以在其中输入您的 Hugging Face 登录凭据。

如果您不是在笔记本中工作,只需在终端中键入以下行

huggingface-cli login

完成此操作后,我们可以定义我们的 Seq2SeqTrainingArguments。与 Trainer 一样,我们使用 TrainingArguments 的子类,其中包含更多字段

from transformers import Seq2SeqTrainingArguments

args = Seq2SeqTrainingArguments(
    f"marian-finetuned-kde4-en-to-fr",
    evaluation_strategy="no",
    save_strategy="epoch",
    learning_rate=2e-5,
    per_device_train_batch_size=32,
    per_device_eval_batch_size=64,
    weight_decay=0.01,
    save_total_limit=3,
    num_train_epochs=3,
    predict_with_generate=True,
    fp16=True,
    push_to_hub=True,
)

除了通常的超参数(如学习率、epochs 数量、批处理大小和一些权重衰减)之外,与我们在前几节中看到的相比,这里有一些变化

  • 我们不设置任何常规评估,因为评估需要一段时间;我们只会在训练之前和之后评估我们的模型一次。
  • 我们设置 fp16=True,这可以加快现代 GPU 上的训练速度。
  • 如上所述,我们设置 predict_with_generate=True
  • 我们使用 push_to_hub=True 在每个 epoch 结束时将模型上传到 Hub。

请注意,您可以使用 hub_model_id 参数指定要推送到的存储库的完整名称(特别是,您必须使用此参数才能推送到组织)。例如,当我们 huggingface-course 组织 推送模型时,我们在 Seq2SeqTrainingArguments 中添加了 hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"。默认情况下,使用的存储库将在您的命名空间中,并以您设置的输出目录命名,因此在我们的例子中,它将是 "sgugger/marian-finetuned-kde4-en-to-fr"(这是我们在本节开头链接到的模型)。

💡 如果您正在使用的输出目录已存在,则它需要是您要推送到的存储库的本地克隆。如果不是,则在定义 Seq2SeqTrainer 时会收到错误,并且需要设置新名称。

最后,我们只需将所有内容传递给 Seq2SeqTrainer

from transformers import Seq2SeqTrainer

trainer = Seq2SeqTrainer(
    model,
    args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation"],
    data_collator=data_collator,
    tokenizer=tokenizer,
    compute_metrics=compute_metrics,
)

在训练之前,我们首先看一下我们的模型获得的分数,以再次检查我们是否没有通过微调使情况变得更糟。此命令将花费一些时间,因此您可以在执行时喝杯咖啡

trainer.evaluate(max_length=max_length)
{'eval_loss': 1.6964408159255981,
 'eval_bleu': 39.26865061007616,
 'eval_runtime': 965.8884,
 'eval_samples_per_second': 21.76,
 'eval_steps_per_second': 0.341}

39 的 BLEU 分数还不错,这反映了我们的模型已经擅长将英语句子翻译成法语句子。

接下来是训练,这也将花费一些时间

trainer.train()

请注意,在训练发生时,每次保存模型时(此处为每个 epoch),它都会在后台上传到 Hub。这样,如果需要,您将能够在另一台机器上恢复训练。

训练完成后,我们再次评估我们的模型——希望我们会看到 BLEU 分数有所提高!

trainer.evaluate(max_length=max_length)
{'eval_loss': 0.8558505773544312,
 'eval_bleu': 52.94161337775576,
 'eval_runtime': 714.2576,
 'eval_samples_per_second': 29.426,
 'eval_steps_per_second': 0.461,
 'epoch': 3.0}

这是一个近 14 分的提升,这很棒。

最后,我们使用 push_to_hub() 方法来确保我们上传模型的最新版本。Trainer 还会起草一个包含所有评估结果的模型卡并上传它。此模型卡包含元数据,可帮助 Model Hub 选择推理演示的小部件。通常,无需说明任何内容,因为它可以通过模型类推断出正确的小部件,但在这种情况下,相同的模型类可用于各种序列到序列问题,因此我们指定它是一个翻译模型

trainer.push_to_hub(tags="translation", commit_message="Training complete")

如果您想检查,此命令返回它刚刚执行的提交的 URL

'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3'

在这个阶段,您可以使用 Model Hub 上的推理小部件来测试您的模型并与您的朋友分享。您已成功地在翻译任务上微调了一个模型——恭喜!

如果您想更深入地研究训练循环,我们现在将向您展示如何使用 🤗 Accelerate 做同样的事情。

自定义训练循环

现在让我们看一下完整的训练循环,以便您可以轻松自定义您需要的部分。它看起来很像我们在 第 2 节第 3 章 中所做的事情。

为训练准备一切

您现在已经多次看到所有这些内容,因此我们将快速浏览代码。首先,在将数据集设置为 "torch" 格式后,我们将从我们的数据集构建 DataLoader,以便我们获得 PyTorch 张量

from torch.utils.data import DataLoader

tokenized_datasets.set_format("torch")
train_dataloader = DataLoader(
    tokenized_datasets["train"],
    shuffle=True,
    collate_fn=data_collator,
    batch_size=8,
)
eval_dataloader = DataLoader(
    tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8
)

接下来,我们重新实例化我们的模型,以确保我们不是从之前的微调继续,而是从预训练模型重新开始

model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)

然后我们需要一个优化器

from transformers import AdamW

optimizer = AdamW(model.parameters(), lr=2e-5)

当我们拥有所有这些对象后,我们可以将它们发送到 accelerator.prepare() 方法。请记住,如果您想在 Colab 笔记本中在 TPU 上进行训练,您将需要将所有这些代码移动到训练函数中,并且不应执行任何实例化 Accelerator 的单元格。

from accelerate import Accelerator

accelerator = Accelerator()
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)

现在我们已将 train_dataloader 发送到 accelerator.prepare(),我们可以使用其长度来计算训练步数。请记住,我们应该始终在准备数据加载器后执行此操作,因为该方法将更改 DataLoader 的长度。我们使用从学习率到 0 的经典线性计划

from transformers import get_scheduler

num_train_epochs = 3
num_update_steps_per_epoch = len(train_dataloader)
num_training_steps = num_train_epochs * num_update_steps_per_epoch

lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)

最后,为了将我们的模型推送到 Hub,我们将需要在工作文件夹中创建一个 Repository 对象。首先登录 Hugging Face Hub,如果您尚未登录。我们将从我们想要给模型的模型 ID 确定存储库名称(随意将 repo_name 替换为您自己的选择;它只需要包含您的用户名,这就是函数 get_full_repo_name() 所做的)

from huggingface_hub import Repository, get_full_repo_name

model_name = "marian-finetuned-kde4-en-to-fr-accelerate"
repo_name = get_full_repo_name(model_name)
repo_name
'sgugger/marian-finetuned-kde4-en-to-fr-accelerate'

然后我们可以将该存储库克隆到本地文件夹中。如果它已经存在,则此本地文件夹应该是我们正在使用的存储库的克隆

output_dir = "marian-finetuned-kde4-en-to-fr-accelerate"
repo = Repository(output_dir, clone_from=repo_name)

我们现在可以通过调用 repo.push_to_hub() 方法上传我们保存在 output_dir 中的任何内容。这将帮助我们在每个 epoch 结束时上传中间模型。

训练循环

我们现在准备好编写完整的训练循环。为了简化其评估部分,我们定义了 postprocess() 函数,该函数接受预测和标签,并将其转换为我们的 metric 对象期望的字符串列表

def postprocess(predictions, labels):
    predictions = predictions.cpu().numpy()
    labels = labels.cpu().numpy()

    decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)

    # Replace -100 in the labels as we can't decode them.
    labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
    decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)

    # Some simple post-processing
    decoded_preds = [pred.strip() for pred in decoded_preds]
    decoded_labels = [[label.strip()] for label in decoded_labels]
    return decoded_preds, decoded_labels

训练循环看起来很像第 2 节第 3 章中的循环,评估部分有一些不同之处 — 因此让我们关注这一点!

首先要注意的是,我们使用 generate() 方法来计算预测,但这是我们基础模型的方法,而不是 🤗 Accelerate 在 prepare() 方法中创建的包装模型的方法。这就是为什么我们首先解包模型,然后调用此方法。

第二件事是,与token classification一样,两个进程可能已将输入和标签填充为不同的形状,因此我们使用 accelerator.pad_across_processes() 使预测和标签在调用 gather() 方法之前具有相同的形状。如果我们不这样做,评估将出错或永远挂起。

from tqdm.auto import tqdm
import torch

progress_bar = tqdm(range(num_training_steps))

for epoch in range(num_train_epochs):
    # Training
    model.train()
    for batch in train_dataloader:
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)

    # Evaluation
    model.eval()
    for batch in tqdm(eval_dataloader):
        with torch.no_grad():
            generated_tokens = accelerator.unwrap_model(model).generate(
                batch["input_ids"],
                attention_mask=batch["attention_mask"],
                max_length=128,
            )
        labels = batch["labels"]

        # Necessary to pad predictions and labels for being gathered
        generated_tokens = accelerator.pad_across_processes(
            generated_tokens, dim=1, pad_index=tokenizer.pad_token_id
        )
        labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100)

        predictions_gathered = accelerator.gather(generated_tokens)
        labels_gathered = accelerator.gather(labels)

        decoded_preds, decoded_labels = postprocess(predictions_gathered, labels_gathered)
        metric.add_batch(predictions=decoded_preds, references=decoded_labels)

    results = metric.compute()
    print(f"epoch {epoch}, BLEU score: {results['score']:.2f}")

    # Save and upload
    accelerator.wait_for_everyone()
    unwrapped_model = accelerator.unwrap_model(model)
    unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
    if accelerator.is_main_process:
        tokenizer.save_pretrained(output_dir)
        repo.push_to_hub(
            commit_message=f"Training in progress epoch {epoch}", blocking=False
        )
epoch 0, BLEU score: 53.47
epoch 1, BLEU score: 54.24
epoch 2, BLEU score: 54.44

完成此操作后,您应该拥有一个模型,该模型的结果与使用 Seq2SeqTrainer 训练的模型非常相似。您可以查看我们使用此代码在huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate训练的模型。如果您想测试对训练循环的任何调整,您可以直接通过编辑上面显示的代码来实现!

使用微调模型

我们已经向您展示了如何使用我们在 Model Hub 上微调的模型以及推理小部件。要在 pipeline 中本地使用它,我们只需指定正确的模型标识符

from transformers import pipeline

# Replace this with your own checkpoint
model_checkpoint = "huggingface-course/marian-finetuned-kde4-en-to-fr"
translator = pipeline("translation", model=model_checkpoint)
translator("Default to expanded threads")
[{'translation_text': 'Par défaut, développer les fils de discussion'}]

正如预期的那样,我们的预训练模型将其知识适应于我们对其进行微调的语料库,并且现在它将英语单词“threads”翻译成法语官方版本,而不是单独留下它。“plugin”也是如此

translator(
    "Unable to import %1 using the OFX importer plugin. This file is not the correct format."
)
[{'translation_text': "Impossible d'importer %1 en utilisant le module externe d'importation OFX. Ce fichier n'est pas le bon format."}]

领域自适应的另一个很好的例子!

✏️ 轮到你了! 该模型对您之前识别的带有“email”一词的示例返回什么?

< > 在 GitHub 上更新