自 2017 年問世以來,Transformer 架構迅速成為自然語言處理(NLP)領域的主導力量。這種創新的神經網路架構不僅在各種語言任務上創下了令人矚目的成績,更徹底改變了我們建構語言模型的方式。從生成逼真的新聞報導、改善搜尋引擎查詢結果,到建立能説笑話的聊天機器人,Transformer 的應用範圍令人驚嘆。
作為一名深耕 NLP 領域多年的技術工作者,玄貓觀察到 Transformer 模型的崛起代表了一個重要的技術轉折點。過去幾年,我見證了這項技術如何從學術研究迅速發展為實用工具,並在各種應用中展現出驚人的潛力。
在這篇技術中,我將帶領大家深入瞭解 Transformer 架構,以及如何使用 Hugging Face 提供的強大工具來開發各種 NLP 應用。無論你是資料科學家、開發者,還是對自然語言處理有興趣的技術愛好者,這篇文章都將幫助你掌握這項改變遊戲規則的技術。
Transformer 模型的崛起與影響
Transformer 架構的出現可以説是 NLP 領域的一次重大突破。在此之前,迴圈神經網路(RNN)和長短期記憶網路(LSTM)是處理序列資料的主流選擇。然而,這些模型在處理長序列時面臨著嚴重的瓶頸,尤其是在捕捉長距離依賴關係方面表現不佳。
Transformer 透過一種稱為「注意力機制」(Attention Mechanism)的創新方法解決了這個問題。這種機制允許模型直接關注輸入序列中的任何部分,無需按順序處理資料,從而實作了更高效的平行計算和更好的長距離依賴關係捕捉能力。
這種架構的另一個重要優勢是可擴充套件性。與傳統的 RNN 相比,Transformer 模型可以更有效地利用現代 GPU 和 TPU 的平行計算能力,使得訓練更大規模的模型成為可能。這直接促成了 BERT、GPT、T5 等大型預訓練語言模型的誕生,這些模型徹底改變了 NLP 領域的發展方向。
Hugging Face:降低 Transformer 應用門檻
儘管 Transformer 模型功能強大,但實際應用這些技術仍然面臨著不少挑戰。這就是 Hugging Face 生態系統的價值所在。
Hugging Face Transformers 是一個 Python 深度學習函式庫,它提供了一套易用的 API 來使用預訓練模型,大幅降低了 NLP 技術的應用門檻。在我的實踐中,這個函式庫的出現極大地加速了從概念到實際應用的過程,讓開發者可以專注於解決實際問題,而非陷入模型實作的技術細節。
Hugging Face 的另一個重要貢獻是建立了一個豐富的模型與資料集生態系統。Model Hub 上有數千個預訓練模型,涵蓋了從通用語言理解到專業領域的各種需求。這種開放分享的理念顯著加速了 NLP 技術的發展和應用。
Transformer 架構深度解析
要有效地應用 Transformer 模型,首先需要理解其核心機制。在這一部分,我將解析 Transformer 的基本架構和工作原理。
注意力機制:Transformer 的核心
Transformer 的核心創新在於其注意力機制,特別是「自注意力」(Self-Attention)機制。這種機制允許模型在處理每個輸入元素時,考慮整個輸入序列中的所有其他元素。
在實踐中,自注意力機制透過計算查詢(Query)、鍵(Key)和值(Value)之間的關係來實作。對於輸入序列中的每個位置,模型會計算一個注意力分數,表示該位置與序列中其他位置的關聯強度。這些分數隨後用於加權值向量,生成該位置的上下文表示。
以下是自注意力機制的簡化實作:
import torch
import torch.nn.functional as F
def self_attention(query, key, value):
# 計算注意力分數
scores = torch.matmul(query, key.transpose(-2, -1)) / torch.sqrt(torch.tensor(key.shape[-1], dtype=torch.float32))
# 應用 softmax 獲得注意力權重
attention_weights = F.softmax(scores, dim=-1)
# 加權求和得到上下文表示
output = torch.matmul(attention_weights, value)
return output, attention_weights
這段程式碼實作了 Transformer 中的自注意力機制核心計算過程。首先,透過將查詢矩陣(query)與鍵矩陣(key)的轉置相乘來計算注意力分數,並除以鍵向量維度的平方根進行縮放,這是為了防止在高維度情況下梯度消失問題。接著使用 softmax 函式將分數轉換為機率分佈,得到注意力權重。最後,將這些權重與值矩陣(value)相乘,得到加權後的上下文表示。這個過程使模型能夠「關注」輸入序列中最相關的部分,有效捕捉長距離依賴關係。
多頭注意力機制
單一的自注意力機制雖然強大,但它只能從一個角度捕捉序列中的關係。為了增強模型的表達能力,Transformer 引入了「多頭注意力」(Multi-Head Attention)機制,允許模型同時從多個角度學習不同的表示。
在多頭注意力中,查詢、鍵和值首先被線性投影到多個子網路,然後在每個子網路中獨立計算自注意力。最後,這些平行計算的結果被連線起來並再次線性投影,形成最終輸出。
class MultiHeadAttention(torch.nn.Module):
def __init__(self, d_model, num_heads):
super().__init__()
self.num_heads = num_heads
self.d_model = d_model
# 確保 d_model 可以被 num_heads 整除
assert d_model % num_heads == 0
self.depth = d_model // num_heads
# 定義線性投影層
self.wq = torch.nn.Linear(d_model, d_model)
self.wk = torch.nn.Linear(d_model, d_model)
self.wv = torch.nn.Linear(d_model, d_model)
self.final_linear = torch.nn.Linear(d_model, d_model)
def split_heads(self, x, batch_size):
# 將輸入分割成多個頭
x = x.view(batch_size, -1, self.num_heads, self.depth)
return x.permute(0, 2, 1, 3)
def forward(self, query, key, value):
batch_size = query.shape[0]
# 線性投影
query = self.wq(query)
key = self.wk(key)
value = self.wv(value)
# 分割頭
query = self.split_heads(query, batch_size)
key = self.split_heads(key, batch_size)
value = self.split_heads(value, batch_size)
# 計算注意力
scaled_attention, attention_weights = self_attention(query, key, value)
# 重組多頭結果
scaled_attention = scaled_attention.permute(0, 2, 1, 3).contiguous()
concat_attention = scaled_attention.view(batch_size, -1, self.d_model)
# 最終線性投影
output = self.final_linear(concat_attention)
return output, attention_weights
這個 MultiHeadAttention
類別實作了 Transformer 中的多頭注意力機制。該機制的核心思想是讓模型能夠同時從不同的表示子網路學習訊息。
首先,初始化方法設定了頭數(num_heads
)和模型維度(d_model
),並確保模型維度可以被頭數整除。然後定義了三個線性層用於投影查詢、鍵和值,以及一個用於合併多頭輸出的最終線性層。
split_heads
方法將輸入張量重塑並置換維度,使其適合多頭處理。在前向傳播中,輸入首先透過線性層投影,然後分割為多個頭。每個頭獨立計算自注意力,之後將所有頭的輸出重新組合並透過最終線性層。這種設計允許模型平行地關注不同的特徵模式,大增強了模型的表達能力。
位置編碼:解決位置訊息缺失問題
與 RNN 不同,Transformer 在處理輸入時不考慮序列順序,這意味著它缺乏位置訊息。為瞭解決這個問題,Transformer 引入了「位置編碼」(Positional Encoding),將位置訊息直接注入到輸入嵌入中。
原始 Transformer 使用正弦和餘弦函式的組合來產生位置編碼,這種方法有一個有趣的性質:它允許模型外推到比訓練時見過的序列更長的序列。
def get_positional_encoding(sequence_length, d_model):
position = torch.arange(sequence_length).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model))
pos_encoding = torch.zeros(sequence_length, d_model)
pos_encoding[:, 0::2] = torch.sin(position * div_term)
pos_encoding[:, 1::2] = torch.cos(position * div_term)
return pos_encoding
這個函式實作了 Transformer 中的位置編碼生成。位置編碼是解決 Transformer 模型無法感知序列順序問題的關鍵機制。
函式接收序列長度和模型維度作為引數,然後建立一個位置編碼矩陣。具體實作使用了正弦和餘弦函式的組合:對於每個位置,偶數索引使用正弦函式,奇數索引使用餘弦函式。這些函式的頻率按位置遞減,使得每個位置都有獨特的編碼。
這種根據三角函式的位置編碼有一個重要特性:它允許模型理解相對位置關係,因為任何固定偏移的位置之間都存線上性關係。這使得模型能夠泛化到訓練中未見過的序列長度,是 Transformer 架構的一個巧妙設計。
前饋神經網路與層標準化
除了注意力機制外,Transformer 還包含前饋神經網路(Feed-Forward Network,FFN)和層標準化(Layer Normalization)元件。
前饋網路通常由兩個線性變換與一個非線性啟用函式(如 ReLU)組成,用於處理注意力機制輸出的特徵。層標準化則有助於穩定訓練過程,加速收斂。
class FeedForward(torch.nn.Module):
def __init__(self, d_model, d_ff):
super().__init__()
self.linear1 = torch.nn.Linear(d_model, d_ff)
self.linear2 = torch.nn.Linear(d_ff, d_model)
def forward(self, x):
return self.linear2(torch.nn.functional.relu(self.linear1(x)))
class EncoderLayer(torch.nn.Module):
def __init__(self, d_model, num_heads, d_ff, dropout_rate=0.1):
super().__init__()
self.mha = MultiHeadAttention(d_model, num_heads)
self.ffn = FeedForward(d_model, d_ff)
self.layernorm1 = torch.nn.LayerNorm(d_model)
self.layernorm2 = torch.nn.LayerNorm(d_model)
self.dropout1 = torch.nn.Dropout(dropout_rate)
self.dropout2 = torch.nn.Dropout(dropout_rate)
def forward(self, x):
# 多頭注意力與殘差連線
attn_output, _ = self.mha(x, x, x)
attn_output = self.dropout1(attn_output)
out1 = self.layernorm1(x + attn_output)
# 前饋網路與殘差連線
ffn_output = self.ffn(out1)
ffn_output = self.dropout2(ffn_output)
out2 = self.layernorm2(out1 + ffn_output)
return out2
這段程式碼定義了 Transformer 編碼器層的兩個核心元件:前饋神經網路和完整的編碼器層結構。
FeedForward
類別實作了一個簡單的前饋網路,由兩個線性變換和一個 ReLU 啟用函式組成。這個網路處理每個位置的特徵,允許模型學習更複雜的非線性關係。
EncoderLayer
類別則實作了完整的 Transformer 編碼器層。它包含多頭注意力機制、前饋網路、層標準化和 dropout 層。前向傳播過程中使用了兩個殘差連線(Residual Connection):一個在多頭注意力之後,另一個在前饋網路之後。這些殘差連線有助於解決深度網路中的梯度消失問題,使得訊息可以更有效地向後傳播。
層標準化則幫助穩定訓練過程,透過標準化每個樣本的特徵來減少內部協變數偏移(Internal Covariate Shift),從而加速模型收斂。整個設計使 Transformer 能夠高效地學習複雜的語言表示。
編碼器-解碼器架構
完整的 Transformer 模型由編碼器和解碼器兩部分組成,形成一個編碼器-解碼器(Encoder-Decoder)架構。編碼器負責處理輸入序列並生成上下文表示,而解碼器則利用這些表示生成輸出序列。
解碼器的結構類別似於編碼器,但增加了一個額外的注意力層,用於關注編碼器的輸出。此外,解碼器的自注意力機制使用遮罩(Mask)來確保生成過程中只能看到已經生成的輸出。
這種架構特別適合機器翻譯等序列到序列(Sequence-to-Sequence)任務,但在實踐中,我們經常只使用編碼器(如 BERT)或只使用解碼器(如 GPT)部分,取決於具體任務需求。
Hugging Face Transformers 函式庫入門
瞭解了 Transformer 的基本原理後,讓我們看如何使用 Hugging Face Transformers 函式庫來實際應用這些模型。
安裝與基本設定
首先,我們需要安裝 Transformers 函式庫及其依賴:
pip install transformers datasets
如果你計劃使用 GPU 加速,還應該安裝適當版本的 PyTorch 或 TensorFlow。
使用預訓練模型:文字分類別範例
讓我們從一個簡單的文字分類別任務開始。以下範例展示瞭如何使用預訓練的 BERT 模型來對電影評論進行情感分析:
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
# 載入預訓練的模型和分詞器
model_name = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name)
# 準備輸入文字
texts = ["I love this movie, it's amazing!",
"This film is terrible, I hated it."]
# 對文字進行分詞和編碼
inputs = tokenizer(texts, padding=True, truncation=True, return_tensors="pt")
# 進行預測
with torch.no_grad():
outputs = model(**inputs)
predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
# 解釋結果
for i, text in enumerate(texts):
sentiment = "positive" if predictions[i][1] > predictions[i][0] else "negative"
confidence = predictions[i][1] if sentiment == "positive" else predictions[i][0]
print(f"Text: {text}")
print(f"Sentiment: {sentiment} (confidence: {confidence:.3f})")
這段程式碼展示瞭如何使用 Hugging Face Transformers 函式庫進行情感分析。首先,我們使用 AutoTokenizer
和 AutoModelForSequenceClassification
類別載入了一個預訓練的 DistilBERT 模型,該模型已經在 SST-2 情感分析資料集上進行了微調。
Tokenizer 負責將原始文字轉換為模型可以理解的數字表示,包括將文字分割為子詞(tokens)、增加特殊標記([CLS], [SEP]等),並將所有輸入轉換為相同長度(padding)。padding=True
確保批次中的所有序列長度相同,truncation=True
則會截斷過長的序列,return_tensors="pt"
指定回傳 PyTorch 張量。
模型接收這些處理過的輸入並輸出 logits(未標準化的預測分數)。我們使用 softmax 函式將這些分數轉換為機率,然後根據機率判斷情感極性並計算信心值。整個過程展示了 Transformers 函式庫的易用性,只需幾行程式碼就能實作複雜的 NLP 任務。
Pipeline API:簡化的介面
對於常見任務,Transformers 提供了更簡潔的 Pipeline API,進一步降低了使用門檻:
from transformers import pipeline
# 建立情感分析 pipeline
sentiment_analyzer = pipeline("sentiment-analysis")
# 使用 pipeline 進行預測
results = sentiment_analyzer([
"I love this movie, it's amazing!",
"This film is terrible, I hated it."
])
for i, result in enumerate(results):
print(f"Text {i+1}: {result['label']} (score: {result['score']:.3f})")
這段程式碼展示了 Hugging Face Transformers 函式庫中 Pipeline API 的使用,它是一個更高層級的抽象,為常見 NLP 任務提供了簡化的介面。
透過呼叫 pipeline()
函式並指定任務型別(這裡是"sentiment-analysis"),我們建立了一個情感分析 pipeline。這個 pipeline 在背後自動載入適合該任務的預訓練模型和分詞器,預設使用與前一個例子相同的模型。
使用 pipeline 進行預測只需一行程式碼,將文字列表傳入 pipeline 物件即可。pipeline 會處理所有必要的預處理(分詞、編碼等)和後處理(將模型輸出轉換為人類可理解的結果)。
回傳結果是一個字典列表,每個字典包含預測的標籤(“POSITIVE"或"NEGATIVE”)和置信度分數。這種簡化的 API 設計使得即使是 NLP 新手也能快速佈署複雜的語言模型,是 Hugging Face 生態系統易用性的絕佳體現。
Transformers 支援的任務型別
Hugging Face Transformers 函式庫支援多種 NLP 任務,每種任務都有對應的模型類別和 pipeline:
- 文字分類別:情感分析、主題分類別等
- 命名實體識別:識別文字中的人名、地名、組織名等實體
- 問答系統:根據上下文回答問題
- 文字生成:故事生成、文章續寫等
- 摘要生成:生成文字摘要
- 機器翻譯:在不同語言間翻譯文字
- 特徵提取:從文字中提取向量表示
以下是使用 pipeline API 進行命名實體識別的範例:
from transformers import pipeline
# 建立命名實體識別 pipeline
ner = pipeline("ner")
# 分析文字
text = "Apple is looking at buying U.K. startup for $1 billion"
results = ner(text)
# 顯示結果
for result in results:
print(f"Entity: {result['word']}, Type: {result['entity']}, Score: {result['score']:.3f}")
這段程式碼展示瞭如何使用 Hugging Face pipeline API 進行命名實體識別(Named Entity Recognition, NER)任務。命名實體識別是指從非結構化文字中識別並分類別實體提及,如人名、組織、地點等。
首先,我們透過指定任務型別 “ner” 建立了一個命名實體識別 pipeline。當沒有指定具體模型時,pipeline 會自動載入適合該任務的預訓練模型(預設為 dbmdz/bert-large-cased-finetuned-conll03-english
)。
然後,我們將一個包含多個潛在實體的範例文字傳入 pipeline 進行分析。pipeline 會處理文字分詞,並識別其中的實體。
回傳的結果是一個列表,包含識別到的每個實體及其相關訊息:word
表示識別的實體文字,entity
表示實體型別(如 ORG 代表組織,LOC 代表地點,PER 代表人物等),score
表示模型對該預測的置信度。
在這個例子中,模型應該能夠識別出 “Apple” 為組織(ORG),“U.K.” 為地點(LOC),"$1 billion" 為金額(MONEY)。這展示了預訓練 Transformer 模型在實體識別任務上的強大能力。
微調 Transformer 模型
預訓練模型雖然強大,但對於特定領域的任務,通常需要進行微調(Fine-tuning)以獲得最佳效能。
資料準備與預處理
微調的第一步是準備適當的資料集。Hugging Face 的 Datasets 函式庫提供了許多常用的 NLP 資料集,也支援載入自定義資料。
以下是使用 IMDB 電影評論資料集進行情感分析微調的範例:
from datasets import load_dataset
from transformers import AutoTokenizer
# 載入 IMDB 資料集
dataset = load_dataset("imdb")
# 載入分詞器
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
# 定義預處理函式
def preprocess_function(examples):
return tokenizer(examples["text"], truncation=True, padding="max_length", max_length=512)
# 對資料集進行預處理
tokenized_dataset = dataset.map(preprocess_function, batched=True)
這段程式碼展示瞭如何準備資料用於微調 Transformer 模型。首先,我們使用 Hugging Face 的 datasets
函式庫載入 IMDB 電影評論資料集,這是一個包含 50,000 條帶有正面/負面標籤的電影評論的二分類別資料集。
接著,我們載入與目標模型比對的分詞器(這裡使用 BERT 的分詞器)。分詞器將原始文字轉換為模型可理解的數值表示。
然後,我們定義了一個預處理函式 preprocess_function
,它接收文字樣本並應用以下處理:
truncation=True
:截斷超過最大長度的文字padding="max_length"
:將所有序列填充到相同長度max_length=512
:設定序列的最大長度為 512 個標記
最後,我們使用 map
方法將此預處理函式應用於整個資料集,batched=True
引數啟用批處理以加速處理。這種預處理將原始文字轉換為包含輸入 ID、注意力掩碼等必要元素的格式,為模型微調做好準備。