深度學習新手必學:使用 Pytorch 搭建一個 N-Gram 模型

本文由集智小仙女發表在集智AI學園公眾號上,歡迎關注集智AI學園微信公眾號:swarmAI。

前言

在使用語音搜索時,用戶的輸入語音常常含糊其詞。比如 「daiyugang」 到底是 「戴玉剛」 還是 「帶浴缸」 ,僅憑聲學根本無法判斷。這時候就需要使用以往的大量歷史搜索數據訓練一個 N-gram 模型,依靠模型來估計出用戶更可能的輸入。

N-gram 的基本原理

N-gram 是計算機語言學和概率論範疇內的概念,是指給定的一段文本或語音中N個項目(item)的序列。項目(item)可以是音節、字母、單詞。

N=1 時 N-gram 又稱為 unigram,N=2 稱為 bigram,N=3 稱為 trigram,以此類推。實際應用通常採用 bigram 和 trigram 進行計算。

舉例來說:「你今天休假了嗎」

它的 bigram 依次為:

你今,今天,天休,休假,假了,了嗎

製造這種語言模型的原因是基於這麼一種思想:在整個語言環境中,句子T的出現概率是由組成T的N個item的出現概率組成的,如下公式所示:

P(T)=P(W1W2W3Wn)=P(W1)P(W2|W1)P(W3|W1W2)…P(Wn|W1W2…Wn-1)

以上公式難以實際應用。

此時出現馬爾科夫模型,該模型認為,一個詞的出現僅僅依賴於它前面出現的幾個詞。這就大大簡化了上述公式:

P(T)≈P(W1)P(W2|W1)P(W3|W2)…P(Wn|Wn-1)

N-gram 的用途

輸入法

用前幾個字預測後幾個字,這個功能在我們每天都在用的輸入法里及其重要。比如,我們曾經最愛用的「智能ABC」,據說就使用了 N-gram 技術。

語音識別

同樣的用途,N-gram 模型也被應用在語音識別領域。比如用戶在使用語音搜索的時候,輸入的語音常常含糊其詞。比如語音「daiyugang」到底是「戴玉剛」還是「帶浴缸」,僅憑聲學模型根本無法判斷用戶到底說的是什麼詞。這時候廠商就需要通過以往的大量歷史搜索數據訓練一個 N-gram 模型,依靠模型來估計出用戶更可能的輸入。

詞嵌入

你可能會疑惑,哎之前不是講了用 Word2Vec 和 GloVe 做詞嵌入嗎?怎麼 N-gram 模型也來插一腿?

是這樣子滴,通常在使用 N-gram 模型時,模型的輸入是每個詞的獨熱編碼向量,而每個向量的長度正是「詞典」的大小。這麼一來,在 N-gram 模型得到良好的訓練後,可以使用模型輸入層的每個神經元(代表每個詞)所對應的權重參數,作為這個詞的詞向量。經過 N-gram 模型嵌入得到的詞向量不同於 Word2Vec 和 GloVe,在自然語言處理領域有其獨特的價值。

使用 PyTorch 實現簡單 N-gram 語言模型

在前面的資料中,有講到馬爾科夫模型,即認為一個詞的出現僅僅依賴於它前面出現的幾個詞

我們今天就打造一個用前兩個詞預測第三個詞的 N-gram 模型。我們準備了一篇莎士比亞的14行詩作為文本,並按順序將整首詩拆成一個一個的單詞。將前兩個詞作為訓練數據,將第三個詞作為目標數據,以此類推整個文本,生成訓練數據集。

然後我們把訓練數據(前兩個詞)傳入到我們的 N-gram 模型(也就是我們的神經網路)中,讓 N-gram 模型學習去預測第三個詞。

處理數據

首先讓我們編寫代碼,處理數據。

import torchnimport torch.autograd as autogradnimport torch.nn as nnnimport torch.nn.functional as Fnimport torch.optim as optimnn# 數據我們使用的是莎士比亞的14行詩ntest_sentence = """When forty winters shall besiege thy brow,nAnd dig deep trenches in thy beautys field,nThy youths proud livery so gazed on now,nWill be a totterd weed of small worth held:nThen being asked, where all thy beauty lies,nWhere all the treasure of thy lusty days;nTo say, within thine own deep sunken eyes,nWere an all-eating shame, and thriftless praise.nHow much more praise deservd thy beautys use,nIf thou couldst answer This fair child of minenShall sum my count, and make my old excuse,nProving his beauty by succession thine!nThis were to be new made when thou art old,nAnd see thy blood warm when thou feelst it cold.""".split()nn# 將單詞序列轉化為數據元組列表,n# 其中的每個元組格式為([ word_i-2, word_i-1 ], target word)ntrigrams = [ ([test_sentence[i], test_sentence[i+1]], test_sentence[i+2]) for i in range(len(test_sentence) - 2) ]nn# 列印出前3條數據,注意觀察數據的結構nprint(trigrams[:3])n

[([When, forty], winters), ([forty, winters], shall), ([winters, shall], besiege)]n

可以觀察到文本被拆成了「三元組」,即 ([ 第一個詞, 第二個次 ], 目標第三個詞)。

# 給14行詩建立單詞表n# set 即去除重複的詞nvocab = set(test_sentence)n# 建立詞典,它比單詞表多了每個詞的索引nword_to_ix = { word: i for i, word in enumerate(vocab) }nnprint(len(vocab))nn97n

準備詞嵌入

我們現在要用的並不是前面講的 N-gram 模型的詞嵌入,而是為了減少模型的輸入大小而進行的「普通」詞嵌入。

我們特意把單詞表的長度列印出來,如果使用獨熱編碼的形式編碼單詞,那麼每個詞向量的長度為 97。

那讓我們想一下,在不使用詞嵌入的情況下,一個詞向量長97,而我們模型的輸入是兩個詞,那麼神經網路模型的輸入層就需要 97*2=194 個神經元。

輸入層的規模越大,模型訓練和學習的難度就越大,效果越差。

若使用詞嵌入,設置嵌入維度為 10, 那麼就可以將神經網路的輸入層 194 個神經元轉化為 20 個神經元,你說這嵌入值不值~

# 上下文大小n# 即 前兩個詞nCONTEXT_SIZE = 2n# 嵌入維度nEMBEDDING_DIM = 10n

建立模型

解決了數據的問題,下面我們正式搭建 N-Gram 模型:NGramLanguageModeler。

模型繼承自 torch.nn.Module,我們之前講過,用於搭建神經網路的組件都可以從 torch.nn.Module 中繼承,在構建神經網路時,我們需要重寫它們的 forward() 方法。

class NGramLanguageModeler(nn.Module):nn # 初始化時需要指定:單詞表大小、想要嵌入的維度大小、上下文的長度n def __init__(self, vocab_size, embedding_dim, context_size):n # 繼承自nn.Module,例行執行父類super 初始化方法n super(NGramLanguageModeler, self).__init__()n # 建立詞嵌入模塊n self.embeddings = nn.Embedding(vocab_size, embedding_dim)n # 線性層1n self.linear1 = nn.Linear(context_size * embedding_dim, 128)n # 線性層2,隱藏層 hidden_size 為128n self.linear2 = nn.Linear(128, vocab_size)nn # 重寫的網路正向傳播方法n # 只要正確定義了正向傳播n # PyTorch 可以自動進行反向傳播n def forward(self, inputs):n # 將輸入進行「嵌入」,並轉化為「行向量」n embeds = self.embeddings(inputs).view((1, -1))n # 嵌入後的數據通過線性層1後,進行非線性函數 ReLU 的運算n out = F.relu(self.linear1(embeds))n # 通過線性層2後n out = self.linear2(out)n # 通過 log_softmax 方法將結果映射為概率的logn # log 概率是用於下面計算負對數似然損失函數時方便使用的n log_probs = F.log_softmax(out)n return log_probsn

開始訓練

下面開始訓練,在編寫訓練的代碼之前,對於不熟悉 Python lambda 表達式的同學,我詳細解釋一下這一句代碼:

context_idxs = list(map(lambda w: word_to_ix[w], context))n

首先 lambda 表達式通常是在需要一個函數,但是又不想費神去命名一個函數的場合下使用,相當於一個匿名函數。

所以 lambda w: word_to_ix[w] 就等於:

def no_name_function( x ):n return word_to_ix[w]n

它的功能就是通過查表返回一個單詞的索引。

而 map(lambda w: word_to_ix[w], context) 代表將 context 的內容,挨個輸入(映射)到 lambda 表達式所定義的匿名函數里。

如果 context 為 [When, forty],那麼查表得 [68, 15],也就是最終 context_idxs 的值。

losses = []n# 損失函數為 負對數似然損失函數(Negative Log Likelihood)nloss_function = nn.NLLLoss()n# 實例化我們的模型,傳入:n# 單詞表的大小、嵌入維度、上下文長度nmodel = NGramLanguageModeler(len(vocab), EMBEDDING_DIM, CONTEXT_SIZE)n# 優化函數使用隨機梯度下降演算法,學習率設置為0.001noptimizer = optim.SGD(model.parameters(), lr=0.001)nnfor epoch in range(1000):n total_loss = 0n # 循環context上下文,比如:[When, forty]n # target,比如:wintersn for context, target in trigrams:nn # 步驟1:準備數據n # 將context如「[When, forty]」n # 轉化為索引,如[68, 15]n # 再建立為 PyTorch Variable 變數,以計算梯度n context_idxs = list(map(lambda w: word_to_ix[w], context))n context_var = autograd.Variable( torch.LongTensor(context_idxs) )nn # 步驟2:清空梯度值,防止上次的梯度累計n model.zero_grad()nn # 步驟3:運行網路的正向傳播,獲得 log 概率n log_probs = model(context_var)nn # 步驟4:計算損失函數n # 傳入 target Variablen loss = loss_function(log_probs, autograd.Variable(torch.LongTensor([word_to_ix[target]])))nn # 步驟5:進行反向傳播並更新梯度n loss.backward()n optimizer.step()nn total_loss += loss.data[0]n losses.append(total_loss)nnprint(Finished)n

Finishedn

訓練完成,我們來觀察下訓練的效果

import matplotlib.pyplot as pltnimport matplotlib.ticker as tickern%matplotlib inlinennplt.figure()nplt.plot(losses)n

[<matplotlib.lines.Line2D at 0x10ad02e80>]n

損失大幅度降低並穩定在一個值,說明我們的訓練是成功的(或者說是過擬合了)。

那麼訓練的模型有什麼用呢?上面我們講到 N-gram 模型可以拿來做詞嵌入,而我們這次拿莎士比亞的14行詩訓練模型,實際上就是對14行詩里的所有辭彙進行了一次詞嵌入。我們這次先不講嵌入後的詞向量如何使用,因為我們在後面的教程中還會用到它!

那麼我們本次的教程到這裡就結束了,N-gram 模型非常重要,未來有意從事自然語言處理相關工作的同學一定要重視起來。

另外下一次教程會講 NLP 中另一個常用模型 CBOW

有興趣的同學請關注我們公眾號的最新文章。如果您感覺可以看懂我的文章,那非常感謝您進行轉發或分享讓您的朋友也可以看到我的文章,您們的閱讀就是我繼續寫下去的動力,謝謝大家!

文章內容參考:

N-gram 的原理、用途和研究(blog.sciencenet.cn/blog

http://blog.csdn.net/ahmanz/article/details/51273500)


如果您有任何關於Pytorch方面的問題,歡迎進【集智—清華】火炬群與大家交流探討,添加集智小助手微信swarmaAI,備註(pytorch交流群),小助手會拉你入群。

關注集智AI學園公眾號

獲取更多更有趣的AI教程吧!

搜索微信公眾號:swarmAI

集智AI學園QQ群:426390994

學園網站: campus.swarma.org

weixin.qq.com/r/FzpGXp3 (二維碼自動識別)


推薦閱讀:

項目推薦:自然場景中文文字檢測和不定長中文OCR識別
PyTorch實戰指南
【筆記】Finding Tiny Faces
PyTorch中如何使用tensorboard可視化
深度學習入門該用PyTorch還是Keras?熱門公開課換框架背後的學問

TAG:PyTorch | 深度学习DeepLearning | 人工智能 |