在上一節(jié)中凑懂,我們介紹了一下自然語言處理里面最基本的單邊和雙邊的 n gram 模型嗽元,用 word embedding和n gram 模型對一句話中的某個詞做預(yù)測,下面我們將使用LSTM來做判別每個詞的詞性环础,因為同一個單詞有著不同的詞性宾巍,比如book可以表示名詞,也可以表示動詞丈甸,所以我們需要訓(xùn)練一下網(wǎng)絡(luò)來得到詞性的判斷糯俗。
LSTM 詞性判斷
LSTM的網(wǎng)絡(luò)結(jié)構(gòu)在之前已經(jīng)介紹過了,如果忘了的同學(xué)可以去前面看看睦擂。我們首先介紹一下如何做每個詞詞性的判斷得湘。
首先,我們定義好一個LSTM網(wǎng)絡(luò)顿仇,然后給出一個句子淘正,每個句子都有很多個詞構(gòu)成摆马,每個詞可以用一個詞向量表示,這樣一句話就可以形成一個序列鸿吆,我們將這個序列依次傳入LSTM囤采,然后就可以得到與序列等長的輸出,每個輸出都表示的是一種詞性伞剑,比如名詞斑唬,動詞之類的,還是一種分類問題黎泣,每個單詞都屬于幾種詞性中的一種恕刘。
我們可以思考一下為什么LSTM在這個問題里面起著重要的作用。如果我們完全孤立的對一個詞做詞性的判斷這樣我們需要特別高維的詞向量抒倚,但是對于LSTM褐着,它有著一個記憶的特性,這樣我們就能夠通過這個單詞前面記憶的一些詞語來對其做一個判斷托呕,比如前面如果是my含蓉,那么他緊跟的詞有很大可能就是一個名詞,這樣就能夠充分的利用上文來做這個問題项郊。
同時我們還可以通過引入字符來增強表達(dá)馅扣,什么意思呢?也就是說一個單詞有一些前綴和后綴着降,比如-ly這種后綴很大可能是一個副詞差油,這樣我們就能夠在字符水平得到一個詞性判斷的更好結(jié)果。
具體怎么做呢任洞?還是用LSTM蓄喇。每個單詞有不同的字母組成,比如 apple 由a p p l e構(gòu)成交掏,我們同樣給這些字符詞向量妆偏,這樣形成了一個長度為5的序列,然后傳入另外一個LSTM網(wǎng)絡(luò)盅弛,只取最后輸出的狀態(tài)層作為它的一種字符表達(dá)钱骂,我們并不需要關(guān)心到底提取出來的字符表達(dá)是什么樣的,在learning的過程中這些都是會被更新的參數(shù)熊尉,使得最終我們能夠正確預(yù)測罐柳。
原理看著挺讓人煩的,這個時候看代碼反而更快狰住,所以如果前面的原理你沒有理解清楚张吉,那么看看代碼,說不行你就恍然大悟了催植。
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"])
]
這是一個簡單的訓(xùn)練數(shù)據(jù)肮蛹,兩句話勺择,每句話的每個單詞的詞性由后面給出。
接著我們需要給這些單詞和詞性一個編碼
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)
這樣每個單詞就用一個數(shù)字表示伦忠,每種詞性也用一個數(shù)字表示省核,這些之前都接觸過。
alphabet = 'abcdefghijklmnopqrstuvwxyz'
character_to_idx = {}
for i in range(len(alphabet)):
character_to_idx[alphabet[i]] = i
同時我們需要將從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個字符赋咽,然后通過nn.Embedding得到詞向量旧噪,接著傳入LSTM網(wǎng)絡(luò),得到狀態(tài)輸出h脓匿,然后通過h[1]得到我們想要的hidden state淘钟。
這樣我們對于每個單詞,通過CharLSTM就能夠得到相應(yīng)的字符表示陪毡。
詞性LSTM
接著我們來完成我們的目標(biāo)米母,分析每個單詞的詞性,首先定義好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
看著有點復(fù)雜毡琉,我們慢慢來解釋铁瞒。首先n_word 和 n_dim來定義單詞的詞向量維度,n_char和char_dim來定義字符的詞向量維度桅滋,char_hidden表示CharLSTM輸出的維度精拟,n_hidden表示每個單詞作為序列輸入的LSTM輸出維度,最后n_tag表示輸出的詞性的種類虱歪。
接著開始前向傳播,不僅要傳入一個編碼之后的句子栅表,同時還需要傳入原本的單詞笋鄙,因為需要對字符做一個LSTM,所以傳入的參數(shù)多了一個word_data表示一個句子的所有單詞怪瓶。
然后就是將每個單詞傳入CharLSTM萧落,得到的結(jié)果和單詞的詞向量拼在一起形成一個新的輸入,將輸入傳入LSTM里面洗贰,得到輸出找岖,最后接一個全連接層,將輸出維數(shù)定義為label的數(shù)目敛滋。
這就是基本的思路许布,我就不具體解釋每句話的含義了,留給大家自己看看绎晃,特別要注意里面有一些unsqueeze和squeeze是因為LSTM的輸入要求要帶上batch_size蜜唾,torch.cat里面0和1分別表示沿著行和列來拼接杂曲。
結(jié)果
經(jīng)過300個epoch,loss降到了0.2左右
最后我們來預(yù)測一下 Everybody ate the apple 這句話每個詞的詞性袁余,一共有3種詞性擎勘,DET,NN颖榜,V棚饵。最后得到的結(jié)果為
一共有4行,每行里面取最大的掩完,那么第一個詞的詞性就是NN噪漾,第二個詞是V,第三個詞是DET藤为,第四個詞是NN怪与。這個是相符的。
以上我們介紹了RNN在圖像處理以及自然語言處理上的應(yīng)用缅疟,RNN還有更多的應(yīng)用分别,比如做image captioning,機器翻譯等等存淫,感興趣的同學(xué)可以自己在github上找一找耘斩。
下一章將是本次教程的倒數(shù)第二個部分,Generative Adversarial Networks桅咆,生成對抗網(wǎng)絡(luò)括授。
本文代碼已經(jīng)上傳到了github上
歡迎查看我的知乎專欄,深度煉丹
歡迎訪問我的博客