隨著深度學習技術的發展,預訓練語言模型在自然語言處理領域取得了顯著的成果。然而,如何將這些預訓練模型應用於特定任務,例如文字分類別,仍然是一個需要深入研究的課題。本文將重點探討如何透過微調技術,將預訓練語言模型 DistilBERT 應用於文字分類別任務,並涵蓋資料預處理、模型選擇、訓練流程、評估指標等關鍵步驟。同時,我們也將探討如何利用 Hugging Face Transformers 函式庫簡化模型訓練和佈署流程,以及如何分析評估結果並進行模型分享。對於需要在有限計算資源下進行模型訓練的場景,本文也將介紹一些引數高效微調和計算資源最佳化技術。
本章目標
本章的目標是建立對語言模型微調的堅實基礎。為此,我們將涵蓋以下主題:
- 使用微調後的編碼器模型對文字進行分類別
- 瞭解編碼器基礎模型在現代語言模型中的角色
- 使用解碼器模型生成特定風格的文字
- 透過指令微調解決多個任務
- 引入引數高效微調技術,以便在較小的GPU上訓練模型
- 減少計算資源的技術,以便在推理時使用較少的計算資源
文字分類別
在深入探討生成模型之前,瞭解微調預訓練模型的一般流程是很有必要的。序列分類別是一個經典的機器學習問題,涉及將類別賦予給定輸入。這可以應用於垃圾郵件檢測、情感識別、意圖分類別和偽造內容檢測等挑戰。
微調流程
- 識別資料集:為了適應語言模型以進行文字分類別,我們需要一個標記資料集。
- 定義模型型別:根據任務需求選擇編碼器、解碼器或編碼器-解碼器模型。
- 選擇基礎模型:選擇一個符合需求的預訓練模型。
- 預處理資料集:根據模型需求對資料進行預處理。
- 定義評估指標:選擇適合任務的評估指標。
- 訓練模型:使用標記資料集對模型進行微調。
- 分享結果:分享微調後的模型和結果。
資料集探索
使用Hugging Face Datasets等平臺可以找到合適的公共資料集。AG News資料集是一個常用的非商業資料集,用於文字分類別模型的基準測試和資料探勘、資訊檢索和資料流等領域的研究。
程式碼實作
from datasets import load_dataset
raw_datasets = load_dataset("fancyzhx/ag_news")
print(raw_datasets)
這段程式碼載入AG News資料集,並展示其結構,包括文字和標籤兩個欄位,共有120,000個訓練樣本。
選擇合適的模型型別
在進行新聞文章主題分類別任務時,我們需要選擇一個合適的模型型別。根據前面的章節,我們可以使用三種型別的變換器(Transformers),具體取決於我們要解決的任務型別。
編碼器模型(Encoder Models)
這類別模型能夠從輸入序列中獲得豐富的語義表示,捕捉輸入的含義,可以用於各種依賴於輸入語義資訊的任務(例如,識別文字中的實體或分類別序列)。在這些嵌入之上新增一個小型網路,可以用於訓練特定的下游任務。
解碼器模型(Decoder Models)
這類別模型設計用於生成新的序列,例如文字。它們接受輸入(通常是嵌入或上下文),並產生連貫的輸出序列,使其適合於文字生成任務。
編碼器-解碼器模型(Encoder-Decoder Models)
這類別模型非常適合於需要將輸入序列轉換為不同的輸出序列的任務,例如機器翻譯或摘要。編碼器處理輸入,而解碼器生成相應的輸出。
考慮到新聞文章主題分類別任務,我們有三種可能的方法:
零或幾次學習(Zero or Few-Shot Learning):使用高品質的預訓練模型,解釋任務(例如,“分類別為這四個類別”),然後讓模型完成剩餘的工作。這種方法不需要任何微調,現在在強大的預訓練模型中很常見——單個模型可以透過簡單的提示解決許多工。
文字生成模型:微調一個文字生成模型,以生成標籤(例如,“商業”)給定一個新聞文章。這裡我們可以使用解碼器或編碼器-解碼器模型。
編碼器模型與分類別頭:使用編碼器模型,並在其上新增一個分類別頭來進行新聞文章主題分類別。這種方法直接利用編碼器的語義表示進行分類別。
每種方法都有其優點和缺點,選擇哪種方法取決於具體的任務需求和可用的資源。接下來,我們將更深入地探討如何實作這些方法,並比較其優缺點。
選擇合適的基礎模型
在進行文字分類別任務時,選擇一個合適的基礎模型是非常重要的。這個模型應該具備以下特點:根據編碼器的架構、足夠小以便在幾分鐘內在GPU上進行微調、具有良好的預訓練結果、能夠處理短序列文字。
BERT是一個很好的基礎編碼器架構,但考慮到我們想要快速訓練並且計算資源有限,使用DistilBERT會是一個更好的選擇。DistilBERT比BERT小40%,速度快60%,同時保留了97%的BERT能力。根據這個基礎模型,我們可以對其進行微調,以適應多種下游任務,如回答問題或分類別文字。
除了原始的BERT和DistilBERT外,還有許多其他模型可以用作基礎模型,例如RoBERTa、ALBERT、Electra、DeBERTa、Longformer、LuKE、MobileBERT和Reformer。每個模型都有其自己的訓練程式,並在原始BERT模型的基礎上進行了改進。選擇哪個模型取決於您的具體需求,但使用DistilBERT作為起點是合理的,尤其是考慮到計算資源的限制。
預處理資料集
如第2章所述,每個語言模型都有其自己的分詞器。要對DistilBERT進行微調,我們必須確保整個資料集都是使用與預訓練模型相同的分詞器進行分詞的。我們可以使用AutoTokenizer載入適當的分詞器,然後定義一個函式來分詞批次樣本。Transformers函式庫要求批次中的所有輸入都具有相同的長度,因此透過設定padding=True
,我們可以在樣本中新增零,使其長度相同。
需要注意的是,變換器模型有一個最大上下文大小限制,即語言模型在進行預測時可以使用的最大令牌數。對於DistilBERT,這個限制是512個令牌,因此不應該嘗試使用它來處理整本章。幸運的是,大多數樣本都是小摘要,但有些可能仍然超過這個令牌限制。為了處理這種情況,可以使用truncation=True
,它將截斷所有樣本以適應模型的上下文長度。然而,這種方法有一個權衡:截斷文字意味著一些可能有用的資訊可能會丟失。
處理變換器的長上下文是一個活躍的研究領域。對於涉及編碼器基礎模型和長上下文的情況,可以嘗試幾種策略:
- 使用專門的長上下文變換器模型,例如Longformer。
- 將文字分成較小的段落並分別處理。
- 使用滑動視窗方法以塊為單位處理文字。
- 在預處理步驟中總結文字,然後將總結輸入模型。
選擇哪種策略取決於任務和模型。例如,如果您想分析一本章的情感,可以使用塊處理並分別分析書的不同部分。如果您想分類別一篇長文章的主題,可以總結文章然後分類別總結。
雖然模型帶有預設的上下文長度,但透過技術如旋轉嵌入,可以使用更長甚至無限的上下文長度。稍後我們將更深入地探討這些技術。
實作分詞功能
from transformers import AutoTokenizer
checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
def tokenize_function(batch):
return tokenizer(
batch["text"],
truncation=True,
padding=True,
return_tensors="pt"
)
# 測試分詞功能
tokenize_function(raw_train_dataset[:2])
內容解密:
在這段程式碼中,我們首先從Transformers函式庫中匯入AutoTokenizer
類別。然後,我們指定基礎模型為distilbert-base-uncased
,並使用from_pretrained
方法載入相應的分詞器。定義了tokenize_function
函式後,我們可以使用它來對批次樣本進行分詞。函式中,我們呼叫tokenizer
方法,並傳入批次樣本的文字、設定truncation=True
以截斷超過最大上下文長度的樣本、設定padding=True
以確保所有樣本具有相同的長度,以及指定傳回張量的格式為PyTorch(“pt”)。
最後,我們測試了tokenize_function
,傳入了前兩個樣本進行分詞,並觀察了輸出的結果。輸出結果包括了注意力遮罩(attention_mask)和輸入ID(input_ids),它們將被用於後續的模型訓練和預測過程中。
圖表翻譯:
graph LR A[批次樣本] -->|傳入|> B[分詞器] B --> C[截斷和填充] C --> D[輸出張量] D --> E[模型訓練]
在這個流程圖中,我們展示了從批次樣本到輸出張量的過程。首先,批次樣本被傳入分詞器,然後經過截斷和填充處理,以確保所有樣本具有相同的長度。最後,處理後的樣本被轉換為PyTorch張量,並輸出給後續的模型訓練過程。
文字分類別與DistilBERT模型
在自然語言處理(NLP)中,文字分類別是一項基本任務,涉及將給定的文字分配到預先定義的類別中。為了完成這項任務,我們可以使用像DistilBERT這樣的預訓練語言模型。DistilBERT是一種根據BERT(Bidirectional Encoder Representations from Transformers)的模型,但它的引數量更少,計算效率更高。
文字分類別流程
文字預處理:首先,我們需要對文字資料進行預處理。這通常包括將文字轉換為小寫、移除特殊字元和標點符號、以及將文字分詞(tokenize)。
文字分詞:分詞是指將文字拆分成個別的單詞或子詞(subword)。這一步驟對於模型理解文字至關重要。
建立資料集:在進行文字分類別之前,我們需要建立一個包含標籤的資料集。每個文字樣本都應該有一個對應的標籤,表示它屬於哪一類別。
模型選擇和訓練:選擇合適的模型是非常重要的。DistilBERT是一個不錯的選擇,因為它在效率和效能之間取得了良好的平衡。一旦選擇了模型,就需要對其進行訓練。
DistilBERT文字分類別實作
以下是使用DistilBERT進行文字分類別的簡單示例:
from transformers import DistilBertTokenizer, DistilBertForSequenceClassification
import torch
# 載入預訓練的DistilBERT模型和分詞器
tokenizer = DistilBertTokenizer.from_pretrained('distilbert-base-uncased')
model = DistilBertForSequenceClassification.from_pretrained('distilbert-base-uncased', num_labels=8)
# 定義一個簡單的函式來對單個文字樣本進行分類別
def classify_text(text):
inputs = tokenizer(text, return_tensors='pt')
outputs = model(**inputs)
logits = outputs.logits
return torch.argmax(logits)
# 測試分類別函式
text = "This is a sample text for classification."
label = classify_text(text)
print(f"分類別結果:{label}")
資料集建立和批次處理
在實際應用中,我們通常需要處理大量的文字資料。為了提高效率,我們可以使用批次處理的方式。以下示例展示瞭如何建立一個簡單的資料集並對其進行批次處理:
from transformers import Dataset, DatasetDict
from torch.utils.data import DataLoader
# 假設我們已經有了文字資料和對應的標籤
texts = ["text1", "text2", "text3"]
labels = [0, 1, 0]
# 建立資料集
dataset = Dataset.from_dict({"text": texts, "label": labels})
# 對資料集進行批次處理
batch_size = 16
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
# 使用批次處理的資料進行模型訓練或評估
for batch in dataloader:
# 對批次資料進行處理
inputs = tokenizer(batch["text"], return_tensors='pt', padding=True, truncation=True)
labels = torch.tensor(batch["label"])
# 對模型輸出進行處理
outputs = model(**inputs, labels=labels)
loss = outputs.loss
# 列印批次損失
print(f"批次損失:{loss.item()}")
評估指標定義
除了監控訓練過程中的損失外,定義一些下游指標來評估和監控模型的效能也是非常重要的。這裡,我們將使用 evaluate
函式庫,這是一個方便的工具,具有標準化的介面,適用於各種指標。指標的選擇取決於任務。對於序列分類別,合適的候選指標包括:
精確度(Accuracy)
代表正確預測的比例,提供了模型整體效能的高階概覽。它適合於平衡的資料集,並且易於解釋。然而,在不平衡的資料集中,它可能會產生誤導。
精確率(Precision)
這是正確標記的正例例項與所有預測為正例的例項之比。它幫助我們瞭解模型對正例預測的準確性。當假陽性成本高時,例如在垃圾郵件檢測中,應該使用精確率。
召回率(Recall)
這個指標表示實際正例中被正確預測的比例。它反映了模型捕捉所有正例的能力,如果有許多假陰性,召回率將較低。當假陰性成本高時,例如在醫學診斷中,應該使用召回率。
F1 分數
F1 分數是精確率和召回率的調和平均,提供了一個平衡的衡量標準,考慮了假陽性和假陰性,並對精確率和召回率之間的強烈差異進行懲罰。F1 分數常用於不平衡的資料集,並且是分類別任務的一個良好的預設指標。
evaluate
函式庫中的指標提供了描述屬性和計算方法,以獲得給定標籤和模型預測的指標:
import evaluate
accuracy = evaluate.load("accuracy")
print(accuracy.description)
輸出:
('Accuracy is the proportion of correct predictions among the total '
'number of cases processed. It can be computed with:'
'Accuracy = (TP + TN) / (TP + TN + FP + FN)'
' Where:'
'TP: True positive'
'TN: True negative'
'FP: False positive'
'FN: False negative')
內容解密:
上述程式碼載入了 accuracy
指標,並列印了其描述。這個描述解釋了精確度的計算公式,即正確預測(真陽性和真陰性)的總數除以所有案例的總數。
圖表翻譯:
flowchart TD A[開始] --> B[載入 accuracy 指標] B --> C[列印指標描述] C --> D[計算精確度] D --> E[輸出結果]
此圖表展示了載入 accuracy
指標、列印其描述、計算精確度以及輸出結果的流程。
自然語言處理中的文字分類別
文字分類別是一種常見的自然語言處理任務,涉及將給定的文字分類別為預先定義的類別。這個過程需要一個能夠理解文字含義和語境的模型。
訓練模型
要訓練一個文字分類別模型,首先需要定義一個計算指標的函式,該函式可以根據預測結果和實際標籤計算準確率和 F1 分數。這個函式通常被稱為 compute_metrics
。
from sklearn.metrics import accuracy_score, f1_score
def compute_metrics(pred):
labels = pred.label_ids
preds = pred.predictions.argmax(-1)
# 計算準確率
acc = accuracy_score(labels, preds)
# 計算加權 F1 分數
f1 = f1_score(labels, preds, average="weighted")
return {"accuracy": acc, "f1": f1}
使用 DistilBERT 進行文字分類別
DistilBERT 是一個根據 BERT 的小型模型,相比原始的 BERT 模型,它的引數量更少,但仍然能夠保持相當的效能。要使用 DistilBERT 進行文字分類別,需要在模型的頂部新增一個分類別頭。這個分類別頭通常是一個簡單的神經網路層,負責根據輸入文字的嵌入產生類別機率。
from transformers import AutoModelForSequenceClassification
# 載入 DistilBERT 模型和分類別頭
model = AutoModelForSequenceClassification.from_pretrained("distilbert-base-uncased", num_labels=8)
訓練過程
在訓練過程中,模型會學習如何根據輸入文字產生正確的類別預測。這個過程涉及更新模型的所有引數,包括嵌入層和分類別頭,以使模型對特定任務的效能最佳化。
# 定義訓練引數
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
# 執行訓練
trainer = Trainer(model=model, args=training_args, train_dataset=train_dataset, eval_dataset=eval_dataset, compute_metrics=compute_metrics)
trainer.train()
結果評估
在訓練完成後,需要評估模型在測試集上的效能。這通常涉及計算模型在測試集上的準確率和 F1 分數,以判斷模型的泛化能力。
# 評估模型效能
test_results = trainer.evaluate()
print(test_results)
透過以上步驟,可以成功地使用 DistilBERT 進行文字分類別任務,並評估模型的效能。這個過程展示瞭如何使用預訓練模型進行特定任務的微調,並如何評估模型的效能。
使用 Transformers 進行文字分類別任務
在本文中,我們將使用 Hugging Face 的 Transformers 函式庫來進行文字分類別任務。首先,我們需要載入預訓練模型和分類別頭。
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer
# 載入預訓練模型和分類別頭
num_labels = 4
model = AutoModelForSequenceClassification.from_pretrained("distilbert-base-uncased", num_labels=num_labels)
tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")
接下來,我們需要定義訓練引數。這包括批次大小、訓練epoch數、評估策略等。
from transformers import TrainingArguments
# 定義訓練引數
batch_size = 32
training_args = TrainingArguments(
"classifier-chapter4",
push_to_hub=True,
num_train_epochs=2,
eval_strategy="epoch",
per_device_train_batch_size=batch_size,
per_device_eval_batch_size=batch_size,
)
然後,我們需要建立 Trainer 類別的例項。Trainer 類別負責管理訓練過程,包括計算指標、儲存模型等。
from transformers import Trainer
# 建立 Trainer 類別的例項
trainer = Trainer(
model=model,
args=training_args,
compute_metrics=lambda pred: {"accuracy": torch.sum(pred.label_ids == pred.predictions.argmax(-1))},
train_dataset=small_split,
eval_dataset=tokenized_datasets["test"],
tokenizer=tokenizer,
)
最後,我們可以開始訓練模型了。
# 開始訓練模型
trainer.train()
在訓練過程中,Trainer 會自動計算指標、儲存模型等。當訓練完成後,我們可以使用模型進行預測。
內容解密:
AutoModelForSequenceClassification
類別用於載入預訓練模型和分類別頭。TrainingArguments
類別用於定義訓練引數。Trainer
類別用於管理訓練過程。compute_metrics
引數用於定義計算指標的函式。train_dataset
和eval_dataset
引數用於指定訓練和評估資料集。tokenizer
引數用於指定使用的tokenizer。
圖表翻譯:
graph LR A[載入預訓練模型和分類別頭] --> B[定義訓練引數] B --> C[建立 Trainer 類別的例項] C --> D[開始訓練模型] D --> E[計算指標和儲存模型]
在上面的流程圖中,我們可以看到整個訓練過程的流程。首先,我們載入預訓練模型和分類別頭。然後,我們定義訓練引數。接下來,我們建立 Trainer 類別的例項。最後,我們開始訓練模型,並計算指標和儲存模型。
模型評估結果分析
在進行模型評估時,我們獲得了一系列指標以衡量模型的效能。評估損失(eval_loss)分別為0.2624和0.2433,評估準確率(eval_accuracy)分別為0.9117和0.9184,評估F1分數(eval_f1)分別為0.9118和0.9183。這些指標表明模型在評估集上的表現相當不錯,尤其是準確率和F1分數都接近92%。
執行時間和效率分析
除了模型的準確率和損失之外,我們還關注了評估過程的執行時間和效率。評估執行時間(eval_runtime)分別為15.2709和14.5161秒,評估每秒處理樣本數(eval_samples_per_second)分別為497.678和523.557,評估每秒步驟數(eval_steps_per_second)分別為15.585和16.396。這些資料表明模型在評估過程中的效率相當高,能夠快速地處理大量的樣本。
訓練過程分析
對於模型的訓練過程,我們也進行了相關的分析。訓練執行時間(train_runtime)為213.9327秒,訓練每秒處理樣本數(train_samples_per_second)為93.487,訓練每秒步驟數(train_steps_per_second)為2.926。訓練損失(train_loss)為0.2714。這些資料給出了模型訓練過程中的詳細資訊,有助於我們瞭解模型的訓練效率和效果。
模型分享
如果您希望將最終的模型分享給其他人使用,您可以透過呼叫push_to_hub
方法來實作模型的分享。這樣,其他人就可以輕鬆地存取和使用您的模型。
使用Trainer進行模型訓練和佈署
雖然使用Trainer可能看起來像是一個黑盒,但在底層,它只是在執行常規的PyTorch訓練迴圈,就像我們在第3章中訓練簡單的diffusion模型一樣。從頭開始撰寫這樣的迴圈可能看起來像這樣:
首先,我們需要匯入必要的模組,包括AdamW最佳化器和學習率排程器:
from transformers import AdamW, get_scheduler
接下來,我們需要定義最佳化器和學習率排程器:
optimizer = AdamW(model.parameters(), lr=5e-5)
lr_scheduler = get_scheduler("linear",...)
然後,我們可以開始訓練迴圈:
for epoch in range(num_epochs):
for batch in train_dataloader:
batch = {k: v.to(device) for k, v in batch.items()}
outputs = model(**batch)
loss = outputs.loss
loss.backward()
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
在這個迴圈中,我們會:
- 遍歷所有的epoch。
- 遍歷所有的batch。
- 將batch移動到裝置上並執行模型。
- 計算損失並反向傳播。
- 更新模型引數,調整學習率,並重置梯度為零。
Trainer會為我們處理這些事情,包括進行評估和預測,將模型推播到Hub,訓練多個GPU,儲存即時檢查點,記錄等等。
如果你將模型推播到Hub,其他人現在可以使用AutoModel或pipeline()存取它。讓我們試試一個例子:
# 使用pipeline作為高階別的幫助工具
from transformers import pipeline
這樣,我們就可以使用pipeline()來進行文字分類別等任務。
文字分類別模型評估
在進行文字分類別任務時,評估模型的效能是一個非常重要的步驟。這裡,我們使用了一個名為 pipeline
的函式來建立一個文字分類別模型,並使用了 genaibook/classifier-chapter4
這個模型來進行預測。
首先,我們使用了 pipe
函式來對一段文字進行預測,結果顯示該文字被分類別為「Sports」類別,且得分為 0.8631355166435242。這表明模型對於這段文字的分類別是正確的。
接下來,我們深入探討了模型的評估指標。使用 Trainer.predict
或 pipe.predict
函式可以得到預測結果,包括預測的標籤、標籤 ID 和評估指標。這裡,我們使用了 pipe.predict
函式來得到預測結果,並將其與實際標籤進行比較。
深入剖析語言模型微調的流程和技術細節後,本章涵蓋了從文字分類別任務出發,理解編碼器模型的應用、資料集處理、模型選擇、訓練流程到最終評估指標的完整流程。透過實作範例和程式碼解說,我們不僅學習瞭如何使用 DistilBERT 等預訓練模型進行微調,更深入探討了不同評估指標的選用策略,例如在不平衡資料集中 F1 分數的價值,以及考量執行時間和效率的重要性。技術堆疊的各層級協同運作中體現了 Transformer 模型的靈活性,從而可以根據任務需求調整模型結構和訓練引數。此外,本章也介紹了引數高效的微調技術和資源節約策略,這對於在有限資源環境下訓練和佈署模型至關重要。展望未來,隨著模型架構的持續最佳化和訓練資料的規模化增長,預期文字分類別模型的效能將進一步提升,同時降低計算成本。玄貓認為,掌握這些微調技巧對於自然語言處理的進階學習至關重要,並能有效提升模型在特定任務上的表現。對於資源有限的開發者,善用預訓練模型和引數高效微調策略將是提升模型開發效率的關鍵。