RNN在自然語言處理中的應(yīng)用及其PyTorch實現(xiàn)

作者:廖星宇

對于人類而言,以前見過的事物會在腦海里面留下記憶,雖然隨后記憶會慢慢消失,但是每當(dāng)經(jīng)過提醒,人們往往能夠重拾記憶。在神經(jīng)網(wǎng)絡(luò)的研究中,讓模型充滿記憶力的研究很早便開始了,Saratha Sathasivam 于1982 年提出了霍普菲爾德網(wǎng)絡(luò),但是由于它實現(xiàn)困難,在提出的時候也沒有很好的應(yīng)用場景,所以逐漸被遺忘。深度學(xué)習(xí)的興起又讓人們重新開始研究循環(huán)神經(jīng)網(wǎng)絡(luò)(Recurrent Neural Network),并在序列問題和自然語言處理等領(lǐng)域取得很大的成功。

本文將從循環(huán)神經(jīng)網(wǎng)絡(luò)的基本結(jié)構(gòu)出發(fā),介紹RNN在自然語言處理中的應(yīng)用及其PyTorch 實現(xiàn)。

循環(huán)神經(jīng)網(wǎng)絡(luò)

前一章介紹了卷積神經(jīng)網(wǎng)絡(luò),卷積神經(jīng)網(wǎng)絡(luò)相當(dāng)于人類的視覺,但是它并沒有記憶能力,所以它只能處理一種特定的視覺任務(wù),沒辦法根據(jù)以前的記憶來處理新的任務(wù)。那么記憶力對于網(wǎng)絡(luò)而言到底是不是必要的呢?很顯然在某些問題上是必要的,比如,在一場電影中推斷下一個時間點的場景,這個時候僅依賴于現(xiàn)在的情景并不夠,需要依賴于前面發(fā)生的情節(jié)。對于這樣一些不僅依賴于當(dāng)前情況,還依賴于過去情況的問題,傳統(tǒng)的神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)無法很好地處理,所以基于記憶的網(wǎng)絡(luò)模型是必不可少的。

循環(huán)神經(jīng)網(wǎng)絡(luò)的提出便是基于記憶模型的想法,期望網(wǎng)絡(luò)能夠記住前面出現(xiàn)的特征,并依據(jù)特征推斷后面的結(jié)果,而且整體的網(wǎng)絡(luò)結(jié)構(gòu)不斷循環(huán),因為得名循環(huán)神經(jīng)
網(wǎng)絡(luò)。

循環(huán)神經(jīng)網(wǎng)絡(luò)的基本結(jié)構(gòu)

循環(huán)神經(jīng)網(wǎng)絡(luò)的基本結(jié)構(gòu)特別簡單,就是將網(wǎng)絡(luò)的輸出保存在一個記憶單元中,這個記憶單元和下一次的輸入一起進入神經(jīng)網(wǎng)絡(luò)中。使用一個簡單的兩層網(wǎng)絡(luò)作為示范,在它的基礎(chǔ)上擴充為循環(huán)神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu),我們用圖1簡單地表示。

可以看到網(wǎng)絡(luò)在輸入的時候會聯(lián)合記憶單元一起作為輸入,網(wǎng)絡(luò)不僅輸出結(jié)果,還會將結(jié)果保存到記憶單元中,圖1就是一個最簡單的循環(huán)神經(jīng)網(wǎng)絡(luò)在一次輸入時的結(jié)構(gòu)示意圖。

輸入序列的順序改變, 會改變網(wǎng)絡(luò)的輸出結(jié)果,這是因為記憶單元的存在,使得兩個序列在順序改變之后記憶單元中的元素也改變了,所以會影響最終的輸出結(jié)果。

大數(shù)據(jù)圖1 將一個數(shù)據(jù)點傳入網(wǎng)絡(luò)圖1是序列中一個數(shù)據(jù)點傳入網(wǎng)絡(luò)的示意圖,那么整個序列如何傳入網(wǎng)絡(luò)呢?將序列中的每個數(shù)據(jù)點依次傳入網(wǎng)絡(luò)即可,如圖2所示。

大數(shù)據(jù)

圖2 將整個序列傳入網(wǎng)絡(luò)無論序列有多長,都能不斷輸入網(wǎng)絡(luò),最終得到結(jié)果。可能看到這里,讀者會有一些疑問,圖2中每一個網(wǎng)絡(luò)是不是都是獨立的權(quán)重?對于這個問題,先考慮一下如果是不同的序列,那么圖2 中格子的數(shù)目就是不同的,對于一個網(wǎng)絡(luò)結(jié)構(gòu),不太可能出現(xiàn)這種參數(shù)數(shù)目變化的情況。

事實上,這里再次使用了參數(shù)共享的概念,也就是說雖然上面有三個格子,其實它們都是同一個格子,而網(wǎng)絡(luò)的輸出依賴于輸入和記憶單元,可以用圖5.5表示。

如圖5.5所示,左邊就是循環(huán)神經(jīng)網(wǎng)絡(luò)實際的網(wǎng)絡(luò)流,右邊是將其展開的結(jié)果,可以看到網(wǎng)絡(luò)中具有循環(huán)結(jié)構(gòu),這也是循環(huán)神經(jīng)網(wǎng)絡(luò)名字的由來。同時根據(jù)循環(huán)神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)也可以看出它在處理序列類型的數(shù)據(jù)上具有天然的優(yōu)勢,因為網(wǎng)絡(luò)本身就是一個序列結(jié)構(gòu),這也是所有循環(huán)神經(jīng)網(wǎng)絡(luò)最本質(zhì)的結(jié)構(gòu)。

大數(shù)據(jù)

圖3 網(wǎng)絡(luò)的輸入和記憶單元循環(huán)神經(jīng)網(wǎng)絡(luò)也可以有很深的網(wǎng)絡(luò)層結(jié)構(gòu),如圖4所示。

大數(shù)據(jù)

圖4 深層網(wǎng)絡(luò)結(jié)構(gòu)可以看到網(wǎng)絡(luò)是單方向的,這代表網(wǎng)絡(luò)只能知道單側(cè)的信息,有的時候序列的信息不只是單邊有用,雙邊的信息對預(yù)測結(jié)果也很重要,比如語音信號,這時候就需要看到兩側(cè)信息的循環(huán)神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)。這并不需要用兩個循環(huán)神經(jīng)網(wǎng)絡(luò)分別從左右兩邊開始讀取序列輸入,使用一個雙向的循環(huán)神經(jīng)網(wǎng)絡(luò)就能完成這個任務(wù),如圖5所示。

大數(shù)據(jù)

圖5 雙向循環(huán)神經(jīng)網(wǎng)絡(luò)使用雙向循環(huán)神經(jīng)網(wǎng)絡(luò),網(wǎng)絡(luò)會先從序列的正方向讀取數(shù)據(jù),再從反方向讀取數(shù)據(jù),最后將網(wǎng)絡(luò)輸出的兩種結(jié)果合在一起形成網(wǎng)絡(luò)的最終輸出結(jié)果。

自然語言處理的應(yīng)用

循環(huán)神經(jīng)網(wǎng)絡(luò)目前在自然語言處理中應(yīng)用最為火熱,所以這一小節(jié)將介紹自然語言處理中如何使用循環(huán)神經(jīng)網(wǎng)絡(luò)。

詞嵌入

首先介紹自然語言處理中的第一個概念——詞嵌入(word embedding),也可以稱為詞向量。

圖像分類問題會使用one-hot 編碼,比如一共有五類,那么屬于第二類的話,它的編碼就是(0, 1, 0, 0, 0),對于分類問題,這樣當(dāng)然特別簡明。但是在自然語言處理中,因為單詞的數(shù)目過多,這樣做就行不通了,比如有10000 個不同的詞,那么使用one-hot這樣的方式來定義,效率就特別低,每個單詞都是10000 維的向量,其中只有一位是1,其余都是0,特別占用內(nèi)存。除此之外,也不能體現(xiàn)單詞的詞性,因為每一個單詞都是one-hot,雖然有些單詞在語義上會更加接近,但是one-hot 沒辦法體現(xiàn)這個特點,所以必須使用另外一種方式定義每一個單詞,這就引出了詞嵌入。

詞嵌入到底是什么意思呢?其實很簡單,對于每個詞,可以使用一個高維向量去表示它,這里的高維向量和one-hot 的區(qū)別在于,這個向量不再是0 和1 的形式,向量的每一位都是一些實數(shù),而這些實數(shù)隱含著這個單詞的某種屬性。這樣解釋可能不太直觀,先舉四個例子,下面有4 段話:

The cat likes playing ball.The kitty likes playing wool.The dog likes playing ball.The boy likes playing ball.

重點分析里面的4 個詞,cat、kitty、dog 和boy。如果使用one-hot,那么cat 就可以表示成(1, 0, 0, 0),kitty 就可以表示成(0, 1, 0, 0),但是cat 和kitty 其實都表示小貓,所以這兩個詞語義是接近的,但是one-hot 并不能體現(xiàn)這個特點。

下面使用詞嵌入的方式來表示這4 個詞,假如使用一個二維向量(a, b) 來表示一個詞,其中a,b 分別代表這個詞的一種屬性,比如a 代表是否喜歡玩球,b 代表是否喜歡玩毛線,并且這個數(shù)值越大表示越喜歡,這樣就能夠定義每一個詞的詞嵌入,并且通過這個來區(qū)分語義,下面來解釋一下原因。

對于cat,可以定義它的詞嵌入是(-1, 4),因為它不喜歡玩球,喜歡玩毛線;而對于kitty,它的詞嵌入可以定義為(-2, 5);那么對于dog,它的詞嵌入就是(3, -2),因為它喜歡玩球,不喜歡玩毛線;最后對于boy,它的詞向量就是(-2, -3),因為這兩樣?xùn)|西他都不喜歡。定義好了這樣的詞嵌入,怎么去定義它們之間的語義相似度呢?可以通過詞向量之間的夾角來定義它們的相似度。下面先將每個詞向量都在坐標(biāo)系中表示出來,如圖6所示。

大數(shù)據(jù)

圖6 不同詞向量的夾角圖6 就顯示出了不同詞向量之間的夾角,可以發(fā)現(xiàn)kitty 和cat 的夾角更小,所以它們更加相似的,而dog 和boy 之間夾角很大,所以它們不相似。

通過這樣一個簡單的例子能夠看出詞嵌入對于單詞的表示具有很好的優(yōu)勢,但是問題來了,對于一個詞,怎么知道如何去定義它的詞嵌入?如果向量的維數(shù)只有5 維,可能還能定義出來,如果向量的維數(shù)是100 維,那么怎么知道每一維體是多少呢?

這個問題可以交給神經(jīng)網(wǎng)絡(luò)去解決,只需要定義我們想要的維度,比如100 維,神經(jīng)網(wǎng)絡(luò)就會自己去更新每個詞嵌入中的元素。而之前介紹過詞嵌入的每個元素表示一種屬性,當(dāng)然對于維數(shù)比較低的時候,可能我們能夠推斷出每一維具體的屬性含義,然而維度比較高之后,我們并不需要關(guān)心每一維到底代表著什么含義,因為每一維都是網(wǎng)絡(luò)自己學(xué)習(xí)出來的屬性,只需要知道詞向量的夾角越小,表示它們之間的語義更加接近就可以了。這就好比卷積網(wǎng)絡(luò)會對一張圖片提取出很厚的特征圖,并不需要關(guān)心網(wǎng)絡(luò)提取出來的特征到底是什么,只需要知道抽象的特征能夠幫助我們分類圖像就可以了。

詞嵌入的PyTorch 實現(xiàn)

詞嵌入在PyTorch 中是如何實現(xiàn)的呢?下面來具體實現(xiàn)一下。

PyTorch 中的詞嵌入是通過函數(shù)nn.Embedding(m, n) 來實現(xiàn)的,其中m 表示所有的單詞數(shù)目,n 表示詞嵌入的維度,下面舉一個例子:

1 word_to_ix = {'hello': 0, 'world': 1}2 embeds = nn.Embedding(2, 5)3 hello_idx = torch.LongTensor([word_to_ix['hello']])4 hello_idx = Variable(hello_idx)5 hello_embed = embeds(hello_idx)6 print(hello_embed)

上面就是輸出的hello 的詞嵌入,下面來解釋一下代碼。首先需要給每個單詞建立一個對應(yīng)下標(biāo),這樣每個單詞都可以用一個數(shù)字去表示,比如需要hello 的時候,就可以用0來表示,用這種方式,訪問每個詞會特別方便。接著是詞嵌入的定義nn.Embedding(2,5),如上面介紹過的,表示有兩個詞,每個詞向量是5 維,也就是一個2 * 5 的矩陣,只不過矩陣中的元素是可以被學(xué)習(xí)更新的,所以如果有1000 個詞,每個詞向量希望是100 維,就可以這樣定義詞嵌入nn.Embedding(1000, 100)。訪問每一個詞的詞向量需要將tensor 轉(zhuǎn)換成Variable,因為詞向量也是網(wǎng)絡(luò)中更新的參數(shù),所以在計算圖中,需要通過Variable 去訪問。另外這里的詞向量只是初始的詞向量,并沒有經(jīng)過學(xué)習(xí)更新,需要建立神經(jīng)網(wǎng)絡(luò)優(yōu)化更新,修改詞向量里面的參數(shù)使得詞向量能夠表示不同的詞,且語義相近的詞能夠有更小的夾角。

以上介紹了詞嵌入在PyTorch 中是如何實現(xiàn)的,下一節(jié)將介紹詞嵌入是如何更新的,以及它如何結(jié)合N Gram 語言模型進行預(yù)測。

N Gram 模型

首先介紹N Gram 模型的原理和它要解決的問題。在一篇文章中,每一句話都是由很多單詞組成的,而且這些單詞的排列順序也是非常重要的。在一句話中,是否可以由前面幾個詞來預(yù)測這些詞后面的一個單詞?比如在“I lived in France for 10 years, I can speak _ .”這句話中,我們希望能夠預(yù)測最后這個詞是French。

知道想要解決的問題后,就可以引出N Gram 語言模型了。對于一句話T,它由w1;w2;…wn 這n 個詞構(gòu)成,可以得到下面的公式:

大數(shù)據(jù)

但是這樣的一個模型存在著一些缺陷,比如參數(shù)空間過大,預(yù)測一個詞需要前面所有的詞作為條件來計算條件概率,所以在實際中沒辦法使用。為了解決這個問題,引入了馬爾科夫假設(shè),也就是說這個單詞只與前面的幾個詞有關(guān)系,并不是和前面所有的詞都有關(guān)系,有了這個假設(shè),就能夠在實際中使用N Gram 模型了。

對于這個條件概率,傳統(tǒng)的方法是統(tǒng)計語料中每個單詞出現(xiàn)的頻率,據(jù)此來估計這個條件概率,這里使用詞嵌入的辦法,直接在語料中計算這個條件概率,然后最大化條件概率從而優(yōu)化詞向量,據(jù)此進行預(yù)測。

單詞預(yù)測的PyTorch 實現(xiàn)

首先給出一段文章作為訓(xùn)練集:

1 CONTEXT_SIZE = 22 EMBEDDING_DIM = 103 # We will use Shakespeare Sonnet 24 test_sentence = """When forty winters shall besiege thy brow,5 And dig deep trenches in thy beauty's field,6 Thy youth's proud livery so gazed on now,7 Will be a totter'd weed of small worth held:8 Then being asked, where all thy beauty lies,9 Where all the treasure of thy lusty days;10 To say, within thine own deep sunken eyes,11 Were an all-eating shame, and thriftless praise.12 How much more praise deserv'd thy beauty's use,13 If thou couldst answer 'This fair child of mine14 Shall sum my count, and make my old excuse,'15 Proving his beauty by succession thine!16 This were to be new made when thou art old,17 And see thy blood warm when thou feel'st it cold.""".split()

CONTEXT_SIZE 表示想由前面的幾個單詞來預(yù)測這個單詞,這里設(shè)置為2,就是說我們希望通過這個單詞的前兩個單詞來預(yù)測這一個單詞,EMBEDDING_DIM 表示詞嵌入的維數(shù)。

接著建立訓(xùn)練集,遍歷所有語料來創(chuàng)建,將數(shù)據(jù)整理好,需要將單詞分三個組,每個組前兩個作為傳入的數(shù)據(jù),而最后一個作為預(yù)測的結(jié)果。

1 trigram = [((test_sentence[i], test_sentence[i+1]), test_sentence[i+2])2 for i in range(len(test_sentence)-2)]

將每個單詞編碼,即用數(shù)字來表示每個單詞,只有這樣才能夠傳入nn.Embedding得到詞向量。

1 vocb = set(test_sentence) # 通過set將重復(fù)的單詞去掉2 word_to_idx = {word: i for i, word in enumerate(vocb)}3 idx_to_word = {word_to_idx[word]: word for word in word_to_idx}

然后可以定義N Gram 模型如下:

1 class NgramModel(nn.Module):2 def __init__(self, vocb_size, context_size, n_dim):3 super(NgramModel, self).__init__()4 self.n_word = vocb_size5 self.embedding = nn.Embedding(self.n_word, n_dim)6 self.linear1 = nn.Linear(context_size*n_dim, 128)7 self.linear2 = nn.Linear(128, self.n_word)89 def forward(self, x):10 emb = self.embedding(x)11 emb = emb.view(1, -1)12 out = self.linear1(emb)13 out = F.relu(out)14 out = self.linear2(out)15 log_prob = F.log_softmax(out)16 return log_prob

模型需要傳入的參數(shù)有三個,分別是所有的單詞數(shù)、預(yù)測單詞所依賴的單詞數(shù)、即CONTEXT_SIZE 和詞向量的維度。網(wǎng)絡(luò)在向前傳播中,首先傳入單詞得到詞向量,模型是根據(jù)前面兩個詞預(yù)測第三個詞的,所以需要傳入兩個詞,得到的詞向量是(2, 100),然后將詞向量展開成(1, 200),接著經(jīng)過線性變換,經(jīng)過relu 激活函數(shù),再經(jīng)過一個線性變換,輸出的維數(shù)是單詞總數(shù),最后經(jīng)過一個log softmax 激活函數(shù)得到概率分布,最大化條件概率,可以用下面的公式表示:

大數(shù)據(jù)

在網(wǎng)絡(luò)的訓(xùn)練中,不僅會更新線性層的參數(shù),還會更新詞嵌入中的參數(shù),訓(xùn)練100次模型,可以發(fā)現(xiàn)loss 已經(jīng)降到了0.37,也可以通過預(yù)測來檢測模型是否有效:

1 word, label = trigram[3]2 word = Variable(torch.LongTensor([word_to_idx[i] for i in word]))3 out = ngrammodel(word)4 _, predict_label = torch.max(out, 1)5 predict_word = idx_to_word[predict_label.data[0][0]]6 print('real word is {}, predict word is {}'.format(label, predict_word))

運行上面的代碼,可以發(fā)現(xiàn)真實的單詞跟預(yù)測的單詞都是一樣的,雖然這是在訓(xùn)練集上,但是在一定程度上也說明這個小模型能夠處理N Gram 模型的問題。

上面介紹了如何通過最簡單的單邊N Gram 模型預(yù)測單詞,還有一種復(fù)雜一點的N Gram 模型通過雙邊的單詞來預(yù)測中間的單詞,這種模型有個專門的名字,叫Continuous Bag-of-Words model(CBOW),具體內(nèi)容差別不大,就不再贅述。

詞性判斷

上面只使用了詞嵌入和N Gram 模型進行自然語言處理,還沒有真正使用循環(huán)神經(jīng)網(wǎng)絡(luò),下面介紹RNN 在自然語言處理中的應(yīng)用。在這個例子中,我們將使用LSTM 做詞性判斷,因為同一個單詞有著不同的詞性,比如book 可以表示名詞,也可以表示動詞,所以需要結(jié)合前后文給出具體的判斷。先介紹使用LSTM 做詞性判斷的原理。

基本原理

定義好一個LSTM 網(wǎng)絡(luò),然后給出一個由很多個詞構(gòu)成的句子,根據(jù)前面的內(nèi)容,每個詞可以用一個詞向量表示,這樣一句話就可以看做是一個序列,序列中的每個元素都是一個高維向量,將這個序列傳入LSTM,可以得到與序列等長的輸出,每個輸出都表示為對詞性的判斷,比如名詞、動詞等。從本質(zhì)上看,這是一個分類問題,雖然使用了LSTM,但實際上是根據(jù)這個詞前面的一些詞來對它進行分類,看它是屬于幾種詞性中的哪一種。

思考一下為什么LSTM 在這個問題里面起著重要的作用。如果完全孤立地對一個詞做詞性的判斷,往往無法得到比較準確的結(jié)果,但是通過LSTM,根據(jù)它記憶的特性,就能夠通過這個單詞前面記憶的一些詞語來對它做一個判斷,比如前面的單詞如果是my,那么它緊跟的詞很有可能就是一個名詞,這樣就能夠充分地利用上文來處理這個問題。

字符增強

還可以通過引入字符來增強表達,這是什么意思呢?就是說一些單詞存在著前綴或者后綴,比如-ly 這種后綴很可能是一個副詞,這樣我們就能夠在字符水平上對詞性進行進一步判斷,把兩種方法集成起來,能夠得到一個更好的結(jié)果。

在實現(xiàn)上還是用LSTM,只是這次不再將句子作為一個序列,而是將每個單詞作為一個序列。每個單詞由不同的字母組成,比如apple 由a p p l e 構(gòu)成,給這些字符建立詞向量,形成了一個長度為5 的序列,將它傳入LSTM 網(wǎng)絡(luò),只取最后輸出的狀態(tài)層作為它的一種字符表達,不需要關(guān)心提取出來的字符表達到底是什么樣,它作為一種抽象的特征,能夠更好地預(yù)測結(jié)果。

詞性判斷的PyTorch 實現(xiàn)

作為演示,使用一個簡單的訓(xùn)練數(shù)據(jù),下面有兩句話,每句話中的每個詞都給出了詞性:

1 training_data = [2 ("The dog ate the apple".split(), ["DET", "NN", "V", "DET", "NN"]),3 ("Everybody read that book".split(), ["NN", "V", "DET", "NN"])4 ]

接著對單詞和詞性由a 到z 的字符進行編碼:

1 word_to_idx = {}2 tag_to_idx = {}3 for context, tag in training_data:4 for word in context:5 if word not in word_to_idx:6 word_to_idx[word] = len(word_to_idx)7 for label in tag:8 if label not in tag_to_idx:9 tag_to_idx[label] = len(tag_to_idx)1011 alphabet = 'abcdefghijklmnopqrstuvwxyz'12 character_to_idx = {}13 for i in range(len(alphabet)):14 character_to_idx[alphabet[i]] = i

接著先定義字符水準上的LSTM,定義方式和之前類似:

1 class CharLSTM(nn.Module):2 def __init__(self, n_char, char_dim, char_hidden):3 super(CharLSTM, self).__init__()4 self.char_embedding = nn.Embedding(n_char, char_dim)5 self.char_lstm = nn.LSTM(char_dim, char_hidden, batch_first=True)67 def forward(self, x):8 x = self.char_embedding(x)9 _, h = self.char_lstm(x)10 return h[0]

定義兩層結(jié)構(gòu):第一層是詞嵌入,第二層是LSTM。在網(wǎng)絡(luò)的前向傳播中,先將單詞的n 個字符傳入網(wǎng)絡(luò),再通過nn.Embedding 得到詞向量,接著傳入LSTM 網(wǎng)絡(luò),得到隱藏狀態(tài)輸出h,然后通過h[0] 得到想要的輸出狀態(tài)。對于每個單詞,都可以通過CharLSTM 用相應(yīng)的字符表示。

接著完成目標(biāo),分析每個單詞的詞性,首先定義好詞性的LSTM 網(wǎng)絡(luò):

1 class LSTMTagger(nn.Module):2 def __init__(self, n_word, n_char, char_dim, n_dim, char_hidden,3 n_hidden, n_tag):4 super(LSTMTagger, self).__init__()5 self.word_embedding = nn.Embedding(n_word, n_dim)6 self.char_lstm = CharLSTM(n_char, char_dim, char_hidden)7 self.lstm = nn.LSTM(n_dim+char_hidden, n_hidden, batch_first=True)8 self.linear1 = nn.Linear(n_hidden, n_tag)910 def forward(self, x, word_data):11 word = [i for i in word_data]12 char = torch.FloatTensor()13 for each in word:14 word_list = []15 for letter in each:16 word_list.append(character_to_idx[letter.lower()])17 word_list = torch.LongTensor(word_list)18 word_list = word_list.unsqueeze(0)19 tempchar = self.char_lstm(Variable(word_list).cuda())20 tempchar = tempchar.squeeze(0)21 char = torch.cat((char, tempchar.cpu().data), 0)22 char = char.squeeze(1)23 char = Variable(char).cuda()24 x = self.word_embedding(x)25 x = torch.cat((x, char), 1)26 x = x.unsqueeze(0)27 x, _ = self.lstm(x)28 x = x.squeeze(0)29 x = self.linear1(x)30 y = F.log_softmax(x)31 return y

看著有點復(fù)雜,慢慢來介紹。首先使用n_word 和n_dim 定義單詞的詞向量矩陣的維度,n_char 和char_dim 定義字符的詞向量維度,char_hidden 表示字符水準上的LSTM 輸出的維度,n_hidden 表示每個單詞作為序列輸入LSTM 的輸出維度,最后n_tag 表示輸出的詞性分類。介紹完里面參數(shù)的含義,下面具體介紹其中網(wǎng)絡(luò)的向前傳播。

學(xué)習(xí)過PyTorch 的動態(tài)圖結(jié)構(gòu),網(wǎng)絡(luò)的向前傳播就非常簡單了。因為要使用字符增強,所以在傳入一個句子作為序列的同時,還需要傳入句子中的單詞,用word_data表示。動態(tài)圖結(jié)構(gòu)使得前向傳播中可以使用for 循環(huán)將每個單詞都傳入CharLSTM,得到的結(jié)果和單詞的詞向量拼在一起作為新的序列輸入,將它傳入LSTM 中,最后接一個全連接層,將輸出維數(shù)定義為詞性的數(shù)目。

這是基本的思路,就不具體解釋每句話的含義了,只是要注意代碼里面有unsqueeze和squeeze 的操作,原因前面介紹過,LSTM 的輸入要帶上batch_size,所以需要將維度擴大。

網(wǎng)絡(luò)訓(xùn)練經(jīng)過了300 次,loss 降到了0.16 左右。為了驗證模型的準確性,可以預(yù)測“Everybody ate the apple”這句話中每個詞的詞性,一共有三種詞:DET、NN、V。最后得到的結(jié)果如圖7所示。

結(jié)果是一個4 行3 列的向量,每一行表示一個單詞,每一列表示一種詞性,從左到右的詞性分別是DET、NN、V。從每行里面取最大值,那么第一個詞的詞性就是NN,第二個詞是V,第三個詞是DET,第四個詞是NN,與想要的結(jié)果相符。

大數(shù)據(jù)

圖7 網(wǎng)絡(luò)訓(xùn)練結(jié)果以上,通過幾個簡單的例子介紹了循環(huán)神經(jīng)網(wǎng)絡(luò)在自然語言處理中的應(yīng)用,當(dāng)然真正的應(yīng)用會更多,同時也更加復(fù)雜,這里就不再深入介紹了,對自然語言處理感興趣的讀者可以進行更深入地探究。

極客網(wǎng)企業(yè)會員

免責(zé)聲明:本網(wǎng)站內(nèi)容主要來自原創(chuàng)、合作伙伴供稿和第三方自媒體作者投稿,凡在本網(wǎng)站出現(xiàn)的信息,均僅供參考。本網(wǎng)站將盡力確保所提供信息的準確性及可靠性,但不保證有關(guān)資料的準確性及可靠性,讀者在使用前請進一步核實,并對任何自主決定的行為負責(zé)。本網(wǎng)站對有關(guān)資料所引致的錯誤、不確或遺漏,概不負任何法律責(zé)任。任何單位或個人認為本網(wǎng)站中的網(wǎng)頁或鏈接內(nèi)容可能涉嫌侵犯其知識產(chǎn)權(quán)或存在不實內(nèi)容時,應(yīng)及時向本網(wǎng)站提出書面權(quán)利通知或不實情況說明,并提供身份證明、權(quán)屬證明及詳細侵權(quán)或不實情況證明。本網(wǎng)站在收到上述法律文件后,將會依法盡快聯(lián)系相關(guān)文章源頭核實,溝通刪除相關(guān)內(nèi)容或斷開相關(guān)鏈接。

2017-11-30
RNN在自然語言處理中的應(yīng)用及其PyTorch實現(xiàn)
作者:廖星宇 對于人類而言,以前見過的事物會在腦海里面留下記憶,雖然隨后記憶會慢慢消失,但是每當(dāng)經(jīng)過提醒,人們往往能夠重拾記憶。在神經(jīng)網(wǎng)絡(luò)的研究中,讓模型充滿

長按掃碼 閱讀全文