在實作自定義Transformer模型時,繼承預訓練模型類別是一個極為有效的策略。這不僅讓我們能夠重用成熟的架構,還能直接利用預訓練權重,大縮短開發週期。
實際上,當我在處理多語言命名實體辨識專案時,發現透過繼承RobertaPreTrainedModel
並僅實作必要的方法,可以節省大量時間。模型載入、權重初始化等複雜操作都被封裝在父類別中,使我們能專注於任務特定的邏輯。
為什麼選擇XLM-RoBERTa
XLM-RoBERTa是一個強大的多語言模型,它在100多種語言上進行了預訓練。對於多語言NER任務,這個模型有幾個顯著優勢:
- 語言覆寫廣泛,可以處理多種語言的文字
- 根據RoBERTa架構,效能優於早期的多語言模型
- 子詞標記化策略適合處理多語言環境中的未知詞彙
在我的實驗中,XLM-RoBERTa在低資源語言上的表現尤其令人印象深刻,即使只有少量標註資料,也能達到相當好的辨識效果。
標記化策略的重要性
NER任務中的標記化策略至關重要,因為它直接影響標籤與標記的對齊方式。在實作過程中,我注意到幾個關鍵點:
子詞處理:當單詞被分割成多個子詞時,通常只有第一個子詞保留標籤,其餘子詞被標記為-100(在訓練中忽略)
特殊標記處理:像
<s>
和</s>
這樣的特殊標記不應該有實體標籤,因此也被標記為-100標籤一致性:確保標籤與單詞的對應關係在標記化過程中保持一致,這對模型學習正確的實體邊界至關重要
我在處理德語和法語資料時發現,某些複合詞被分割成多個子詞的情況特別常見,這使得標記化策略的選擇變得更加重要。
結合自定義模型與資料處理的完整流程
綜合上述內容,我們可以總結出一個用於多語言NER任務的完整流程:
- 建立自定義模型類別,繼承自適當的預訓練模型基礎類別
- 準備標籤到索引和索引到標籤的對映
- 使用自定義設定載入預訓練模型權重
- 實作適當的標記化策略,處理子詞和標籤對齊
- 使用處理好的資料微調模型
- 評估模型並在新文字上應用
這種流程結合了Hugging Face Transformers函式庫的強大功能與靈活的自定義能力,使我們能夠快速構建高效的NER系統。
在實際應用中,這種方法不僅適用於多語言NER,還可以擴充套件到其他序列標註任務,如詞性標註(POS tagging)、組塊分析(chunking)等。透過調整標籤集和微調策略,同樣的架構可以適應不同的語言處理需求。
自定義Transformer模型的實作雖然看似複雜,但透過繼承現有類別並理解標記化策略,我們可以構建出既強大又靈活的NLP系統。這種方法在實際專案中能夠大提高開發效率,同時保持模型效能的競爭力。
多語言命名實體識別的實作與最佳化
在自然語言處理領域中,命名實體識別(Named Entity Recognition,NER)是一個基礎但極為重要的任務。當我們需要處理多語言環境時,這項任務的複雜度就會大幅提升。本文將帶你深入瞭解如何利用XLM-RoBERTa這類別強大的多語言模型進行跨語言的命名實體識別。
標籤對齊:連線詞元與實體標籤
在處理命名實體識別時,一個核心挑戰是將分詞後的詞元(tokens)與對應的標籤(labels)正確對齊。由於Transformer模型的分詞器會將單詞拆分成更小的單位,我們需要精確處理這種一對多的對映關係。
以下是一個完整的函式,它封裝了所有將詞元與標籤對齊的邏輯:
def tokenize_and_align_labels(examples):
tokenized_inputs = xlmr_tokenizer(examples["tokens"], truncation=True,
is_split_into_words=True)
labels = []
for idx, label in enumerate(examples["ner_tags"]):
word_ids = tokenized_inputs.word_ids(batch_index=idx)
previous_word_idx = None
label_ids = []
for word_idx in word_ids:
if word_idx is None or word_idx == previous_word_idx:
label_ids.append(-100)
else:
label_ids.append(label[word_idx])
previous_word_idx = word_idx
labels.append(label_ids)
tokenized_inputs["labels"] = labels
return tokenized_inputs
這個函式處理了NER任務中的一個關鍵問題:如何處理WordPiece或SentencePiece等子詞分詞方式導致的標籤對齊問題。這裡的核心邏輯是:
- 首先使用XLM-RoBERTa的分詞器將輸入文字分詞,設定
is_split_into_words=True
表明輸入已經是分詞後的結果 - 對每個樣本,取得詞元到原始單詞的對映關係(word_ids)
- 遍歷每個詞元的對映ID:
- 如果詞元是特殊標記(如
[CLS]
)或是前一個單詞的延續(子詞),則標籤設為-100(在PyTorch中會被忽略) - 否則,使用原始單詞的標籤
- 如果詞元是特殊標記(如
- 最後將生成的標籤列表增加到分詞後的輸入中
這種處理確保了只有單詞的第一個子詞會被賦予實際標籤,避免了模型對同一個單詞的不同部分重複學習相同的標籤。
接下來,我們定義一個函式來編碼整個資料集:
def encode_panx_dataset(corpus):
return corpus.map(tokenize_and_align_labels, batched=True,
remove_columns=['langs', 'ner_tags', 'tokens'])
這個簡潔的函式將我們前面定義的標籤對齊邏輯應用到整個資料集。它使用了Hugging Face Datasets函式庫的map
方法,這允許我們批次處理資料並移除已經不需要的原始列。這種批處理方式大提高了資料預處理的效率。
現在我們可以編碼德語資料集:
panx_de_encoded = encode_panx_dataset(panx_ch["de"])
評估指標:如何衡量NER模型的效能
評估NER模型時,我們關注的是模型識別完整實體的能力,而不僅是單個標籤的正確率。這裡我們使用seqeval
函式庫來計算精確率、召回率和F1分數。
from seqeval.metrics import classification_report
y_true = [["O", "O", "O", "B-MISC", "I-MISC", "I-MISC", "O"],
["B-PER", "I-PER", "O"]]
y_pred = [["O", "O", "B-MISC", "I-MISC", "I-MISC", "I-MISC", "O"],
["B-PER", "I-PER", "O"]]
print(classification_report(y_true, y_pred))
seqeval
是專門為序列標注任務(如NER)設計的評估函式庫。它的特點是:
- 要求完整實體比對:只有當一個實體的所有標籤(包括B-、I-字首)都正確預測時,才算一次正確預測
- 支援多種評估指標:提供精確率、召回率、F1分數等多種指標
- 能夠分別評估每種實體型別的效能
在這個例子中,MISC實體的預測完全錯誤(預測多了一個I-MISC標籤),所以F1分數為0;而PER實體預測完全正確,F1分數為1。總體F1分數是0.5。
為了在訓練過程中使用這些指標,我們需要一個函式來將模型輸出轉換為seqeval
所需的格式:
import numpy as np
def align_predictions(predictions, label_ids):
preds = np.argmax(predictions, axis=2)
batch_size, seq_len = preds.shape
labels_list, preds_list = [], []
for batch_idx in range(batch_size):
example_labels, example_preds = [], []
for seq_idx in range(seq_len):
# Ignore label IDs = -100
if label_ids[batch_idx, seq_idx] != -100:
example_labels.append(index2tag[label_ids[batch_idx][seq_idx]])
example_preds.append(index2tag[preds[batch_idx][seq_idx]])
labels_list.append(example_labels)
preds_list.append(example_preds)
return preds_list, labels_list
這個函式實作了從模型輸出到seqeval
所需格式的轉換,主要步驟包括:
- 透過argmax找出每個位置的預測標籤索引
- 對批次中的每個樣本和每個序列位置:
- 跳過被標記為-100的位置(這些通常是填充符或子詞的延續)
- 將標籤索引轉換回實際的標籤字元串(如"B-PER")
- 最終回傳兩個列表的列表,分別包含真實標籤和預測標籤
這個處理確保了我們只評估那些有意義的位置(通常是單詞的第一個子詞),而忽略其他位置,從而得到更準確的評估結果。
微調XLM-RoBERTa模型
有了資料和評估指標,我們可以開始微調XLM-RoBERTa模型。我們的策略是先在德語資料集上微調基礎模型,然後評估它在法語、義大利語和英語上的零樣本跨語言效能。
首先定義訓練引數:
from transformers import TrainingArguments
num_epochs = 3
batch_size = 24
logging_steps = len(panx_de_encoded["train"]) // batch_size
model_name = f"{xlmr_model_name}-finetuned-panx-de"
training_args = TrainingArguments(
output_dir=model_name, log_level="error", num_train_epochs=num_epochs,
per_device_train_batch_size=batch_size,
per_device_eval_batch_size=batch_size, evaluation_strategy="epoch",
save_steps=1e6, weight_decay=0.01, disable_tqdm=False,
logging_steps=logging_steps, push_to_hub=True)
這裡我設定了一些關鍵的訓練引數:
- 訓練3個epoch,這通常足以讓模型在NER任務上取得良好效能
- 批次大小為24,這是在大多數GPU上能夠高效執行的一個平衡值
- 每個epoch結束後評估一次模型(
evaluation_strategy="epoch"
) - 設定較大的
save_steps
以停用中間檢查點儲存,加速訓練 - 增加適當的權重衰減(0.01)以減少過擬合風險
- 啟用模型推播到Hugging Face Hub的功能
接下來,我們需要定義如何計算評估指標:
from seqeval.metrics import f1_score
def compute_metrics(eval_pred):
y_pred, y_true = align_predictions(eval_pred.predictions,
eval_pred.label_ids)
return {"f1": f1_score(y_true, y_pred)}
這個函式將在每次評估時被呼叫,它使用前面定義的align_predictions
函式將模型的原始輸出轉換為seqeval
可處理的格式,然後計算F1分數。F1分數是精確率和召回率的調和平均,在NER任務中是最常用的綜合效能指標。
我們還需要一個資料整理器來處理批次中的填充:
from transformers import DataCollatorForTokenClassification
data_collator = DataCollatorForTokenClassification(xlmr_tokenizer)
DataCollatorForTokenClassification
是Transformers函式庫為標記分類別任務(如NER)提供的專用資料整理器。它的主要功能是:
- 將批次中的序列填充到相同長度
- 對標籤序列也進行相應的填充,填充值為-100(這個值會在損失計算中被忽略)
- 正確處理注意力遮罩(attention masks)和其他必要的輸入
這比一般的資料整理器更適合NER任務,因為它能正確處理標籤序列的填充問題。
為了方便訓練多個模型,我們定義一個模型初始化函式:
def model_init():
return (XLMRobertaForTokenClassification
.from_pretrained(xlmr_model_name, config=xlmr_config)
.to(device))
這個函式在每次train()
呼叫開始時被執行,它的作用是:
- 從預訓練的XLM-RoBERTa模型建立一個新的標記分類別模型
- 使用我們之前定義的設定(包含標籤數量等訊息)
- 將模型移動到指定裝置(GPU或CPU)
這種方式比直接初始化模型更靈活,特別是在需要多次訓練不同模型的場景中。
現在我們可以建立Trainer並開始訓練:
from transformers import Trainer
trainer = Trainer(model_init=model_init, args=training_args,
data_collator=data_collator, compute_metrics=compute_metrics,
train_dataset=panx_de_encoded["train"],
eval_dataset=panx_de_encoded["validation"],
tokenizer=xlmr_tokenizer)
trainer.train()
trainer.push_to_hub(commit_message="Training completed!")
訓練結果顯示模型在三個epoch後達到了0.865的F1分數,這對NER任務來説是相當不錯的成績。
模型測試與錯誤分析
為了確認模型的實際效果,我們可以在一個簡單的德語例子上測試:
text_de = "Jeff Dean ist ein Informatiker bei Google in Kalifornien"
tag_text(text_de, tags, trainer.model, xlmr_tokenizer)
結果顯示模型成功識別出了人名(Jeff Dean)、組織(Google)和地點(Kalifornien)。
錯誤分析的重要性
雖然單個例子的成功令人鼓舞,但真正的模型評估需要進行全面的錯誤分析。在我多年的NLP實踐中,發現錯誤分析是提升模型效能最有效的方法之一。
常見的訓練失敗模式包括:
- 過度遮罩:不小心遮罩了太多標記,包括一些標籤,導致模型無法學習這些標籤
- 資料不平衡:某些實體型別的樣本太少,導致模型在這些型別上表現不佳
- 邊界檢測問題:模型難以區分實體的開始和結束位置
- 跨語言差異:不同語言中實體表達方式的差異導致模型在零樣本遷移時表現下降
進行錯誤分析時,我推薦關注以下幾個方面:
- 分析每種實體型別的效能,找出模型表現較差的型別
- 檢查實體邊界預測錯誤的案例,瞭解模型在區分實體邊界時的困難
- 對比不同長度實體的識別效能,找出是否存在長實體識別困難的問題
- 分析不同語言間的效能差異,瞭解跨語言遷移的挑戰
透過系統化的錯誤分析,我們可以針對性地改進模型,例如調整資料增強策略、修改標籤處理方式或最佳化訓練引數。
跨語言零樣本遷移
XLM-RoBERTa的一個強大特性是它能夠實作跨語言零樣本遷移(zero-shot cross-lingual transfer)。這意味著我們可以在一種語言上訓練模型,然後直接在其他語言上應用,而無需在目標語言上進行任何微調。
這種能力源於XLM-RoBERTa的多語言預訓練過程,在這個過程中,模型學習了不同語言間的共同特徵和模式。這使得模型能夠建立一個跨語言的語義空間,在這個空間中,不同語言表達相同概念的詞彙和短語被對映到相近的位置。
在NER任務中,跨語言遷移特別有價值,因為:
- 許多實體(如人名、組織名和地名)在不同語言中有相似的上下文模式
- 某些實體(如國際公司名稱)在不同語言中可能保持相同或相似的形式
- 實體的語法標記(如首字母大寫)在許多語言中是一致的
在實際應用中,我發現這種跨語言遷移能力可以大減少標註資料的需求,特別是對於資源匱乏的語言。例如,我們可以利用豐富的英語或德語NER資料來訓練模型,然後將其應用於缺乏標註資料的語言,如希臘語或芬蘭語。
不過,跨語言遷移的效果會受到語言間差異的影響。一般來説,語言關係越近(如德語和荷蘭語),遷移效果越好;語言關係越遠(如英語和日語),遷移效果可能會下降。
在下一節中,我們將探討如何評估和最佳化這種跨語言遷移能力,以及如何處理語言間的差異。
模型效能最佳化策略
在實際佈署多語言NER模型時,我總結了幾個有效的最佳化策略:
- 多語言微調:在多種語言的混合資料上微調模型,可以提高模型在所有目標語言上的效能
- 標籤一致性:確保不同語言間的標籤體系一致,這對跨語言遷移至關重要
- 資料增強:使用機器翻譯生成更多訓練資料,特別是對於資源匱乏的語言
- 整合學習:結合多個模型的預測,可以提高系統的穩定性和效能
- 後處理規則:根據特定領域知識增加後處理規則,修正常見錯誤
在最佳化模型時,還需要考慮計算效率和佈署環境的限制。例如,在移動裝置或邊緣裝置上佈署時,可能需要模型壓縮或量化技術來減小模型體積並提高推理速度。
多語言NER是一個不斷發展的領域,新的預訓練模型和微調技術不斷湧現。保持對最新研究的關注,並根據具體應用場景靈活調整策略,是提高多語言NER系統效能的關鍵。
透過合理的資料處理、模型選擇和最佳化策略,我們可以構建高效、準確的多語言NER系統,為跨語言訊息提取和理解提供強大支援。這些技術不僅適用於學術研究,也能在實際業務中創造價值,如多語言搜尋引擎、跨語言知識圖譜構建和國際新聞分析等。
深入剖析模型錯誤:多語言命名實體識別的關鍵技巧
在開發多語言命名實體識別(NER)系統時,模型表現往往不如預期,這時錯誤分析就成為找出問題根源的關鍵工具。當模型效能不佳時,深入分析錯誤案例不僅能揭示程式碼中難以察覺的缺陷,更能幫助我們理解模型的強項與弱點。這些洞察對於將模型佈署到生產環境尤為重要。
在實務中,我發現即使模型表現良好與程式碼無明顯問題,錯誤分析仍能提供寶貴的見解。讓我們一起探討如何透過錯誤分析改進多語言NER系統。
常見的效能評估陷阱
在開始錯誤分析前,我們需要警惕一些常見的評估陷阱:
compute_metrics()
函式可能存在高估模型真實效能的缺陷- 在NER任務中,將零類別或O實體視為普通類別時,由於其數量遠超其他類別,會嚴重扭曲準確率和F1分數
透過損失函式深入分析
分析模型錯誤的最有效方法之一是檢查驗證集中損失值最高的樣本。我們可以擴充套件先前用於序列分類別的分析函式,計算樣本序列中每個標記的損失值。
讓我們定義一個可應用於驗證集的方法:
from torch.nn.functional import cross_entropy
def forward_pass_with_label(batch):
# 將字典列表轉換為適合資料整理器的字典列表
features = [dict(zip(batch, t)) for t in zip(*batch.values())]
# 對輸入和標籤進行填充,並將所有張量放到裝置上
batch = data_collator(features)
input_ids = batch["input_ids"].to(device)
attention_mask = batch["attention_mask"].to(device)
labels = batch["labels"].to(device)
with torch.no_grad():
# 將資料傳遞給模型
output = trainer.model(input_ids, attention_mask)
# logit尺寸: [batch_size, sequence_length, classes]
# 預測類別為類別軸上logit值最大的類別
predicted_label = torch.argmax(output.logits, axis=-1).cpu().numpy()
# 在展平批次維度後計算每個標記的損失
loss = cross_entropy(output.logits.view(-1, 7),
labels.view(-1), reduction="none")
# 還原批次維度並轉換為numpy陣列
loss = loss.view(len(input_ids), -1).cpu().numpy()
return {"loss": loss, "predicted_label": predicted_label}
這個函式接收一個批次的資料,進行以下操作:
- 將資料重新組織為適合模型處理的格式
- 使用模型進行前向傳遞,但不計算梯度(使用
torch.no_grad()
) - 從模型輸出中找出每個位置預測機率最高的標籤
- 計算每個標記的交叉熵損失,不進行平均(設定
reduction="none"
) - 重塑損失張量以還原批次結構
- 回傳每個標記的損失和預測標籤
現在我們可以將這個函式應用到整個驗證集,並將所有資料載入DataFrame進行進一步分析:
valid_set = panx_de_encoded["validation"]
valid_set = valid_set.map(forward_pass_with_label, batched=True, batch_size=32)
df = valid_set.to_pandas()
標記與標籤的可讀性處理
標記和標籤仍以ID編碼形式存在,為了更容易閲讀結果,我們將它們映射回字元串。對於標籤為-100的填充標記,我們指定一個特殊標籤「IGN」,以便之後可以過濾它們。同時,我們透過截斷到輸入的長度來去除損失和預測標籤欄位中的所有填充:
index2tag[-100] = "IGN"
df["input_tokens"] = df["input_ids"].apply(
lambda x: xlmr_tokenizer.convert_ids_to_tokens(x))
df["predicted_label"] = df["predicted_label"].apply(
lambda x: [index2tag[i] for i in x])
df["labels"] = df["labels"].apply(
lambda x: [index2tag[i] for i in x])
df['loss'] = df.apply(
lambda x: x['loss'][:len(x['input_ids'])], axis=1)
df['predicted_label'] = df.apply(
lambda x: x['predicted_label'][:len(x['input_ids'])], axis=1)
這段程式碼進行資料後處理,使其更易於人類解讀:
- 將-100的索引對映到「IGN」標籤,代表應被忽略的填充標記
- 將輸入ID轉換回實際標記文字
- 將標籤ID和預測標籤ID轉換為對應的標籤名稱
- 截斷損失和預測標籤,僅保留實際輸入長度的部分,去除填充
展開標記級別分析
每個列包含一個樣本的標記、標籤、預測標籤等列表。我們需要展開這些列表,使每個標記單獨成為一行。pandas的explode()
函式可以幫助我們一行程式碼完成這個操作:
df_tokens = df.apply(pd.Series.explode)
df_tokens = df_tokens.query("labels != 'IGN'")
df_tokens["loss"] = df_tokens["loss"].astype(float).round(2)
這段程式碼:
- 使用
explode()
函式將每行的列表展開,為每個列表元素建立一行 - 過濾掉標記為「IGN」的填充標記
- 將損失值從numpy陣列轉換為標準浮點數並四捨五入到兩位小數
按標記分析錯誤模式
將資料整理成這種形式後,我們可以按輸入標記對其進行分組,並匯總每個標記的損失:
(
df_tokens.groupby("input_tokens")[["loss"]]
.agg(["count", "mean", "sum"])
.droplevel(level=0, axis=1) # 去除多級列
.sort_values(by="sum", ascending=False)
.reset_index()
.round(2)
.head(10)
.T
)
從結果中,我觀察到幾種模式:
空格標記總損失最高,這不足為奇,因為它也是列表中最常見的標記。但它的平均損失遠低於列表中的其他標記,表示模型在分類別它時並不困難。
像「in」、「von」、「der」和「und」這樣的詞相對頻繁出現。它們經常與命名實體一起出現,有時還是其中的一部分,這解釋了為什麼模型可能混淆它們。
括號、斜線和單詞開頭的大寫字母較少見,但平均損失相對較高,值得進一步調查。
按標籤分析錯誤
我們還可以按標籤ID分組,檢視每個類別的損失:
(
df_tokens.groupby("labels")[["loss"]]
.agg(["count", "mean", "sum"])
.droplevel(level=0, axis=1)
.sort_values(by="mean", ascending=False)
.reset_index()
.round(2)
.T
)
分析結果顯示,B-ORG(組織實體的開始標記)具有最高的平均損失,這意味著確定組織的開始對我們的模型來説是一個挑戰。
混淆矩陣揭示模型弱點
我們可以透過繪製標記分類別的混淆矩陣,進一步分析問題所在:
from sklearn.metrics import ConfusionMatrixDisplay, confusion_matrix
def plot_confusion_matrix(y_preds, y_true, labels):
cm = confusion_matrix(y_true, y_preds, normalize="true")
fig, ax = plt.subplots(figsize=(6, 6))
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=labels)
disp.plot(cmap="Blues", values_format=".2f", ax=ax, colorbar=False)
plt.title("Normalized confusion matrix")
plt.show()
plot_confusion_matrix(df_tokens["labels"], df_tokens["predicted_label"], tags.names)
這個函式建立並顯示一個標準化的混淆矩陣:
- 使用scikit-learn的
confusion_matrix
函式運算混淆矩陣,並按行進行標準化 - 設定圖表大小為6x6
- 使用
ConfusionMatrixDisplay
顯示混淆矩陣,使用藍色調 - 為圖表增加標題並顯示
從混淆矩陣可以看出,我們的模型最容易混淆B-ORG和I-ORG實體。除此之外,它在分類別其餘實體方面相當不錯,這從混淆矩陣接近對角線的性質可以明顯看出。
分析高損失序列
檢查了標記級錯誤後,讓我們看損失值高的序列。為此,我們首先編寫一個函式,幫助我們顯示帶有標籤和損失的標記序列:
def get_samples(df):
for _, row in df.iterrows():
labels, preds, tokens, losses = [], [], [], []
for i, mask in enumerate(row["attention_mask"]):
if i not in {0, len(row["attention_mask"])}:
labels.append(row["labels"][i])
preds.append(row["predicted_label"][i])
tokens.append(row["input_tokens"][i])
losses.append(f"{row['loss'][i]:.2f}")
df_tmp = pd.DataFrame({"tokens": tokens, "labels": labels,
"preds": preds, "losses": losses}).T
yield df_tmp
df["total_loss"] = df["loss"].apply(sum)
df_tmp = df.sort_values(by="total_loss", ascending=False).head(3)
for sample in get_samples(df_tmp):
display(sample)
這個函式:
- 遍歷DataFrame中的每一行
- 為每一行建立標籤、預測、標記和損失的列表
- 過濾掉特殊標記(序列的開始和結束標記)
- 建立一個臨時DataFrame來顯示這些訊息
- 使用生成器逐個產生這些臨時DataFrame
然後,我們:
- 計算每個樣本的總損失
- 按總損失降序排序並選取前3個樣本
- 使用我們的函式顯示這些樣本
發現標註錯誤
分析高損失樣本揭示了一個有趣的發現:這些樣本的標籤似乎有問題。例如,「聯合國」和「中非共和國」被標記為人物(PER),而「8. Juli」(7月8日)被標記為組織(ORG)。
這種發現非常有價值,因為它表明我們的資料集可能存在標註錯誤,這會直接影響模型的學習效果。在實際專案中,我總是建議進行這種詳細的錯誤分析,因為它可能揭示不僅是模型問題,還有資料問題。
改進多語言NER模型的策略
根據錯誤分析的結果,我建議以下改進策略:
資料清理:識別並修正訓練資料中的標註錯誤,特別是那些導致高損失的樣本
實體邊界處理:加強模型對實體開始(B-標籤)和實體內部(I-標籤)的區分能力,可以考慮使用特定的損失權重或增強這類別樣本的訓練
特殊標記處理:為括號、斜線等特殊標記提供更多訓練範例,或考慮在預處理階段特別處理這些標記
跨語言一致性:確保在不同語言中相同實體(如「聯合國」)的標註保持一致
評估指標調整:重新考慮評估指標,確保不會因為O標籤(非實體)的大量存在而扭曲結果
多語言NER的實用技巧
在實踐中,我發現多語言NER面臨的挑戰遠超單語言系統。以下是一些實用技巧:
處理不同語言的命名實體特徵
不同語言對命名實體有不同的表示方式。例如,德語中所有名詞(包括非實體)都大寫,而英語只有專有名詞大寫。這種差異會影響模型的學習效果。
我的建議是使用預先訓練的多語言模型(如XLM-RoBERTa)作為基礎,這些模型已經學習了不同語言的語法特徵,可以更好地適應各種語言的NER任務。
資料增強策略
對於資源有限的語言,可以考慮以下資料增強策略:
跨語言遷移:利用資源豐富語言的標註資料來改進資源有限語言的模型
實體替換:建立新的訓練範例,透過用同類別的其他實體替換已知實體
上下文變化:保持實體不變,但更改其周圍的上下文
處理特殊情況
在多語言環境中,一些特殊情況需要特別注意:
混合語言文字:文字中可能同時出現多種語言,模型需要能夠處理這種情況
音譯實體:同一實體在不同語言中可能有不同的拼寫(如「莫斯科」vs「Moscow」)
文化特定實體:某些實體可能只在特定文化或語言環境中存在
#與展望
透過深入的錯誤分析,我們不僅能夠識別模型的弱點,還能發現資料集中的問題。這種分析對於改進多語言NER系統至關重要,特別是在處理複雜的跨語言場景時。
在NER系統開發過程中,我始終強調錯誤分析的重要性。它不僅幫助我們理解模型的行為,還能指導我們進行有針對性的改進。特別是對於多語言NER系統,理解不同語言中實體表示的差異,以及模型如何處理這些差異,是提高系統效能的關鍵。
隨著預訓練多語言模型的不斷發展,我們有理由相信多語言NER系統的效能會持續提升。然而,細致的錯誤分析和針對性的最佳化仍將是推動這一進步的核心驅動力。
自動標註資料的優缺點:銀標準與金標準的差異
在處理命名實體識別(NER)任務時,資料標註的品質對模型效能至關重要。自動生成的標註通常被稱為「銀標準」(silver standard),相對於人工標註的「金標準」(gold standard)。雖然自動標註方法可以快速處理大量資料,但它難免會產生一些不合理的標籤。
值得注意的是,這類別問題並非自動標註方法所獨有。即使是人類工作者在標註資料時,也可能因為注意力不集中或對句子理解有誤而產生錯誤。這提醒我們在評估模型表現時,應當考慮到訓練資料本身可能存在的缺陷。
特殊字元處理的挑戰
在之前的分析中,我發現括號和斜線等特殊符號的損失值相對較高。讓我們來看幾個包含開括號的序列範例:
df_tmp = df.loc[df["input_tokens"].apply(lambda x: u"\u2581(" in x)].head(2)
for sample in get_samples(df_tmp):
display(sample)
這段程式碼篩選出含有開括號的樣本,並顯示其中的前兩個。u"\u2581("
是Unicode編碼的開括號,其中\u2581
是XLM-R分詞器用來表示詞首的特殊符號。
分析結果顯示了兩個樣本:
第一個樣本:「Hama (Unternehmen)」
- 標準標籤將整個括號內容視為組織實體的一部分
- 模型預測也將整個內容視為組織實體
第二個樣本:「Keskkula (Martna)」
- 標準標籤將括號內容視為地點實體的一部分
- 模型同樣將整個內容視為地點實體
通常在命名實體識別中,我們不會將括號及其內容視為實體的一部分。然而,這些自動標註的資料卻採用了這種方式。在其他例子中,括號內容常包含地理説明,雖然這確實也是一個地點,但從標註的角度來看,我們可能希望將其與原始地點分開處理。
這個資料集由不同語言的維基百科文章組成,文章標題常在括號中包含某種解釋。例如,第一個範例中,括號內的「Unternehmen」在英文中意為「公司」,表示Hama是一家企業。這些細節對於模型佈署非常重要,因為它們可能影響整個處理流程的下游效能。
透過這種相對簡單的分析,我已識別出模型和資料集的一些弱點。在實際應用中,我們會反覆進行資料清理、模型重訓練和錯誤分析,直到達到令人滿意的效能。
雖然上面只分析了單一語言的錯誤,但我們同樣關注模型在不同語言間的表現。接下來,讓我們進行一些實驗,看XLM-R的跨語言遷移能力有多強。
跨語言遷移能力評估
既然我們已經在德語資料上微調了XLM-R模型,現在可以透過Trainer的predict()方法來評估它在其他語言上的遷移能力。由於我們計劃評估多種語言,讓我們建立一個簡單的函式來實作這一目標:
def get_f1_score(trainer, dataset):
return trainer.predict(dataset).metrics["test_f1"]
這個函式接收訓練好的模型和測試資料集,回傳在測試集上的F1分數。F1分數是精確率(precision)和召回率(recall)的調和平均值,是評估NER模型效能的常用指標。
讓我們使用這個函式檢查模型在德語測試集上的表現,並將分數儲存在字典中:
f1_scores = defaultdict(dict)
f1_scores["de"]["de"] = get_f1_score(trainer, panx_de_encoded["test"])
print(f"F1-score of [de] model on [de] dataset: {f1_scores['de']['de']:.3f}")
結果顯示:
F1-score of [de] model on [de] dataset: 0.868
這對NER任務來説是相當不錯的結果。我們的指標在85%左右,模型似乎在ORG(組織)實體上遇到最大挑戰,這可能是因為訓練資料中這類別實體較少,與許多組織名稱在XLM-R的詞彙表中較為罕見。
跨語言零樣本測試
那麼其他語言呢?讓我們先看在德語上微調的模型如何處理法語文字:
text_fr = "Jeff Dean est informaticien chez Google en Californie"
tag_text(text_fr, tags, trainer.model, xlmr_tokenizer)
結果顯示模型成功識別了法陳述式子中的人名(Jeff Dean)、組織名(Google)和地點名(Californie)。雖然人名和組織名在兩種語言中相同,但模型確實正確標記了「Kalifornien」的法語翻譯。
接下來,讓我們量化我們的德語模型在整個法語測試集上的表現,方法是編寫一個簡單的函式,對資料集進行編碼並生成分類別報告:
def evaluate_lang_performance(lang, trainer):
panx_ds = encode_panx_dataset(panx_ch[lang])
return get_f1_score(trainer, panx_ds["test"])
f1_scores["de"]["fr"] = evaluate_lang_performance("fr", trainer)
print(f"F1-score of [de] model on [fr] dataset: {f1_scores['de']['fr']:.3f}")
結果顯示:
F1-score of [de] model on [fr] dataset: 0.714
雖然我們看到微平均指標下降了約15個百分點,但請記住,我們的模型從未見過任何標記過的法語樣本!一般來説,效能下降的幅度與語言之間的「距離」有關。雖然德語和法語都被歸類別為印歐語系,但它們實際上屬於不同的語系:分別是日耳曼語系和羅曼語系。
現在,讓我們評估在義大利語上的表現。由於義大利語也是羅曼語系,我們預計會得到與法語相似的結果:
f1_scores["de"]["it"] = evaluate_lang_performance("it", trainer)
print(f"F1-score of [de] model on [it] dataset: {f1_scores['de']['it']:.3f}")
結果顯示:
F1-score of [de] model on [it] dataset: 0.692
確實,結果符合我們的預期。最後,讓我們檢查在英語上的表現,英語屬於日耳曼語系:
f1_scores["de"]["en"] = evaluate_lang_performance("en", trainer)
print(f"F1-score of [de] model on [en] dataset: {f1_scores['de']['en']:.3f}")
結果顯示:
F1-score of [de] model on [en] dataset: 0.589
令人驚訝的是,我們的模型在英語上表現最差,儘管直覺上我們可能認為德語應該比法語更接近英語。在德語上微調並對法語和英語進行零樣本遷移後,接下來讓我們探討何時直接在目標語言上微調是有意義的。
何時零樣本遷移是合理的?
到目前為止,我們已經看到在德語料函式庫上微調XLM-R可以獲得約85%的F1分數,並且無需額外訓練,模型就能在我們語料函式庫中的其他語言上取得適度的表現。問題是,這些結果有多好,與在單語料函式庫上微調的XLM-R模型相比如何?
在本文中,我將透過在不斷增加規模的訓練集上微調XLM-R來探討法語料函式庫的這個問題。透過這種方式追蹤效能,我們可以確定零樣本跨語言遷移在哪個點上更具優勢,這在實踐中可以用於指導是否收集更多標記資料的決策。
為了簡化,我們將保持與德語料函式庫微調執行相同的超引數,只是我們將調整TrainingArguments的logging_steps引數以適應不斷變化的訓練集大小。我們可以將這一切包裝在一個簡單的函式中,該函式接收對應於單語料函式庫的DatasetDict物件,按num_samples對其進行下取樣,並在該樣本上微調XLM-R以回傳最佳epoch的指標:
def train_on_subset(dataset, num_samples):
train_ds = dataset["train"].shuffle(seed=42).select(range(num_samples))
valid_ds = dataset["validation"]
test_ds = dataset["test"]
training_args.logging_steps = len(train_ds) // batch_size
trainer = Trainer(model_init=model_init, args=training_args,
data_collator=data_collator, compute_metrics=compute_metrics,
train_dataset=train_ds, eval_dataset=valid_ds, tokenizer=xlmr_tokenizer)
trainer.train()
if training_args.push_to_hub:
trainer.push_to_hub(commit_message="Training completed!")
f1_score = get_f1_score(trainer, test_ds)
return pd.DataFrame.from_dict(
{"num_samples": [len(train_ds)], "f1_score": [f1_score]})
這個函式接受一個資料集和樣本數量,然後:
- 從訓練集中隨機選擇指定數量的樣本
- 建立訓練器並設定適當的日誌步數
- 訓練模型
- 如果需要,將模型推播到Hub
- 計算測試集上的F1分數
- 回傳包含樣本數量和F1分數的DataFrame
就像我們在德語料函式庫上微調時一樣,我們還需要將法語料函式庫編碼為輸入ID、注意力掩碼和標籤ID:
panx_fr_encoded = encode_panx_dataset(panx_ch["fr"])
接下來,讓我們透過在250個樣本的小訓練集上執行來檢查我們的函式是否正常工作:
training_args.push_to_hub = False
metrics_df = train_on_subset(panx_fr_encoded, 250)
metrics_df
結果顯示:
num_samples f1_score
0 250 0.137329
我們可以看到,只有250個樣本時,在法語上的微調遠不如從德語的零樣本遷移。現在,讓我們將訓練集大小增加到500、1,000、2,000和4,000個樣本,以瞭解效能如何提高:
for num_samples in [500, 1000, 2000, 4000]:
metrics_df = metrics_df.append(
train_on_subset(panx_fr_encoded, num_samples), ignore_index=True)
我們可以透過繪製測試集上的F1分數作為增加訓練集大小的函式,來比較在法語樣本上微調與從德語進行零樣本跨語言遷移的效果:
fig, ax = plt.subplots()
ax.axhline(f1_scores["de"]["fr"], ls="--", color="r")
metrics_df.set_index("num_samples").plot(ax=ax)
plt.legend(["Zero-shot from de", "Fine-tuned on fr"], loc="lower right")
plt.ylim((0, 1))
plt.xlabel("Number of Training Samples")
plt.ylabel("F1 Score")
plt.show()
零樣本遷移的實用價值分析
從圖表中可以看出,零樣本遷移在訓練樣本數量達到約750個之前都保持競爭力,之後在法語上微調達到與在德語上微調時相似的效能水平。儘管如此,這個結果不容小覷!根據我的經驗,讓領域工作者標記甚至數百份檔案都可能成本高昂,尤其是對於NER任務,其標記過程非常細緻。
這個發現具有重要的實用意義。在許多實際場景中,我們可能面臨這樣的選擇:是投入資源收集更多目標語言的標記資料,還是利用已有的其他語言資源進行零樣本遷移。這個分析為我們提供了一個實用的參考點 - 當可用的標記樣本少於約750個時,從相關語言進行零樣本遷移可能是更具成本效益的選擇。
這種方法的價值在低資源語言或專業領域中尤為明顯,在這些情況下,取得大量標記資料可能特別困難或昂貴。XLM-R等多語言模型的跨語言遷移能力為這些場景提供了一個有效的解決方案,使我們能夠以最小的標註成本構建功能強大的NER系統。
在實際工作中,我經常需要在有限的標註資源下實作最佳效果,這類別分析幫助我做出更明智的決策,平衡標註成本與模型表現之間的權衡。對於組織來説,這意味著可以更有效地分配資源,在適當的情況下利用跨語言遷移來減少對大量標記資料的依賴。
多語言模型的這種能力代表了自然語言處理領域的一個重要進步,使我們能夠更快、更經濟地擴充套件NLP應用到更多語言和領域。隨著這些模型的不斷發展,我們可以期待跨語言遷移效能的進一步提升,為更多語言和任務提供有效支援。
多語言模型微調:打破語言藩籬的技術探索
在自然語言處理領域,多語言模型的出現極大地改變了我們處理跨語言任務的方式。當面對多語言資料集時,我們不必為每種語言單獨訓練模型,而是可以利用預訓練的多語言模型進行遷移學習。在之前的實驗中,我們已經看到從德語到法語或義大利語的零樣本跨語言遷移會導致效能下降約15個百分點。那麼,有沒有方法可以減輕這種效能下降呢?
答案是肯定的,其中一個有效的策略就是同時在多種語言上進行微調。這篇文章將探討這種方法,並分析其在不同語言間的表現。
同時在多種語言上微調模型
當我們面對多語言NLP任務時,一個常見的困境是如何有效處理語言間的差異。我發現,同時在多個語言上微調模型是一個非常有效的解決方案。讓我們透過實際操作來看這種方法的效果。
首先,我們需要將不同語言的資料集合併。以下是將德語和法語料函式庫合併的方法:
from datasets import concatenate_datasets
def concatenate_splits(corpora):
multi_corpus = DatasetDict()
for split in corpora[0].keys():
multi_corpus[split] = concatenate_datasets(
[corpus[split] for corpus in corpora]).shuffle(seed=42)
return multi_corpus
panx_de_fr_encoded = concatenate_splits([panx_de_encoded, panx_fr_encoded])
這段程式碼實作了一個關鍵功能:將多個語言的資料集合併成一個多語言資料集。函式concatenate_splits()
接收一個資料集列表,然後遍歷第一個資料集的所有分割(通常是train、validation和test),將每個分割中的所有資料集合併並隨機打亂。這裡使用了固定的隨機種子(42)確保結果的可重現性。最後,我們將德語和法語的編碼資料集合併,建立一個統一的訓練資料。
接下來,我們使用與之前相同的超引數設定來訓練模型,只需更新日誌步驟、模型和資料集:
training_args.logging_steps = len(panx_de_fr_encoded["train"]) // batch_size
training_args.push_to_hub = True
training_args.output_dir = "xlm-roberta-base-finetuned-panx-de-fr"
trainer = Trainer(
model_init=model_init,
args=training_args,
data_collator=data_collator,
compute_metrics=compute_metrics,
tokenizer=xlmr_tokenizer,
train_dataset=panx_de_fr_encoded["train"],
eval_dataset=panx_de_fr_encoded["validation"]
)
trainer.train()
trainer.push_to_hub(commit_message="Training completed!")
在這段程式碼中,我們設定了訓練引數並初始化了Trainer物件。關鍵部分是指定了輸出目錄名稱,反映了我們正在使用德語和法語微調XLM-RoBERTa模型。logging_steps
引數設定為訓練集大小除以批次大小,這樣可以在每個epoch結束時記錄一次。我們啟用了push_to_hub
功能,這樣訓練完成後模型會自動上載到Hugging Face Hub。最後,我們使用合併後的德語和法語資料集進行訓練,並將模型推播到Hub。
多語言微調的效果評估
訓練完成後,我們需要評估模型在各個語言測試集上的表現:
for lang in langs:
f1 = evaluate_lang_performance(lang, trainer)
print(f"F1-score of [de-fr] model on [{lang}] dataset: {f1:.3f}")
執行這段程式碼後,我們得到了以下結果:
F1-score of [de-fr] model on [de] dataset: 0.866
F1-score of [de-fr] model on [fr] dataset: 0.868
F1-score of [de-fr] model on [it] dataset: 0.815
F1-score of [de-fr] model on [en] dataset: 0.677
這段程式碼遍歷了所有目標語言,對每種語言的測試集進行評估。結果顯示,在德語和法語上同時微調的模型在法語測試集上的表現大幅提升,達到了與德語測試集相當的水平。更有趣的是,模型在未見過的義大利語和英語上的表現也提高了約10個百分點!這表明,即使僅增加另一種語言的訓練資料,也能顯著提升模型在未見過語言上的效能。
單語言微調與多語言微調的比較分析
為了全面評估多語言學習的效果,我們需要比較單獨在每種語言上微調與在所有語言上同時微調的效果差異。讓我們首先在剩餘的語言上進行單獨微調:
corpora = [panx_de_encoded]
# 排除德語的迭代
for lang in langs[1:]:
training_args.output_dir = f"xlm-roberta-base-finetuned-panx-{lang}"
# 在單語言語料函式庫上微調
ds_encoded = encode_panx_dataset(panx_ch[lang])
metrics = train_on_subset(ds_encoded, ds_encoded["train"].num_rows)
# 在共同字典中收集F1分數
f1_scores[lang][lang] = metrics["f1_score"][0]
# 將單語言語料函式庫增加到要合併的語料函式庫列表中
corpora.append(ds_encoded)
這段程式碼遍歷了除德語外的所有語言,並在每種語言的資料集上單獨微調模型。對於每種語言,我們首先設定輸出目錄,然後對該語言的資料集進行編碼。接著使用train_on_subset
函式在完整的訓練集上微調模型,並記錄F1分數。最後,我們將每個單語言資料集增加到語料函式庫列表中,為下一步的多語言微調做準備。
現在,我們將所有語言的資料集合併,建立一個包含所有四種語言的多語言語料函式庫:
corpora_encoded = concatenate_splits(corpora)
有了多語言語料函式庫後,我們可以進行模型訓練:
training_args.logging_steps = len(corpora_encoded["train"]) // batch_size
training_args.output_dir = "xlm-roberta-base-finetuned-panx-all"
trainer = Trainer(
model_init=model_init,
args=training_args,
data_collator=data_collator,
compute_metrics=compute_metrics,
tokenizer=xlmr_tokenizer,
train_dataset=corpora_encoded["train"],
eval_dataset=corpora_encoded["validation"]
)
trainer.train()
trainer.push_to_hub(commit_message="Training completed!")
這段程式碼設定了訓練引數並初始化了Trainer物件,用於在所有語言的合併資料集上進行微調。我們將輸出目錄設為"xlm-roberta-base-finetuned-panx-all",表明這是在所有語言上微調的模型。訓練完成後,模型會自動上載到Hugging Face Hub。
最後,我們需要評估模型在每種語言的測試集上的表現:
for idx, lang in enumerate(langs):
f1_scores["all"][lang] = get_f1_score(trainer, corpora[idx]["test"])
scores_data = {
"de": f1_scores["de"],
"each": {lang: f1_scores[lang][lang] for lang in langs},
"all": f1_scores["all"]
}
f1_scores_df = pd.DataFrame(scores_data).T.round(4)
f1_scores_df.rename_axis(index="Fine-tune on", columns="Evaluated on", inplace=True)
f1_scores_df
這段程式碼會生成一個表格,總結我們多語言實驗的主要結果:
Fine-tune on | de | fr | it | en |
---|---|---|---|---|
de | 0.8677 | 0.7141 | 0.6923 | 0.5890 |
each | 0.8677 | 0.8505 | 0.8192 | 0.7068 |
all | 0.8682 | 0.8647 | 0.8575 | 0.7870 |
這個表格非常有價值,它清晰地展示了不同微調策略的效果:
- “de"行表示只在德語上微調的模型在各語言上的表現
- “each"行表示在各自語言上單獨微調的模型在各自語言上的表現
- “all"行表示在所有語言上同時微調的模型在各語言上的表現
從結果可以看出,在所有語言上同時微調的模型在每種語言上都取得了最佳或接近最佳的效能,特別是在英語上,效能提升非常顯著。
多語言學習的關鍵發現
根據上述實驗結果,我們可以得出幾個重要結論:
多語言學習能顯著提升效能:特別是當跨語言遷移的目標語言屬於相似的語言族時。在我們的實驗中,德語、法語和義大利語在"all"類別中達到了相似的效能,這表明這些語言彼此更相似,而與英語的差異較大。
語言族內的跨語言遷移更有效:作為一般策略,專注於語言族內的跨語言遷移是明智的,特別是在處理像日語這樣使用不同文字系統的語言時。
資源分享效應:即使是增加一種相關語言的訓練資料,也能提高模型在未見過語言上的效能。這表明多語言模型能夠從不同語言中學習到分享的語言特徵。
平衡的資源分配:在多語言微調中,確保不同語言的訓練資料量相對平衡是重要的,這有助於模型均衡地學習各種語言的特性。
與模型互動:利用Hugging Face Hub小工具
在本文中,我們向Hugging Face Hub上載了多個微調模型。除了在本地機器上使用pipeline()
函式與這些模型互動外,Hub還提供了便於這類別工作流程的小工具。以下是我們的transformersbook/xlm-roberta-base-finetuned-panx-all
檢查點的範例,可以看到它在識別德語文字中的所有實體方面表現出色。
這些互動式工具讓我們能夠直觀地測試模型效能,無需編寫額外的程式碼,大提高了開發效率。