並獲得增強型文件體驗
開始使用
令牌分類
我們將要探索的第一個應用是令牌分類。這個通用任務涵蓋了任何可以表述為“將標籤分配給句子中的每個令牌”的問題,例如
- 命名實體識別 (NER):找到句子中的實體(例如人、地點或組織)。這可以透過為每個令牌分配一個標籤來表述,方法是為每個實體設定一個類別,併為“無實體”設定一個類別。
- 詞性標註 (POS):將句子中的每個詞標記為對應於特定的詞性(例如名詞、動詞、形容詞等)。
- 分塊:找到屬於同一實體的令牌。這個任務(可以與 POS 或 NER 結合)可以表述為為任何位於塊開頭的令牌分配一個標籤(通常為
B-),為位於塊內部的令牌分配另一個標籤(通常為I-),並將第三個標籤(通常為O)分配給不屬於任何塊的令牌。
當然,還有許多其他型別的令牌分類問題;這些只是幾個代表性的例子。在本節中,我們將對 NER 任務微調一個模型 (BERT),然後它將能夠計算像這樣預測
您可以在 Hub 中找到我們將訓練並上傳的模型,並仔細檢查其預測 這裡.
準備資料
首先,我們需要一個適合令牌分類的資料集。在本節中,我們將使用 CoNLL-2003 資料集,其中包含來自路透社的新聞報道。
💡 只要您的資料集包含分成單詞及其對應標籤的文字,您就可以將此處描述的資料處理程式調整到您自己的資料集。如果您需要複習如何在 Dataset 中載入自己的自定義資料,請參考 第 5 章。
CoNLL-2003 資料集
要載入 CoNLL-2003 資料集,我們使用 🤗 Datasets 庫中的 load_dataset() 方法
from datasets import load_dataset
raw_datasets = load_dataset("conll2003")這將下載並快取資料集,就像我們在 第 3 章 中針對 GLUE MRPC 資料集所做的那樣。檢查此物件顯示了存在的列以及訓練集、驗證集和測試集之間的劃分
raw_datasets
DatasetDict({
train: Dataset({
features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'],
num_rows: 14041
})
validation: Dataset({
features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'],
num_rows: 3250
})
test: Dataset({
features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'],
num_rows: 3453
})
})特別是,我們可以看到資料集包含我們之前提到的三個任務的標籤:NER、POS 和分塊。與其他資料集的一個重大區別是,輸入文字不是作為句子或文件呈現的,而是作為單詞列表(最後一列稱為 tokens,但它包含單詞,因為這些是預分詞的輸入,仍然需要透過分詞器進行子詞分詞)。
讓我們看一下訓練集的第一個元素
raw_datasets["train"][0]["tokens"]['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.']由於我們要執行命名實體識別,我們將檢視 NER 標籤
raw_datasets["train"][0]["ner_tags"][3, 0, 7, 0, 0, 0, 7, 0, 0]這些是用於訓練的整數標籤,但當我們要檢查資料時,它們不一定有用。就像文字分類一樣,我們可以透過檢視資料集的 features 屬性來訪問這些整數與標籤名稱之間的對應關係
ner_feature = raw_datasets["train"].features["ner_tags"]
ner_featureSequence(feature=ClassLabel(num_classes=9, names=['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'], names_file=None, id=None), length=-1, id=None)所以,此列包含作為 ClassLabel 序列的元素。序列元素的型別位於此 ner_feature 的 feature 屬性中,我們可以透過檢視該 feature 的 names 屬性來訪問名稱列表
label_names = ner_feature.feature.names label_names
['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC']我們在 第 6 章 中深入研究 token-classification 管道時已經看到了這些標籤,但為了快速複習
O表示該詞不對應於任何實體。B-PER/I-PER表示該詞對應於人實體的開頭/位於人實體內部。B-ORG/I-ORG表示該詞對應於組織實體的開頭/位於組織實體內部。B-LOC/I-LOC表示該詞對應於位置實體的開頭/位於位置實體內部。B-MISC/I-MISC表示該詞對應於其他實體的開頭/位於其他實體內部。
現在,解碼我們之前看到的標籤,我們得到以下結果
words = raw_datasets["train"][0]["tokens"]
labels = raw_datasets["train"][0]["ner_tags"]
line1 = ""
line2 = ""
for word, label in zip(words, labels):
full_label = label_names[label]
max_length = max(len(word), len(full_label))
line1 += word + " " * (max_length - len(word) + 1)
line2 += full_label + " " * (max_length - len(full_label) + 1)
print(line1)
print(line2)'EU rejects German call to boycott British lamb .'
'B-ORG O B-MISC O O O B-MISC O O'對於混合 B- 和 I- 標籤的示例,以下是同一程式碼對訓練集索引為 4 的元素返回的結果
'Germany \'s representative to the European Union \'s veterinary committee Werner Zwingmann said on Wednesday consumers should buy sheepmeat from countries other than Britain until the scientific advice was clearer .'
'B-LOC O O O O B-ORG I-ORG O O O B-PER I-PER O O O O O O O O O O O B-LOC O O O O O O O'我們可以看到,跨越兩個詞的實體(例如“歐洲聯盟”和“Werner Zwingmann”)將第一個詞分配為 B- 標籤,將第二個詞分配為 I- 標籤。
✏️ 您的回合! 列印具有 POS 或分塊標籤的相同兩個句子。
處理資料
像往常一樣,我們的文字需要在模型能夠理解之前轉換為標記 ID。正如我們在第 6 章中所見,標記分類任務的一個重大區別是,我們擁有預標記的輸入。幸運的是,標記器 API 可以輕鬆處理這個問題;我們只需要使用一個特殊的標誌來警告 tokenizer。
首先,讓我們建立我們的 tokenizer 物件。正如我們之前所說,我們將使用 BERT 預訓練模型,因此我們將從下載和快取相關的標記器開始
from transformers import AutoTokenizer
model_checkpoint = "bert-base-cased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)你可以用Hub中你喜歡的任何其他模型替換 model_checkpoint,或者用你儲存了預訓練模型和標記器的本地資料夾替換它。唯一的約束是標記器需要由 🤗 Tokenizers 庫支援,因此有一個“快速”版本可用。你可以在這個大表中看到所有帶快速版本的架構,並且要檢查你使用的 tokenizer 物件是否確實由 🤗 Tokenizers 支援,你可以檢視它的 is_fast 屬性
tokenizer.is_fast
True為了標記預標記的輸入,我們可以像往常一樣使用我們的 tokenizer,並且只需新增 is_split_into_words=True
inputs = tokenizer(raw_datasets["train"][0]["tokens"], is_split_into_words=True)
inputs.tokens()['[CLS]', 'EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'la', '##mb', '.', '[SEP]']正如我們所見,標記器添加了模型使用的特殊標記(開頭是 [CLS],結尾是 [SEP]),並且大多數單詞保持不變。然而,單詞 lamb 被標記為兩個子詞,la 和 ##mb。這會在我們的輸入和標籤之間引入不匹配:標籤列表只有 9 個元素,而我們的輸入現在有 12 個標記。考慮到特殊標記很簡單(我們知道它們在開頭和結尾),但我們還需要確保將所有標籤與相應的單詞對齊。
幸運的是,由於我們使用的是快速標記器,因此我們可以使用 🤗 Tokenizers 的強大功能,這意味著我們可以輕鬆地將每個標記對映到其相應的單詞(如第 6 章中所示)
inputs.word_ids()
[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None]經過一些小的修改,我們可以擴充套件標籤列表以匹配標記。我們將應用的第一個規則是,特殊標記的標籤為 -100。這是因為預設情況下 -100 是一個在我們將使用的損失函式(交叉熵)中被忽略的索引。然後,每個標記都與啟動它所在的單詞的標記具有相同的標籤,因為它們屬於同一個實體。對於單詞內部但不在開頭的標記,我們將 B- 替換為 I-(因為標記不是實體的開頭)
def align_labels_with_tokens(labels, word_ids):
new_labels = []
current_word = None
for word_id in word_ids:
if word_id != current_word:
# Start of a new word!
current_word = word_id
label = -100 if word_id is None else labels[word_id]
new_labels.append(label)
elif word_id is None:
# Special token
new_labels.append(-100)
else:
# Same word as previous token
label = labels[word_id]
# If the label is B-XXX we change it to I-XXX
if label % 2 == 1:
label += 1
new_labels.append(label)
return new_labels讓我們在第一句話上試試看
labels = raw_datasets["train"][0]["ner_tags"]
word_ids = inputs.word_ids()
print(labels)
print(align_labels_with_tokens(labels, word_ids))[3, 0, 7, 0, 0, 0, 7, 0, 0]
[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100]正如我們所見,我們的函式為開頭和結尾的兩個特殊標記添加了 -100,並且為被分成兩個標記的單詞添加了一個新的 0。
✏️ 輪到你了!一些研究人員更喜歡為每個單詞只分配一個標籤,並將 -100 分配給給定單詞中的其他子標記。這樣做是為了避免分成許多子標記的長單詞對損失產生過大的影響。更改前面的函式,按照此規則將標籤與輸入 ID 對齊。
為了預處理整個資料集,我們需要標記所有輸入,並在所有標籤上應用 align_labels_with_tokens()。為了利用快速標記器的速度優勢,最好同時標記大量文字,因此我們將編寫一個處理示例列表的函式,並使用 Dataset.map() 方法,選項為 batched=True。唯一與之前的示例不同的是,word_ids() 函式需要獲取我們需要單詞 ID 的示例的索引,當標記器的輸入是文字列表(或者在我們的例子中,是單詞列表的列表)時,所以我們也添加了這個
def tokenize_and_align_labels(examples):
tokenized_inputs = tokenizer(
examples["tokens"], truncation=True, is_split_into_words=True
)
all_labels = examples["ner_tags"]
new_labels = []
for i, labels in enumerate(all_labels):
word_ids = tokenized_inputs.word_ids(i)
new_labels.append(align_labels_with_tokens(labels, word_ids))
tokenized_inputs["labels"] = new_labels
return tokenized_inputs請注意,我們還沒有填充輸入;我們將在稍後建立批次時使用資料整理器進行填充。
我們現在可以將所有預處理一步到位地應用到資料集的其他拆分上
tokenized_datasets = raw_datasets.map(
tokenize_and_align_labels,
batched=True,
remove_columns=raw_datasets["train"].column_names,
)我們已經完成了最困難的部分!現在資料已經過預處理,實際的訓練將與我們在第 3 章中所做的非常相似。
使用訓練器 API 微調模型
使用 Trainer 的實際程式碼與之前相同;唯一的變化是資料被整理成批次的方式和指標計算函式。
資料整理
我們不能像在第 3 章中那樣簡單地使用 DataCollatorWithPadding,因為這隻會填充輸入(輸入 ID、注意力掩碼和標記型別 ID)。這裡我們的標籤應該以與輸入完全相同的方式填充,以便它們保持相同的大小,使用 -100 作為值,以便相應的預測在損失計算中被忽略。
這一切都由DataCollatorForTokenClassification 完成。與 DataCollatorWithPadding 相似,它需要用於預處理輸入的 tokenizer
from transformers import DataCollatorForTokenClassification
data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer)為了在一些樣本上測試這一點,我們只需在標記化的訓練集的示例列表上呼叫它
batch = data_collator([tokenized_datasets["train"][i] for i in range(2)])
batch["labels"]tensor([[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100],
[-100, 1, 2, -100, -100, -100, -100, -100, -100, -100, -100, -100]])讓我們將這與資料集中的第一個和第二個元素的標籤進行比較
for i in range(2):
print(tokenized_datasets["train"][i]["labels"])[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100]
[-100, 1, 2, -100]正如我們所見,第二組標籤已經使用 -100 填充到第一個標籤的長度。
指標
為了讓 Trainer 在每個 epoch 計算指標,我們需要定義一個 compute_metrics() 函式,它接收預測和標籤陣列,並返回一個包含指標名稱和值的字典。
用於評估標記分類預測的傳統框架是seqeval。要使用此指標,我們首先需要安裝 seqeval 庫
!pip install seqeval
然後我們可以像我們在第 3 章中所做的那樣,透過 evaluate.load() 函式載入它
import evaluate
metric = evaluate.load("seqeval")此指標的行為不像標準精度:它實際上將接受標籤列表作為字串,而不是整數,因此我們需要在將它們傳遞給指標之前完全解碼預測和標籤。讓我們看看它是如何工作的。首先,我們將獲取第一個訓練示例的標籤
labels = raw_datasets["train"][0]["ner_tags"]
labels = [label_names[i] for i in labels]
labels['B-ORG', 'O', 'B-MISC', 'O', 'O', 'O', 'B-MISC', 'O', 'O']然後,我們可以透過只更改索引 2 處的值來建立這些值的偽預測
predictions = labels.copy()
predictions[2] = "O"
metric.compute(predictions=[predictions], references=[labels])請注意,指標接受預測列表(不僅僅是單個預測)和標籤列表。以下是輸出
{'MISC': {'precision': 1.0, 'recall': 0.5, 'f1': 0.67, 'number': 2},
'ORG': {'precision': 1.0, 'recall': 1.0, 'f1': 1.0, 'number': 1},
'overall_precision': 1.0,
'overall_recall': 0.67,
'overall_f1': 0.8,
'overall_accuracy': 0.89}這將返回很多資訊!我們獲得了每個單獨實體的精度、召回率和 F1 分數,以及整體分數。對於我們的指標計算,我們只保留整體分數,但你可以隨意調整 compute_metrics() 函式以返回你想要報告的所有指標。
此 compute_metrics() 函式首先獲取 logits 的 argmax 以將其轉換為預測(像往常一樣,logits 和機率順序相同,因此我們不需要應用 softmax)。然後,我們必須將標籤和預測從整數轉換為字串。我們刪除所有標籤為 -100 的值,然後將結果傳遞給 metric.compute() 方法
import numpy as np
def compute_metrics(eval_preds):
logits, labels = eval_preds
predictions = np.argmax(logits, axis=-1)
# Remove ignored index (special tokens) and convert to labels
true_labels = [[label_names[l] for l in label if l != -100] for label in labels]
true_predictions = [
[label_names[p] for (p, l) in zip(prediction, label) if l != -100]
for prediction, label in zip(predictions, labels)
]
all_metrics = metric.compute(predictions=true_predictions, references=true_labels)
return {
"precision": all_metrics["overall_precision"],
"recall": all_metrics["overall_recall"],
"f1": all_metrics["overall_f1"],
"accuracy": all_metrics["overall_accuracy"],
}現在,這已經完成,我們幾乎準備好定義我們的 Trainer 了。我們只需要一個要微調的 model!
定義模型
由於我們正在處理標記分類問題,我們將使用AutoModelForTokenClassification類。在定義此模型時,要記住的最重要的事情是傳遞有關我們擁有的標籤數量的資訊。最簡單的方法是用num_labels引數傳遞該數字,但如果我們想要一個像本節開頭看到的那樣工作的不錯的推理小部件,最好設定正確的標籤對應關係。
它們應該由兩個字典id2label和label2id設定,它們包含從 ID 到標籤的對映以及反之亦然。
id2label = {i: label for i, label in enumerate(label_names)}
label2id = {v: k for k, v in id2label.items()}現在我們可以直接將它們傳遞給AutoModelForTokenClassification.from_pretrained()方法,它們將被設定在模型的配置中,然後正確地儲存並上傳到 Hub。
from transformers import AutoModelForTokenClassification
model = AutoModelForTokenClassification.from_pretrained(
model_checkpoint,
id2label=id2label,
label2id=label2id,
)就像我們在第三章中定義AutoModelForSequenceClassification一樣,建立模型會發出警告,表明某些權重沒有被使用(來自預訓練頭的權重),而其他一些權重是隨機初始化的(來自新的標記分類頭),並且此模型應該進行訓練。我們將在稍後進行訓練,但首先讓我們仔細檢查一下我們的模型是否具有正確的標籤數量。
model.config.num_labels
9⚠️ 如果你的模型具有錯誤的標籤數量,則在稍後呼叫Trainer.train()方法時會遇到一個模糊的錯誤(類似於“CUDA error: device-side assert triggered”)。這是使用者報告此類錯誤的 bug 的首要原因,所以請確保你進行此檢查以確認你擁有預期的標籤數量。
微調模型
我們現在準備訓練我們的模型!在定義我們的Trainer之前,我們只需要做兩件事:登入 Hugging Face 並定義我們的訓練引數。如果你在筆記本中工作,有一個便捷函式可以幫助你完成此操作。
from huggingface_hub import notebook_login
notebook_login()這將顯示一個小部件,你可以在其中輸入你的 Hugging Face 登入憑據。
如果你沒有在筆記本中工作,只需在終端中鍵入以下命令列。
huggingface-cli login
完成此操作後,我們可以定義我們的TrainingArguments。
from transformers import TrainingArguments
args = TrainingArguments(
"bert-finetuned-ner",
evaluation_strategy="epoch",
save_strategy="epoch",
learning_rate=2e-5,
num_train_epochs=3,
weight_decay=0.01,
push_to_hub=True,
)你之前已經看到過其中大部分內容:我們設定了一些超引數(例如學習率、要訓練的 epoch 數量和權重衰減),並且指定了push_to_hub=True來表明我們希望在每個 epoch 結束時儲存模型並對其進行評估,並且我們希望將結果上傳到模型中心。請注意,你可以使用hub_model_id引數指定要推送到其中的儲存庫的名稱(特別是,你必須使用此引數推送到組織)。例如,當我們將模型推送到huggingface-course 組織時,我們在TrainingArguments中添加了hub_model_id="huggingface-course/bert-finetuned-ner"。預設情況下,使用的儲存庫將位於你的名稱空間中,並以你設定的輸出目錄命名,因此在我們的例子中它將是"sgugger/bert-finetuned-ner"。
💡 如果要使用的輸出目錄已經存在,它需要是你要推送到其中的儲存庫的本地克隆。如果不是,你將在定義Trainer時遇到錯誤,並且需要設定一個新名稱。
最後,我們只需要將所有內容傳遞給Trainer並啟動訓練。
from transformers import Trainer
trainer = Trainer(
model=model,
args=args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["validation"],
data_collator=data_collator,
compute_metrics=compute_metrics,
tokenizer=tokenizer,
)
trainer.train()請注意,在訓練過程中,每次儲存模型(這裡,每個 epoch)時,它都會在後臺上傳到 Hub。這樣,如果需要,你可以在另一臺機器上恢復訓練。
訓練完成後,我們使用push_to_hub()方法確保我們上傳了模型的最新版本。
trainer.push_to_hub(commit_message="Training complete")如果要檢查它,此命令將返回剛剛執行的提交的 URL。
'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed'Trainer還會草擬包含所有評估結果的模型卡並將其上傳。在此階段,你可以使用模型中心上的推理小部件測試你的模型並與你的朋友分享。你已經成功地針對標記分類任務微調了一個模型——恭喜!
如果你想更深入地瞭解訓練迴圈,我們現在將向你展示如何使用 🤗 Accelerate 做同樣的事情。
自定義訓練迴圈
現在讓我們看看完整的訓練迴圈,這樣你就可以輕鬆地自定義你需要的部分。它看起來很像我們在第三章中所做的事情,評估部分有一些變化。
準備所有內容進行訓練
首先,我們需要從我們的資料集構建DataLoader。我們將重用我們的data_collator作為collate_fn並打亂訓練集,但不打亂驗證集。
from torch.utils.data import DataLoader
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
)接下來,我們將重新例項化我們的模型,以確保我們不是從之前的微調繼續,而是再次從 BERT 預訓練模型開始。
model = AutoModelForTokenClassification.from_pretrained(
model_checkpoint,
id2label=id2label,
label2id=label2id,
)然後我們將需要一個最佳化器。我們將使用經典的AdamW,它類似於Adam,但權重衰減的應用方式有所修復。
from torch.optim import AdamW
optimizer = AdamW(model.parameters(), lr=2e-5)擁有所有這些物件後,我們可以將它們傳送到accelerator.prepare()方法。
from accelerate import Accelerator
accelerator = Accelerator()
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
model, optimizer, train_dataloader, eval_dataloader
)🚨 如果你在 TPU 上進行訓練,你需要將從上面單元格開始的所有程式碼移入一個專用的訓練函式。有關更多詳細資訊,請參見第三章。
現在我們已經將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,如果你還沒有登入。我們將從我們想要給模型的模型 ID 中確定儲存庫名稱(你可以隨意用自己的選擇替換repo_name;它只需要包含你的使用者名稱,這就是get_full_repo_name()函式所做的事情)。
from huggingface_hub import Repository, get_full_repo_name
model_name = "bert-finetuned-ner-accelerate"
repo_name = get_full_repo_name(model_name)
repo_name'sgugger/bert-finetuned-ner-accelerate'然後,我們可以在本地資料夾中克隆該儲存庫。如果它已經存在,此本地資料夾應該是我們正在使用的儲存庫的現有克隆。
output_dir = "bert-finetuned-ner-accelerate"
repo = Repository(output_dir, clone_from=repo_name)我們現在可以透過呼叫repo.push_to_hub()方法上傳我們在output_dir中儲存的任何內容。這將有助於我們在每個 epoch 結束時上傳中間模型。
訓練迴圈
我們現在準備編寫完整的訓練迴圈。為了簡化它的評估部分,我們定義了這個postprocess()函式,它接收預測和標籤,並將它們轉換為字串列表,就像我們的metric物件所期望的那樣。
def postprocess(predictions, labels):
predictions = predictions.detach().cpu().clone().numpy()
labels = labels.detach().cpu().clone().numpy()
# Remove ignored index (special tokens) and convert to labels
true_labels = [[label_names[l] for l in label if l != -100] for label in labels]
true_predictions = [
[label_names[p] for (p, l) in zip(prediction, label) if l != -100]
for prediction, label in zip(predictions, labels)
]
return true_labels, true_predictions然後我們可以編寫訓練迴圈。在定義一個進度條來跟蹤訓練過程後,迴圈有三個部分。
- 訓練本身,它是在
train_dataloader上進行經典的迭代,透過模型進行正向傳播,然後進行反向傳播和最佳化器步驟。 - 評估,其中在獲取我們模型在批次上的輸出後出現了一個新穎之處:由於兩個程序可能將輸入和標籤填充到了不同的形狀,因此我們需要使用
accelerator.pad_across_processes()使預測和標籤在呼叫gather()方法之前具有相同的形狀。如果我們不這樣做,評估將要麼出錯,要麼永遠掛起。然後,我們將結果傳送到metric.add_batch()並在評估迴圈結束後呼叫metric.compute()。 - 儲存和上傳,我們首先儲存模型和標記器,然後呼叫
repo.push_to_hub()。請注意,我們使用引數blocking=False告訴 🤗 Hub 庫以非同步程序進行推送。這樣,訓練可以正常繼續,而此(長時間)指令在後臺執行。
以下是訓練迴圈的完整程式碼。
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 eval_dataloader:
with torch.no_grad():
outputs = model(**batch)
predictions = outputs.logits.argmax(dim=-1)
labels = batch["labels"]
# Necessary to pad predictions and labels for being gathered
predictions = accelerator.pad_across_processes(predictions, dim=1, pad_index=-100)
labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100)
predictions_gathered = accelerator.gather(predictions)
labels_gathered = accelerator.gather(labels)
true_predictions, true_labels = postprocess(predictions_gathered, labels_gathered)
metric.add_batch(predictions=true_predictions, references=true_labels)
results = metric.compute()
print(
f"epoch {epoch}:",
{
key: results[f"overall_{key}"]
for key in ["precision", "recall", "f1", "accuracy"]
},
)
# 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
)如果這是你第一次看到用 🤗 Accelerate 儲存的模型,讓我們花點時間檢查與它相關的三行程式碼。
accelerator.wait_for_everyone() unwrapped_model = accelerator.unwrap_model(model) unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
第一行是不言自明的:它告訴所有程序在繼續之前等待每個人都到達該階段。這樣做是為了確保我們在儲存之前在每個程序中擁有相同的模型。然後,我們獲取unwrapped_model,它是我們定義的基本模型。accelerator.prepare()方法會更改模型以在分散式訓練中工作,因此它將不再具有save_pretrained()方法;accelerator.unwrap_model()方法會撤消該步驟。最後,我們呼叫save_pretrained(),但告訴該方法使用accelerator.save()而不是torch.save()。
完成此操作後,你應該擁有一個模型,它的結果與使用Trainer訓練的模型非常相似。你可以在huggingface-course/bert-finetuned-ner-accelerate上檢視我們使用此程式碼訓練的模型。如果你想測試對訓練迴圈的任何調整,你可以直接透過編輯上面顯示的程式碼來實現它們!
使用微調模型
我們已經向您展示瞭如何使用我們在模型中心上微調的模型以及推斷小部件。為了在 `pipeline` 中本地使用它,您只需指定正確的模型識別符號
from transformers import pipeline
# Replace this with your own checkpoint
model_checkpoint = "huggingface-course/bert-finetuned-ner"
token_classifier = pipeline(
"token-classification", model=model_checkpoint, aggregation_strategy="simple"
)
token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.")[{'entity_group': 'PER', 'score': 0.9988506, 'word': 'Sylvain', 'start': 11, 'end': 18},
{'entity_group': 'ORG', 'score': 0.9647625, 'word': 'Hugging Face', 'start': 33, 'end': 45},
{'entity_group': 'LOC', 'score': 0.9986118, 'word': 'Brooklyn', 'start': 49, 'end': 57}]太好了!我們的模型與預設模型一樣有效地工作!