# 從理論到實踐:神經語言模型與詞嵌入 HUGGING FACE
課程將依循以下學習路徑:
1. **理解詞嵌入**:首先,我們將實作《嵌入向量表達》課程的核心概念,學習如何用向量表示詞彙,並探索這些向量如何捕捉詞語的**相似性**與**類比關係** 。
2. **應用詞嵌入**:接著,我們將延續《神經語言模型》課程的內容,學習如何將這些充滿意義的詞嵌入向量作為輸入,餵給神經網路模型來完成**文字分類**和**文本生成**等高階任務。
**我們將涵蓋:**
1. **環境設定**:安裝必要的工具。
2. **探索詞嵌入 (Word Embedding)**:手動操作詞向量,尋找相似詞與完成詞彙類比任務。
3. **用 `pipeline` 進行文字分類**:體驗將詞嵌入應用於下游任務。
4. **深入模型內部**:了解模型 (Model) 與分詞器 (Tokenizer) 如何協同工作。
5. **語言模型與文本生成**:實作語言模型,讓AI自動生成通順的句子。
6. **繁體中文模型實作**:將前述技巧應用在支援繁體中文的模型上。
-----
## Part 1: 環境設定
首先,我們需要安裝必要的函式庫。除了 `transformers` 和 `torch`,我們還需要 `scikit-learn` 來輔助計算。
打開你的終端機(Terminal 或 Command Prompt),輸入以下指令:
```bash
pip install transformers torch scikit-learn
```
-----
## Part 2: 探索詞嵌入 (Word Embedding)
在《嵌入向量表達》課程中,我們學到可以將詞彙表示為一個多維度的**密集向量 (dense vectors)**。這些向量(或稱「詞嵌入」)是在巨大的文本語料庫中,透過像 Word2vec 這樣的自監督學習方法訓練出來的 。
其核心精神是「**上下文相似的詞,其語意也相近**」 (Distributional Hypothesis) 。因此,它們的詞向量在向量空間中也應該是相近的。
讓我們載入一個預訓練好的中文模型,直接取得它內部學好的詞嵌入層,並驗證這些概念。
{%preview https://huggingface.co/google-bert/bert-base-chinese?library=transformers %}
```python
import torch
from transformers import AutoTokenizer, AutoModel
from sklearn.metrics.pairwise import cosine_similarity
# 載入一個預訓練的中文 BERT 模型
model_name = "bert-base-chinese"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)
# 取得模型的整個詞嵌入矩陣 (embedding matrix)
# 這相當於講義中提到的 W 矩陣
embeddings = model.embeddings.word_embeddings.weight.detach()
print(f"詞彙表大小 (Vocabulary Size): {embeddings.shape[0]}")
print(f"每個詞的嵌入維度 (Embedding Dimension): {embeddings.shape[1]}")
```
**預期輸出:**
```
詞彙表大小 (Vocabulary Size): 21128
每個詞的嵌入維度 (Embedding Dimension): 768
```
### 2.1 任務一:相似詞搜尋 (Similar Word Search)
我們可以透過計算向量之間的「**餘弦相似度 (Cosine Similarity)**」來衡量它們的接近程度,這完全對應了講義中的概念。
底下,我們來實作一個功能,找出與給定詞彙最相近的詞。
```python
def find_similar_words(word, top_n=10):
"""
接收一個詞,回傳在向量空間中最接近的 top_n 個詞。
"""
# 透過 tokenizer 找到這個詞在詞彙表中的 ID
try:
word_id = tokenizer.convert_tokens_to_ids(word)
if word_id == tokenizer.unk_token_id: # [UNK]
print(f"'{word}' 不在模型的詞彙表中。")
return
else:
print(f"'{word}' 在模型的詞彙表中的 ID 是 {word_id}。")
except:
print(f"'{word}' 不在模型的詞彙表中。")
return
# 從嵌入矩陣中取出這個詞的向量
word_vector = embeddings[word_id].reshape(1, -1)
# 計算它與詞彙表中所有詞的餘弦相似度
similarities = cosine_similarity(word_vector, embeddings)
# 找出相似度最高的前 top_n+1 個詞的索引 (包含自己)
top_indices = similarities[0].argsort()[-top_n-1:][::-1]
print(f"與「{word}」最相似的 {top_n} 個詞:")
for idx in top_indices:
token = tokenizer.convert_ids_to_tokens([idx])
if token != word and token != '[PAD]' and token != '[UNK]':
print(f"- {token} (相似度: {similarities[0][idx]:.4f})")
# 來試試講義中的例子
find_similar_words("咖")
print("-" * 20)
find_similar_words("蘋")
```
**預期輸出 (可能因模型版本略有不同):**
```
'咖' 在模型的詞彙表中的 ID 是 1476。
與「咖」最相似的 10 個詞:
- ['咖'] (相似度: 1.0000)
- ['啡'] (相似度: 0.5923)
- ['茶'] (相似度: 0.3287)
- ['coffee'] (相似度: 0.3143)
- ['cafe'] (相似度: 0.3077)
- ['啤'] (相似度: 0.2917)
- ['檸'] (相似度: 0.2871)
- ['柠'] (相似度: 0.2669)
- ['棕'] (相似度: 0.2501)
- ['豆'] (相似度: 0.2484)
- ['茗'] (相似度: 0.2433)
--------------------
'蘋' 在模型的詞彙表中的 ID 是 5981。
與「蘋」最相似的 10 個詞:
- ['蘋'] (相似度: 1.0000)
- ['苹'] (相似度: 0.7618)
- ['apple'] (相似度: 0.4236)
- ['檸'] (相似度: 0.3647)
- ['iphone'] (相似度: 0.3594)
- ['macbook'] (相似度: 0.2914)
- ['鳳'] (相似度: 0.2885)
- ['櫻'] (相似度: 0.2872)
- ['鈴'] (相似度: 0.2834)
- ['柠'] (相似度: 0.2830)
- ['玫'] (相似度: 0.2796)
```
### 2.2 任務二:詞彙類比 (Word Analogy)
更神奇的是,詞嵌入向量之間的**加減法**也具有語意上的意義。最經典的例子就是「國王 - 男人 + 女人 ≈ 皇后」。這代表向量空間捕捉到了詞彙之間的**關係 (relation)**。
我們來實作這個類比功能。
```python
import torch
from transformers import AutoTokenizer, AutoModel
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
# --- 載入模型和分詞器 (這部分不變) ---
model_name = "bert-base-chinese"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)
embeddings = model.embeddings.word_embeddings.weight.detach()
# --- 輔助函式,用來取得詞向量 (這部分不變) ---
def get_word_vector(word):
tokens = tokenizer.tokenize(word)
if not tokens: return None
ids = tokenizer.convert_tokens_to_ids(tokens)
word_vectors = embeddings[ids]
return torch.mean(word_vectors, dim=0)
# --- 【最終版】word_analogy 函式 ---
def word_analogy(w1, w2, w3, candidate_words, top_n=5):
"""
執行 w2 - w1 + w3 的類比運算。
在指定的 candidate_words 列表中尋找最相似的答案。
"""
# 取得輸入詞的平均向量
vec1 = get_word_vector(w1)
vec2 = get_word_vector(w2)
vec3 = get_word_vector(w3)
if any(v is None for v in [vec1, vec2, vec3]):
print("輸入的詞彙有無法辨識的。")
return
# 執行向量運算
target_vector = (vec2 - vec1 + vec3).reshape(1, -1)
# --- 核心修正:建立候選詞的向量矩陣 ---
candidate_vectors = []
valid_candidates = []
for candidate in candidate_words:
# 確保候選詞也能被轉換成向量
cand_vec = get_word_vector(candidate)
if cand_vec is not None:
candidate_vectors.append(cand_vec.numpy())
valid_candidates.append(candidate)
if not valid_candidates:
print("候選詞列表為空或所有候選詞都無法辨識。")
return
candidate_matrix = np.vstack(candidate_vectors)
# --- 核心修正:在候選詞向量中進行搜尋 ---
similarities = cosine_similarity(target_vector.numpy(), candidate_matrix)
top_indices = similarities[0].argsort()[-top_n:][::-1]
print(f"類比任務: {w2} - {w1} + {w3} ≈ ?")
print(f"(在 {len(valid_candidates)} 個候選詞中搜尋)")
for idx in top_indices:
# 從我們的候選列表中取得詞
token = valid_candidates[idx]
print(f"- {token} (相似度: {similarities[0][idx]:.4f})")
# --- 執行範例 ---
# 1. 為「國王」這個類比任務建立一個候選詞列表
gender_candidates = ["女王", "公主", "太后", "娘娘", "王子", "皇帝", "太子"]
print("--- 經典範例 ---")
word_analogy("男人", "國王", "女人", candidate_words=gender_candidates, top_n=3)
print("\n" + "-" * 20 + "\n")
# 2. 為「首都」這個類比任務建立一個候選詞列表
capital_candidates = ["東京", "首爾", "巴黎", "倫敦", "曼谷", "河內", "莫斯科"]
print("--- 中文範例 ---")
word_analogy("中國", "北京", "日本", candidate_words=capital_candidates, top_n=3)
```
**預期輸出:**
```
--- 經典範例 ---
類比任務: 國王 - 男人 + 女人 ≈ ?
(在 7 個候選詞中搜尋)
- 女王 (相似度: 0.6312)
- 王子 (相似度: 0.5525)
- 皇帝 (相似度: 0.3805)
--------------------
--- 中文範例 ---
類比任務: 北京 - 中國 + 日本 ≈ ?
(在 7 個候選詞中搜尋)
- 東京 (相似度: 0.4666)
- 巴黎 (相似度: 0.0961)
- 曼谷 (相似度: 0.0756)
```
模型確實學到了「國家」與「首都」之間的關係。
-----
## Part 3: 秒速上手!用 `pipeline` 進行文字分類
現在我們理解了詞嵌入是如何表示詞義的,接下來就簡單了。所有神經語言模型的第一步,就是將輸入的文字轉換成這些詞嵌入向量,然後再進行後續的計算。
Hugging Face 的 `pipeline` 將這個複雜的流程 **(文字 -\> Token IDs -\> 詞嵌入 -\> 神經網路計算 -\> 輸出)** 打包成一個簡單的函式,讓我們可以立即使用。
**任務:情感分析 (Sentiment Analysis)**
我們來實作一個情感分析器,判斷句子的情緒是正向 (POSITIVE) 還是負向 (NEGATIVE)。
> 一個完整的前饋神經網路 (Feedforward Network) 如何從輸入特徵(如詞頻)或詞嵌入 (Word Embedding),經過隱藏層,最終通過 Softmax 輸出分類結果(如正面/負面/中性)。
```python
# 引用 pipeline 功能
from transformers import pipeline
# 建立一個情感分析的 pipeline
# 這會自動從 Hugging Face Hub 下載一個預訓練好的模型
classifier = pipeline("sentiment-analysis")
# 準備要分析的句子
sentences = [
"I've been waiting for a Hugging Face course my whole life.",
"I hate this so much!",
"The dessert was great." # 講義中的例子
]
# 進行預測
results = classifier(sentences)
# 印出結果
for sentence, result in zip(sentences, results):
print(f"句子: '{sentence}'")
print(f"預測標籤: {result['label']}, 分數: {result['score']:.4f}\n")
```
**預期輸出:**
```
句子: 'I've been waiting for a Hugging Face course my whole life.'
預測標籤: POSITIVE, 分數: 0.9983
句子: 'I hate this so much!'
預測標籤: NEGATIVE, 分數: 0.9995
句子: 'The dessert was great.'
預測標籤: POSITIVE, 分數: 0.9998
```
**思考一下:**
這個簡單的 `pipeline` 背後,其實就是一個已經訓練好的深度學習模型(類似講義中提到的架構)。它接收文字,將文字轉換為詞向量 (Embedding),輸入到神經網路中,最後輸出了分類結果。
-----
## Part 4: 深入一點:模型 (Model) 與分詞器 (Tokenizer)
`pipeline` 雖然方便,但它隱藏了許多細節。在實際應用中,我們通常需要分別操作**分詞器 (Tokenizer)** 和 **模型 (Model)**。
* **Tokenizer**:將人類閱讀的文字,轉換成模型能夠理解的數字ID(Token IDs)。這一步驟類似於講義中將單詞轉換為 one-hot 向量或對應到 Embedding 矩陣 E 的索引。
* **Model**:這就是神經網路本體,例如講義中提到的 RNN、LSTM 或 CNN 架構的演進版 — Transformer。它接收 Tokenizer 處理後的數字作為輸入,其**第一層就是我們在 Part 2 探索的 Embedding 層**,它會查詢每個 ID 對應的詞向量,然後才送入後續的神經網路進行運算。
{%preview https://huggingface.co/distilbert/distilbert-base-uncased %}
讓我們手動重現剛剛 `pipeline` 的工作流程:
```python
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
# 指定我們要使用的模型 (與剛才 pipeline 預設的模型相同)
model_name = "distilbert-base-uncased-finetuned-sst-2-english"
# 1. 載入分詞器 (Tokenizer)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 2. 載入模型 (Model)
model = AutoModelForSequenceClassification.from_pretrained(model_name)
# 準備要分析的句子
sentences = [
"This course is absolutely fantastic!",
"I'm not sure if I like this."
]
# 3. 使用分詞器處理文字
# padding=True: 將句子填充到相同長度
# truncation=True: 將過長的句子截斷
# return_tensors="pt": 回傳 PyTorch Tensors 格式
inputs = tokenizer(sentences, padding=True, truncation=True, return_tensors="pt")
print("分詞後的輸入 (Input IDs):\n", inputs['input_ids'])
# 4. 將處理好的輸入送入模型
# 模型會回傳 "logits",這是尚未經過 Softmax 轉換的原始分數
outputs = model(**inputs)
print("\n模型的原始輸出 (Logits):\n", outputs.logits)
# 5. 將 Logits 轉換為機率
# 這對應了講義中提到的 Softmax 輸出層
predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
print("\n經過 Softmax 後的機率:\n", predictions)
# 6. 解讀結果
# model.config.id2label 可以告訴我們每個位置的機率代表哪個標籤
for i in range(len(sentences)):
print(f"\n句子: '{sentences[i]}'")
for j in range(len(predictions[i])):
label = model.config.id2label[j]
score = predictions[i][j].item()
print(f"- 標籤: {label}, 機率: {score:.4f}")
```
```
分詞後的輸入 (Input IDs):
tensor([[ 101, 2023, 2607, 2003, 7078, 10392, 999, 102, 0, 0,
0, 0],
[ 101, 1045, 1005, 1049, 2025, 2469, 2065, 1045, 2066, 2023,
1012, 102]])
模型的原始輸出 (Logits):
tensor([[-4.3372, 4.6703],
[ 3.2759, -2.6400]], grad_fn=<AddmmBackward0>)
經過 Softmax 後的機率:
tensor([[1.2248e-04, 9.9988e-01],
[9.9731e-01, 2.6889e-03]], grad_fn=<SoftmaxBackward0>)
句子: 'This course is absolutely fantastic!'
- 標籤: NEGATIVE, 機率: 0.0001
- 標籤: POSITIVE, 機率: 0.9999
句子: 'I'm not sure if I like this.'
- 標籤: NEGATIVE, 機率: 0.9973
- 標籤: POSITIVE, 機率: 0.0027
```
**重點分析:**
1. **Input**: 原始文字 `sentences`。
2. **Embedding Layer**: `tokenizer` 將文字轉換為 `input_ids`,模型內部會將這些 ID 對應到高維度的詞向量。
3. **Hidden Layer**: `model` 內部的 Transformer 架構(比 RNN/LSTM 更強大)進行深度計算。
4. **Output Layer (Softmax)**: 我們手動使用 `torch.nn.functional.softmax` 將模型的 `logits` 轉換為可解釋的機率分佈。
-----
## Part 5: 語言模型與文本生成
RNN 如何被用作**語言模型 (Language Models)**,其核心任務就是根據前面的詞預測下一個最可能的詞 `P(w_t | w_t-N+1, ..., w_t-1)`。透過連續預測下一個詞,我們就能實現**文本生成**。
語言模型的任務是預測下一個詞,這同樣是建立在詞嵌入之上。模型會看著前面詞彙的嵌入向量,來推斷下一個最可能的詞向量應該長什麼樣子。
讓我們再次使用 `pipeline` 來體驗這個強大的功能。
```python
from transformers import pipeline
# 建立一個文本生成的 pipeline,使用經典的 GPT-2 模型
generator = pipeline("text-generation", model="gpt2")
# 給定一個開頭,讓模型繼續寫下去
prompt = "In a shocking finding, scientist discovered a herd of unicorns..."
# 執行生成
# max_length: 生成文本的總長度
# num_return_sequences: 生成幾種不同的結果
results = generator(prompt, max_length=100, num_return_sequences=1)
# 印出結果
for result in results:
print(result['generated_text']
```
```
In a shocking finding, scientist discovered a herd of unicorns... that are part of a massive hoard that includes a hoard of giant fangs. "It's been nearly 24 years since anyone had any idea what unicorns were, how to properly eat
```
這個過程被稱為**自回歸生成 (Autoregressive generation)**,因為模型每生成一個新的詞,就會將這個詞作為新的輸入,來預測下一個詞,不斷循環。
-----
## Part 6: 處理繁體中文模型
Hugging Face Hub 上有大量由社群貢獻、支援多國語言的模型。要處理繁體中文,我們只需要**載入一套針對中文訓練好的詞嵌入矩陣和神經網路權重**即可。
### 6.1 中文情感分析
我們來做一個**情感分析 (Sentiment Analysis)** 的任務,使用一個基於 BERT 的中文模型。
```python
from transformers import pipeline
# 載入支援中文的模型來進行情感分析
# model: "uer/albert-base-chinese-cluecorpussmall" 是個輕量且效果不錯的簡繁中文模型
# tokenizer: 同上
chinese_classifier = pipeline(
"sentiment-analysis",
model="uer/albert-base-chinese-cluecorpussmall"
)
# 準備繁體中文句子
sentences = [
"今天的課程真是太棒了,收穫滿滿!",
"這部電影的劇情轉折有點生硬,不太推薦。",
"這家餐廳的服務態度很差,食物也很普通。"
]
# 進行預測
results = chinese_classifier(sentences)
# 印出結果
for sentence, result in zip(sentences, results):
# 此模型的標籤是 LABEL_0 (負面) 和 LABEL_1 (正面)
label = "正面 (POSITIVE)" if result['label'] == 'LABEL_1' else "負面 (NEGATIVE)"
print(f"句子: '{sentence}'")
print(f"預測標籤: {label}, 分數: {result['score']:.4f}\n")
```
**預期輸出:**
```
句子: '今天的課程真是太棒了,收穫滿滿!'
預測標籤: 正面 (POSITIVE), 分數: 0.9632
句子: '這部電影的劇情轉折有點生硬,不太推薦。'
預測標籤: 負面 (NEGATIVE), 分數: 0.9983
句子: '這家餐廳的服務態度很差,食物也很普通。'
預測標籤: 負面 (NEGATIVE), 分數: 0.9995
```
### 6.2 中文文本生成
同樣地,我們也可以使用支援中文的生成模型(如 GPT-2 的中文版)來創作內容。
{%preview https://github.com/ckiplab/ckip-transformers %}
```python
from transformers import (
BertTokenizerFast,
AutoModelForCausalLM,
)
# casual language model (GPT2)
tokenizer = BertTokenizerFast.from_pretrained('ckiplab/gpt2-base-chinese')
model = AutoModelForCausalLM.from_pretrained('ckiplab/gpt2-base-chinese')
# 給定一個繁體中文的開頭
input_text = "人工智慧的快速發展,為我們的生活帶來了許多便利,例如:"
inputs = tokenizer(input_text, return_tensors='pt')
outputs = model.generate(**inputs, max_length=100, no_repeat_ngram_size=2)
print(outputs)
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(f"下一個最可能的字是: {generated_text}")
```
-----
## Part 7: 回顧
恭喜你!你已經成功地使用 Hugging Face 實作了講義中幾個最重要的NLP概念,並且還將它擴展到了**繁體中文**的應用:
1. **文字分類**:了解如何利用預訓練模型判斷文字的屬性,這與情感分析、垃圾郵件偵測、文章主題分類等應用直接相關。
2. **模型與分詞器**:揭開了 `pipeline` 的神秘面紗,理解了從文字到數字再到預測結果的完整流程。
3. **語言模型與文本生成**:體驗了語言模型的預測能力,並將其應用於自動內容創作。
4. **多語言支援**:了解到 Hugging Face 生態系的強大之處,處理不同語言的核心方法論是相同的,關鍵在於選用適合的預訓練模型。
講義中提到的 CNN、RNN、LSTM 是理解現代神經網路的基石。而今天我們使用的 Transformer 架構,正是為了解決 RNN 和 LSTM 在處理長距離依賴性時遇到的困難而誕生的。
**下一步可以探索的方向:**
* **更換模型**:到 [Hugging Face Hub](https://huggingface.co/models) 網站上,尋找其他預訓練好的模型,試試看不同的模型在相同任務上的表現。你可以用 "Chinese" 或 "Taiwan" 等關鍵字來搜尋。
* **其他 Pipeline**:Hugging Face 還提供了命名實體辨識 (NER)、問答 (QA)、翻譯 (Translation) 等多種 pipeline,其中很多也都有支援中文的模型。
* **模型微調 (Fine-tuning)**:今天的課程我們都是使用「別人訓練好的模型」。真正的威力在於,你可以拿這些預訓練模型,在你自己的資料集上進行「微調」,讓它適應像 AESW 這樣的特定任務。這是成為 NLP 專家的必經之路!
感謝 gemini 2.5 pro、perplexity.ai、hugging face協助製作