PyTorch:LSTMの実践(CoNLL)
CoNLLについて
CoNLLは、「Conference on Computational Natural Language Learning」の略称。
色々と自然言語処理のShared Taskを開催しており、コーパスが自由に利用できるようになっている。
今回は「CoNLL 2003 shared task (NER) data」を利用する。
shared taskの詳細はこちら。
Language-Independent Named Entity Recognition (II)
尚、コーパスか下記よりダウンロードした。
NER/corpus/CoNLL-2003 at master · synalp/NER · GitHub
フォーマットは以下のようなタブ区切りになっている。
固有表現抽出を試す場合、4列目がラベルになる。
U.N. NNP I-NP I-ORG official NN I-NP O Ekeus NNP I-NP I-PER heads VBZ I-VP O for IN I-PP O Baghdad NNP I-NP I-LOC . . O O
前処理
とりあえず、下記のオブジェクトを作って保存する。
- word2idx(単語:インデックスのdictionary)
- label2idx(ラベル:インデックスのdictionary)
- sents_idx(文章の単語をインデックス変換したリスト)
- labels_idx(文章のラベルをインデックス変換したリスト)
前処理のコードはここに置いた。
LSTM_CoNLL_PyTorch/preprocessing.ipynb at master · kento1109/LSTM_CoNLL_PyTorch · GitHub
尚、コードの一部はutilsを利用した。
model.utils — LM-LSTM-CRF documentation
学習
今回は標準的なLSTMで試す。
(CRFやBidirectional等は行わない)
コードはほとんどチュートリアルのものを利用した。
LSTMクラスは以下の通り(チュートリアルのまま)
class LSTMTagger(nn.Module): def __init__(self, embedding_dim, hidden_dim, vocab_size, tagset_size): super(LSTMTagger, self).__init__() self.hidden_dim = hidden_dim self.word_embeddings = nn.Embedding(vocab_size, embedding_dim) # The LSTM takes word embeddings as inputs, and outputs hidden states # with dimensionality hidden_dim. self.lstm = nn.LSTM(embedding_dim, hidden_dim) # The linear layer that maps from hidden state space to tag space self.hidden2tag = nn.Linear(hidden_dim, tagset_size) self.hidden = self.init_hidden() def init_hidden(self): # Before we've done anything, we dont have any hidden state. # Refer to the Pytorch documentation to see exactly # why they have this dimensionality. # The axes semantics are (num_layers, minibatch_size, hidden_dim) return (autograd.Variable(torch.zeros(1, 1, self.hidden_dim)), autograd.Variable(torch.zeros(1, 1, self.hidden_dim))) def forward(self, sentence): embeds = self.word_embeddings(sentence) lstm_out, self.hidden = self.lstm( embeds.view(len(sentence), 1, -1), self.hidden) tag_space = self.hidden2tag(lstm_out.view(len(sentence), -1)) tag_scores = F.log_softmax(tag_space, dim=1) return tag_scores
prepare_sequenceで系列データをテンソル化してautogradでラッピングする。
def prepare_sequence(idxs): tensor = torch.LongTensor(idxs) return autograd.Variable(tensor)
学習前に1文だけ予測してみる。
print labels_idx[0] # ground truth >> [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0] inputs = prepare_sequence(sents_idx[0]) tag_scores = model(inputs) _, pred_tag = torch.max(tag_scores.data, 1) print(pred_tag) >> 5 5 1 1 3 1 1 5 3 3 5
予測が全くランダムにラベリングされていることが分かる。
学習はチュートリアルから少し変えている。
# optimizer = optim.SGD(model.parameters(), lr=0.1) optimizer = optim.RMSprop(model.parameters()) EPOCHS = 20 loss_list = [] for epoch in range(EPOCHS): # again, normally you would NOT do 300 epochs, it is toy data # for sentence, tags in training_data: print "epoch", epoch + 1 , "/" , EPOCHS loss_ = 0 for i, (sentence, tags) in enumerate(zip(sents_idx, labels_idx)): # Step 1. Remember that Pytorch accumulates gradients. # We need to clear them out before each instance model.zero_grad() # Also, we need to clear out the hidden state of the LSTM, # detaching it from its history on the last instance. model.hidden = model.init_hidden() # Step 2. Get our inputs ready for the network, that is, turn them into # Variables of word indices. sentence_in = prepare_sequence(sentence) targets = prepare_sequence(tags) # Step 3. Run our forward pass. tag_scores = model(sentence_in) # Step 4. Compute the loss, gradients, and update the parameters by # calling optimizer.step() loss = loss_function(tag_scores, targets) loss.backward() optimizer.step() loss_ += loss.data[0] print 'loss: %.4f' % (loss_ / (i + 1)) loss_list.append(loss_ / (i + 1))
エポック毎にロスが減少していることが分かる。
予測
学習後に同様の文章のラベルを予測してみる。
inputs = prepare_sequence(sents_idx[0]) tag_scores = model(inputs) _, pred_tag = torch.max(tag_scores.data, 1) print(pred_tag) >> 0 0 1 0 0 0 0 0 0 0 0
正解ラベルと一致していることが分かる。
Pytorchで標準のLSTMで系列ラベリングが出来ることを確認できた。
※全コードは以下
LSTM_CoNLL_PyTorch/LSTM.ipynb at master · kento1109/LSTM_CoNLL_PyTorch · GitHub