Pytorch LSTM 詞性判斷


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

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

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

具體怎么做呢?還是用LSTM。每個單詞有不同的字母組成,比如 apple 由a p p l e構成,我們同樣給這些字符詞向量,這樣形成了一個長度為5的序列,然后傳入另外一個LSTM網絡,只取最后輸出的狀態層作為它的一種字符表達,我們並不需要關心到底提取出來的字符表達是什么樣的,在learning的過程中這些都是會被更新的參數,使得最終我們能夠正確預測。

  1 import torch
  2 import torch.nn.functional as F
  3 from torch import nn, optim
  4 from torch.autograd import Variable
  5 
  6 training_data = [("The dog ate the apple".split(),
  7                   ["DET", "NN", "V", "DET", "NN"]),
  8                  ("Everybody read that book".split(), ["NN", "V", "DET",
  9                                                        "NN"])]
 10 # 每個單詞就用一個數字表示,每種詞性也用一個數字表示
 11 word_to_idx = {}
 12 tag_to_idx = {}
 13 for context, tag in training_data:
 14     for word in context:
 15         if word not in word_to_idx:
 16             # 對詞進行編碼
 17             word_to_idx[word] = len(word_to_idx)
 18     for label in tag:
 19         if label not in tag_to_idx:
 20             # 對詞性編碼
 21             tag_to_idx[label] = len(tag_to_idx)
 22 alphabet = 'abcdefghijklmnopqrstuvwxyz'
 23 character_to_idx = {}
 24 for i in range(len(alphabet)):
 25     # 對字母編碼
 26     character_to_idx[alphabet[i]] = i
 27 
 28 # 字符LSTM
 29 class CharLSTM(nn.Module):
 30     def __init__(self, n_char, char_dim, char_hidden):
 31         super(CharLSTM, self).__init__()
 32         self.char_embedding = nn.Embedding(n_char, char_dim)
 33         self.char_lstm = nn.LSTM(char_dim, char_hidden, batch_first=True)
 34 
 35     def forward(self, x):
 36         x = self.char_embedding(x)
 37         _, h = self.char_lstm(x)
 38         # 取隱層
 39         return h[0]
 40 
 41 
 42 class LSTMTagger(nn.Module):
 43     def __init__(self, n_word, n_char, char_dim, n_dim, char_hidden, n_hidden,
 44                  n_tag):
 45         super(LSTMTagger, self).__init__()
 46         self.word_embedding = nn.Embedding(n_word, n_dim)
 47         self.char_lstm = CharLSTM(n_char, char_dim, char_hidden)
 48         self.lstm = nn.LSTM(n_dim + char_hidden, n_hidden, batch_first=True)
 49         self.linear1 = nn.Linear(n_hidden, n_tag)
 50 
 51     def forward(self, x, word):
 52         char = torch.FloatTensor()
 53         for each in word:
 54             char_list = []
 55             for letter in each:
 56                 # 對詞進行字母編碼
 57                 char_list.append(character_to_idx[letter.lower()])
 58             char_list = torch.LongTensor(char_list)
 59             char_list = char_list.unsqueeze(0)
 60             if torch.cuda.is_available():
 61                 tempchar = self.char_lstm(Variable(char_list).cuda())
 62             else:
 63                 tempchar = self.char_lstm(Variable(char_list))
 64             tempchar = tempchar.squeeze(0)
 65             char = torch.cat((char, tempchar.cpu().data), 0)
 66         if torch.cuda.is_available():
 67             char = char.cuda()
 68         char = Variable(char)
 69         x = self.word_embedding(x)
 70         x = torch.cat((x, char), 1) # char編碼與word編碼cat
 71         x = x.unsqueeze(0)
 72         # 取輸出層 句長*n_hidden
 73         x, _ = self.lstm(x)
 74         x = x.squeeze(0)
 75         x = self.linear1(x)
 76         y = F.log_softmax(x)
 77         return y
 78 
 79 
 80 model = LSTMTagger(
 81     len(word_to_idx), len(character_to_idx), 10, 100, 50, 128, len(tag_to_idx))
 82 if torch.cuda.is_available():
 83     model = model.cuda()
 84 criterion = nn.CrossEntropyLoss()
 85 optimizer = optim.SGD(model.parameters(), lr=1e-2)
 86 
 87 
 88 def make_sequence(x, dic):
 89     idx = [dic[i] for i in x]
 90     idx = Variable(torch.LongTensor(idx))
 91     return idx
 92 
 93 
 94 for epoch in range(300):
 95     print('*' * 10)
 96     print('epoch {}'.format(epoch + 1))
 97     running_loss = 0
 98     for data in training_data:
 99         word, tag = data
100         word_list = make_sequence(word, word_to_idx)
101         tag = make_sequence(tag, tag_to_idx)
102         if torch.cuda.is_available():
103             word_list = word_list.cuda()
104             tag = tag.cuda()
105         # forward
106         out = model(word_list, word)
107         loss = criterion(out, tag)
108         running_loss += loss.data[0]
109         # backward 三步常規操作
110         optimizer.zero_grad()
111         loss.backward()
112         optimizer.step()
113     print('Loss: {}'.format(running_loss / len(data)))
114 print()
115 input = make_sequence("Everybody ate the apple".split(), word_to_idx)
116 if torch.cuda.is_available():
117     input = input.cuda()
118 model.eval() #對dropout和batch normalization的操作在訓練和測試的時候是不一樣
119 out = model(input, "Everybody ate the apple".split())
120 print(out)

首先n_word 和 n_dim來定義單詞的詞向量維度,n_char和char_dim來定義字符的詞向量維度,char_hidden表示CharLSTM輸出的維度,n_hidden表示每個單詞作為序列輸入的LSTM輸出維度,最后n_tag表示輸出的詞性的種類。

接着開始前向傳播,不僅要傳入一個編碼之后的句子,同時還需要傳入原本的單詞,因為需要對字符做一個LSTM,所以傳入的參數多了一個word_data表示一個句子的所有單詞。

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

特別要注意里面有一些unsqueeze(增維)和squeeze(降維)是因為LSTM的輸入要求要帶上batch_size(這里是1),torch.cat里面0和1分別表示沿着行和列來拼接。

預測一下 Everybody ate the apple 這句話每個詞的詞性,一共有3種詞性,DET,NN,V。最后得到的結果為:

 

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

參考自:https://sherlockliao.github.io/2017/06/05/lstm%20language/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM