返回文章列表

零樣本學習與少樣本學習文字分類別實務

本文探討在標註資料有限或缺乏的情況下,如何利用零樣本學習和少樣本學習技術有效進行文字分類別。文章涵蓋了使用預訓練語言模型進行零樣本分類別、自定義目標詞彙預測、文字蘊涵模型的應用、以及如何調整零樣本學習的引數(Top-k

機器學習 自然語言處理

預訓練語言模型如 BERT,可應用於零樣本學習的文字分類別任務。透過設計提示語,引導模型預測遮罩詞彙,進而判斷文字主題。除了預設詞彙預測,開發者還能自定義目標詞彙,取得特定詞彙的機率分數。更進一步,利用 MNLI 等文字蘊涵模型,能將待分類別文字視為前提,並建構假設,藉由蘊涵分數判斷文字與特定主題的關聯性。Transformers 函式庫提供零樣本分類別管道,方便開發者快速實作。針對實際應用,文章探討了 Top-k 和閾值兩種預測策略的選擇與效能比較,並提供提升零樣本學習效能的技巧,例如最佳化標籤名稱和調整假設範本。最後,文章也介紹了少樣本學習中的資料增強技術,例如反向翻譯和詞元擾動,以及如何使用大語言模型的嵌入向量進行文字分類別,為有限資料場景提供更全面的解決方案。

無標籤資料的處理:零樣本學習(Zero-Shot Classification)

在某些情境下,我們可能完全沒有標註資料可用。零樣本學習是一種利用預訓練模型進行分類別任務的方法,無需額外的微調。

使用預訓練語言模型進行零樣本分類別

我們可以利用像 BERT 這樣的預訓練語言模型,透過設計特定的提示(Prompt)來進行零樣本分類別。

from transformers import pipeline

# 載入預訓練的 BERT 模型
pipe = pipeline("fill-mask", model="bert-base-uncased")

# 定義電影描述和提示語
movie_desc = "The main characters of the movie madacascar are a lion, a zebra, a giraffe, and a hippo. "
prompt = "The movie is about [MASK]."

# 使用 fill-mask 管道進行預測
output = pipe(movie_desc + prompt)

for element in output:
    print(f"Token {element['token_str']}:\t{element['score']:.3f}%")

內容解密:

  1. 載入模型:使用 transformers 函式庫載入預訓練的 BERT 模型,並建立一個 fill-mask 管道。
  2. 設計提示語:建構一個包含 [MASK] 標記的提示語,用於引導模型進行分類別。
  3. 預測結果:模型會輸出最可能的詞彙來填充 [MASK],並給出對應的機率分數。

自定義目標詞彙進行預測

除了讓模型輸出最可能的詞彙外,我們也可以直接查詢特定詞彙的機率。

output = pipe(movie_desc + prompt, targets=["animals", "cars"])

for element in output:
    print(f"Token {element['token_str']}:\t{element['score']:.3f}%")

內容解密:

  1. 指定目標詞彙:透過 targets 引數指定我們感興趣的詞彙,如 “animals” 和 “cars”。
  2. 取得機率分數:模型會輸出這些指定詞彙對應的機率分數,用於判斷文字的主題。

使用零樣本學習進行文字分類別

在許多實際應用中,我們經常面臨缺乏標註資料的挑戰。幸運的是,根據Transformers的零樣本學習(Zero-Shot Learning)為我們提供了一種解決方案。本篇文章將探討如何利用預訓練的文字蘊涵(Text Entailment)模型來實作零樣本文字分類別。

文字蘊涵與零樣本學習

文字蘊涵是一種代理任務(Proxy Task),旨在判斷兩個文欄位落之間的邏輯關係,例如蘊涵、矛盾或中立。像Multi-Genre NLI Corpus(MNLI)和Cross-Lingual NLI Corpus(XNLI)這樣的資料集被用來訓練模型以檢測文字之間的蘊涵和矛盾關係。

資料集結構

每個樣本包含三個部分:前提(Premise)、假設(Hypothesis)和標籤(Label)。標籤可以是蘊涵、矛盾或中立。

前提假設標籤
His favourite color is blue.He is into heavy metal music.neutral
She finds the joke hilarious.She thinks the joke is not funny at all.contradiction
The house was recently built.The house is new.entailment

利用MNLI模型進行零樣本分類別

我們可以利用在MNLI資料集上訓練的模型來構建一個零樣本分類別器,而無需任何標註資料。核心思想是將待分類別的文字視為前提,並建構假設為「This example is about {label}.」,其中插入類別名稱。蘊涵分數(Entailment Score)告訴我們該前提與特定主題相關的可能性。

使用Transformers函式庫實作零樣本分類別

from transformers import pipeline

# 初始化零樣本分類別管道,指定使用GPU加速
pipe = pipeline("zero-shot-classification", device=0)

# 待分類別的文字樣本
sample = ds["train"][0]
print(f"Labels: {sample['labels']}")

# 進行零樣本分類別
output = pipe(sample["text"], all_labels, multi_label=True)
print(output["sequence"][:400])

print("\nPredictions:")
for label, score in zip(output["labels"], output["scores"]):
    print(f"{label}, {score:.2f}")

輸出結果

Labels: ['new model']
Add new CANINE model
New model addition

## Model description
Google recently proposed a new **C**haracter **A**rchitecture with **N**o tokenization **I**n **N**eural **E**ncoders architecture (CANINE). Not only the title is exciting:
> Pipelined NLP systems have largely been superseded by end-to-end neural modeling, yet nearly all commonly-used models still require an explicit tokeni

Predictions:
new model, 0.98
tensorflow or tf, 0.37
examples, 0.34
usage, 0.30
pytorch, 0.25
documentation, 0.25
model training, 0.24
tokenization, 0.17
pipeline, 0.16

內容解密:

  1. 零樣本分類別管道初始化:使用pipeline("zero-shot-classification", device=0)初始化零樣本分類別模型,並指定使用GPU(device=0)進行加速。
  2. 文字分類別:透過將待分類別文字和候選標籤傳入管道,獲得每個標籤的分數。
  3. 結果解析:輸出結果顯示了模型對文字的分類別預測,其中「new model」的分數最高,表明模型認為該文字最可能是關於新模型的。

大規模應用零樣本學習

為了將零樣本學習應用於整個驗證集,我們定義了一個函式zero_shot_pipeline,並使用map()方法將其應用於資料集中的每個樣本。

def zero_shot_pipeline(example):
    output = pipe(example["text"], all_labels, multi_label=True)
    example["predicted_labels"] = output["labels"]
    example["scores"] = output["scores"]
    return example

ds_zero_shot = ds["valid"].map(zero_shot_pipeline)

選擇預測標籤的方法

獲得預測分數後,我們需要決定如何選擇最終的預測標籤。可以透過以下兩種方法實作:

  1. 閾值法:設定一個閾值,選擇所有分數高於該閾值的標籤。
  2. Top-K法:選擇分數最高的K個標籤。

get_preds函式實作

def get_preds(example, threshold=None, topk=None):
    preds = []
    if threshold:
        for label, score in zip(example["predicted_labels"], example["scores"]):
            if score >= threshold:
                preds.append(label)
    elif topk:
        for i in range(topk):
            preds.append(example["predicted_labels"][i])
    else:
        raise ValueError("Set either `threshold` or `topk`.")
    return {"pred_label_ids": list(np.squeeze(mlb.transform([preds])))}

get_clf_report函式實作

def get_clf_report(ds):
    y_true = np.array(ds["label_ids"])
    y_pred = np.array(ds["pred_label_ids"])
    return classification_report(
        y_true, y_pred, target_names=mlb.classes_, zero_division=0,
        output_dict=True)

內容解密:

  1. get_preds函式:根據給定的閾值或Top-K引數,從預測結果中篩選出最終的預測標籤。
  2. get_clf_report函式:計算並傳回Scikit-learn的分類別報告,用於評估模型的效能。

零樣本學習與少樣本學習的實務探討

在許多自然語言處理(NLP)專案中,我們經常面臨標註資料匱乏的問題。本章節將探討如何在標註資料極少甚至沒有的情況下,仍能有效地訓練模型並獲得良好的效能。

調整零樣本學習的引數

首先,我們來探討零樣本學習(Zero-Shot Learning)中的一個關鍵技術:如何選擇適當的預測策略。我們將比較兩種不同的方法:Top-k 方法和閾值(Threshold)方法。

Top-k 方法的實驗結果

macros, micros = [], []
topks = [1, 2, 3, 4]
for topk in topks:
    ds_zero_shot = ds_zero_shot.map(get_preds, batched=False, fn_kwargs={'topk': topk})
    clf_report = get_clf_report(ds_zero_shot)
    micros.append(clf_report['micro avg']['f1-score'])
    macros.append(clf_report['macro avg']['f1-score'])

plt.plot(topks, micros, label='Micro F1')
plt.plot(topks, macros, label='Macro F1')
plt.xlabel("Top-k")
plt.ylabel("F1-score")
plt.legend(loc='best')
plt.show()

內容解密:

  1. 我們遍歷不同的 topk 值(1、2、3、4),並對每個值計算零樣本學習的預測結果。
  2. 使用 get_clf_report 函式獲得分類別報告,並提取微觀和宏觀 F1 分數。
  3. 將結果繪製成圖表,以直觀比較不同 topk 值下的效能變化。

實驗結果顯示,當 topk=1 時,即選擇分數最高的標籤作為預測結果時,模型表現最佳。這可能是因為大多數範例只有一個標籤。

閾值方法的實驗結果

macros, micros = [], []
thresholds = np.linspace(0.01, 1, 100)
for threshold in thresholds:
    ds_zero_shot = ds_zero_shot.map(get_preds, fn_kwargs={"threshold": threshold})
    clf_report = get_clf_report(ds_zero_shot)
    micros.append(clf_report["micro avg"]["f1-score"])
    macros.append(clf_report["macro avg"]["f1-score"])

plt.plot(thresholds, micros, label="Micro F1")
plt.plot(thresholds, macros, label="Macro F1")
plt.xlabel("Threshold")
plt.ylabel("F1-score")
plt.legend(loc="best")
plt.show()

best_t, best_micro = thresholds[np.argmax(micros)], np.max(micros)
print(f'Best threshold (micro): {best_t} with F1-score {best_micro:.2f}.')
best_t, best_macro = thresholds[np.argmax(macros)], np.max(macros)
print(f'Best threshold (macro): {best_t} with F1-score {best_macro:.2f}.')

內容解密:

  1. 我們測試了不同的閾值(從 0.01 到 1),並計算每個閾值下的 F1 分數。
  2. 將微觀和宏觀 F1 分數繪製成圖表,以觀察閾值對模型效能的影響。
  3. 輸出最佳閾值及其對應的 F1 分數。

結果表明,適當的閾值可以平衡精確率和召回率,但整體表現仍略遜於 Top-1 方法。

與基線模型的比較

接下來,我們將零樣本學習的結果與基線模型(Naive Bayes)進行比較。

ds_zero_shot = ds['test'].map(zero_shot_pipeline)
ds_zero_shot = ds_zero_shot.map(get_preds, fn_kwargs={'topk': 1})
clf_report = get_clf_report(ds_zero_shot)

for train_slice in train_slices:
    macro_scores['Zero Shot'].append(clf_report['macro avg']['f1-score'])
    micro_scores['Zero Shot'].append(clf_report['micro avg']['f1-score'])

plot_metrics(micro_scores, macro_scores, train_samples, "Zero Shot")

內容解密:

  1. 使用零樣本學習管道對測試集進行預測,並計算分類別報告。
  2. 將結果新增到宏觀和微觀分數列表中。
  3. 繪製不同訓練樣本數量下的效能比較圖表。

比較結果顯示,當標註樣本少於 50 時,零樣本學習明顯優於基線模型。即使在標註樣本較多時,零樣本學習在宏觀和微觀 F1 分數上仍表現出色。

提升零樣本學習效能的技巧

若在使用零樣本學習時遇到效能不佳的問題,可以嘗試以下方法:

  • 最佳化標籤名稱:確保標籤名稱具有意義且與文字相關。
  • 調整假設範本:嘗試不同的假設範本,以改善模型對文字的理解。

少樣本學習的探討

在大多數 NLP 專案中,我們至少擁有少量的標註範例。本文將介紹如何充分利用這些寶貴的標註資料。

資料增強技術

資料增強是一種常見的技術,透過對現有資料進行變換來生成新的訓練範例,從而提高模型的效能。

資料增強技術在文字分類別任務中的應用

在機器學習領域,資料增強(Data Augmentation)是一種常見的技術,用於擴增訓練資料集的大小,以提高模型的泛化能力。對於影像資料來說,資料增強相對簡單,例如旋轉、縮放、翻轉等操作,都能產生新的訓練樣本。然而,對於文字資料來說,資料增強則更為複雜,因為簡單的詞語或字元替換可能會完全改變文字的含義。

常見的文字資料增強技術

  1. 反向翻譯(Back Translation):將源語言文字翻譯成一種或多種目標語言,然後再翻譯回源語言。這種方法對於資源豐富的語言或不包含太多領域特定詞彙的語料函式庫效果最佳。

  2. 詞元擾動(Token Perturbations):對訓練集中的文字進行簡單的轉換,如隨機同義詞替換、詞插入、交換或刪除。

例項與應用

表9-2展示了不同型別的文字資料增強技術的範例。為了實作反向翻譯,可以使用像M2M100這樣的機器翻譯模型,而像NlpAug和TextAttack這樣的函式庫則提供了各種詞元擾動的方法。在本文中,我們將重點使用同義詞替換,因為它實作簡單且能傳達資料增強的主要思想。

使用NlpAug進行同義詞替換

from transformers import set_seed
import nlpaug.augmenter.word as naw

set_seed(3)
aug = naw.ContextualWordEmbsAug(model_path="distilbert-base-uncased", device="cpu", action="substitute")
text = "Transformers are the most popular toys"
print(f"Original text: {text}")
print(f"Augmented text: {aug.augment(text)}")

輸出結果顯示,原文字中的「are」被撇號替換,生成了一個新的合成訓練樣本。

資料增強函式實作

def augment_text(batch, transformations_per_example=1):
    text_aug, label_ids = [], []
    for text, labels in zip(batch["text"], batch["label_ids"]):
        text_aug += [text]
        label_ids += [labels]
        for _ in range(transformations_per_example):
            text_aug += [aug.augment(text)]
            label_ids += [labels]
    return {"text": text_aug, "label_ids": label_ids}

將此函式應用於資料集,並重新執行分析後,我們發現少量的資料增強使Naive Bayes分類別器的F1分數提高了約5個點。

使用大語言模型的嵌入向量進行查詢

大語言模型(如GPT-3)已被證明能夠很好地解決有限資料下的任務。它們學習到的文字表示包含了許多維度的資訊,如情感、主題、文字結構等。因此,這些模型的嵌入向量可以用於開發語義搜尋引擎、查詢相似檔案或評論,甚至進行文字分類別。

建立文字分類別器

  1. 使用語言模型嵌入所有已標記的文字。
  2. 對儲存的嵌入向量進行最近鄰搜尋。
  3. 聚合最近鄰的標籤以獲得預測結果。

此方法無需對模型進行微調,而是依賴於選擇一個適合的預訓練模型,該模型最好是在與你的資料集相似的領域上進行預訓練。