《深度學習實戰》第4集:Transformer 架構與自然語言處理(NLP)
在自然語言處理(NLP)領域,Transformer 架構的出現徹底改變了傳統的序列建模方法。它不僅成為現代 NLP 的核心,還推動了諸如 BERT、GPT 等預訓練模型的發展。本集將帶你深入了解 Transformer 的工作原理,并通過實戰項目微調 BERT 模型完成情感分析任務。
1. 自注意力機制與多頭注意力
1.1 自注意力機制(Self-Attention)
自注意力機制是 Transformer 的核心組件,它允許模型在處理輸入序列時關注不同位置的相關性。以下是其工作原理:
- 輸入嵌入:
- 輸入序列被轉換為詞向量表示。
- 計算注意力權重:
- 通過查詢(Query)、鍵(Key)和值(Value)矩陣計算注意力分數。
- 注意力分數公式:
- 加權求和:
- 根據注意力分數對值進行加權求和,得到上下文相關的表示。
1.2 多頭注意力(Multi-Head Attention)
為了捕捉不同子空間中的特征,Transformer 使用多頭注意力機制。每個“頭”獨立計算注意力,然后將結果拼接并線性變換。
2. Transformer 的編碼器-解碼器結構
Transformer 由編碼器(Encoder)和解碼器(Decoder)兩部分組成:
2.1 編碼器(Encoder)
- 編碼器由多個相同的層堆疊而成,每層包含:
- 多頭自注意力層:捕捉輸入序列的全局依賴關系。
- 前饋神經網絡(FFN):進一步提取特征。
- 殘差連接與層歸一化:穩定訓練過程。
2.2 解碼器(Decoder)
- 解碼器同樣由多層組成,但額外增加了:
- 掩碼多頭注意力(Masked Multi-Head Attention):防止未來信息泄露。
- 編碼器-解碼器注意力層:結合編碼器輸出生成目標序列。
好的!為了讓你更好地理解 自注意力機制(Self-Attention) 和 多頭注意力(Multi-Head Attention) 的底層結構和原理,我會用一個生活中的例子來類比,并逐步拆解它們的工作方式。
3. 自注意力機制:一場“會議討論”的比喻
想象一下,你正在參加一場公司會議,會議的主題是“如何提高產品銷量”。會議室里有幾位同事,每個人都有自己的觀點。你需要綜合大家的意見,得出一個全面的結論。
3.1 每個人的觀點
- 假設會議室里的每個人代表輸入序列中的一個單詞。
- 每個人的觀點(比如市場分析、用戶體驗、技術改進等)就是這個單詞的嵌入向量(Embedding Vector)。
3.2 問題來了:如何聽取所有人的意見?
在會議中,你會根據每個人的發言內容,判斷他們的觀點對你當前思考的重要性。這就像自注意力機制的核心思想:計算每個單詞對當前單詞的相關性。
具體步驟:
-
準備材料(生成 Query、Key 和 Value):
- 每個人會準備三份材料:
- Query(提問):你想問的問題,比如“你的建議對我有什么幫助?”
- Key(關鍵詞):每個人的核心觀點,比如“市場分析”或“用戶體驗”。
- Value(具體內容):每個人的具體建議,比如“我們需要增加廣告預算”。
- 這些材料通過線性變換(矩陣乘法)從原始觀點(嵌入向量)生成。
- 每個人會準備三份材料:
-
打分(計算注意力分數):
- 你拿著自己的 Query,去和每個人提供的 Key 對比,看看誰的觀點和你的問題最相關。
- 相關性通過點積計算,結果越大表示越相關。
- 計算公式:
[
\text{Attention Score} = \frac{\text{Query} \cdot \text{Key}}{\sqrt{d_k}}
]
(這里的 (\sqrt{d_k}) 是為了防止分數過大,保持數值穩定。)
-
加權求和(整合信息):
- 根據每個人的得分,計算權重(通過 softmax 歸一化)。
- 然后,根據權重對每個人的 Value 進行加權求和,得到最終的結論。
3.3 總結:自注意力機制的作用
自注意力機制的核心是讓每個單詞都能“看到”整個句子中的其他單詞,并根據它們的相關性調整自己的表示。這樣,模型可以捕捉到全局的上下文信息。
4. 多頭注意力:多個“視角”的討論
回到剛才的會議場景,假設你不僅關心“如何提高產品銷量”,還想知道“哪些用戶群體最重要”、“競爭對手有哪些策略”等多個問題。這時,你可以邀請幾個專家小組,分別從不同角度分析問題。
4.1 多個“專家小組”
- 每個專家小組相當于一個多頭注意力的一個“頭”。
- 每個小組會獨立地進行討論,生成自己的結論。
4.2 如何整合多個小組的意見?
- 每個小組的討論結果(即每個頭的輸出)會被拼接在一起。
- 然后通過一個線性變換(矩陣乘法),將這些結果融合成一個最終的結論。
4.3 多頭注意力的好處
- 不同的“頭”可以關注輸入的不同部分。例如:
- 一個頭可能專注于語法關系(主語和謂語的聯系)。
- 另一個頭可能關注語義關系(情感或主題)。
- 通過多頭注意力,模型可以從多個角度提取特征,從而更全面地理解輸入。
圖解:會議討論與注意力機制的對應關系
會議討論 | 注意力機制 |
---|---|
每個人的觀點 | 輸入序列中的單詞嵌入向量 |
提問(Query) | 查詢向量(Query Vector) |
關鍵詞(Key) | 鍵向量(Key Vector) |
具體內容(Value) | 值向量(Value Vector) |
打分并加權求和 | 注意力分數計算 + 加權求和 |
多個專家小組分別討論 | 多頭注意力的多個“頭” |
一個具體的例子:翻譯句子
假設我們要翻譯一句話:“The cat sat on the mat.”(貓坐在墊子上)。
自注意力機制的作用
- 當處理單詞“cat”時,自注意力機制會讓它“看到”整個句子。
- 它會發現“sat”和“mat”與自己高度相關,因為它們描述了貓的動作和位置。
多頭注意力的作用
- 一個頭可能專注于語法關系(“cat”是主語,“sat”是謂語)。
- 另一個頭可能專注于語義關系(“cat”和“mat”之間存在空間關系)。
- 最終,這些信息被整合起來,幫助模型生成更準確的翻譯。
關于自注意力機制和多頭注意力的總結
- 自注意力機制:就像你在會議上聽取每個人的意見,計算出誰的觀點最重要,并據此做出決策。
- 多頭注意力:就像你邀請多個專家小組,從不同角度分析問題,最后整合所有意見。
通過這種機制,Transformer 模型能夠高效地捕捉輸入序列中的全局依賴關系,從而在自然語言處理任務中表現出色。
5. BERT、GPT 等預訓練模型的原理與應用
5.1 BERT(Bidirectional Encoder Representations from Transformers)
- 特點:
- 雙向編碼:同時考慮上下文信息。
- 預訓練任務:
- Masked Language Model(MLM):預測被遮擋的單詞。
- Next Sentence Prediction(NSP):判斷句子對是否連續。
- 應用場景:
- 文本分類、命名實體識別、問答系統等。
5.2 GPT(Generative Pre-trained Transformer)
- 特點:
- 單向解碼:從左到右生成文本。
- 基于自回歸語言模型。
- 應用場景:
- 文本生成、對話系統、代碼補全等。
6. 實戰項目:使用 Hugging Face Transformers 微調 BERT 模型
我們將使用 Hugging Face 的 transformers
庫微調 BERT 模型,完成情感分析任務。
6.1 數據準備
下載 SST-2數據集,鏈接如下:SST-2下載鏈接
import pandas as pd
from sklearn.model_selection import train_test_split
import os
from transformers import BertTokenizer, BertForSequenceClassification
import torch# 設置代理(如果需要)
# os.environ["HTTP_PROXY"] = "http://your_proxy:port"
# os.environ["HTTPS_PROXY"] = "http://your_proxy:port"# 設置離線模式,使用本地文件
# 定義文件路徑(根據你的實際路徑修改)
train_file = "SST-2/SST-2/train.tsv"
dev_file = "SST-2/SST-2/dev.tsv"
6.2 數據預處理
from transformers import BertTokenizer
# 使用 Pandas 讀取 TSV 文件
try:train_data = pd.read_csv(train_file, sep='\t')test_data = pd.read_csv(dev_file, sep='\t')print("成功加載本地數據集")print(train_data.head())
except Exception as e:print(f"加載本地數據集失敗: {e}")print("請確保數據文件路徑正確")# 嘗試加載本地分詞器或使用備選方案
try:# 嘗試從本地緩存加載cache_dir = "./models_cache"os.makedirs(cache_dir, exist_ok=True)# 使用本地緩存目錄tokenizer = BertTokenizer.from_pretrained("bert-base-uncased", cache_dir=cache_dir,local_files_only=False, # 允許在線下載use_fast=True)print("成功加載分詞器")
except OSError as e:print(f"無法加載BERT分詞器: {e}")print("嘗試使用備選方案...")# 備選方案:使用簡單的分詞方法from sklearn.feature_extraction.text import CountVectorizervectorizer = CountVectorizer(max_features=10000)print("已切換到簡單分詞器 (CountVectorizer)")# 定義預處理函數
def preprocess_data(data):sentences = data["sentence"].tolist()labels = data["label"].tolist()try:# 如果BERT分詞器加載成功if 'tokenizer' in locals():# 對句子進行分詞和編碼encodings = tokenizer(sentences,truncation=True,padding="max_length",max_length=128,return_tensors="pt")return encodings, labels, True # 返回True表示使用BERTelse:# 使用備選分詞方法# 注意:這里只對訓練數據進行fit_transformif 'vectorizer_fitted' not in globals():global vectorizer_fittedvectorizer_fitted = Truefeatures = vectorizer.fit_transform(sentences)else:# 對于測試數據,只進行transformfeatures = vectorizer.transform(sentences)return features, labels, False # 返回False表示使用備選方案except Exception as e:print(f"預處理數據時出錯: {e}")return None, labels, False# 預處理訓練集和測試集
if 'train_data' in locals() and 'test_data' in locals():print("開始預處理數據...")train_features, train_labels, using_bert = preprocess_data(train_data)test_features, test_labels, _ = preprocess_data(test_data)print("數據預處理完成")
6.3 模型定義與訓練
import torch
from torch.utils.data import Dataset
import numpy as np
from sklearn.linear_model import LogisticRegressionclass SSTDataset(Dataset):def __init__(self, encodings, labels):self.encodings = encodingsself.labels = labelsself.is_bert_encoding = isinstance(encodings, dict)def __len__(self):if self.is_bert_encoding:return len(self.labels)else:return self.encodings.shape[0]def __getitem__(self, idx):if self.is_bert_encoding:item = {key: val[idx] for key, val in self.encodings.items()}item["labels"] = torch.tensor(self.labels[idx])return itemelse:# 對于非BERT編碼,返回稀疏向量的密集表示和標簽features = torch.tensor(self.encodings[idx].toarray()[0], dtype=torch.float)label = torch.tensor(self.labels[idx])return {"features": features, "labels": label}# 創建數據集實例
train_dataset = SSTDataset(train_features, train_labels)
test_dataset = SSTDataset(test_features, test_labels)# 根據使用的分詞器選擇不同的模型訓練方法
if using_bert:from transformers import BertForSequenceClassification, Trainer, TrainingArguments# 加載預訓練的 BERT 模型(用于二分類任務)model = BertForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=2)# 定義訓練參數training_args = TrainingArguments(output_dir="./results", # 輸出目錄evaluation_strategy="epoch", # 每個 epoch 后評估模型per_device_train_batch_size=16, # 訓練時的批量大小per_device_eval_batch_size=16, # 驗證時的批量大小num_train_epochs=3, # 訓練輪數weight_decay=0.01, # 權重衰減logging_dir="./logs", # 日志目錄logging_steps=10 # 每 10 步記錄一次日志)# 定義 Trainertrainer = Trainer(model=model,args=training_args,train_dataset=train_dataset,eval_dataset=test_dataset)# 開始訓練trainer.train()
6.4 測試模型
# 測試單句預測test_sentence = "This movie was absolutely fantastic!"inputs = tokenizer(test_sentence, return_tensors="pt", truncation=True, padding=True, max_length=128)outputs = model(**inputs)prediction = outputs.logits.argmax(dim=-1).item()# 輸出結果print("情感分析結果:", "積極" if prediction == 1 else "消極")
else:print("使用備選方案 (LogisticRegression) 進行訓練...")# 將稀疏矩陣轉換為numpy數組進行訓練X_train = train_features.toarray()X_test = test_features.toarray()# 使用邏輯回歸作為備選模型clf = LogisticRegression(max_iter=1000)clf.fit(X_train, train_labels)# 評估模型accuracy = clf.score(X_test, test_labels)print(f"測試集準確率: {accuracy:.4f}")# 測試單句預測test_sentence = "This movie was absolutely fantastic!"# 使用已經訓練好的vectorizer進行轉換test_features = vectorizer.transform([test_sentence])prediction = clf.predict(test_features)[0]# 輸出結果print("情感分析結果:", "積極" if prediction == 1 else "消極")
程序運行結果:
2025-02-27 23:52:05.928189: I tensorflow/core/util/port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.
2025-02-27 23:52:07.648400: I tensorflow/core/util/port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.
成功加載本地數據集sentence label
0 hide new secretions from the parental units 0
1 contains no wit , only labored gags 0
2 that loves its characters and communicates som... 1
3 remains utterly satisfied to remain the same t... 0
4 on the worst revenge-of-the-nerds clichés the ... 0
無法加載BERT分詞器: (MaxRetryError("HTTPSConnectionPool(host='huggingface.co', port=443): Max retries exceeded with url: /bert-base-uncased/resolve/main/tokenizer_config.json (Caused by ProxyError('Unable to connect to proxy', FileNotFoundError(2, 'No such file or directory')))"), '(Request ID: 3fff21e5-ab5a-4c4c-8695-70d49bb4ebdf)')
嘗試使用備選方案...
已切換到簡單分詞器 (CountVectorizer)
開始預處理數據...
數據預處理完成
使用備選方案 (LogisticRegression) 進行訓練...
測試集準確率: 0.8131
情感分析結果: 積極
7. 前沿關聯:超大規模語言模型的能力與挑戰
7.1 超大規模模型
- GPT-4 和 PaLM 等模型擁有數千億參數,能夠生成高質量的文本、代碼甚至圖像描述。
- 能力:
- 上下文理解、多語言支持、零樣本學習。
- 挑戰:
- 計算資源需求高。
- 模型可解釋性差。
- 潛在的偏見與倫理問題。
7.2 未來方向
- 更高效的訓練方法(如稀疏激活、知識蒸餾)。
- 提升模型的可控性與安全性。
總結
Transformer 架構以其強大的自注意力機制和靈活的編碼器-解碼器結構,成為 NLP 領域的基石。通過實戰項目,我們學會了如何使用 Hugging Face 的工具微調 BERT 模型。同時,我們也探討了超大規模語言模型的潛力與挑戰。
希望這篇博客能幫助你更好地理解 Transformer 的原理與應用!如果需要進一步擴展或優化,請隨時告訴我!