入門GPT | 神經概率語言模型(NPLM):讓機器學會“理解”語言
本文介紹了神經概率語言模型(NPLM)的基本原理、結構與實現方法。通過對比傳統N-gram模型,闡述了NPLM如何利用神經網絡自動學習詞語間的深層關系,有效提升語言建模的泛化能力。內容涵蓋數據準備、模型搭建、訓練流程及推理預測。
1. 什么是神經概率語言模型NPLM
2. 為什么需要神經語言模型
2.1 傳統語言模型的問題
2.2 神經概率語言模型(NPLM)的誕生
3. 模型架構詳解
4. 完整代碼實現
4.1 構建語料庫
4.2 生成訓練數據
4.3 模型定義
4.3 實例化NPLM模型
4.3 NPLM模型訓練
5. 訓練過程詳解
5.1 訓練數據的準備
5.2 訓練的四個關鍵步驟
5.2.1 前向傳播(Forward Pass)
5.2.2 計算損失(Loss Calculation)
5.2.3 反向傳播(Backward Pass)
5.2.4 參數更新(Parameter Update)
6. 訓練結果
7. 模型預測
8. 完整的數據流程圖
1. 什么是神經概率語言模型NPLM
神經概率語言模型(Neural Probabilistic Language Models, NPLM)聽起來很復雜,但我們可以把它拆解來理解:
什么是語言模型?
語言模型就像一個"語言預測器"。當你在手機上打字時,輸入法能夠預測你下一個要輸入的詞,這背后就是語言模型在工作。比如:
? 你輸入"今天天氣",模型預測下一個詞可能是"很好"、"不錯"、"糟糕"等
? 你輸入"人工智能",模型預測下一個詞可能是"技術"、"發展"、"應用"等
什么是概率?
概率就是某件事發生的可能性。在語言模型中,我們用概率來表示某個詞出現的可能性。比如:
? 在"今天天氣"后面,"很好"出現的概率可能是30%
? "不錯"出現的概率可能是25%
? "糟糕"出現的概率可能是15%
什么是神經網絡?
神經網絡是一種模仿人腦工作方式的計算模型。就像人腦通過神經元連接來處理信息一樣,神經網絡通過多層計算節點來學習復雜的模式。
2. 為什么需要神經語言模型
2.1 傳統語言模型的問題
在神經概率語言模型(NPLM)出現之前,N-gram模型自20世紀80年代以來一直是概率語言建模的主流范式 。
相關閱讀:???入門GPT(一)| N-Gram 帶你了解自然語言處理(1)??
傳統N-gram模型的局限性:
N-gram模型通過馬爾可夫假設簡化了語言建模的復雜性后,假設一個詞的出現概率僅依賴于其前 n?1 個詞。
一個三元語法模型在預測下一個詞時,只會考慮緊鄰的兩個前文詞語 。當訓練數據中出現未曾見過的詞序列時,N-gram模型通常通過“平滑”和“回退”等技術來分配一個非零概率 。
然而,這種傳統方法存在顯著的局限性。N-gram模型通常只能考慮非常短的上下文窗口,例如1或2個詞,即使在某些情況下可擴展到5個詞,但由于數據稀疏性,大多數預測仍依賴于一個比較短的上下文 。
更重要的是,N-gram模型缺乏對詞語之間語義或語法相似性的理解 。這意味著,如果模型在訓練集中見過“貓在臥室里走路”,它無法將這些知識自然地泛化到“狗在房間里跑”,因為在N-gram的離散表示中,“貓”和“狗”被視為完全不相關的實體。
這種局限性導致訓練數據中的一個句子無法有效地為模型提供關于其語義鄰近句子的信息,從而限制了模型的泛化能力。
維度爆炸:
統計語言建模的一個很大的問題在于“維度爆炸” 。
當試圖建模大量離散隨機變量(如句子中的詞語)的聯合分布時,這個問題尤為突出 。隨著輸入變量數量的增加,模型所需的訓練樣本數量呈指數級增長 。
例如,如果要對自然語言中連續十個單詞的聯合分布建模,而詞匯表大小是100,000,那么理論上就會有 個自由參數需要考慮。
在離散空間中,泛化并不像連續變量那樣有優勢,因為離散變量的任何改變都可能對函數值產生劇烈影響 。這意味著模型在測試時遇到的詞序列,與訓練期間見過的所有詞序列很可能不同,從而導致模型難以對未見過的組合進行準確的概率估計 。
傳統N-gram模型會為詞匯表中的每個詞分配一個高維稀疏向量,其維度與詞匯量大小相同 。
N-gram模型采用局部、離散的統計方法,最終限制了其泛化能力和對語言深層語義的理解。
2.2 神經概率語言模型(NPLM)的誕生
神經概率語?模型(Neural Probabilistic Language Model,NPLM)的起源和發展可以追溯到2003年,當時約書亞·本希奧及其團隊發表了?篇題為“A Neural Probabilistic Language Model”(《?種神經概率語?模型》)的論?.
這項工作標志著語言建模領域的一個重要轉折點,因為它首次將神經網絡引入到語言建模中,并被認為是“第一個(也是最簡單的)用于語言建模的神經網絡架構” 。
NPLM的核心目標是解決傳統N-gram模型面臨的“維度爆炸”問題。
核心思想:分布式表示
NPLM提出通過學習詞語的“分布式表示”來對抗維度爆炸 。
分布式表示是指為詞匯表中的每個詞關聯一個連續值的向量。這些向量將詞語映射到一個低維的特征空間中,其中功能相似的詞語(無論是語義上還是語法上)在這個空間中彼此靠近 。
例如,如果模型學習了“貓”和“狗”在許多語境中可以互換而不改變句子的語法結構,它會學習到它們的嵌入向量應該很接近。
這種表示方式使得模型能夠從每個訓練句子中獲取信息,并將其泛化到語義鄰近句子上 。泛化的實現是因為一個從未見過的詞序列,如果它由與已見過句子中的詞語相似(即具有接近的表示)的詞語組成,也能獲得高概率 。
NPLM模型同時學習兩個關鍵部分:1) 每個詞的分布式表示,以及 2) 基于這些表示的詞序列概率函數 。這種同步學習是NPLM成功的關鍵。
模型通過一個統一的神經網絡架構來學習從輸入詞到輸出概率的整個映射。通過將離散的詞語映射到連續的向量空間,NPLM有效地將高維離散空間中的數據稀疏性問題,轉化為低維連續空間中的函數平滑性問題 。
這使得模型能夠泛化到未見過的組合,因為“相似的詞語預計會有相似的特征向量,并且概率函數是這些特征值的平滑函數” 。
NPLM的出現不僅僅是對N-gram模型的簡單改進,它代表了語言建模領域從純粹的統計方法向基于神經網絡的表征學習的根本性范式轉變。這種轉變不僅有效解決了維度爆炸,更為后續所有現代神經網絡語言模型(包括Word2Vec、RNN、Transformer等)奠定了理論和實踐基礎。
3. 模型架構詳解
來自《A Neural Probabilistic Language Model》
? 圖中的矩陣C?于將輸?的詞(Context,即之前的N個詞,也被稱為上下?詞)映射到?個連續的向量空間。這個過程在論?中稱為“table look-up”,因為我們可以將矩陣C視為?張查找表,通過查找表可以將輸?詞的索引(或One-Hot編碼)轉換為其對應的詞向量表示。矩陣C的參數在所有詞之間共享。這意味著,對于所有的輸?詞,都使?相同的矩陣C來獲取它們的詞向量表示。這有助于減少模型的參數數量,提?模型的泛化能?。
? 通過矩陣C會得到?組詞向量,此時需要對輸?向量進?線性變換(即矩陣乘法和偏置加法),然后將其輸?隱藏層,進?上下?語義信息的學習。因為論?發表時間較早,所以隱藏層使?雙曲正切(tanh)函數作為?線性激活函數,?不是后來常?的 ReLU 函數。在這篇論?發表的2003年,算?還不是很強,所以論?特別注明:這?部分的計算量通常較?。
? 輸出層通常是?個全連接層,?于計算給定上下?條件下每個詞的概率。圖中 “第個輸出” 這句話描述了NPLM的輸出?標。對于每個詞 ,模型計算在給定上下?條件下,?標詞匯 (也就是下?個詞)是詞匯表中第 個詞的概率。此處應???softmax??函數將輸出層的值轉換為概率分布,這也是后來神經?絡的分類輸出層的標準做法。
代碼:
class NPLM(nn.Module):
def__init__(self, voc_size, n_step, embedding_size, n_hidden):
super(NPLM, self).__init__()
self.n_step = n_step # 保存n_step用于forward方法
self.C = nn.Embedding(voc_size, embedding_size) # 定義一個詞嵌入層
# 第一個線性層,其輸入大小為 n_step * embedding_size,輸出大小為 n_hidden
self.linear1 = nn.Linear(n_step * embedding_size, n_hidden)
# 第二個線性層,其輸入大小為 n_hidden,輸出大小為 voc_size,即詞匯表大小
self.linear2 = nn.Linear(n_hidden, voc_size)
defforward(self, X): # 定義前向傳播過程
# 輸入數據 X 張量的形狀為 [batch_size, n_step]
X = self.C(X) # 將 X 通過詞嵌入層,形狀變為 [batch_size, n_step, embedding_size]
X = X.view(-1, self.n_step * self.C.embedding_dim) # 形狀變為 [batch_size, n_step * embedding_size]
# 通過第一個線性層并應用 tanh 激活函數
hidden = torch.tanh(self.linear1(X)) # hidden 張量形狀為 [batch_size, n_hidden]
# 通過第二個線性層得到輸出
output = self.linear2(hidden) # output 形狀為 [batch_size, voc_size]
return output # 返回輸出結果這?定義了?個名為 “NPLM” 的神經概率語?模型類,它繼承? ??PyTorch??? 的 ??nn.Module?? 。在這個類中,我們定義了詞嵌?層和線性層
??self.C??:?個詞嵌?層,?于將輸?數據中的每個詞轉換為固定??的向量表示。??voc_size??? 表示詞匯表??,??embedding_size?? 表示詞嵌?的維度。
??self.linear1??: 第?個線性層,不考慮批次的情況下輸???為 ??n_step * embedding_size???,輸出??為 ??n_hidden???。??n_step??? 表示時間步數,即每個輸?序列的?度;??embedding_size??? 表示詞嵌?的維度;??n_hidden?? 表示隱藏層的??。
??self.linear2??: 第?個線性層,不考慮批次的情況下輸???為 ??n_hidden???,輸出??為 ??voc_size???。??n_hidden??? 表示隱藏層的??,??voc_size??表示詞匯表??。
在NPLM類中,我們還定義了?個名為 ??forward??? 的?法,?于實現模型的前向傳播過程。在這個?法中,?先將輸?數據通過詞嵌?層??self.C??? ,然后 ??X.view(-1, n_step * embedding_size)??? 的?的是在詞嵌?維度上展平張量,也就是把每個輸?序列的詞嵌?連接起來,形成?個?的向量。接著,將該張量傳?第?個線性層 ??self.linear1??? 并應? ??tanh??? 函數,得到隱藏層的輸出。最后,將隱藏層的輸出傳?第?個線性層 ??self.linear2??,得到最終的輸出結果。
4. 完整代碼實現
完整代碼,在公眾號「AI取經路」發消息「NPLM」獲取
4.1 構建語料庫
定義?個?常簡單的數據集,作為實驗語料庫,并整理出該語料庫的詞匯表。
# 構建一個非常簡單的數據集
sentences = ["我 喜歡 玩具", "我 愛 爸爸", "我 討厭 挨打"]
# 將所有句子連接在一起,用空格分隔成多個詞,再將重復的詞去除,構建詞匯表
word_list = list(set(" ".join(sentences).split()))
# 創建一個字典,將每個詞映射到一個唯一的索引
word_to_idx = {word: idx for idx, word in enumerate(word_list)}
# 創建一個字典,將每個索引映射到對應的詞
idx_to_word = {idx: word for idx, word in enumerate(word_list)}
voc_size = len(word_list) # 計算詞匯表的大小
print(' 詞匯表:', word_to_idx) # 打印詞匯到索引的映射字典
print(' 詞匯表大小:', voc_size) # 打印詞匯表大小詞匯表: {'爸爸': 0, '愛': 1, '討厭': 2, '挨打': 3, '喜歡': 4, '玩具': 5, '我': 6}
詞匯表大?。?7
4.2 生成訓練數據
從語料庫中?成批處理數據,作為NPLM的訓練數據,后?會將數據?批?批地輸?神經?絡進?訓練。
# 構建批處理數據
import torch # 導入 PyTorch 庫
import random # 導入 random 庫
batch_size = 2# 每批數據的大小
defmake_batch():
input_batch = [] # 定義輸入批處理列表
target_batch = [] # 定義目標批處理列表
selected_sentences = random.sample(sentences, batch_size) # 隨機選擇句子
for sen in selected_sentences: # 遍歷每個句子
word = sen.split() # 用空格將句子分隔成多個詞
# 將除最后一個詞以外的所有詞的索引作為輸入
input = [word_to_idx[n] for n in word[:-1]] # 創建輸入數據
# 將最后一個詞的索引作為目標
target = word_to_idx[word[-1]] # 創建目標數據
input_batch.append(input) # 將輸入添加到輸入批處理列表
target_batch.append(target) # 將目標添加到目標批處理列表
input_batch = torch.LongTensor(input_batch) # 將輸入數據轉換為張量
target_batch = torch.LongTensor(target_batch) # 將目標數據轉換為張量
return input_batch, target_batch # 返回輸入批處理和目標批處理數據
input_batch, target_batch = make_batch() # 生成批處理數據
print(' 詞匯表:', word_to_idx) # 打印詞匯到索引的映射字典
print(" 輸入數據:",input_batch) # 打印輸入批處理數據
# 將輸入批處理數據中的每個索引值轉換為對應的原始詞
input_words = []
for input_idx in input_batch:
input_words.append([idx_to_word[idx.item()] for idx in input_idx])
print(" 輸入數據對應的原始詞:",input_words)
print(" 目標數據:",target_batch) # 打印目標批處理數據
# 將目標批處理數據中的每個索引值轉換為對應的原始詞
target_words = [idx_to_word[idx.item()] for idx in target_batch]
print(" 目標數據對應的原始詞:",target_words)詞匯表: {'爸爸': 0, '愛': 1, '討厭': 2, '挨打': 3, '喜歡': 4, '玩具': 5, '我': 6}
輸入數據: tensor([[6, 2], [6, 4]]) 輸入數據對應的原始詞: [['我', '討厭'], ['我', '喜歡']]
目標數據: tensor([3, 5]) 目標數據對應的原始詞: ['挨打', '玩具']
4.3 實例化NPLM模型
n_step = 2 # 時間步數,表示每個輸入序列的長度,也就是上下文長度
n_hidden = 2 # 隱藏層大小
embedding_size = 2 # 詞嵌入大小
model = NPLM(n_step, voc_size, embedding_size, n_hidden) # 創建神經概率語言模型實例
print(' NPLM 模型結構:', model) # 打印模型的結構NPLM 模型結構: NPLM(
(C): Embedding(7, 2)
(linear1): Linear(in_features=4, out_features=2, bias=True)
(linear2): Linear(in_features=2, out_features=7, bias=True)
)
4.4 NPLM模型訓練
訓練設置:
import torch.optim as optim # 導入優化器模塊
criterion = nn.CrossEntropyLoss() # 定義損失函數為交叉熵損失
optimizer = optim.Adam(model.parameters(), lr=0.1) # 定義優化器為 Adam,學習率為 0.1訓練循環:
# 訓練模型
for epoch in range(5000): # 設置訓練迭代次數
optimizer.zero_grad() # 清除優化器的梯度
input_batch, target_batch = make_batch() # 創建輸入和目標批處理數據
output = model(input_batch) # 將輸入數據傳入模型,得到輸出結果
loss = criterion(output, target_batch) # 計算損失值
if (epoch + 1) % 1000 == 0: # 每 1000 次迭代,打印損失值
print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))
loss.backward() # 反向傳播計算梯度
optimizer.step() # 更新模型參數5. 訓練過程詳解
5.1 訓練數據的準備
我們使用的訓練數據非常簡單:
原始文本:
"我 喜歡 玩具"
"我 愛 爸爸"
"我 討厭 挨打"轉換為訓練數據集:
上下文: ["我", "喜歡"] → 目標: "玩具"
上下文: ["我", "愛"] → 目標: "爸爸"
上下文: ["我", "討厭"] → 目標: "挨打"核心思想: 給定前面的n_step個詞匯(這里是2個),預測的下一個目標詞匯
5.2 訓練的四個關鍵步驟
5.2.1 前向傳播(Forward Pass)
這一步計算模型的預測結果:
- 輸入處理:將上下文詞匯轉換為索引
- 詞嵌入:查找每個詞匯對應的2維向量
- 向量拼接:將2個詞的向量拼接成4維向量
- 隱藏層計算:通過tanh激活函數處理
- 輸出分數:得到每個詞匯的原始分數
# 前向傳播過程
output = model(input_batch) # 形狀: [1, 7]
# output 包含7個詞匯的分數,對應詞匯表中的每個詞5.2.2 計算損失(Loss Calculation)
使用交叉熵損失函數來衡量預測準確性:
代碼實現:
import torch.optim as optim
criterion = nn.CrossEntropyLoss() # 定義損失函數為交叉熵損失
optimizer = optim.Adam(model.parameters(), lr=0.1) # Adam優化器,學習率0.1
# 計算損失
input_batch, target_batch = make_batch() # 獲取批次數據
output = model(input_batch) # 前向傳播
loss = criterion(output, target_batch) # 計算損失5.2.3 反向傳播(Backward Pass)
使用PyTorch的自動微分計算梯度:
代碼實現:
optimizer.zero_grad() # 清除之前的梯度
loss.backward() # 反向傳播計算梯度5.2.4 參數更新(Parameter Update)
使用Adam優化器更新模型參數:
代碼實現:
optimizer.step() # 更新模型參數6. 訓練結果
模型訓練過程如下:
Epoch: 1000 cost = 0.000403
Epoch: 2000 cost = 0.000120
Epoch: 3000 cost = 0.000055
Epoch: 4000 cost = 0.000028
Epoch: 5000 cost = 0.000016?損失快速下降:從初始的高損失快速降低到接近0
?收斂良好:5000次迭代后損失穩定在很小的值
?學習成功:模型成功學會了簡單的語言模式
7. 模型預測
訓練完成后,可以測試模型的預測能力:
# 進行預測
input_strs = [['我', '討厭'], ['我', '喜歡']] # 需要預測的輸入序列
# 將輸入序列轉換為對應的索引
input_indices = [[word_to_idx[word] for word in seq] for seq in input_strs]
# 將輸入序列的索引轉換為張量
input_batch = torch.LongTensor(input_indices)
# 對輸入序列進行預測,取輸出中概率最大的類別
predict = model(input_batch).data.max(1)[1]
# 將預測結果的索引轉換為對應的詞
predict_strs = [idx_to_word[n.item()] for n in predict.squeeze()]
for input_seq, pred in zip(input_strs, predict_strs):
print(input_seq, '->', pred) # 打印輸入序列和預測結果預期預測結果:
['我', '討厭'] -> 挨打
['我', '喜歡'] -> 玩具這表明模型成功學會了語言模式:
? 理解"我 討厭"后面應該跟"挨打"
? 理解"我 喜歡"后面應該跟"玩具"
8. 完整的數據流程圖
下圖展示了NPLM模型的完整數據處理流程:
詳細步驟說明:
輸入文本:["我", "喜歡"]
目標:預測下一個詞
第1步:詞匯索引化
["我", "喜歡"] → [2, 5] # 根據詞匯表映射
第2步:詞嵌入(每個詞2維)
[2, 5] → [[0.1, -0.2], [0.3, 0.1]] # 查找嵌入矩陣
第3步:向量拼接
[[0.1, -0.2], [0.3, 0.1]] → [0.1, -0.2, 0.3, 0.1] # 4維向量
第4步:隱藏層計算(輸出2維)
[4維向量] → [2維向量] # 通過 Linear(4, 2) + tanh
第5步:輸出層計算
[2維向量] → [7維向量] # 通過 Linear(2, 7),每個位置是一個詞的分數
第6步:Softmax轉換
[2.1, 0.5, -1.0, -2.0, -1.5, -2.0, -1.8] → [0.75, 0.17, 0.04, ...] # 概率分布
第7步:預測結果
最高概率對應"玩具"(索引3),所以預測下一個詞是"玩具"張量形狀變化:
輸入: [batch_size, 2]
→ 嵌入: [batch_size, 2, 2]
→ 拼接: [batch_size, 4]
→ 隱藏層: [batch_size, 2]
→ 輸出層: [batch_size, 7]
→ 概率: [batch_size, 7]本文轉載自??AI取經路??,作者:AI取經路

















