代碼實現:
import torch.optim as optim
from tqdm import tqdm, trange
import numpy as np
import torch
from torch import nn
import torch.nn.functional as FCONTEXT_SIZE = 2raw_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()vocab = set(raw_text)
vocab_size = len(vocab)word_to_idx = {word: i for i, word in enumerate(vocab)}
idx_to_word = {i: word for i, word in enumerate(vocab)}data = []
for i in range(CONTEXT_SIZE, len(raw_text) - CONTEXT_SIZE): # (2, 60)context = ([raw_text[i - (2-j)] for j in range(CONTEXT_SIZE)] # [we,are]+ [raw_text[i + j + 1] for j in range(CONTEXT_SIZE)] # [to,study])target = raw_text[i] # 目標詞data.append((context, target))def make_context_vector(context,word_to_idx):idxs = [word_to_idx[x] for x in context]return torch.tensor(idxs,dtype=torch.long)
print(make_context_vector(data[0][0],word_to_idx))device = 'cuda' if torch.cuda.is_available() else 'mps' if torch.backends.mps.is_available() else 'cpu'
print(device)class 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))out = self.output(out)null_plob = F.log_softmax(out,dim=-1)return null_plobmodel = CBOW(vocab_size,10).to(device)
optimizer = optim.Adam(model.parameters(),lr=0.001)
losses = []
loss_function = nn.NLLLoss()
model.train()
for epoch in tqdm(range(200)):total_loss = 0size = 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)loss = loss_function(train_predict,target)optimizer.zero_grad()loss.backward()optimizer.step()total_loss += loss.item()size += 1avg_loss = total_loss / sizeprint(avg_loss)context = ['People', 'create', 'to', 'direct'] # People create programs to direct
context_vector = make_context_vector(context, word_to_idx).to(device)# 預測的值
model.eval() # 進入到測試模式
predict = model(context_vector)
max_idx = predict.argmax(1) # dim=1表示每一行中的最大值對應的索引號,dim=0表示每一列中的最大值對應的索引號# 獲取詞向量,這個Embedding就是我們需要的詞向量,他只是一個模型的一個中間過程
print("CBOW embedding weight=", model.embeddings.weight) # GPU
W = model.embeddings.weight.cpu().detach().numpy() # .detach(): 這個方法會創建一個新的Tensor,它和原來的Tensor共享# 這意味著這個新的Tensor不會參與梯度的反向傳播,這對于防止在計算梯度時意外修改某些參數很有用。
print(W)# 生成詞嵌入字典,即{單詞1:詞向量1,單詞2:詞向量2...}的格式
word_2_vec = {}
for word in word_to_idx.keys():# 詞向量矩陣中某個詞的索引所對應的那一列即為該詞的詞向量word_2_vec[word] = W[word_to_idx[word], :]
print('jiesu')# 保存訓練后的詞向量為npz文件,numpy處理矩陣的速度非常快,方便后期其他操作
np.savez('word2vec實現.npz', file_1=W)
data = np.load('word2vec實現.npz')
print(data.files)
這段代碼是一個基于 PyTorch 實現的CBOW(連續詞袋模型)?示例,用于訓練詞向量(類似 Word2Vec 的核心功能)。下面逐步分析代碼:
一、核心功能概述
代碼通過一段示例文本訓練 CBOW 模型,最終得到每個單詞的低維向量表示(詞嵌入)。CBOW 的核心思想是:用一個單詞的上下文(周圍單詞)預測該單詞本身,通過這個過程讓模型學習到單詞的語義向量。
二、代碼分步解析
1. 導入依賴庫
import torch.optim as optim # PyTorch優化器
from tqdm import tqdm, trange # 進度條工具
import numpy as np # 數值計算
import torch
from torch import nn # 神經網絡模塊
import torch.nn.functional as F # 神經網絡函數(如激活函數、softmax等)
這些是 PyTorch 深度學習和數據處理的基礎庫,tqdm
用于顯示訓練進度,提高可視化體驗。
2. 數據預處理
(1)定義超參數和原始文本
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()
CONTEXT_SIZE=2
表示:對于每個目標詞,取其前面 2 個詞 + 后面 2 個詞作為上下文(共 4 個詞)。
(2)構建詞匯表
vocab = set(raw_text) # 去重,得到所有唯一單詞
vocab_size = len(vocab) # 詞匯表大小# 單詞→索引映射(用于將單詞轉為數字)
word_to_idx = {word: i for i, word in enumerate(vocab)}
# 索引→單詞映射(用于將數字轉回單詞)
idx_to_word = {i: word for i, word in enumerate(vocab)}
計算機無法直接處理文本,需將單詞轉為數字索引。這里通過兩個字典實現單詞和索引的雙向映射。
(3)構建訓練數據(上下文→目標詞)
data = []
# 遍歷文本,為每個位置的詞構建上下文和目標
for i in range(CONTEXT_SIZE, len(raw_text) - CONTEXT_SIZE):# 上下文 = 目標詞前面2個詞 + 后面2個詞context = ([raw_text[i - (2-j)] for j in range(CONTEXT_SIZE)] # 前面2個詞(如i=2時,取i-2, i-1)+ [raw_text[i + j + 1] for j in range(CONTEXT_SIZE)] # 后面2個詞(如i=2時,取i+1, i+2))target = raw_text[i] # 目標詞(當前位置的詞)data.append((context, target))
例如,對于句子片段We are about to study
:
- 當目標詞是
about
(索引 = 2)時,上下文是前面的We, are
和后面的to, study
,即(["We", "are", "to", "study"], "about")
。
data
最終是一個列表,每個元素是(上下文單詞列表, 目標詞)
的元組,用于模型訓練。
(4)上下文轉向量函數
def make_context_vector(context, word_to_idx):# 將上下文單詞轉為對應的索引,再封裝成PyTorch張量idxs = [word_to_idx[x] for x in context]return torch.tensor(idxs, dtype=torch.long)
將上下文的單詞列表(如["We", "are", "to", "study"]
)轉為索引張量(如[10, 5, 3, 8]
),作為模型的輸入。
3. 設備配置
device = 'cuda' if torch.cuda.is_available() else 'mps' if torch.backends.mps.is_available() else 'cpu'
print(device)
自動選擇訓練設備:優先用 NVIDIA GPU(cuda),其次 Apple GPU(mps),最后用 CPU,以加速訓練。
4. CBOW 模型定義
class CBOW(nn.Module):def __init__(self, vocab_size, embedding_dim):super(CBOW, self).__init__()# 嵌入層:將單詞索引轉為低維向量(vocab_size→embedding_dim)self.embeddings = nn.Embedding(vocab_size, embedding_dim)# 投影層:將嵌入向量映射到128維self.proj = nn.Linear(embedding_dim, 128)# 輸出層:將128維向量映射到詞匯表大小(預測目標詞)self.output = nn.Linear(128, vocab_size)def forward(self, inputs):# 1. 上下文嵌入:獲取輸入的每個單詞的嵌入向量,求和(上下文向量的平均/求和代表上下文語義)embeds = sum(self.embeddings(inputs)).view(1, -1) # 形狀:(1, embedding_dim)# 2. 投影層+激活函數out = F.relu(self.proj(embeds)) # 形狀:(1, 128)# 3. 輸出層+log_softmax(將輸出轉為概率分布的對數)out = self.output(out) # 形狀:(1, vocab_size)log_probs = F.log_softmax(out, dim=-1) # 沿最后一維計算softmax并取對數return log_probs
模型核心邏輯:
嵌入層(embeddings):是訓練的核心,其權重就是最終的詞向量(每個單詞對應一行向量)。
前向傳播:將上下文單詞的嵌入向量求和(代表上下文的整體語義),通過兩層線性網絡,最終輸出對目標詞的概率分布(對數形式)。
5. 模型訓練
# 初始化模型(詞匯表大小,嵌入維度10),并移動到指定設備
model = CBOW(vocab_size, 10).to(device)
# 優化器:Adam(常用的自適應學習率優化器)
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 損失函數:NLLLoss(負對數似然損失,配合log_softmax使用,等價于交叉熵損失)
loss_function = nn.NLLLoss()losses = [] # 記錄損失
model.train() # 切換到訓練模式# 訓練200個epoch
for epoch in tqdm(range(200)):total_loss = 0 # 總損失size = 0 # 樣本數for context, target in data:# 1. 準備數據:上下文向量和目標詞索引(均移動到設備)context_vector = make_context_vector(context, word_to_idx).to(device)target = torch.tensor([word_to_idx[target]]).to(device) # 目標詞的索引# 2. 模型預測predict = model(context_vector)# 3. 計算損失loss = loss_function(predict, target)# 4. 反向傳播+參數更新optimizer.zero_grad() # 清空梯度loss.backward() # 計算梯度optimizer.step() # 更新參數total_loss += loss.item() # 累加損失(轉為Python數值)size += 1# 計算并打印每個epoch的平均損失avg_loss = total_loss / sizeprint(f"Epoch {epoch+1}, Average Loss: {avg_loss}")losses.append(avg_loss)
訓練過程流程:
每個 epoch 遍歷所有訓練樣本(上下文 - 目標詞對)。
對每個樣本,模型根據上下文預測目標詞,計算預測值與真實值的損失。
通過反向傳播更新模型參數(尤其是嵌入層的權重,即詞向量)。
隨著訓練進行,損失逐漸下降,說明模型越來越能通過上下文準確預測目標詞。
6. 模型預測與詞向量提取
(1)測試預測效果
# 測試上下文:"People", "create", "to", "direct"(對應原句"People create programs to direct processes")
context = ['People', 'create', 'to', 'direct']
context_vector = make_context_vector(context, word_to_idx).to(device)model.eval() # 切換到評估模式(關閉 dropout 等訓練特有的操作)
predict = model(context_vector)
max_idx = predict.argmax(1) # 取概率最大的索引(預測的目標詞)
print("預測的目標詞:", idx_to_word[max_idx.item()]) # 理論上應輸出"programs"
用訓練好的模型測試:給定上下文People create ... to direct
,模型應能預測出中間的目標詞programs
。
(2)提取詞向量
# 嵌入層的權重就是詞向量(vocab_size × embedding_dim 的矩陣)
print("CBOW嵌入層權重(詞向量矩陣):", model.embeddings.weight)# 將詞向量矩陣從GPU移到CPU,并轉為numpy數組(脫離計算圖,不參與梯度計算)
W = model.embeddings.weight.cpu().detach().numpy()
CBOW 模型的核心輸出是嵌入層的權重,每個單詞對應的行向量就是該單詞的語義向量(維度為 10,在初始化時指定)。
(3)構建詞 - 向量字典并保存
# 構建{單詞: 詞向量}字典
word_2_vec = {}
for word in word_to_idx.keys():# 詞向量矩陣中,單詞索引對應的行即為該詞的向量word_2_vec[word] = W[word_to_idx[word], :]# 保存詞向量為npz文件(方便后續復用)
np.savez('word2vec實現.npz', word_vectors=W)
# 測試加載
data = np.load('word2vec實現.npz')
print("保存的詞向量鍵名:", data.files) # 輸出 ['word_vectors']
將詞向量整理為字典,并保存為 numpy 格式,方便后續用于語義相似度計算、文本分類等任務。
三、核心總結
- 數據層面:通過滑動窗口從文本中提取 “上下文 - 目標詞” 對,作為訓練樣本。
- 模型層面:CBOW 模型通過嵌入層將單詞轉為向量,用上下文向量的總和預測目標詞,最終學習到的嵌入層權重就是詞向量。
- 訓練目標:通過優化預測損失,讓語義相近的詞(如 “process” 和 “processes”)的向量更接近。
- 輸出:得到每個單詞的低維向量表示,可用于后續 NLP 任務(如語義相似度計算、文本分類等)。
這段代碼是 Word2Vec 中 CBOW 模型的簡化實現,核心思想與原始 Word2Vec 一致,適合理解詞向量的訓練原理。