PyTorch 1.0 Tutorials : テキスト : NLP 深層学習 (4) シーケンスモデルと LSTM ネットワーク (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 12/12/2018 (1.0.0.dev20181211)
* 本ページは、PyTorch 1.0 Tutorials : Text : Deep Learning for NLP with Pytorch : SEQUENCE MODELS AND LONG-SHORT TERM MEMORY NETWORKS を翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
テキスト : NLP 深層学習 (4) シーケンスモデルと LSTM ネットワーク
この時点で、様々な feed-forward ネットワークを見てきました。つまり、ネットワークにより維持される状態はまったくありません。これは私達が望む挙動ではないかもしれません。シーケンスモデルは NLP の中核を成します : それらはそこでは貴方の入力間の時間を通してある種の依存があるモデルです。シーケンスモデルの古典的な例は品詞タギングのための隠れマルコフモデルです。もう一つの例は条件付き確率場 (= CRF, conditional random field) です。
リカレント・ニューラルネットワークはある種の状態を維持するネットワークです。例えば、その出力は次の入力の一部として使用できるでしょう、その結果、情報はネットワークがシークエンスを渡すときに伝播します。LSTM の場合には、シークエンスの各要素のために、対応する隠れ状態 $h_t$ があります、これは原理的にはシークエンスの前の方の任意のポイントからの情報を含むことができます。言語モデルで単語を予測したり、品詞タグ付け、そして無数の他のことに隠れ状態を使用できます。
Pytorch の LSTM
サンプルに進む前に、2, 3 のことを書き留めます。PyTorch の LSTM はその入力の総てに 3D tensor であることを想定します。これらの tensor の軸のセマンティクスは重要です。最初の軸はシークエンス自身で、2 番目はミニバッチのインスタンスをインデックスし、そして 3 番目は入力の要素をインデックスします。ミニバッチ処理は議論していませんので、それは単に無視して 2 番目の軸上では 1 次元だけを常に持つことを仮定しましょう。センテンス “The cow jumped” に渡るシーケンスモデルを実行することを望む場合、私達の入力は次のように見えるはずです :
\[
\begin{split}\begin{bmatrix}
\overbrace{q_\text{The}}^\text{row vector} \\
q_\text{cow} \\
q_\text{jumped}
\end{bmatrix}\end{split}
\]
サイズ 1 の追加の 2nd 次元があることを覚えておくことを除いてです。
加えて、シークエンスを一つずつ調べることができて、その場合には 1st 軸もまたサイズ 1 を持ちます。
素早いサンプルを見てみましょう。
# Author: Robert Guthrie import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim torch.manual_seed(1)
lstm = nn.LSTM(3, 3) # Input dim is 3, output dim is 3 inputs = [torch.randn(1, 3) for _ in range(5)] # make a sequence of length 5 # initialize the hidden state. hidden = (torch.randn(1, 1, 3), torch.randn(1, 1, 3)) for i in inputs: # Step through the sequence one element at a time. # after each step, hidden contains the hidden state. out, hidden = lstm(i.view(1, 1, -1), hidden) # alternatively, we can do the entire sequence all at once. # the first value returned by LSTM is all of the hidden states throughout # the sequence. the second is just the most recent hidden state # (compare the last slice of "out" with "hidden" below, they are the same) # The reason for this is that: # "out" will give you access to all hidden states in the sequence # "hidden" will allow you to continue the sequence and backpropagate, # by passing it as an argument to the lstm at a later time # Add the extra 2nd dimension inputs = torch.cat(inputs).view(len(inputs), 1, -1) hidden = (torch.randn(1, 1, 3), torch.randn(1, 1, 3)) # clean out hidden state out, hidden = lstm(inputs, hidden) print(out) print(hidden)
tensor([[[-0.0187, 0.1713, -0.2944]], [[-0.3521, 0.1026, -0.2971]], [[-0.3191, 0.0781, -0.1957]], [[-0.1634, 0.0941, -0.1637]], [[-0.3368, 0.0959, -0.0538]]], grad_fn=) (tensor([[[-0.3368, 0.0959, -0.0538]]], grad_fn= ), tensor([[[-0.9825, 0.4715, -0.0633]]], grad_fn= ))
サンプル: 品詞タギングのための LSTM
このセクションでは、品詞タグを得るために LSTM を使用します。ビタビ (= Viterbi) (アルゴリズム) や Forward-Backward あるいはそのようなものは使用しませんが、読者への (挑戦的な) 課題として何が起きているのかを見た後でビタビがどのように使用されるかを考えてください。
モデルは次のようなものです : 入力センテンスを $w_1, \dots, w_M$ とします、ここで $w_i \in V$、私達の語彙です。また、$T$ をタグセット、そして $y_i$ を単語 $w_i$ のタグとします。単語 $w_i$ のタグの予測を $\hat{y}_i$ で表します。
これは構造予測、モデルで、ここで出力はシーケンス $\hat{y}_1, \dots, \hat{y}_M$、ここで $\hat{y}_i \in T$ です。
予測を行なうために、センテンスに渡り LSTM を通過させます。時間ステップ $i$ における隠れ状態を $h_i$ として表します。また、各タグに一意なインデックスを割り当てます (単語埋め込みセクションで word_to_ix をどのように持ったかのように)。すると $\hat{y}_i$ のための予測ルールは
\[
\hat{y}_i = \text{argmax}_j \ (\log \text{Softmax}(Ah_i + b))_j
\]
つまり、隠れ状態のアフィン写像の log softmax を取り、そして予測タグはこのベクトルで最大値を持つタグです。これは $A$ のターゲット空間の次元は $|T|$ であることを直ちに包含していることに注意してください。
データを準備します :
def prepare_sequence(seq, to_ix): idxs = [to_ix[w] for w in seq] return torch.tensor(idxs, dtype=torch.long) training_data = [ ("The dog ate the apple".split(), ["DET", "NN", "V", "DET", "NN"]), ("Everybody read that book".split(), ["NN", "V", "DET", "NN"]) ] word_to_ix = {} for sent, tags in training_data: for word in sent: if word not in word_to_ix: word_to_ix[word] = len(word_to_ix) print(word_to_ix) tag_to_ix = {"DET": 0, "NN": 1, "V": 2} # These will usually be more like 32 or 64 dimensional. # We will keep them small, so we can see how the weights change as we train. EMBEDDING_DIM = 6 HIDDEN_DIM = 6
{'The': 0, 'dog': 1, 'ate': 2, 'the': 3, 'apple': 4, 'Everybody': 5, 'read': 6, 'that': 7, 'book': 8}
モデルを作成します :
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 (torch.zeros(1, 1, self.hidden_dim), 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
モデルを訓練します :
model = LSTMTagger(EMBEDDING_DIM, HIDDEN_DIM, len(word_to_ix), len(tag_to_ix)) loss_function = nn.NLLLoss() optimizer = optim.SGD(model.parameters(), lr=0.1) # See what the scores are before training # Note that element i,j of the output is the score for tag j for word i. # Here we don't need to train, so the code is wrapped in torch.no_grad() with torch.no_grad(): inputs = prepare_sequence(training_data[0][0], word_to_ix) tag_scores = model(inputs) print(tag_scores) for epoch in range(300): # again, normally you would NOT do 300 epochs, it is toy data for sentence, tags in training_data: # 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 # Tensors of word indices. sentence_in = prepare_sequence(sentence, word_to_ix) targets = prepare_sequence(tags, tag_to_ix) # 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() # See what the scores are after training with torch.no_grad(): inputs = prepare_sequence(training_data[0][0], word_to_ix) tag_scores = model(inputs) # The sentence is "the dog ate the apple". i,j corresponds to score for tag j # for word i. The predicted tag is the maximum scoring tag. # Here, we can see the predicted sequence below is 0 1 2 0 1 # since 0 is index of the maximum value of row 1, # 1 is the index of maximum value of row 2, etc. # Which is DET NOUN VERB DET NOUN, the correct sequence! print(tag_scores)
tensor([[-1.1389, -1.2024, -0.9693], [-1.1065, -1.2200, -0.9834], [-1.1286, -1.2093, -0.9726], [-1.1190, -1.1960, -0.9916], [-1.0137, -1.2642, -1.0366]]) tensor([[-0.0858, -2.9355, -3.5374], [-5.2313, -0.0234, -4.0314], [-3.9098, -4.1279, -0.0368], [-0.0187, -4.7809, -4.5960], [-5.8170, -0.0183, -4.1879]])
課題: LSTM 品詞 tagger を文字レベル特徴で増強する
上のサンプルでは、各単語は埋め込みを持ち、それは私達のシーケンスモデルへの入力として提供されました。単語の文字に由来する表現で単語埋め込みを増強しましょう。これは本質的に役立つことを期待します、何故ならば接辞 (= affixes) のような文字レベルの情報は品詞の大きなベアリング (軸受け) を持つからです。例えば、接辞 -ly を持つ単語は英語では殆どいつも副詞としてタグ付けされます。
これを行なうために、$c_w$ を単語 $w$ の文字レベル表現としましょう。$x_w$ は前のように単語埋め込みとします。すると私達のシーケンスモデルへの入力は $x_w$ と $c_w$ の結合です。従って $x_w$ が次元 5 を持ち、そして $c_w$ が次元 3 を持つ場合、LSTM は次元 8 の入力を受け取るべきです。
文字レベル表現を得るために、単語の文字に渡り LSTM を遂行します、そして $c_w$ をこの LSTM の最後の隠れ状態とします。ヒントです :
- 貴方のモデルには2つの LSTM があることになります。元の一つは POS タグ・スコアを出力して、そして新しい一つは各単語の文字レベル表現を出力します。
- 文字に渡るシーケンスモデルを遂行するためには、文字を埋め込まなければならないでしょう。文字埋め込みは文字 LSTM への入力となるでしょう。
以上