在自然語言處理領域中,文字生成技術已成為一個蓬勃發展的研究方向。從自動摘要、機器翻譯到內容創作,各種應用場景都需要高品質的文字生成能力。然而,如何評估這些生成模型的品質,以及如何在眾多模型中選擇最適合特定任務的一個,卻是一個充滿挑戰的問題。

四大模型的摘要能力比較

讓我們先來看一個實際例子,比較四個知名的生成模型:GPT-2、T5、BART和PEGASUS在摘要任務上的表現。以下是一段關於奧運會短跑比賽的新聞,以及這些模型生成的摘要:

原文:

美國隊以37.56秒獲得第二名,而在英國隊因交接棒失誤被取消資格後,加拿大隊獲得銅牌。

GPT-2生成的摘要:

Nesta,世界上最快的人。
Gatlin,史上最成功的奧運選手。
Kemar,牙買加傳奇。
Shelly-Ann,史上最快的女性。
Bolt,世界最偉大的運動員。
撐竿跳的團體運動

T5生成的摘要:

尤塞恩·博爾特在男子4x100米接力賽中贏得了他在世界錦標賽的第三枚金牌。
這位26歲的選手在俄羅斯首都的比賽中帶領牙買加隊獲勝。
他現在已經在錦標賽上獲得了八枚金牌,追平了紀錄。

BART生成的摘要:

尤塞恩·博爾特在莫斯科的世界錦標賽上獲得了他的第三枚金牌。
博爾特帶領牙買加隊在男子4x100米接力賽中獲勝。
這位26歲的選手現在已經在世界錦標賽上贏得了八枚金牌。
牙買加女隊也在接力賽中獲得金牌,擊敗了法國隊。

PEGASUS生成的摘要:

尤塞恩·博爾特獲得世界錦標賽第三金。
帶領牙買加隊在男子4x100米接力賽中獲勝。
這是博爾特在錦標賽上的第八枚金牌。
牙買加隊也贏得了女子4x100米接力賽。

摘要比較分析

觀察這些模型的輸出,第一個明顯的發現是GPT-2生成的摘要與其他模型有很大不同。GPT-2沒有提供原文的摘要,而是對人物進行了總結。這種現象在我的實踐中經常發生,GPT-2常會「幻想」或創造事實,因為它並非專門訓練用於生成真實摘要。比如,在撰寫本文時,Nesta並非世界上最快的人,而是排名第九。

將其他三個模型的摘要與真實情況相比,我們可以看到它們之間有顯著的重疊,其中PEGASUS的輸出與實際情況最為相似。

這個簡單的比較讓我們初步瞭解了各模型的表現,但這種比較方式並不繫統。在生產環境中選擇模型時,我們需要更嚴謹的方法。理想情況下,我們應該定義一個評估指標,在某個基準資料集上測量所有模型的表現,然後選擇效能最佳的那個。

但文字生成任務的評估指標如何定義呢?我們熟悉的準確率、召回率和精確度等標準指標並不容易應用於這類別任務。對於每個由人類撰寫的「黃金標準」摘要,可能還有數十個使用同義詞、改述或稍微不同的事實表述方式的摘要同樣可接受。

接下來,讓我們探討一些專門為評估生成文字品質而開發的常用指標。

生成文字品質的測量方法

良好的評估指標非常重要,因為我們不僅在訓練模型時使用它們,在生產環境中也會持續依賴它們。如果評估指標不佳,我們可能無法察覺模型效能的退化;如果評估指標與業務目標不一致,我們可能無法創造任何價值。

評估文字生成任務的效能並不像評估情感分析或命名實體識別等標準分類別任務那樣簡單。以翻譯為例,給定一個英文句子「I love dogs!」並翻譯成西班牙文,可能有多種有效的可能性,如「¡Me encantan los perros!」或「¡Me gustan los perros!」。簡單地檢查與參考翻譯的完全比對並不是最佳方法;即使是人類,在這種評估標準下也會表現不佳,因為我們每個人的寫作方式都略有不同(甚至根據一天或一年中的時間不同,我們自己的寫作風格也會有所變化)。

幸運的是,我們有替代方案。評估生成文字的兩個最常用指標是BLEU和ROUGE。讓我們來看它們的定義和原理。

BLEU評分機制詳解

BLEU(Bilingual Evaluation Understudy)的基本思想很簡單:不是看生成文字中有多少標記與參考文字標記完全對齊,而是看詞或n-gram(n個連續單詞的序列)。BLEU是一個根據精確度的指標,這意味著當我們比較兩個文字時,我們計算生成文字中出現在參考文字中的詞數,然後除以生成文字的長度。

然而,這種原始精確度計算存在一個問題。假設生成的文字只是一遍又一遍地重複同一個詞,而這個詞也出現在參考文字中。如果它重複的次數與參考文字的長度相同,那麼我們就會得到完美的精確度!因此,BLEU論文的作者引入了一個小修改:一個詞只被計算它在參考文字中出現的次數。

為了説明這一點,假設我們有參考文字「the cat is on the mat」和生成文字「the the the the the the」。

從這個簡單的例子中,我們可以計算精確度值如下:

p_vanilla = 6/6
p_mod = 2/6

我們可以看到,這個簡單的修正產生了一個更合理的值。現在讓我們擴充套件這個概念,不僅看單個詞,還要看n-gram。

假設我們有一個生成的句子snt,我們要將其與參考句子snt'進行比較。我們提取所有可能的n階n-gram,並進行計算以獲得精確度p_n

p_n = ∑(n-gram ∈ snt') Count_clip(n-gram) / ∑(n-gram ∈ snt) Count(n-gram)

為了避免獎勵重複的生成,分子中的計數被「剪裁」了。這意味著n-gram的出現計數上限為它在參考句子中出現的次數。另外請注意,這個等式中句子的定義並不是很嚴格,如果你有一個跨越多個句子的生成文字,你會將其視為一個句子。

這個公式計算了生成文字中n-gram的精確度。分子是生成文字中每個n-gram在參考文字中出現的次數(但上限是參考文字中該n-gram的實際出現次數),分母是生成文字中所有n-gram的總數。這樣可以防止模型透過重複使用參考文字中出現的詞語來獲得不合理的高分。

通常,我們要評估的測試集中有多個樣本,所以我們需要稍微擴充套件該等式,對語料函式庫C中的所有樣本進行求和:

p_n = ∑(snt ∈ C) ∑(n-gram ∈ snt') Count_clip(n-gram) / ∑(snt' ∈ C) ∑(n-gram ∈ snt) Count(n-gram)

我們快到終點了。由於我們不考慮召回率,所有短但精確的生成序列相比於更長的句子有優勢。因此,精確度分數偏好短的生成。為了補償這一點,BLEU的作者引入了一個額外的項,即簡短懲罰(brevity penalty):

BR = min(1, e^(1-ℓ_ref/ℓ_gen))

透過取最小值,我們確保這個懲罰永遠不超過1,而當生成文字的長度ℓ_gen小於參考文字的長度ℓ_ref時,指數項變得指數級小。

簡短懲罰是BLEU評分中的一個重要元素,它解決了模型可能透過生成非常短的文字來獲得高精確度的問題。當生成文字比參考文字短時,懲罰值會降低整體分數。例如,如果生成文字只有參考文字長度的一半,懲罰值約為0.61,這會顯著降低最終的BLEU分數。

到這裡,你可能會問,為什麼不使用F1分數來同時考慮召回率呢?答案是在翻譯資料集中,通常有多個參考句子而不是隻有一個,所以如果我們也測量召回率,我們會鼓勵使用所有參考文字中所有詞的翻譯。因此,最好在翻譯中尋求高精確度,並確保翻譯和參考具有相似的長度。

最後,我們可以把所有內容放在一起,得到BLEU分數的計算公式:

BLEU-N = BR × ∏(n=1 to N) p_n^(1/N)

最後一項是修正精確度的幾何平均值,最高到n-gram N。在實踐中,通常報告BLEU-4分數。

這是完整的BLEU計算公式,它將簡短懲罰(BR)與各階n-gram精確度的幾何平均相乘。使用幾何平均而非算術平均是為了確保所有階的n-gram都有合理的精確度;如果任何一階的精確度接近零,整體分數就會很低。BLEU-4意味著考慮了從1-gram到4-gram的所有精確度。

BLEU評分的侷限性

然而,你可能已經看出這個指標存在許多侷限性。例如,它不考慮同義詞,而與推導過程中的許多步驟似乎是臨時的、相當脆弱的啟發式方法。Rachel Tatman的部落格文章《在NLP中評估文字輸出:自擔風險使用BLEU》對BLEU的缺陷進行了精彩的闡述。

總的來説,文字生成領域仍在尋找更好的評估指標,克服像BLEU這樣的指標的侷限性是一個活躍的研究領域。BLEU指標的另一個弱點是它期望文字已經被標記化。如果不使用完全相同的文字標記化方法,這可能導致結果不同。SacreBLEU指標透過內部化標記化過程解決了這個問題。

選擇適合的評估框架

在實際應用中,選擇合適的評估指標需要考慮多個因素。首先,我們需要明確任務的目標和要求。對於摘要任務,我們可能更關心內容的準確性和完整性;而對於對話生成,流暢性和連貫性可能更為重要。

其次,我們需要考慮評估的效率和可行性。有些評估方法可能需要大量人工標注,這在資源有限的情況下不太實際。自動化評估指標如BLEU雖然有侷限性,但在大規模評估中仍然很有價值。

最後,我們可以考慮結合多種評估方法。例如,使用BLEU等自動指標進行初步篩選,然後對表現最佳的幾個模型進行人工評估,以獲得更全面的瞭解。

模型選擇與評估的實用建議

根據我在NLP領域的實踐經驗,以下是一些選擇和評估文字生成模型的實用建議:

  1. 明確定義任務目標:在評估模型之前,明確定義你的任務目標和成功標準。不同的應用場景可能需要不同的評估重點。

  2. 使用多種評估指標:不要僅依賴單一指標。結合使用BLEU、ROUGE等自動指標,以及人工評估,以獲得更全面的瞭解。

  3. 考慮領域特性:在特定領域(如醫療、法律或技術檔案)的應用中,領域知識的準確性可能比語言流暢性更重要。

  4. 定期重新評估:隨著時間的推移,資料分佈可能會發生變化,定期重新評估模型效能有助於及時發現問題。

  5. 關注實際使用者經驗:最終,模型的成功取決於它是否能夠滿足使用者的需求。收集和分析使用者反饋是評估模型效果的重要方式。

隨著深度學習和NLP技術的不斷發展,文字生成模型的能力也在不斷提升。未來,我們可能會看到更加精確、靈活與具有領域適應性的評估方法的出現。這些方法可能會更好地結合自動化評估和人工判斷,提供更全面、更可靠的模型效能衡量標準。

同時,隨著大模型語言的崛起,我們也需要思考如何評估這些模型在長文字生成、知識整合和推理能力等方面的表現。這些新興的評估需求將推動評估方法學的進一步發展。

文字生成技術的評估是一個不斷演進的領域,需要研究者和實踐者的共同努力來推進。透過更好的評估方法,我們能夠開發出更符合人類需求的文字生成系統,為各種應用場景提供更大的價值。

在實際應用中,我建議採用多角度評估策略,結合自動指標和人工判斷,並根據具體任務需求調整評估重點。只有這樣,我們才能真正選擇出最適合特定應用場景的文字生成模型。

文字生成技術的評估雖然充滿挑戰,但也充滿機遇。透過不斷改進評估方法,我們能夠更好地理解和提升模型效能,推動整個領域的進步。

從理論到實踐:評估生成文字的品質

在自然語言處理領域,我們不僅需要生成文字,還需要有效評估這些生成結果的品質。這對於開發高品質的文字生成模型至關重要。在實際應用中,我發現評估生成文字品質的工具可以幫助我們系統性地比較不同模型的表現,而不僅依賴主觀判斷。

Hugging Face 的 Datasets 函式庫提供了多種評估指標,讓我們能夠輕鬆地計算生成文字的分數。今天,我將深入介紹如何使用 SacreBLEU 和 ROUGE 這兩個常用指標來評估文字生成品質,特別是在機器翻譯和文字摘要任務中。

SacreBLEU:精確度優先的評估指標

SacreBLEU 是一個廣泛用於評估生成文字品質的指標,特別適用於機器翻譯任務。使用 Datasets 函式庫載入該指標非常簡單:

from datasets import load_metric
bleu_metric = load_metric("sacrebleu")

SacreBLEU 的工作原理

bleu_metric 物件是 Metric 類別的例項,它的工作方式類別似於聚合器:我們可以使用 add() 增加單個例項,或使用 add_batch() 增加整批樣本。增加完所有需要評估的樣本後,呼叫 compute() 方法計算指標。

讓我們看一個具體例子:

import pandas as pd
import numpy as np

# 增加一個預測和參考文字
bleu_metric.add(
    prediction="the the the the the the", 
    reference=["the cat is on the mat"]
)

# 計算 BLEU 分數
results = bleu_metric.compute(smooth_method="floor", smooth_value=0)
results["precisions"] = [np.round(p, 2) for p in results["precisions"]]

# 顯示結果
pd.DataFrame.from_dict(results, orient="index", columns=["Value"])

這段程式碼展示瞭如何計算 BLEU 分數。首先,我們增加了一個預測文字 “the the the the the the” 和參考文字 “the cat is on the mat”。然後,我們計算 BLEU 分數,設定 smooth_method="floor"smooth_value=0 來關閉平滑處理,這樣當 n-gram 中出現零計數時,分數會直接為零。最後,我們將結果轉換為 DataFrame 進行展示。

結果顯示:

  • 分數為 0.0
  • 1-gram 精確度為 33.33%(2/6)
  • 2/3/4-gram 精確度均為 0
  • 幾何平均為零,因此 BLEU 分數也為零

值得注意的是,BLEU 支援多參考翻譯,這就是為什麼 reference 引數以列表形式傳遞。為了使指標在 n-gram 中的零計數情況下更平滑,BLEU 整合了修改精確度計算的方法,例如在分子中增加常數,這樣缺少 n-gram 不會自動使分數變為零。

更接近的預測範例

現在讓我們看另一個例子,其中預測文字更接近參考文字:

bleu_metric.add(
    prediction="the cat is on mat", 
    reference=["the cat is on the mat"]
)

results = bleu_metric.compute(smooth_method="floor", smooth_value=0)
results["precisions"] = [np.round(p, 2) for p in results["precisions"]]
pd.DataFrame.from_dict(results, orient="index", columns=["Value"])

在這個例子中,預測文字是 “the cat is on mat”,與參考文字 “the cat is on the mat” 只差一個 “the”。結果顯示精確度分數有明顯改善:

  • 1-gram 精確度為 100%
  • 2-gram 精確度為 75%
  • 3-gram 精確度為 66.67%
  • 4-gram 精確度為 50%

對於 4-gram,只有兩個候選項 [“the”, “cat”, “is”, “on”] 和 [“cat”, “is”, “on”, “mat”],其中後者不比對,因此精確度為 0.5。

BLEU 分數在機器翻譯評估中被廣泛使用,因為精確的翻譯通常比包含所有可能和適當詞彙的翻譯更受青睞。

ROUGE:召回率優先的評估指標

在某些應用中,如文字摘要,情況有所不同。我們希望生成的文字包含所有重要訊息,因此更偏向高召回率。這時,ROUGE 分數通常是更適合的選擇。

ROUGE 的工作原理

ROUGE (Recall-Oriented Understudy for Gisting Evaluation) 是專門為摘要等應用開發的,其中高召回率比精確度更重要。其方法與 BLEU 類別似,也是檢視不同 n-gram 在生成文字和參考文字中的出現情況。

不同之處在於,ROUGE 檢查參考文字中有多少 n-gram 也出現在生成文字中。而 BLEU 則相反,檢查生成文字中有多少 n-gram 出現在參考文字中。

研究發現,完全移除精確度可能會產生強烈的負面影響。因此,現代 ROUGE 實作通常會同時測量精確度和召回率,然後將兩者結合在調和平均數中得到 F1 分數。

ROUGE-L:最長公共子串

ROUGE 還有一個單獨的分數 ROUGE-L,用於測量最長公共子串 (LCS)。LCS 可以為任何字元串對計算。例如,“abab” 和 “abc” 的 LCS 是 “ab”,其長度為 2。

為了在樣本間比較這個值,需要進行正規化,否則較長的文字會有優勢。ROUGE 的發明者提出了一種類別似 F 分數的方案,其中 LCS 用參考和生成文字的長度進行正規化,然後將兩個正規化分數混合在一起。

在 Datasets 實作中,計算了兩種 ROUGE 變體:一種按句子計算分數並取摘要的平均值 (ROUGE-L),另一種直接在整個摘要上計算 (ROUGE-Lsum)。

使用 ROUGE 評估摘要

我們可以像這樣載入 ROUGE 指標:

rouge_metric = load_metric("rouge")

接下來,讓我們將 ROUGE 分數應用於之前使用 GPT-2 和其他模型生成的摘要:

reference = dataset["train"][1]["highlights"]
records = []
rouge_names = ["rouge1", "rouge2", "rougeL", "rougeLsum"]

for model_name in summaries:
    rouge_metric.add(prediction=summaries[model_name], reference=reference)
    score = rouge_metric.compute()
    rouge_dict = dict((rn, score[rn].mid.fmeasure) for rn in rouge_names)
    records.append(rouge_dict)

pd.DataFrame.from_records(records, index=summaries.keys())

這段程式碼對之前使用不同模型生成的摘要應用 ROUGE 指標。首先,我們定義參考摘要和要評估的 ROUGE 版本。然後,對每個模型生成的摘要,我們計算 ROUGE 分數並將結果儲存在字典中。最後,我們將所有結果整合到一個 DataFrame 中進行比較。

結果表明:

  • GPT-2 表現最差,這不足為奇,因為它是唯一個沒有明確訓練用於摘要的模型
  • 簡單的三句話基線相比於擁有數十億引數的 transformer 模型表現並不太差!
  • PEGASUS 和 BART 整體表現最佳,但 T5 在 ROUGE-1 和 LCS 分數上略勝一籌

需要注意的是,這些結果僅根據單個範例,因此不太可靠。讓我們在更大的資料集上評估模型效能。

在 CNN/DailyMail 資料集上評估 PEGASUS

現在我們已經掌握了評估模型所需的所有元素:有一個包含測試集的 CNN/DailyMail 資料集,有 ROUGE 指標,還有一個摘要模型。讓我們先評估三句話基線的效能:

def evaluate_summaries_baseline(dataset, metric,
                               column_text="article",
                               column_summary="highlights"):
    summaries = [three_sentence_summary(text) for text in dataset[column_text]]
    metric.add_batch(predictions=summaries,
                    references=dataset[column_summary])
    score = metric.compute()
    return score

這個函式接受一個資料集和一個評估指標,然後對資料集中的每篇文章應用三句話摘要方法,並使用指定的指標評估生成的摘要。它回傳計算出的分數。

由於 CNN/DailyMail 資料集的測試部分有大約 10,000 個樣本,為所有這些文章生成摘要需要大量時間。為了保持計算相對快速,我們將對測試集進行子取樣,並在 1,000 個樣本上執行評估:

test_sampled = dataset["test"].shuffle(seed=42).select(range(1000))
score = evaluate_summaries_baseline(test_sampled, rouge_metric)
rouge_dict = dict((rn, score[rn].mid.fmeasure) for rn in rouge_names)
pd.DataFrame.from_dict(rouge_dict, orient="index", columns=["baseline"]).T

結果顯示,基線模型在更大的測試集上的分數大多低於之前的範例,但仍優於 GPT-2!

評估 PEGASUS 模型

現在讓我們實作一個評估函式來評估 PEGASUS 模型:

from tqdm import tqdm
import torch

device = "cuda" if torch.cuda.is_available() else "cpu"

def chunks(list_of_elements, batch_size):
    """Yield successive batch-sized chunks from list_of_elements."""
    for i in range(0, len(list_of_elements), batch_size):
        yield list_of_elements[i : i + batch_size]

def evaluate_summaries_pegasus(dataset, metric, model, tokenizer,
                              batch_size=16, device=device,
                              column_text="article",
                              column_summary="highlights"):
    article_batches = list(chunks(dataset[column_text], batch_size))
    target_batches = list(chunks(dataset[column_summary], batch_size))
    
    for article_batch, target_batch in tqdm(
        zip(article_batches, target_batches), total=len(article_batches)):
        inputs = tokenizer(article_batch, max_length=1024, truncation=True,
                         padding="max_length", return_tensors="pt")
        
        summaries = model.generate(input_ids=inputs["input_ids"].to(device),
                                 attention_mask=inputs["attention_mask"].to(device),
                                 length_penalty=0.8, num_beams=8, max_length=128)
        
        decoded_summaries = [tokenizer.decode(s, skip_special_tokens=True,
                                           clean_up_tokenization_spaces=True)
                          for s in summaries]
        
        decoded_summaries = [d.replace("<n>", " ") for d in decoded_summaries]
        metric.add_batch(predictions=decoded_summaries, references=target_batch)
    
    score = metric.compute()
    return score

這個評估函式分解了評估過程:

  1. 首先將資料集分割成較小的批次,以便同時處理
  2. 對每個批次,對輸入文章進行標記化,並使用 generate() 函式透過集束搜尋生成摘要
  3. 使用論文中提出的相同生成引數,包括長度懲罰以確保模型不生成過長的序列
  4. 解碼生成的文字,替換 <n> 標記,並將解碼後的文字與參考一起增加到指標中
  5. 最後計算並回傳 ROUGE 分數

讓我們使用 AutoModelForSeq2SeqLM 類別(用於序列到序列生成任務)再次載入模型並進行評估:

from transformers import AutoModelForSeq2SeqLM, AutoTokenizer

model_ckpt = "google/pegasus-cnn_dailymail"
tokenizer = AutoTokenizer.from_pretrained(model_ckpt)
model = AutoModelForSeq2SeqLM.from_pretrained(model_ckpt).to(device)

score = evaluate_summaries_pegasus(test_sampled, rouge_metric,
                                 model, tokenizer, batch_size=8)
rouge_dict = dict((rn, score[rn].mid.fmeasure) for rn in rouge_names)
pd.DataFrame(rouge_dict, index=["pegasus"])

評估結果顯示,PEGASUS 模型在 1,000 個樣本的子集上的 ROUGE 分數與發表的結果非常接近。值得注意的是,損失和每個標記的準確率在一定程度上與 ROUGE 分數解耦。損失獨立於解碼策略,而 ROUGE 分數則受到解碼方法的影響。

評估指標的選擇與應用

在實際開發中,選擇合適的評估指標對於最佳化模型至關重要。根據我的經驗,不同的應用場景需要不同的評估策略:

  1. 機器翻譯 - BLEU 分數更為適合,因為它強調精確度,確保生成的翻譯準確無誤
  2. 文字摘要 - ROUGE 分數更為適合,特別是 ROUGE-L,因為它強調召回率,確保重要訊息被保留
  3. 對話系統 - 可能需要結合多種指標,包括詞彙多樣性和上下文連貫性

此外,自動評估指標雖然有助於系統性比較,但它們並不完全捕捉人類對生成文字品質的判斷。在關鍵應用中,我建議將自動指標與人工評估相結合,以獲得更全面的品質評估。

評估指標的侷限性

使用這些評估指標時,我們需要意識到它們的侷限性:

  1. 參考依賴 - 這些指標依賴於參考文字,而實際上可能存在多種同樣好的表達方式
  2. 表面相似性 - 它們主要測量表面上的詞彙重疊,而非語義理解
  3. 語境忽略 - 它們通常不考慮更廣泛的文字語境和連貫性
  4. 人類判斷差異 - 自動指標與人類判斷之間存在差距,特別是在評估創意文字時

在實踐中,我發現將這些指標作為開發過程中的指導而非絕對標準是最有效的方法。它們提供了一個系統性的框架來比較不同的模型和方法,但最終判斷還應結合具體應用場景和使用者需求。

#與實踐建議

評估生成文字的品質是自然語言處理中的關鍵挑戰。透過 SacreBLEU 和 ROUGE 等指標,我們可以系統性地比較不同模型的表現,並為模型選擇和最佳化提供依據。

在實際應用中,我建議:

  1. 根據任務特性選擇適當的評估指標,翻譯任務傾向於使用 BLEU,摘要任務傾向於使用 ROUGE
  2. 使用足夠大的測試集進行評估,以獲得統計上有意義的結果
  3. 結合多種指標進行全面評估,不要僅依賴單一指標
  4. 認識到自動指標的侷限性,必要時輔以人工評估
  5. 將評估結果與實際應用場景結合,考慮最終使用者的需求和期望

透過這種系統性的評估方法,我們可以不斷改進文字生成模型,朝著生成更自然、更有用的文字的目標前進。

在下一篇文章中,我將探討如何最佳化生成策略,以進一步提高模型的效能。

文字摘要評估指標的實用性與侷限

在發展文字摘要系統時,選擇合適的評估指標至關重要。從實際經驗來看,ROUGE和BLEU這類別根據n-gram比對的指標與人類判斷的相關性明顯優於簡單的損失函式或準確率指標。在建立文字生成模型時,我們應該優先關注這些指標,並謹慎探索和選擇解碼策略。

然而,即使是ROUGE和BLEU這樣的標準指標也遠非完美。在開發摘要系統時,我發現這些自動評估指標常無法捕捉到摘要的連貫性、事實準確性以及上下文相關性等重要特質。因此,在實際應用中,這些指標應該與人工評估相輔相成,而非完全取代人類判斷。

有了評估函式作為指導,接下來讓我們實際訓練一個文字摘要模型,並探討其在實際應用中的表現。

對話摘要模型訓練實戰

SAMSum資料集介紹與分析

為了建立一個實用的文字摘要模型,我選擇了三星開發的SAMSum資料集作為訓練基礎。這個資料集由對話內容及其簡明摘要組成,非常適合企業場景中的應用,例如客戶與支援中心之間的互動摘要,這能有效改善客戶服務並識別客戶請求中的常見模式。

讓我們先載入資料集並檢視一個範例:

dataset_samsum = load_dataset("samsum")
split_lengths = [len(dataset_samsum[split]) for split in dataset_samsum]
print(f"Split lengths: {split_lengths}")
print(f"Features: {dataset_samsum['train'].column_names}")
print("\nDialogue:")
print(dataset_samsum["test"][0]["dialogue"])
print("\nSummary:")
print(dataset_samsum["test"][0]["summary"])

執行上述程式碼,我們得到以下輸出:

Split lengths: [14732, 819, 818]
Features: ['id', 'dialogue', 'summary']

Dialogue:
Hannah: Hey, do you have Betty's number?
Amanda: Lemme check
Hannah: <file_gif>
Amanda: Sorry, can't find it.
Amanda: Ask Larry
Amanda: He called her last time we were at the park together
Hannah: I don't know him well
Hannah: <file_gif>
Amanda: Don't be shy, he's very nice
Hannah: If you say so..
Hannah: I'd rather you texted him
Amanda: Just text him
Hannah: Urgh.. Alright
Hannah: Bye
Amanda: Bye bye

Summary:
Hannah needs Betty's number but Amanda doesn't have it. She needs to contact Larry.

這段程式碼展示了SAMSum資料集的基本結構。從輸出可以看出,資料集分為訓練集(14732筆)、驗證集(819筆)和測試集(818筆)。每筆資料包含三個欄位:id、dialogue(對話內容)和summary(摘要)。範例中的對話模擬了SMS或WhatsApp中的聊天內容,包含表情符號和GIF佔位符。摘要則簡潔地概括了對話的重點。

PEGASUS模型在SAMSum上的表現評估

在開始訓練前,我想先評估預訓練的PEGASUS模型在SAMSum資料集上的表現。這可以作為我們的基準線,幫助我們瞭解微調的效果。

pipe_out = pipe(dataset_samsum["test"][0]["dialogue"])
print("Summary:")
print(pipe_out[0]["summary_text"].replace(" .<n>", ".\n"))

產生的摘要如下:

Summary:
Amanda: Ask Larry Amanda: He called her last time we were at the park together.
Hannah: I'd rather you texted him.
Amanda: Just text him .

這段程式碼使用預訓練的PEGASUS模型對測試集中的第一個對話進行摘要。從結果可以看出,模型主要採用了抽取式摘要的方法,從對話中提取關鍵句子。這種方法可能在CNN/DailyMail等新聞資料集上表現不錯,但SAMSum的摘要更傾向於抽象式的,需要理解對話內容並用新的語言表達。

為了確認這一觀察,我們對整個測試集進行ROUGE評估:

score = evaluate_summaries_pegasus(dataset_samsum["test"], rouge_metric, model,
                                  tokenizer, column_text="dialogue",
                                  column_summary="summary", batch_size=8)
rouge_dict = dict((rn, score[rn].mid.fmeasure) for rn in rouge_names)
pd.DataFrame(rouge_dict, index=["pegasus"])

評估結果:

           rouge1    rouge2   rougeL   rougeLsum
pegasus   0.296168  0.087803  0.229604  0.229514

這段程式碼對PEGASUS模型在整個測試集上的表現進行了ROUGE評估。結果顯示,模型在各項ROUGE指標上的表現都不理想,特別是ROUGE-2(衡量連續兩個詞的比對度)僅達到0.088。這印證了我們的猜測:預訓練的PEGASUS模型並不適合直接用於對話摘要任務,因為它的訓練資料分佈與SAMSum差異較大。

在實際開發中,我經常在模型訓練前先建立評估管道,這有兩個優勢:一是可以直接用指標衡量訓練成功與否,二是能提供一個良好的基準線。如果微調後模型在ROUGE指標上沒有立即改善,就説明訓練過程可能出了問題。

PEGASUS模型微調準備

在處理資料進行訓練前,讓我們先了解輸入和輸出的長度分佈:

d_len = [len(tokenizer.encode(s)) for s in dataset_samsum["train"]["dialogue"]]
s_len = [len(tokenizer.encode(s)) for s in dataset_samsum["train"]["summary"]]
fig, axes = plt.subplots(1, 2, figsize=(10, 3.5), sharey=True)
axes[0].hist(d_len, bins=20, color="C0", edgecolor="C0")
axes[0].set_title("Dialogue Token Length")
axes[0].set_xlabel("Length")
axes[0].set_ylabel("Count")
axes[1].hist(s_len, bins=20, color="C0", edgecolor="C0")
axes[1].set_title("Summary Token Length")
axes[1].set_xlabel("Length")
plt.tight_layout()
plt.show()

這段程式碼分析了訓練集中對話和摘要的標記(token)長度分佈。從生成的直方圖可以看出,大多數對話的長度在100-200個標記之間,明顯短於CNN/DailyMail的文章。同樣,摘要也相對簡短,大約在20-40個標記之間(相當於一條推文的長度)。這種長度分析對設定模型的最大輸入長度和批次大小非常有幫助。

接下來,我們需要對資料集進行標記化處理。根據前面的分析,我們將對話和摘要的最大長度分別設為1024和128:

def convert_examples_to_features(example_batch):
    input_encodings = tokenizer(example_batch["dialogue"], max_length=1024,
                              truncation=True)
    
    with tokenizer.as_target_tokenizer():
        target_encodings = tokenizer(example_batch["summary"], max_length=128,
                                   truncation=True)
    
    return {"input_ids": input_encodings["input_ids"],
           "attention_mask": input_encodings["attention_mask"],
           "labels": target_encodings["input_ids"]}

dataset_samsum_pt = dataset_samsum.map(convert_examples_to_features,
                                     batched=True)
columns = ["input_ids", "labels", "attention_mask"]
dataset_samsum_pt.set_format(type="torch", columns=columns)

這段程式碼定義了一個函式,將對話和摘要轉換為模型可接受的特徵格式。注意tokenizer.as_target_tokenizer()上下文管理器的使用 - 某些模型在解碼器輸入中需要特殊標記,這個上下文告訴標記器它正在為解碼器進行標記化處理。

函式回傳三個關鍵元素:

  1. input_ids:對話內容的標記ID
  2. attention_mask:指示哪些標記應被模型注意
  3. labels:摘要的標記ID,作為訓練目標

接著,我們使用map函式將整個資料集轉換為PyTorch格式的張量。

序列到序列資料整理與教師強制

在訓練序列到序列(seq2seq)模型時,資料整理是一個關鍵步驟。我們需要建立一個資料整理器(collator),它在批次被送入模型前被呼叫。對於摘要任務,我們不僅需要堆積積疊輸入,還需要準備解碼器側的目標。

PEGASUS是一個編碼器-解碼器Transformer模型,採用經典的seq2seq架構。在seq2seq設定中,一種常見的方法是在解碼器中應用"教師強制"(teacher forcing)。使用這種策略,解碼器除了接收編碼器輸出外,還接收由一個位置偏移的標籤作為輸入標記。這樣,在預測下一個標記時,解碼器能看到偏移一位的真實標籤作為輸入。

例如,當生成"Transformers are awesome for text summarization"這句話時,解碼器的輸入和標籤如下:

步驟解碼器輸入標籤
1[PAD]Transformers
2[PAD, Transformers]are
3[PAD, Transformers, are]awesome
4[PAD, Transformers, are, awesome]for
5[PAD, Transformers, are, awesome, for]text
6[PAD, Transformers, are, awesome, for, text]summarization

我們之所以將標籤偏移一位,是為了確保解碼器只能看到先前的真實標籤,而不是當前或未來的標籤。這種偏移足以實作這一目標,因為解碼器使用遮罩自注意力機制,會遮罩當前和未來的所有輸入。

在準備批次時,我們透過將標籤向右偏移一位來設定解碼器輸入。之後,我們確保標籤中的填充標記被損失函式忽略,方法是將它們設為-100。不過,我們實際上不需要手動完成這些步驟,因為DataCollatorForSeq2Seq可以為我們處理所有這些工作:

from transformers import DataCollatorForSeq2Seq
seq2seq_data_collator = DataCollatorForSeq2Seq(tokenizer, model=model)

這段程式碼建立了一個專門用於序列到序列模型的資料整理器。DataCollatorForSeq2Seq會自動處理標籤偏移、填充遮罩等操作,大簡化了我們的工作。這是Hugging Face Transformers函式庫的一個強大特性,它處理了許多序列到序列訓練中的複雜細節。

設定訓練引數與梯度累積策略

接下來,我們設定訓練引數:

from transformers import TrainingArguments, Trainer
training_args = TrainingArguments(
    output_dir='pegasus-samsum', num_train_epochs=1, warmup_steps=500,
    per_device_train_batch_size=1, per_device_eval_batch_size=1,
    weight_decay=0.01, logging_steps=10, push_to_hub=True,
    evaluation_strategy='steps', eval_steps=500, save_steps=1e6,
    gradient_accumulation_steps=16)

這段程式碼設定了訓練的各種引數。有一個特別值得注意的引數是gradient_accumulation_steps。由於PEGASUS模型較大,我們不得不將批次大小設為1。然而,太小的批次大小可能會影響收斂。

為瞭解決這個問題,我採用了梯度累積技術。顧名思義,這種技術不是一次性計算整個批次的梯度,而是處理更小的批次並累積梯度。當累積了足夠的梯度後,才執行最佳化步驟。這種方法雖然比一次性處理慢一些,但能有效模擬更大批次的效果,而不會超出GPU記憶體限制。

在實際應用中,我發現梯度累積是處理大型模型時的一個重要策略。它允許我們在有限資源下有效訓練複雜模型,尤其是在處理像PEGASUS這樣的大型預訓練模型時。