NLP 課程文件

翻譯

Hugging Face's logo
加入 Hugging Face 社群

並獲得增強型文件體驗

開始使用

翻譯

Ask a Question Open In Colab Open In Studio Lab

現在讓我們深入瞭解翻譯。這是另一個 序列到序列任務,這意味著它是一個可以被表述為從一個序列到另一個序列的問題。從這個意義上說,這個問題與 摘要 非常接近,您可以將我們在這裡看到的改編為其他序列到序列問題,例如

  • 風格遷移: 建立一個模型,翻譯以特定風格編寫的文字到另一種風格(例如,正式到非正式,或莎士比亞英語到現代英語)
  • 生成式問答: 建立一個模型,根據上下文生成問題的答案

如果您擁有兩種(或更多種)語言的足夠大的語料庫,您可以像我們在 因果語言建模 部分中那樣從頭開始訓練一個新的翻譯模型。但是,微調一個現有的翻譯模型會更快,無論是您想要微調到特定語言對的多語言模型(如 mT5 或 mBART),還是您想要微調到您的特定語料庫的專門用於從一種語言到另一種語言的翻譯的模型。

在本節中,我們將在 KDE4 資料集 上微調一個預訓練的 Marian 模型,該模型預訓練用於從英語到法語的翻譯(因為很多 Hugging Face 員工都說法語和英語)。我們將使用的模型是在來自 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”。在訓練資料集中找到第一個使用這個詞語的樣本。它被翻譯成什麼了?預訓練模型如何翻譯同一個英語句子?

處理資料

你現在應該知道流程了:所有文字都需要轉換為一組標記 ID,以便模型可以理解它們。對於此任務,我們需要對輸入和目標進行標記。我們的第一個任務是建立我們的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欄位中。如果你忘記指定你正在標記標籤,它們將被輸入標記器標記,在 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>']

我們可以看到,使用英語標記器預處理法語句子會導致更多標記,因為標記器不認識任何法語單詞(除了在英語中也出現的單詞,比如“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:

⚠️我們不關注目標的注意力掩碼,因為模型不需要它。相反,與填充標記對應的標籤應設定為-100,以便它們在損失計算中被忽略。這將在我們稍後使用資料整理器完成,因為我們正在應用動態填充,但是如果你在這裡使用填充,你應該調整預處理函式以將對應於填充標記的所有標籤設定為-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、注意力掩碼和標記型別 ID)。我們的標籤也應該填充到標籤中遇到的最大長度。並且,如前所述,用於填充標籤的填充值應該為-100,而不是標記器的填充標記,以確保這些填充值在損失計算中被忽略。

所有這些都是由 DataCollatorForSeq2Seq完成的。像DataCollatorWithPadding一樣,它接受用於預處理輸入的tokenizer,但它也接受model。這是因為此資料整理器還將負責準備解碼器輸入 ID,它們是帶有特殊標記開頭的標籤的移位版本。由於這種移位對於不同的架構略有不同,DataCollatorForSeq2Seq需要知道model物件。

from transformers import DataCollatorForSeq2Seq

data_collator = DataCollatorForSeq2Seq(tokenizer, model=model)

要在一些樣本上測試它,我們只需在來自我們標記的訓練集的示例列表上呼叫它即可。

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,並使用一個注意力掩碼來確保它不會使用它試圖預測的標記之後的標記,以加快訓練速度。在推理期間,我們將無法使用它們,因為我們沒有標籤,所以最好用相同的設定來評估我們的模型。

正如我們在 第 1 章中所看到的,解碼器透過逐個預測標記來執行推理——這在 🤗 Transformers 中由generate()方法在幕後實現。如果我們設定predict_with_generate=TrueSeq2SeqTrainer將允許我們使用該方法進行評估。

用於翻譯的傳統指標是 BLEU 分數,它在 2002 年的一篇文章中由 Kishore Papineni 等人提出。BLEU 分數評估翻譯與標籤的接近程度。它不衡量模型生成的輸出的可理解性或語法正確性,而是使用統計規則來確保生成的輸出中的所有單詞也出現在目標中。此外,還有一些規則會懲罰重複相同的單詞(如果它們在目標中沒有重複),以及輸出比目標更短的句子(以避免模型輸出像“the”這樣的句子)。

BLEU 的一個缺點是它期望文字已經過標記,這使得很難比較使用不同標記器的模型之間的分數。因此,今天用於基準測試翻譯模型的最常用指標是 SacreBLEU,它透過標準化標記步驟解決了這個問題(以及其他問題)。要使用此指標,我們首先需要安裝 SacreBLEU 庫。

!pip install sacrebleu

然後我們可以像我們在 第 3 章中所做的那樣,透過evaluate.load()載入它。

import evaluate

metric = evaluate.load("sacrebleu")

此指標將接受文字作為輸入和目標。它被設計為接受幾個可接受的目標,因為同一個句子的翻譯通常有多個可接受的版本——我們使用的 dataset 只提供了一個,但在 NLP 中,發現提供多個句子作為標籤的 dataset 並不少見。因此,預測應該是一個句子列表,但參考應該是一個句子列表的列表。

讓我們試一個例子。

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(標記器會自動對填充標記做同樣的事情)。

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,這樣你就可以將你的結果上傳到模型中心。筆記本中有一個方便的函式可以幫助你完成此操作

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,
)

除了通常的超引數(如學習率、迭代次數、批次大小和一些權重衰減)外,與我們在前幾節中看到的相比,這裡有一些更改

  • 我們沒有設定任何常規評估,因為評估需要一段時間;我們只會在訓練之前和之後評估一次我們的模型。
  • 我們設定 fp16=True,這將加快現代 GPU 上的訓練速度。
  • 我們設定 predict_with_generate=True,如上所述。
  • 我們使用 push_to_hub=True 在每個迭代結束時將模型上傳到模型中心。

請注意,你可以使用 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()

請注意,在訓練過程中,每次模型被儲存(這裡,每個迭代)都會在後臺上傳到模型中心。這樣,你就可以在必要時在另一臺機器上恢復訓練。

訓練完成後,我們再次評估我們的模型——希望我們能在 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 還草擬了一個包含所有評估結果的模型卡並將其上傳。此模型卡包含元資料,有助於模型中心為推理演示選擇小部件。通常,無需說任何話,因為它可以從模型類推斷出正確的小部件,但在這種情況下,同一個模型類可用於各種序列到序列問題,因此我們指定它是一個翻譯模型

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

此命令返回它剛剛執行的提交的 URL,如果你想檢查它

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

在此階段,你可以使用模型中心上的推理小部件測試你的模型並與你的朋友分享。你已經成功地在翻譯任務上微調了一個模型——恭喜!

如果你想更深入地研究訓練迴圈,我們現在將向你展示如何使用 🤗 Accelerate 做同樣的事情。

自定義訓練迴圈

現在讓我們看一下完整的訓練迴圈,這樣你就可以輕鬆地自定義你需要的部分。它看起來很像我們在 第 2 節第 3 章 中所做的。

準備所有用於訓練的內容

您已經多次看到了這一切,因此我們將快速瀏覽程式碼。首先,我們將從我們的資料集構建 DataLoader,在將資料集設定為 "torch" 格式後,以便獲得 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,
)

最後,為了將模型推送到模型中心,我們需要在工作資料夾中建立一個 Repository 物件。首先登入到 Hugging Face 模型中心,如果你還沒有登入。我們將從我們要給模型的模型 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 中的任何內容上傳。這將幫助我們在每個迭代結束時上傳中間模型。

訓練迴圈

我們現在準備編寫完整的訓練迴圈。為了簡化其評估部分,我們定義了這個 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() 方法計算預測,但這在我們基礎模型上的一個方法,而不是在 prepare() 方法中建立的包裝模型 🤗 Accelerate 上。這就是為什麼我們首先解包模型,然後呼叫此方法。

第二件事是,與 令牌分類 一樣,兩個程序可能將輸入和標籤填充到不同的形狀,因此我們使用 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。如果你想測試對訓練迴圈的任何調整,你可以直接透過編輯上面顯示的程式碼來實現它們!

使用微調的模型

我們已經向您展示瞭如何在模型中心使用我們微調的模型和推理小部件。要將其在本地用於 `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”單詞的樣本中返回了什麼?

© . This site is unofficial and not affiliated with Hugging Face, Inc.