Recognizing Textual EntailmentをLSTMで解く

NLPディープラーニング周りの可視化手法が知りたくて色々読んでいる中でReasoning about Entailment with Neural AttentionでRecognizing Textual Entailment(以下RTE)という問題を解いていたので、データセットも公開されていることもあり試しにチャレンジしてみることにしました。

タスク

RTEはテキスト間の含意を推論するタスクです。与えられた2つの文章間の関係性を、幾つかの含意を表すラベルで表現します。データセットによってラベルの表現方法は異なりますが、今回はThe Stanford NLP Groupが公開しているSNLIのデータセットを考えます。

The Stanford Natural Language Inference (SNLI) Corpus

SNLIでは、2つのテキストのペアに対してentailment, contradiction, neutralという3つのラベルが付けられています。

例えば、

Text: A soccer game with multiple males playing.

Hpyothesis: Some men are playing a sport.

という2つの文章があった場合に、どちらも複数の男性がサッカーをしているという意味なので、これはentailmentです。

一方で、

Text: A black race car starts up in front of a crowd of people.

Hypothesis: A man is driving down a lonely road.

の場合、車を運転している状況は同じかもしれませんがcrowdとlonelyが矛盾しているのでcontradictionです。

他にどちらとも言えないというneutralというラベルの合計3つのラベルがあります。

Text:A smiling costumed woman is holding an umbrella.

Hypothesis:A happy woman in a fairy costume holds an umbrella.

いわばKaggleで開催されたQuoraコンペティションの多値バージョンという感じですね。Quoraでは質問文が意味的に同じかどうかを二値分類するタスクでしたが、RTEはその意味的な種類を判定する少し難しい問題になっています。

ただし、RTEも場合によってはcontradictionとneutralをまとめてしまい、entrailemnt/Nonentrailmentの二値分類として解く場合もあるようです。また、京大黒橋研が公開しているTextual Entailment 評価データでは◎、〇、△、×の4クラスに分類しており、こちらも二値分類に変換したデータセットが同様に公開されています。

データセット

データセットはSNLIのページからダウンロードすることができます。

The Stanford Natural Language Processing Group

LSTM構築

今回はQuoraコンペティションabhishekが公開しているDNNの構造を参考にします。下記のコードのように、それぞれの文章の入力をEmbedding layerとLSTMに入れて、それを結合したのちに幾つか層を重ねる形式です。

model1 = Sequential()
model1.add(Embedding(num_words + 1,
                     embedding_dim,
                     weights=[embedding_matrix],
                     trainable=False))
model1.add(LSTM(embedding_dim, recurrent_dropout=0.5, dropout=0.5))

model2 = Sequential()
model2.add(Embedding(num_words + 1,
                     embedding_dim,
                     weights=[embedding_matrix],
                     trainable=False))
model2.add(LSTM(embedding_dim, recurrent_dropout=0.5, dropout=0.5))

model = Sequential()
model.add(Merge([model1, model2], mode="concat"))
model.add(BatchNormalization())

model.add(Dense(300))
model.add(PReLU())
model.add(Dropout(0.5))
model.add(BatchNormalization())

model.add(Dense(300))
model.add(PReLU())
model.add(Dropout(0.5))
model.add(BatchNormalization())

model.add(Dense(300))
model.add(PReLU())
model.add(Dropout(0.5))
model.add(BatchNormalization())

model.add(Dense(3, activation="sigmoid"))
model.compile(loss="categorical_crossentropy",
              optimizer="adam",
              metrics=["accuracy"]
              )

今回使用したソースコード全体はGithubにて公開しています。

https://github.com/yagays/rte_snli

結果

今回train約50万文を学習に使用し、test約1万文に対して評価した結果、Test accuracyが80.5%となりました。また、混合行列は下記のようになりました。

f:id:yag_ays:20170812232350p:plain

Classification Reportはこんな感じ。特段苦手なラベルがあるわけではないものの、neutralはちょっと低いですね。contradictionやentailmentと比較して、neutralはラベル付けの基準が人によって異なっていたり、判断が難しいことが影響しているのかもしれません。

# {"contradiction":0, "entailment":1, "neutral":2} 
              precision    recall  f1-score   support

          0       0.86      0.78      0.82      3237
          1       0.78      0.86      0.82      3368
          2       0.77      0.77      0.77      3219

avg / total       0.80      0.80      0.80      9824

現在SNLIプロジェクトページにて公開されているSOTAが88.8%なので、まだまだですね。まあSNLI論文のベンチマークの77.6%よりは上回ったので、今回は良しとしましょう。

参考