在自然語言處理(NLP)領域,詞向量(Word Embedding)是將文本轉換為數值向量的核心技術。它能讓計算機“理解”詞語的語義關聯,例如“國王”和“女王”的向量差可能與“男人”和“女人”的向量差相似。而Word2Vec作為經典的詞向量訓練模型,其核心思想是通過上下文預測目標詞(或反之)。本文將以 --CBOW(連續詞袋模型)為例,帶你從代碼到原理,一步步實現一個簡單的詞向量訓練過程。
一、CBOW模型簡介
CBOW(Continuous Bag-of-Words)是Word2Vec的兩種核心模型之一。其核心思想是:給定目標詞的上下文窗口內的所有詞,預測目標詞本身。例如,對于句子“We are about to study”,若上下文窗口大小為2(即目標詞左右各取2個詞),則當目標詞是“about”時,上下文是“We, are, to, study”,模型需要根據這4個詞預測出“about”。
CBOW的優勢在于通過平均上下文詞向量來預測目標詞,計算效率高;缺點是對低頻詞不友好。本文將實現的CBOW模型包含詞嵌入層、投影層和輸出層,最終輸出目標詞的概率分布。
二、環境準備與數據預處理
2.1 進度條庫安裝
pip install torch numpy tqdm
2.2 語料庫與基礎設置
我們使用一段英文文本作為語料庫,并定義上下文窗口大小(CONTEXT_SIZE=2
,即目標詞左右各取2個詞):
CONTEXT_SIZE = 2 # 上下文窗口大小(左右各2個詞)
raw_text = """We are about to study the idea of a computational process.
Computational processes are abstract beings that inhabit computers.
As they evolve, processes manipulate other abstract things called data.
The evolution of a process is directed by a pattern of rules
called a program. People create programs to direct processes. In effect,
we conjure the spirits of the computer with our spells.""".split() # 按空格分割成單詞列表
2.3 構建詞匯表與映射
為了將文本轉換為模型可處理的數值,需要先構建詞匯表(所有唯一詞),并為每個詞分配唯一索引:
vocab = set(raw_text) # 去重后的詞匯表(集合)
vocab_size = len(vocab) # 詞匯表大小(本文示例中為49)# 詞到索引的映射(如:"We"→0,"are"→1)
word_to_idx = {word: i for i, word in enumerate(vocab)}
# 索引到詞的反向映射(如:0→"We",1→"are")
idx_to_word = {i: word for i, word in enumerate(vocab)}
三、生成訓練數據:上下文-目標詞對
CBOW的訓練數據是“上下文詞列表”與“目標詞”的配對。例如,若目標詞是raw_text[i]
,則上下文是[raw_text[i-2], raw_text[i-1], raw_text[i+1], raw_text[i+2]]
(假設窗口大小為2)。
3.1 數據生成邏輯
通過遍歷語料庫,跳過前CONTEXT_SIZE
和后CONTEXT_SIZE
個詞(避免越界),生成上下文-目標詞對:
data = []
for i in range(CONTEXT_SIZE, len(raw_text) - CONTEXT_SIZE):# 左上下文:取i-2, i-1(j從0到1,2-j對應2,1)left_context = [raw_text[i - (2 - j)] for j in range(CONTEXT_SIZE)]# 右上下文:取i+1, i+2(j從0到1,i+j+1對應i+1, i+2)right_context = [raw_text[i + j + 1] for j in range(CONTEXT_SIZE)]context = left_context + right_context # 合并上下文(共4個詞)target = raw_text[i] # 目標詞(當前中心詞)data.append((context, target))
3.2 示例驗證
以i=2
為例:
- 左上下文:
i-2=0
(“We”),i-1=1
(“are”)→["We", "are"]
- 右上下文:
i+1=3
(“to”),i+2=4
(“study”)→["to", "study"]
- 上下文合并:
["We", "are", "to", "study"]
- 目標詞:
raw_text[2]
(“about”)
四、CBOW模型實現(PyTorch)
4.1 模型結構設計
CBOW模型的核心是通過上下文詞的詞向量預測目標詞。模型結構包含三層:
- 詞嵌入層(Embedding):將詞的索引映射為低維稠密向量(如10維)。
- 投影層(Linear):將拼接后的詞向量投影到更高維度(如128維),增加非線性表達能力。
- 輸出層(Linear):將投影后的向量映射回詞匯表大小,通過Softmax輸出目標詞的概率分布。
import torch
import torch.nn as nn
import torch.nn.functional as Fclass CBOW(nn.Module): # 神經網絡def __init__(self, vocab_size, embedding_dim):super(CBOW, self).__init__() # 父類的初始化self.embeddings = nn.Embedding(vocab_size, embedding_dim)self.proj = nn.Linear(embedding_dim, 128)self.output = nn.Linear(128, vocab_size)def forward(self, inputs):embeds = sum(self.embeddings(inputs)).view(1, -1)out = F.relu(self.proj(embeds)) # nn.relu() 激活層out = self.output(out)nll_prob = F.log_softmax(out, dim=-1) # softmax交叉熵return nll_prob
五、模型訓練與優化
5.1 初始化模型與超參數
設置詞向量維度(embedding_dim=10
)、學習率(lr=0.001
)、訓練輪數(epochs=200
),并初始化模型、優化器和損失函數:
vocab_size = 49
model = CBOW(vocab_size, 10).to(device)
optimizer=torch.optim.Adam(model.parameters(),lr=0.001)
losses = []# 存儲損失的集合 losses: []
loss_function = nn.NLLLoss() #NLLLoss損失函數(當分類列表非常多的情況),將多個類
5.2 訓練循環邏輯
遍歷每個訓練輪次(epoch),對每個上下文-目標詞對進行前向傳播、損失計算、反向傳播和參數更新:
model.train()
for epoch in tqdm(range(200)):#開始訓練total_loss = 0for context, target in data:context_vector = make_context_vector(context, word_to_idx).to(device)target = torch.tensor([word_to_idx[target]]).to(device)# 開始前向傳播train_predict = model(context_vector) # 可以不寫forward,torch的內置功能,loss = loss_function(train_predict, target) # 計算損失# 反向傳播optimizer.zero_grad() # 梯度值清零loss.backward() # 反向傳播計算得到每個參數的梯度optimizer.step() # 根據梯度更新網絡參數total_loss += loss.item()
六、詞向量提取與應用
訓練完成后,模型的詞嵌入層(model.embeddings.weight
)中存儲了每個詞的向量表示。我們可以將其提取并保存,用于后續任務(如文本分類、相似度計算)。
6.1 提取詞向量
# 將詞向量從GPU移至CPU,并轉換為NumPy數組
W = model.embeddings.weight.cpu().detach().numpy()
print("詞向量矩陣形狀:", W.shape) # (vocab_size, embedding_dim) → (49, 10)
6.2 生成詞-向量映射字典
word_2_vec = {}
for word, idx in word_to_idx.items():word_2_vec[word] = W[idx] # 每個詞對應詞向量矩陣中的一行
print("示例詞向量('process'):", word_2_vec["process"])
6.3 保存詞向量
使用np.savez
保存詞向量矩陣,方便后續加載使用:
import numpy as np
np.savez('word2vec實現.npz', word_vectors=W) # 保存為npz文件# 加載驗證
data = np.load('word2vec實現.npz')
loaded_vectors = data['word_vectors']
print("加載的詞向量形狀:", loaded_vectors.shape) # 應與原始矩陣一致