Word2Vec模型詳解:CBOW與Skip-gram
目錄
- 模型概述
- 理論基礎
- CBOW模型詳解
- Skip-gram模型詳解
- 模型對比
- 代碼實現詳解
- 訓練過程分析
- 應用場景
- 實驗結果
- 總結
模型概述
Word2Vec是一種用于生成詞向量的神經網絡模型,由Google在2013年提出。它包含兩種主要架構:
- CBOW (Continuous Bag of Words): 根據上下文詞匯預測目標詞
- Skip-gram: 根據目標詞預測上下文詞匯
這兩種模型都能將詞匯轉換為高維密集向量,使得語義相近的詞在向量空間中距離較近。
理論基礎
基本假設
Word2Vec基于分布式假設:在相似上下文中出現的詞匯具有相似的語義。
核心思想
通過神經網絡學習詞匯的分布式表示,使得:
- 語義相似的詞匯在向量空間中距離較近
- 詞匯間的語義關系通過向量運算體現
- 支持詞匯類比推理(如:國王-男人+女人=皇后)
CBOW模型詳解
模型架構
上下文詞匯 → 嵌入層 → 平均池化 → 線性層 → 目標詞概率
CBOW模型通過上下文詞匯的平均嵌入來預測中心詞匯。
代碼實現
1. 模型定義
# 定義CBOW模型類,繼承自PyTorch的神經網絡模塊
class CBOWModel(nn.Module):def __init__(self, vocab_size, embedding_dim):# 調用父類構造函數super(CBOWModel, self).__init__()# 創建詞嵌入層,將詞匯索引映射到密集向量self.embedding = nn.Embedding(vocab_size, embedding_dim)# 創建線性層,將嵌入向量映射到詞匯表大小的輸出self.linear = nn.Linear(embedding_dim, vocab_size)def forward(self, context_words):# 前向傳播函數,context_words形狀: [batch_size, context_size]# 獲取上下文詞匯的嵌入向量embeds = self.embedding(context_words) # 形狀: [batch_size, context_size, embedding_dim]# 計算上下文詞匯嵌入的平均值mean_embed = torch.mean(embeds, dim=1) # 形狀: [batch_size, embedding_dim]# 通過線性層得到最終輸出out = self.linear(mean_embed) # 形狀: [batch_size, vocab_size]return out
關鍵特點:
- 單一嵌入層:所有詞匯共享同一個嵌入空間
- 平均池化:對上下文詞匯嵌入求平均
- 分類任務:輸出目標詞匯的概率分布
2. 數據集創建
def create_cbow_dataset(corpus, window_size=2):"""創建CBOW訓練數據集"""# 初始化數據列表data = []# 遍歷語料庫,跳過邊界位置for i in range(window_size, len(corpus) - window_size):# 初始化上下文詞匯列表context = []# 當前位置的詞匯作為目標詞target = corpus[i]# 收集目標詞周圍的上下文詞匯for j in range(i - window_size, i + window_size + 1):# 跳過目標詞本身if j != i:# 添加上下文詞匯到列表context.append(corpus[j])# 將上下文和目標詞作為訓練樣本data.append((context, target))# 返回訓練數據集return data
數據格式:每個樣本為 ([上下文詞匯列表], 目標詞)
3. 完形填空功能
def cloze_test(model, sentence, mask_position, word_to_id, id_to_word, window_size=2, top_k=5):"""完形填空函數"""# 將句子分割成詞匯列表words = sentence.lower().replace('.', ' .').split()# 檢查mask位置是否有效if mask_position >= len(words) or mask_position < 0:print(f"無效的mask位置: {mask_position}")return None# 保存被遮掩的真實詞匯true_word = words[mask_position]print(f"被遮掩的詞匯: '{true_word}'")# 提取上下文詞匯(排除mask位置)context_words = []for i in range(max(0, mask_position - window_size), min(len(words), mask_position + window_size + 1)):if i != mask_position and words[i] in word_to_id:context_words.append(word_to_id[words[i]])# 進行預測with torch.no_grad():context_tensor = torch.tensor(context_words, dtype=torch.long).unsqueeze(0)output = model(context_tensor)probabilities = F.softmax(output, dim=1)# 獲取top-k預測結果top_probs, top_indices = torch.topk(probabilities, top_k, dim=1)# 顯示預測結果predictions = []for i in range(top_k):word_idx = top_indices[0][i].item()prob = top_probs[0][i].item()predicted_word = id_to_word[word_idx]predictions.append((predicted_word, prob))print(f"{predicted_word}\t\t{prob:.4f}")return predictions
完形填空原理:CBOW天然適合完形填空,因為它就是根據上下文預測目標詞。
Skip-gram模型詳解
模型架構
目標詞 → 目標嵌入層 ↘→ 相似度計算 → 上下文詞概率
上下文詞 → 上下文嵌入層 ↗
Skip-gram使用兩個獨立的嵌入層,通過負采樣進行訓練。
代碼實現
1. 模型定義
# 定義Skip-gram模型類,繼承自PyTorch的神經網絡模塊
class SkipGramModel(nn.Module):def __init__(self, vocab_size, embedding_dim):# 調用父類構造函數super(SkipGramModel, self).__init__()# 創建輸入詞嵌入層,將目標詞索引映射到密集向量self.target_embedding = nn.Embedding(vocab_size, embedding_dim)# 創建輸出詞嵌入層,用于預測上下文詞self.context_embedding = nn.Embedding(vocab_size, embedding_dim)def forward(self, target_word, context_words):# 前向傳播函數# target_word: [batch_size] - 目標詞索引# context_words: [batch_size] - 上下文詞索引# 獲取目標詞的嵌入向量target_embed = self.target_embedding(target_word) # [batch_size, embedding_dim]# 獲取上下文詞的嵌入向量context_embed = self.context_embedding(context_words) # [batch_size, embedding_dim]# 計算目標詞和上下文詞的相似度得分score = torch.sum(target_embed * context_embed, dim=1) # [batch_size]return score
關鍵特點:
- 雙嵌入層:目標詞和上下文詞有不同的嵌入空間
- 點積相似度:計算目標詞和上下文詞嵌入的內積
- 回歸任務:輸出相似度得分
2. 負采樣訓練
def negative_sampling(vocab_size, num_negatives=5):"""負采樣:隨機選擇負樣本詞匯"""# 隨機生成負樣本詞匯索引negative_samples = torch.randint(0, vocab_size, (num_negatives,))return negative_samplesdef train_skipgram(model, train_data, vocab_size, epochs=100, learning_rate=0.01, num_negatives=5):"""訓練Skip-gram模型(使用負采樣)"""# 定義二元交叉熵損失函數criterion = nn.BCEWithLogitsLoss()# 定義Adam優化器optimizer = optim.Adam(model.parameters(), lr=learning_rate)for epoch in range(epochs):total_loss = 0for target, context in train_data:# 正樣本訓練target_tensor = torch.tensor([target], dtype=torch.long)context_tensor = torch.tensor([context], dtype=torch.long)positive_label = torch.tensor([1.0], dtype=torch.float)positive_score = model(target_tensor, context_tensor)positive_loss = criterion(positive_score, positive_label)# 負樣本訓練negative_samples = negative_sampling(vocab_size, num_negatives)negative_labels = torch.zeros(num_negatives, dtype=torch.float)target_repeated = target_tensor.repeat(num_negatives)negative_scores = model(target_repeated, negative_samples)negative_loss = criterion(negative_scores, negative_labels)# 總損失total_sample_loss = positive_loss + negative_loss# 反向傳播和參數更新optimizer.zero_grad()total_sample_loss.backward()optimizer.step()total_loss += total_sample_loss.item()return losses
負采樣原理:
- 正樣本:真實的(目標詞, 上下文詞)對,標簽為1
- 負樣本:隨機的(目標詞, 非上下文詞)對,標簽為0
- 避免計算整個詞匯表的softmax,提高訓練效率
3. 上下文預測功能
def predict_context_words(model, target_word, word_to_id, id_to_word, top_k=5):"""預測給定目標詞的上下文詞匯"""target_idx = word_to_id[target_word]target_tensor = torch.tensor([target_idx], dtype=torch.long)model.eval()with torch.no_grad():# 計算目標詞與所有詞匯的相似度得分scores = []vocab_size = len(word_to_id)for word_idx in range(vocab_size):context_tensor = torch.tensor([word_idx], dtype=torch.long)score = model(target_tensor, context_tensor)scores.append((word_idx, score.item()))# 按得分降序排序并返回top-k結果scores.sort(key=lambda x: x[1], reverse=True)predictions = []for i in range(min(top_k, len(scores))):word_idx, score = scores[i]predicted_word = id_to_word[word_idx]if predicted_word != target_word:predictions.append((predicted_word, score))return predictions
上下文預測原理:Skip-gram擅長預測給定詞匯的可能上下文,適用于詞匯擴展和同義詞發現。
模型對比
特性 | CBOW | Skip-gram |
---|---|---|
輸入 | 上下文詞匯 | 目標詞 |
輸出 | 目標詞概率 | 上下文詞得分 |
嵌入層 | 單一共享嵌入 | 雙獨立嵌入 |
訓練數據量 | 較少 | 較多 |
計算復雜度 | 較低 | 較高 |
適用場景 | 完形填空、語言模型 | 詞匯擴展、相似詞發現 |
對頻率的敏感性 | 對高頻詞效果好 | 對低頻詞效果好 |
數學表示
CBOW目標函數:
maximize Σ log P(w_t | context(w_t))
Skip-gram目標函數:
maximize Σ Σ log P(w_c | w_t)
其中:
w_t
: 目標詞w_c
: 上下文詞context(w_t)
: 目標詞的上下文窗口
代碼實現詳解
共享組件
1. 數據預處理
def preprocess(text):"""文本預處理函數"""# 轉換為小寫并分割單詞word = text.lower().replace('.', ' .').split(' ')# 創建詞匯到ID的映射word_to_id = {}id_to_word = {}for w in word:if w not in word_to_id:tmp = len(word_to_id)word_to_id[w] = tmpid_to_word[tmp] = w# 獲得ID列表corpus = [word_to_id[w] for w in word]return corpus, word_to_id, id_to_word
2. 相似度計算
def cos_similarity(x, y, eps=1e-8):"""計算余弦相似度"""nx = x / (np.sqrt(np.sum(x ** 2)) + eps)ny = y / (np.sqrt(np.sum(y ** 2)) + eps)return np.dot(nx, ny)
3. 詞向量可視化
def visualize_embeddings(word_vectors, word_to_id, max_words=50):"""可視化詞向量(使用PCA降維到2D)"""from sklearn.decomposition import PCAwords = list(word_vectors.keys())[:max_words]vectors = [word_vectors[word] for word in words]pca = PCA(n_components=2)vectors_2d = pca.fit_transform(vectors)plt.figure(figsize=(12, 8))plt.scatter(vectors_2d[:, 0], vectors_2d[:, 1], alpha=0.7)for i, word in enumerate(words):plt.annotate(word, (vectors_2d[i, 0], vectors_2d[i, 1]))plt.title('Word Embeddings Visualization (PCA)')plt.show()
訓練流程對比
CBOW訓練流程
- 提取上下文詞匯窗口
- 計算上下文詞匯嵌入的平均值
- 通過線性層預測目標詞
- 使用交叉熵損失進行優化
Skip-gram訓練流程
- 選擇目標詞和一個上下文詞
- 計算正樣本得分和損失
- 進行負采樣,計算負樣本損失
- 使用二元交叉熵損失進行優化
訓練過程分析
超參數設置
# 共同超參數
window_size = 2 # 上下文窗口大小
embedding_dim = 100 # 詞向量維度
epochs = 100 # 訓練輪數
learning_rate = 0.01 # 學習率# Skip-gram特有參數
num_negatives = 5 # 負采樣數量
訓練數據量對比
以示例文本為例:
- CBOW: 15個訓練樣本(每個位置一個樣本)
- Skip-gram: 60個訓練樣本(每個(目標詞,上下文詞)對一個樣本)
損失函數分析
CBOW損失曲線特點:
- 初始損失較高(約3.0)
- 快速收斂到接近0
- 曲線較為平滑
Skip-gram損失曲線特點:
- 初始損失很高(約7.6)
- 收斂較慢,存在波動
- 最終損失相對較高(約1.3)
應用場景
CBOW適用場景
-
完形填空任務
# 例:根據"you say ___ and I"預測"goodbye" cloze_test(model, "you say goodbye and I say hello", 2, word_to_id, id_to_word)
-
語言模型構建
- 文本生成
- 語法檢查
- 自動補全
-
高頻詞處理
- 對常見詞匯效果更好
- 適合處理語法功能詞
Skip-gram適用場景
-
詞匯擴展和發現
# 例:根據"love"找到相關詞匯 predict_context_words(model, "love", word_to_id, id_to_word)
-
同義詞挖掘
- 發現語義相似詞匯
- 構建同義詞詞典
-
低頻詞處理
- 對罕見詞匯效果更好
- 適合處理專業術語
-
詞匯類比推理
# 概念上的:king - man + woman ≈ queen
實驗結果
訓練效果對比
測試文本: “you say goodbye and I say hello. what are you talking about? I love natural language processing.”
CBOW結果
相似詞查找:
- "you" 的相似詞: are, language, goodbye, i, about?
- "say" 的相似詞: goodbye, what, natural, love, talking
- "hello" 的相似詞: and, love, are, goodbye, about?完形填空準確性: 中等
收斂速度: 快
Skip-gram結果
相似詞查找:
- "you" 的相似詞: say, language, natural, love, hello
- "say" 的相似詞: talking, hello, you, language, i
- "hello" 的相似詞: goodbye, ., say, language, about?上下文預測能力: 強
詞匯發現能力: 強
性能分析
指標 | CBOW | Skip-gram |
---|---|---|
訓練速度 | 快 | 慢 |
內存消耗 | 低 | 高 |
收斂穩定性 | 高 | 中等 |
詞匯發現能力 | 中等 | 強 |
語法理解能力 | 強 | 中等 |
代碼使用指南
運行CBOW模型
cd nlp
python cbow_pytorch.py
功能包括:
- 模型訓練和損失可視化
- 相似詞查找
- 批量完形填空測試
- 交互式完形填空
- 詞向量可視化
運行Skip-gram模型
cd nlp
python skipgram_pytorch.py
功能包括:
- 負采樣訓練
- 相似詞查找
- 上下文詞預測
- 詞向量可視化
自定義使用
# 導入模型
from cbow_pytorch import CBOWModel, train_cbow
from skipgram_pytorch import SkipGramModel, train_skipgram# 準備數據
text = "your custom text here"
corpus, word_to_id, id_to_word = preprocess(text)# 訓練CBOW
cbow_model = CBOWModel(len(word_to_id), 100)
cbow_data = create_cbow_dataset(corpus)
train_cbow(cbow_model, cbow_data)# 訓練Skip-gram
skipgram_model = SkipGramModel(len(word_to_id), 100)
skipgram_data = create_skipgram_dataset(corpus)
train_skipgram(skipgram_model, skipgram_data, len(word_to_id))
優化建議
模型改進方向
-
層次化Softmax
- 替代負采樣,提高大詞匯表訓練效率
- 基于詞頻構建二叉樹結構
-
子詞信息
- 引入字符級或子詞級信息
- 處理未登錄詞和形態變化
-
動態窗口
- 根據距離調整上下文權重
- 更好捕捉長距離依賴
工程優化
-
批處理訓練
# 當前實現是單樣本訓練,可以改為批處理 batch_size = 32
-
數據并行
# 使用PyTorch的DataParallel進行多GPU訓練 model = nn.DataParallel(model)
-
內存優化
- 使用gradient checkpointing
- 動態詞匯表
總結
核心要點
-
CBOW與Skip-gram是互補的:
- CBOW:上下文→目標詞,適合完形填空
- Skip-gram:目標詞→上下文,適合詞匯發現
-
訓練策略不同:
- CBOW:使用交叉熵損失,直接優化
- Skip-gram:使用負采樣,避免昂貴的softmax
-
應用場景各異:
- CBOW:語言建模、語法任務
- Skip-gram:語義分析、詞匯擴展
選擇建議
- 小數據集:選擇CBOW,訓練更穩定
- 大數據集:選擇Skip-gram,效果更好
- 完形填空:使用CBOW
- 詞匯發現:使用Skip-gram
- 平衡考慮:可以同時訓練兩個模型并集成
擴展方向
- FastText: 加入子詞信息
- GloVe: 結合全局統計信息
- ELMo: 上下文相關的詞向量
- BERT: 雙向Transformer編碼器
這兩個模型為現代NLP奠定了重要基礎,理解它們的原理和實現對深入學習詞嵌入技術具有重要意義。