# 從理論到實踐:神經語言模型與詞嵌入 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協助製作