
如果你問一個剛接觸大語言模型的朋友“GPT 到底是怎么理解我們說的話的” 十有八九他會提到一個詞Embedding。這個詞聽起來很玄乎翻譯成“嵌入”或“向量化”但依然讓人摸不著頭腦。它不像“神經網絡”那樣有生物學的類比也不像“注意力機制”那樣有直觀的比喻。很多人只是模糊地知道哦就是把文字變成一串數字。但這串數字為什么就能代表意義它又是怎么被“搓”出來的這正是理解現代 AI尤其是 GPT 這類模型的第一道也是最關鍵的一道門檻。今天我們不滿足于只當一個 API 調用者而是要親手“搓”一個極簡版的 GPT 核心組件從零開始看看Embedding 究竟是如何給冷冰冰的字符注入豐富語義的。你會發現它的本質并非魔法而是一套精巧的、可解釋的數學工程。我們將從一個最簡單的文本例子出發用 Python 代碼一步步實現一個微型“詞嵌入”模型。這個過程會讓你徹底明白為什么獨熱編碼One-Hot是“啞巴”而 Embedding 是“智者”模型是如何從大量文本中“學習”到詞語之間的關系的比如“國王” - “男人” “女人” ≈ “女王”我們手動實現的 Embedding和 GPT 等大模型中動輒數百維的 Embedding 在思路上有何異同讀完本文你不僅能透徹理解 Embedding 的原理更能獲得一種“手感”——一種從底層把握 AI 模型核心運作方式的直覺。這對于后續調優模型、設計提示詞、甚至進行模型微調都至關重要。1. 從“啞巴”到“智者”為什么我們需要 Embedding在計算機眼里所有文字最初都是沒有意義的符號。我們最早教會計算機“認識”詞語的方法叫做獨熱編碼One-Hot Encoding。假設我們的詞匯表只有三個詞[貓, 狗, 魚]?!柏垺?表示為[1, 0, 0]“狗” 表示為[0, 1, 0]“魚” 表示為[0, 0, 1]獨熱編碼的問題顯而易見維度災難詞匯表有 5 萬個詞每個詞就是一個 5 萬維的向量其中只有一個是 1其余全是 0。極其稀疏計算和存儲效率低下。語義缺失在這種表示下“貓”和“狗”的向量 ([1,0,0]和[0,1,0]) 之間的幾何距離如歐氏距離與“貓”和“魚”的距離 ([1,0,0]和[0,0,1]) 是一樣的。計算機完全無法感知“貓”和“狗”都是寵物關系更近。無法處理新詞遇到詞匯表外的詞直接抓瞎。Embedding 要解決的正是這兩個核心痛點。它的目標是將每個詞映射到一個低維、稠密、連續的向量空間中。在這個空間里向量的長度維度是固定的比如 50、100、300 維遠小于詞匯表大小。向量的每個位置都是一個浮點數包含了豐富的語義信息。語義相似的詞其向量在空間中的位置也接近。那么關鍵問題來了這個充滿語義信息的向量是怎么來的答案是從上下文中學習而來。2. 核心思想“觀其友知其人”的分布式假設語言學中有一個著名的分布式假設Distributional Hypothesis一個詞的含義是由它周圍的上下文決定的。通俗講“經常出現在相似語境中的詞其含義也相似”。例如“蘋果”這個詞如果它經常和“吃”、“甜”、“水果”一起出現那它很可能指水果如果它經常和“公司”、“手機”、“iOS”一起出現那它很可能指科技品牌。Embedding 模型如經典的 Word2Vec正是基于這一思想。它通過讓模型完成一個“填空題”任務來學習給定一個中心詞預測它周圍可能出現的詞或反之。在完成這個預測任務的過程中模型被迫去學習每個詞的向量表示使得在相似上下文中的詞具有相似的向量。接下來我們就用最經典的Skip-gram模型Word2Vec 的一種思路來手搓一個微型 Embedding 生成器。3. 環境準備一個干凈的 Python playground我們不需要復雜的深度學習框架僅用numpy進行基礎數學運算即可。這能讓我們更清晰地看到每一步的計算。# 建議創建一個新的虛擬環境 python -m venv venv_embedding # 激活環境 (Windows) venv_embedding\Scripts\activate # 激活環境 (Mac/Linux) source venv_embedding/bin/activate # 安裝唯一依賴 pip install numpy準備好你的代碼編輯器我們從一個簡單的語料開始。4. 手搓 Embedding四步實現一個微型語義學習機我們將整個過程分解為四個可執行的步驟。4.1 第一步準備數據與構建詞匯表我們使用一個極小的句子集合作為語料以便觀察中間過程。# corpus.py import numpy as np # 1. 定義微型語料 corpus [ the king is a man, the queen is a woman, the man is strong, the woman is wise, the cat is a pet, the dog is a pet ] # 2. 文本預處理轉小寫分詞 def preprocess(text): return text.lower().split() tokenized_corpus [preprocess(sentence) for sentence in corpus] print(分詞后的語料, tokenized_corpus) # 3. 構建詞匯表 vocab set() for sentence in tokenized_corpus: for word in sentence: vocab.add(word) vocab sorted(list(vocab)) # 排序以保證順序固定 word_to_id {word: idx for idx, word in enumerate(vocab)} id_to_word {idx: word for word, idx in word_to_id.items()} print(詞匯表, vocab) print(詞匯到ID的映射, word_to_id)運行這段代碼你會得到分詞后的語料 [[the, king, is, a, man], [the, queen, is, a, woman], ...] 詞匯表 [a, cat, dog, is, king, man, pet, queen, strong, the, wise, woman] 詞匯到ID的映射 {a: 0, cat: 1, dog: 2, is: 3, king: 4, man: 5, pet: 6, queen: 7, strong: 8, the: 9, wise: 10, woman: 11}現在每個詞都有了唯一的數字 ID。但這還是獨熱編碼的起點。4.2 第二步生成訓練數據中心詞與上下文詞對Skip-gram 模型的任務是給定一個中心詞預測它窗口大小內的所有上下文詞。 我們設置一個窗口大小window_size為 1即只看中心詞左邊一個詞和右邊一個詞。# data_generator.py def generate_training_data(tokenized_corpus, word_to_id, window_size1): 生成Skip-gram模型需要的訓練數據對 (center_word_id, context_word_id) training_pairs [] vocab_size len(word_to_id) for sentence in tokenized_corpus: sentence_ids [word_to_id[word] for word in sentence] for center_idx, center_word_id in enumerate(sentence_ids): # 確定上下文窗口的起止索引 start max(0, center_idx - window_size) end min(len(sentence_ids), center_idx window_size 1) for context_idx in range(start, end): if context_idx ! center_idx: # 跳過中心詞自己 context_word_id sentence_ids[context_idx] training_pairs.append((center_word_id, context_word_id)) return np.array(training_pairs) # 生成數據 training_pairs generate_training_data(tokenized_corpus, word_to_id, window_size1) print(訓練數據對 (中心詞ID, 上下文詞ID)) print(training_pairs[:10]) # 打印前10對 print(f總共生成 {len(training_pairs)} 個訓練對)輸出示例訓練數據對 (中心詞ID, 上下文詞ID) [[ 9 4] # (the, king) [ 4 9] # (king, the) [ 4 3] # (king, is) ... ] 總共生成 56 個訓練對我們的模型就要學習從9the預測4king從4king預測9the和3is這樣的關系。4.3 第三步搭建并訓練一個極簡的 Embedding 模型這是最核心的部分。我們將實現一個沒有隱藏層的“神經網絡”它本質上是在學習兩個矩陣W1 (Embedding 矩陣)形狀為(vocab_size, embedding_dim)。它的每一行就是對應詞的詞向量輸入向量。W2 (輸出矩陣)形狀為(embedding_dim, vocab_size)。用于將中心詞向量映射到整個詞匯表上的概率分布。模型流程將中心詞的獨熱編碼(1, vocab_size)乘以 W1得到其詞向量(1, embedding_dim)。將詞向量乘以 W2得到 logits(1, vocab_size)。對 logits 應用 softmax得到預測每個詞作為上下文詞的概率。用交叉熵損失比較預測概率和真實上下文詞也是獨熱編碼的差異。通過梯度下降更新 W1 和 W2。注意在實際的 Word2Vec 中會使用負采樣等技巧來加速訓練。為了極致簡化我們這里使用完整的 softmax。# simple_embedding_model.py import numpy as np class SimpleEmbeddingModel: def __init__(self, vocab_size, embedding_dim, learning_rate0.01): self.vocab_size vocab_size self.embedding_dim embedding_dim self.lr learning_rate # 隨機初始化權重矩陣 # W1 就是我們要的 Embedding 矩陣 self.W1 np.random.randn(vocab_size, embedding_dim) * 0.01 # 輸入到隱藏層 self.W2 np.random.randn(embedding_dim, vocab_size) * 0.01 # 隱藏層到輸出 def forward(self, center_word_id): 前向傳播輸入中心詞ID返回預測的概率分布 # 1. 獲取中心詞的 one-hot 向量 (1, vocab_size) x np.zeros((1, self.vocab_size)) x[0, center_word_id] 1 # 2. 計算隱藏層即詞向量 h x * W1 self.h np.dot(x, self.W1) # (1, embedding_dim) # 3. 計算輸出層 logits u h * W2 self.u np.dot(self.h, self.W2) # (1, vocab_size) # 4. 應用 softmax 得到概率分布 y_hat # 防止數值溢出減去最大值 exp_u np.exp(self.u - np.max(self.u)) self.y_hat exp_u / np.sum(exp_u, axis1, keepdimsTrue) # (1, vocab_size) return self.y_hat def backward(self, center_word_id, context_word_id): 反向傳播計算梯度并更新權重 # 1. 準備輸入和真實標簽的 one-hot x np.zeros((1, self.vocab_size)) x[0, center_word_id] 1 y np.zeros((1, self.vocab_size)) y[0, context_word_id] 1 # 2. 計算輸出層的誤差 (梯度) # 對于交叉熵損失 softmax輸出層的誤差 δ y_hat - y delta_output self.y_hat - y # (1, vocab_size) # 3. 計算 W2 和隱藏層的梯度 # dL/dW2 h.T * δ_output grad_W2 np.dot(self.h.T, delta_output) # (embedding_dim, vocab_size) # 隱藏層的誤差 δ_hidden δ_output * W2.T delta_hidden np.dot(delta_output, self.W2.T) # (1, embedding_dim) # 4. 計算 W1 的梯度 # dL/dW1 x.T * δ_hidden grad_W1 np.dot(x.T, delta_hidden) # (vocab_size, embedding_dim) # 5. 更新參數 (SGD) self.W2 - self.lr * grad_W2 self.W1 - self.lr * grad_W1 def train(self, training_pairs, epochs100): 訓練模型 for epoch in range(epochs): total_loss 0 np.random.shuffle(training_pairs) # 打亂數據 for center_id, context_id in training_pairs: # 前向傳播 y_hat self.forward(center_id) # 計算交叉熵損失僅用于監控 loss -np.log(y_hat[0, context_id] 1e-8) # 加上極小值防止log(0) total_loss loss # 反向傳播并更新 self.backward(center_id, context_id) if (epoch 1) % 20 0: avg_loss total_loss / len(training_pairs) print(fEpoch {epoch1}, Average Loss: {avg_loss:.4f}) def get_embedding(self, word): 獲取訓練好的詞向量 if word in word_to_id: word_id word_to_id[word] return self.W1[word_id] # 直接返回 W1 矩陣的對應行 else: return None # 初始化并訓練模型 vocab_size len(vocab) embedding_dim 5 # 為了可視化我們使用很小的維度 model SimpleEmbeddingModel(vocab_size, embedding_dim, learning_rate0.1) print(開始訓練...) model.train(training_pairs, epochs200) print(訓練完成)運行這段代碼你會看到損失在逐漸下降。我們的微型模型正在從幾十個“中心詞-上下文詞”對中學習規律。4.4 第四步觀察結果與語義驗證訓練完成后model.W1這個矩陣就是我們千辛萬苦得到的Embedding 矩陣它的每一行對應一個詞的向量。# evaluation.py # 1. 查看所有詞的 Embedding print(訓練得到的詞向量Embedding) for word in vocab: vec model.get_embedding(word) print(f{word:10s}: {np.round(vec, 4)}) # 保留4位小數便于觀察 # 2. 計算詞向量之間的余弦相似度 def cosine_similarity(vec_a, vec_b): 計算兩個向量的余弦相似度 dot_product np.dot(vec_a, vec_b) norm_a np.linalg.norm(vec_a) norm_b np.linalg.norm(vec_b) return dot_product / (norm_a * norm_b 1e-8) # 防止除零 # 3. 測試一些有趣的語義關系 print(\n--- 語義相似度測試 ---) test_pairs [(king, queen), (man, woman), (cat, dog), (strong, wise), (king, pet)] for word1, word2 in test_pairs: vec1 model.get_embedding(word1) vec2 model.get_embedding(word2) if vec1 is not None and vec2 is not None: sim cosine_similarity(vec1, vec2) print(f相似度 {word1} vs {word2}: {sim:.4f}) # 4. 嘗試經典的向量運算國王 - 男人 女人 ≈ 女王 print(\n--- 向量運算king - man woman ≈ queen ? ---) vec_king model.get_embedding(king) vec_man model.get_embedding(man) vec_woman model.get_embedding(woman) vec_queen model.get_embedding(queen) if all(v is not None for v in [vec_king, vec_man, vec_woman, vec_queen]): vec_result vec_king - vec_man vec_woman # 找出與結果向量最相似的詞 similarities {} for word in vocab: vec model.get_embedding(word) sim cosine_similarity(vec_result, vec) similarities[word] sim # 按相似度排序 sorted_similarities sorted(similarities.items(), keylambda x: x[1], reverseTrue) print(與向量 (king - man woman) 最相似的詞) for word, sim in sorted_similarities[:5]: # 顯示前5個 print(f {word}: {sim:.4f}) print(f (真實的 queen 相似度: {cosine_similarity(vec_result, vec_queen):.4f}))運行結果分析你的具體數值會因隨機初始化而不同但趨勢一致詞向量你會看到每個詞都被表示為一個 5 維的稠密向量例如king: [ 0.12 -0.45 0.78 0.02 -0.33]。語義相似度cat和dog的相似度會非常高因為它們共享“寵物”上下文。king和queen的相似度也會較高共享“王室”上下文。king和pet的相似度會很低。向量運算最激動人心的部分。你會發現計算出的vec_king - vec_man vec_woman這個向量與queen這個詞向量的余弦相似度很可能是最高的這證明了我們的微型模型已經捕捉到了“性別”和“王室地位”這種抽象的語義關系。這就是 Embedding 注入意義的過程模型通過完成“預測上下文”這個代理任務被迫將具有相似上下文的詞如“貓”和“狗”映射到向量空間中相近的位置并將語義關系如“國王對男人”類似于“女王對女人”編碼為向量空間中的平移關系。5. 從我們的“玩具”到 GPT 的“工業級” Embedding我們手搓的模型揭示了 Embedding 的核心學習機制但與現代大模型如 GPT中的 Embedding 相比還有幾個關鍵區別特性我們的手搓模型GPT 等大模型的 Embedding訓練目標預測固定窗口內的上下文詞Skip-gram。預測下一個詞自回歸或完形填空MLM目標更復雜。輸入單元詞Word。更細粒度的子詞Subword如 Byte-Pair Encoding能處理未登錄詞。向量維度5維示例。通常為768、1024、4096甚至更高維表達能力極強。上下文感知靜態。每個詞只有一個固定的向量。動態上下文相關。同一個詞在不同句子中會有不同的向量表示通過 Transformer 的自注意力機制實現。這是質的飛躍。位置信息無。包含位置編碼Positional Encoding讓模型知道詞的順序。學習方式作為模型參數單獨訓練。通常是整個巨型 Transformer 模型的一部分與所有其他參數一起進行端到端訓練。核心進階點從靜態到動態我們的模型學到的king的向量是固定的。但在 GPT 中“king”這個詞的初始向量Token Embedding只是一個起點。當句子“The king ruled the country.”輸入模型后經過多層 Transformer 的自注意力機制計算每個詞包括“king”的表示都會與句中所有其他詞進行交互最終形成一個融合了全句信息的上下文相關表示。這才是 GPT 能理解復雜語義和指代關系的根本。6. 常見問題與排查思路在實踐 Embedding 相關項目時你可能會遇到以下問題問題現象可能原因排查方式解決方案詞向量相似度計算不合理所有詞都差不多。1. 模型未充分訓練。2. 學習率設置不當。3. 語料太小或質量太差。4. 向量維度太低。1. 檢查訓練損失是否已收斂。2. 可視化詞向量如用 PCA 降維到2D看圖。3. 檢查訓練數據量。1. 增加訓練輪數epochs。2. 調整學習率嘗試更小值。3. 使用更大、更相關的語料。4. 適當增加embedding_dim。訓練過程損失不下降或變為 NaN。1. 學習率過高導致梯度爆炸。2. 初始化權重值過大。3. 未對 softmax 做數值穩定處理。1. 打印每輪損失和梯度范數。2. 檢查W1、W2初始化值。1. 大幅降低學習率如從0.1調到0.01。2. 使用更小的隨機初始化標準差如* 0.001。3. 確保 softmax 計算時減去了最大值。無法處理新詞OOV。使用了基于詞的模型詞匯表固定。確認輸入詞是否在word_to_id字典中。1. 在預處理時替換為UNK標記。2.更優方案使用子詞分詞如 BPE從根本上解決 OOV。向量運算如類比推理效果差。1. 語料不足以支撐該語義關系。2. 模型容量不足維度太低。3. 訓練目標不適合捕捉此類關系。1. 檢查語料中相關詞的出現頻率和共現模式。2. 嘗試更大的embedding_dim。1. 使用領域相關的大規模語料。2. 使用更成熟的模型如 Gensim 的 Word2Vec。3. 考慮使用更先進的 Embedding 方法如 GloVe, FastText。7. 最佳實踐與工程建議理解了原理后在實際項目中應用 Embedding 時應遵循以下最佳實踐語料質量大于數量用于訓練 Embedding 的語料必須與你的下游任務高度相關。用通用維基百科語料訓練出的向量在醫療或法律領域任務上效果可能不佳。優先使用預訓練模型除非有非常特殊的領域和充足的數據否則不要從頭訓練。像Sentence-Transformers、OpenAI 的 text-embedding 系列或M3E等模型提供了高質量、開箱即用的通用 Embedding。理解靜態與動態 Embedding 的適用場景靜態 Embedding如 Word2Vec適用于詞匯級語義相似度計算、作為簡單模型的輸入特征。速度快資源消耗低。動態/上下文 Embedding如 BERT, GPT適用于需要理解句子整體語義、一詞多義、復雜邏輯關系的任務如文本分類、問答、語義檢索。計算成本高。Embedding 的維度選擇并非維度越高越好。更高的維度能容納更多信息但也需要更多數據來訓練且可能引入噪聲。通常 300-768 維是一個較好的平衡點。可以通過在下游任務上的表現來選擇。標準化與相似度計算在使用余弦相似度檢索前對 Embedding 向量進行 L2 標準化是標準操作。這能確保相似度計算完全依賴于向量方向不受長度影響。即vec_normalized vec / np.linalg.norm(vec)。生產環境部署對于海量向量的相似度檢索務必使用專門的向量數據庫Vector Database如 Milvus、Pinecone、Qdrant、Weaviate 等。它們針對高維向量的近似最近鄰搜索ANN進行了極致優化遠超傳統數據庫的線性掃描性能。8. 總結與后續方向通過這次“手搓”我們揭開了 Embedding 的神秘面紗。它的核心思想樸素而有力讓詞語在向量空間中的“位置”由其上下文“鄰居”決定。我們從獨熱編碼的“荒漠”出發構建了一個能夠學習“國王-男人女人≈女王”這種抽象關系的稠密語義空間。本文帶你走通了最關鍵的幾步理解了為什么從獨熱編碼的缺陷到分布式假設的必要性。看到了怎么做用 Skip-gram 目標構建訓練數據通過前向/反向傳播更新 Embedding 矩陣。驗證了效果通過余弦相似度和向量運算直觀感受到語義被編碼進了數字。要進一步深入你可以復現完整 Word2Vec加入負采樣Negative Sampling或層次 Softmax使其能處理大規模語料。探索 GloVe研究基于全局詞頻統計的另一種經典 Embedding 方法。深入 Transformer理解如何通過自注意力機制實現上下文相關的動態 Embedding這是通往 BERT、GPT 等現代模型的鑰匙。實踐向量數據庫選一個開源向量數據庫將本文生成的或下載的預訓練 Embedding 導入實現一個簡單的語義搜索系統。Embedding 是連接符號世界與連續向量世界的橋梁是讓機器理解人類語言語義的基石。希望這次從零開始的手工體驗能讓你下次在調用model.encode()或看到embedding_dim768時眼前浮現的不再是黑盒而是那個由上下文編織而成的、充滿意義的數學空間。建議收藏本文當你需要向他人解釋 Embedding或自己遺忘想重溫時這份從零構建的代碼就是最好的備忘錄。