LLM 課程文件

翻譯

Hugging Face's logo
加入 Hugging Face 社群

並獲得增強的文件體驗

開始使用

翻譯

Ask a Question Open In Colab Open In Studio Lab

現在讓我們深入研究翻譯。這是另一個序列到序列任務,這意味著它是一個可以被表述為從一個序列到另一個序列的問題。從這個意義上說,這個問題與摘要非常接近,你可以將我們在這裡看到的內容應用於其他序列到序列問題,例如:

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

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

在本節中,我們將在 KDE4 資料集上微調一個預訓練的 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 欄位中。如果你忘記表明你正在對標籤進行標記化,它們將被輸入標記器標記化,這在 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)

為了在幾個樣本上測試它,我們只需在來自我們標記化訓練集的一個示例列表上呼叫它:

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,以加快訓練速度。在推理期間,我們無法使用這些,因為我們沒有標籤,因此使用相同的設定評估我們的模型是個好主意。

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

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

BLEU 的一個弱點是它期望文字已經被標記化,這使得比較使用不同標記器的模型之間的分數變得困難。因此,目前翻譯模型基準測試中最常用的指標是 SacreBLEU,它透過標準化標記化步驟來解決這個弱點(以及其他弱點)。要使用這個指標,我們首先需要安裝 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,這樣您就可以將結果上傳到模型 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,
)

除了常用的超引數(如學習率、epoch 數量、批次大小和一些權重衰減)外,與前幾節相比,這裡還有一些變化:

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

請注意,您可以使用 hub_model_id 引數指定要推送到 Hub 的儲存庫全名(特別是,您必須使用此引數才能推送到組織)。例如,當我們推送到 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}

BLEU 分數達到 39,還算不錯,這反映了我們的模型已經很擅長將英語句子翻譯成法語句子。

接下來是訓練,這也需要一些時間:

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 還會草擬一份包含所有評估結果的模型卡片並將其上傳。這份模型卡片包含元資料,有助於模型 Hub 選擇用於推理演示的 widget。通常,無需進行任何說明,因為它可以透過模型類別推斷出正確的 widget,但在這種情況下,相同的模型類別可以用於各種序列到序列問題,因此我們指定它是一個翻譯模型:

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

此命令返回它剛剛提交的 URL,如果您想檢查它:

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

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

如果您想更深入地瞭解訓練迴圈,我們現在將向您展示如何使用 🤗 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 torch.optim 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 分類一樣,兩個程序可能已將輸入和標籤填充到不同的形狀,因此我們使用 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 處檢視我們使用此程式碼訓練的模型。如果您想測試訓練迴圈的任何調整,可以直接透過編輯上面顯示的程式碼來實現!

使用微調模型

我們已經向您展示瞭如何在模型 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 上更新

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