10分鐘快速入門PyTorch (9)

在上一節(jié)中,我們介紹了一下自然語言處理里面最基本的單邊和雙邊的 n gram 模型,用 word embedding和n gram 模型對(duì)一句話中的某個(gè)詞做預(yù)測(cè),下面我們將使用LSTM來做判別每個(gè)詞的詞性,因?yàn)橥粋€(gè)單詞有著不同的詞性,比如book可以表示名詞,也可以表示動(dòng)詞,所以我們需要訓(xùn)練一下網(wǎng)絡(luò)來得到詞性的判斷。

LSTM 詞性判斷

LSTM的網(wǎng)絡(luò)結(jié)構(gòu)在之前已經(jīng)介紹過了,如果忘了的同學(xué)可以去前面看看。我們首先介紹一下如何做每個(gè)詞詞性的判斷。

首先,我們定義好一個(gè)LSTM網(wǎng)絡(luò),然后給出一個(gè)句子,每個(gè)句子都有很多個(gè)詞構(gòu)成,每個(gè)詞可以用一個(gè)詞向量表示,這樣一句話就可以形成一個(gè)序列,我們將這個(gè)序列依次傳入LSTM,然后就可以得到與序列等長的輸出,每個(gè)輸出都表示的是一種詞性,比如名詞,動(dòng)詞之類的,還是一種分類問題,每個(gè)單詞都屬于幾種詞性中的一種。

我們可以思考一下為什么LSTM在這個(gè)問題里面起著重要的作用。如果我們完全孤立的對(duì)一個(gè)詞做詞性的判斷這樣我們需要特別高維的詞向量,但是對(duì)于LSTM,它有著一個(gè)記憶的特性,這樣我們就能夠通過這個(gè)單詞前面記憶的一些詞語來對(duì)其做一個(gè)判斷,比如前面如果是my,那么他緊跟的詞有很大可能就是一個(gè)名詞,這樣就能夠充分的利用上文來做這個(gè)問題。

同時(shí)我們還可以通過引入字符來增強(qiáng)表達(dá),什么意思呢?也就是說一個(gè)單詞有一些前綴和后綴,比如-ly這種后綴很大可能是一個(gè)副詞,這樣我們就能夠在字符水平得到一個(gè)詞性判斷的更好結(jié)果。

具體怎么做呢?還是用LSTM。每個(gè)單詞有不同的字母組成,比如 apple 由a p p l e構(gòu)成,我們同樣給這些字符詞向量,這樣形成了一個(gè)長度為5的序列,然后傳入另外一個(gè)LSTM網(wǎng)絡(luò),只取最后輸出的狀態(tài)層作為它的一種字符表達(dá),我們并不需要關(guān)心到底提取出來的字符表達(dá)是什么樣的,在learning的過程中這些都是會(huì)被更新的參數(shù),使得最終我們能夠正確預(yù)測(cè)。

原理看著挺讓人煩的,這個(gè)時(shí)候看代碼反而更快,所以如果前面的原理你沒有理解清楚,那么看看代碼,說不行你就恍然大悟了。

Code

準(zhǔn)備數(shù)據(jù)

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

這是一個(gè)簡(jiǎn)單的訓(xùn)練數(shù)據(jù),兩句話,每句話的每個(gè)單詞的詞性由后面給出。

接著我們需要給這些單詞和詞性一個(gè)編碼

word_to_idx = {}
tag_to_idx = {}
for context, tag in training_data:
    for word in context:
        if word not in word_to_idx:
            word_to_idx[word] = len(word_to_idx)
    for label in tag:
        if label not in tag_to_idx:
            tag_to_idx[label] = len(tag_to_idx)

這樣每個(gè)單詞就用一個(gè)數(shù)字表示,每種詞性也用一個(gè)數(shù)字表示,這些之前都接觸過。

alphabet = 'abcdefghijklmnopqrstuvwxyz'
character_to_idx = {}
for i in range(len(alphabet)):
    character_to_idx[alphabet[i]] = i

同時(shí)我們需要將從a到z的字符也編碼。

字符LSTM

接著我們定義字符水平的LSTM

class CharLSTM(nn.Module):
    def __init__(self, n_char, char_dim, char_hidden):
        super(CharLSTM, self).__init__()
        self.char_embedding = nn.Embedding(n_char, char_dim)
        self.char_lstm = nn.LSTM(char_dim, char_hidden, batch_first=True)

    def forward(self, x):
        x = self.char_embedding(x)
        _, h = self.char_lstm(x)
        return h[1]

看看上面的代碼,首先定義好embedding和lstm,接著傳入n個(gè)字符,然后通過nn.Embedding得到詞向量,接著傳入LSTM網(wǎng)絡(luò),得到狀態(tài)輸出h,然后通過h[1]得到我們想要的hidden state。

這樣我們對(duì)于每個(gè)單詞,通過CharLSTM就能夠得到相應(yīng)的字符表示。

詞性LSTM

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

class LSTMTagger(nn.Module):
    def __init__(self, n_word, n_char, char_dim, n_dim, char_hidden,
                 n_hidden, n_tag):
        super(LSTMTagger, self).__init__()
        self.word_embedding = nn.Embedding(n_word, n_dim)
        self.char_lstm = CharLSTM(n_char, char_dim, char_hidden)
        self.lstm = nn.LSTM(n_dim+char_hidden, n_hidden, batch_first=True)
        self.linear1 = nn.Linear(n_hidden, n_tag)

    def forward(self, x, word_data):
        word = [i for i in word_data]
        char = torch.FloatTensor()
        for each in word:
            word_list = []
            for letter in each:
                word_list.append(character_to_idx[letter.lower()])
            word_list = torch.LongTensor(word_list)
            word_list = word_list.unsqueeze(0)
            tempchar = self.char_lstm(Variable(word_list).cuda())
            tempchar = tempchar.squeeze(0)
            char = torch.cat((char, tempchar.cpu().data), 0)
        char = char.squeeze(1)
        char = Variable(char).cuda()
        x = self.word_embedding(x)
        x = torch.cat((x, char), 1)
        x = x.unsqueeze(0)
        x, _ = self.lstm(x)
        x = x.squeeze(0)
        x = self.linear1(x)
        y = F.log_softmax(x)
        return y

看著有點(diǎn)復(fù)雜,我們慢慢來解釋。首先n_word 和 n_dim來定義單詞的詞向量維度,n_char和char_dim來定義字符的詞向量維度,char_hidden表示CharLSTM輸出的維度,n_hidden表示每個(gè)單詞作為序列輸入的LSTM輸出維度,最后n_tag表示輸出的詞性的種類。

接著開始前向傳播,不僅要傳入一個(gè)編碼之后的句子,同時(shí)還需要傳入原本的單詞,因?yàn)樾枰獙?duì)字符做一個(gè)LSTM,所以傳入的參數(shù)多了一個(gè)word_data表示一個(gè)句子的所有單詞。

然后就是將每個(gè)單詞傳入CharLSTM,得到的結(jié)果和單詞的詞向量拼在一起形成一個(gè)新的輸入,將輸入傳入LSTM里面,得到輸出,最后接一個(gè)全連接層,將輸出維數(shù)定義為label的數(shù)目。

這就是基本的思路,我就不具體解釋每句話的含義了,留給大家自己看看,特別要注意里面有一些unsqueeze和squeeze是因?yàn)長STM的輸入要求要帶上batch_size,torch.cat里面0和1分別表示沿著行和列來拼接。

結(jié)果

經(jīng)過300個(gè)epoch,loss降到了0.2左右

1

最后我們來預(yù)測(cè)一下 Everybody ate the apple 這句話每個(gè)詞的詞性,一共有3種詞性,DET,NN,V。最后得到的結(jié)果為

2

一共有4行,每行里面取最大的,那么第一個(gè)詞的詞性就是NN,第二個(gè)詞是V,第三個(gè)詞是DET,第四個(gè)詞是NN。這個(gè)是相符的。

以上我們介紹了RNN在圖像處理以及自然語言處理上的應(yīng)用,RNN還有更多的應(yīng)用,比如做image captioning,機(jī)器翻譯等等,感興趣的同學(xué)可以自己在github上找一找。

下一章將是本次教程的倒數(shù)第二個(gè)部分,Generative Adversarial Networks,生成對(duì)抗網(wǎng)絡(luò)。


本文代碼已經(jīng)上傳到了github

歡迎查看我的知乎專欄,深度煉丹

歡迎訪問我的博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容