TadaoYamaokaの日記

山岡忠夫 Home で公開しているプログラムの開発ネタを中心に書いていきます。

AIで質問の回答を選ぶ

ディープラーニングを使って自然言語の質問に、自然言語の選択肢から回答することを試します。

例えば、
Which of the following is the primary advantage of sexual reproduction when compared to asexual reproduction?
という質問文に、
(A) There is a greater number of offspring.
(B) There is more food available to offspring.
(C) There is greater genetic variety in offspring.
(D) There is a longer development time for offspring.
という選択肢から回答します。(答えは、C)


試すのは以下の論文の方法です。
[1511.04108] LSTM-based Deep Learning Models for Non-factoid Answer Selection

質問と回答をそれぞれLSTMに入力して出力ベクトルのコサイン値のヒンジ損失を最小化するように学習します。

質問と回答は自然言語のため、単語単位にword2vecでベクトル化(Word Embedding)を行い、LSTMに入力します。

LSTMの出力を平均もしくは最大プーリングした出力ベクトルのコサイン値について、正解と不正解のマージンを最大化(ヒンジ損失を最小化)します。


これを一から実装するのは大変そうなので、公開されている実装がないか調べたところ、Kerasでの実装が見つかりました。
github.com

ただし、この実装は質問と回答のLSTMの出力ベクトルをマージして正解か不正解かを2値で出力し、損失関数をcategorical_crossentropyとしています。

どちらの方がよいかは実験してみないとわかりませんが、とりえずこのKerasの実装を試してみました。

準備

適当な作業ディレクトリに移動して、gitでリポジトリをcloneします。

git clone https://github.com/sujitpal/dl-models-for-qa.git

README.mdに書かれているとおり、データセットとモデル格納用のディレクトリを作成します。

cd dl-models-for-qa
mkdir data
cd data
mkdir comp_data
mkdir models

データセットダウンロード

README.mdに書かれている「AI2 8th Grade Science Questions (No Diagrams)」データセットをダウンロードします。
リンク先のURLでは見つからなかったので、以下の場所から入手しました。
dataset-sts/data/hypev/ai2-8grade at master · brmson/dataset-sts · GitHub
に書かれている
http://aristo-public-data.s3.amazonaws.com/AI2-8thGr-NDMC-Feb2016.zip
をダウンロードします。

ダウンロードしたzipに含まれる8thGr-NDMC-Train.csvを使用します。

データの加工

ダウンロードしたファイルはそのままではファイル形式が異なるため使用できません。

No. <tab> 質問文 <tab> 正解 <tab> 回答文A <tab> 回答文B <tab> 回答文C <tab> 回答文D

という形式に加工します。

以下のようなスクリプトでカンマ区切りのCSVをタブ区切りに変換した後、テキストエディタ正規表現置換で回答の選択肢を分割して、Excelで列を並べ替えました。

import argparse
import pandas as pd
import re

parser = argparse.ArgumentParser(description='convert csv')
parser.add_argument('input', type=str, help='input')
parser.add_argument('output', type=str, help='output')
args = parser.parse_args()

df = pd.read_csv(args.input)
df.to_csv(args.output, sep='\t', columns=('AnswerKey', 'question'), header=False, index=False)

文字化けする行があったので削除しました。
保存するときは改行コードをLFにする必要があります。

加工したファイルを、上記で作成したcomp_dataディレクトリに格納します。

Word2Vecの学習済みモデルのダウンロード

単語のベクトル化(Word Embedding)には、word2vecの学習済みモデルを使用します。
README.mdに書かれているGoogleNews Word2Vec modelから、GoogleNews-vectors-negative300.bin.gzをダウンロードして、上記で作成したcomp_dataディレクトリに格納します。

Python3用にスクリプト修正

Python3を使用している場合は、Python2用のコードになっているため修正が必要です。
フォークして修正したレポジトリを公開しました。
GitHub - TadaoYamaoka/dl-models-for-qa: Keras DL models to answer 8th grade science multiple choice questions (Kaggle AllenAI competition).

学習実行

srcディレクトリに移動して学習用スクリプトを実行します。
複数アルゴリズムが実装されていますが、今回は「QA-LSTM with Attention」を試しました。

python qa-lstm-attn.py

以下のように結果が表示されます。

C:\Anaconda3\lib\site-packages\gensim\utils.py:860: UserWarning: detected Windows; aliasing chunkize to chunkize_serial
  warnings.warn("detected Windows; aliasing chunkize to chunkize_serial")
Using TensorFlow backend.
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:135] successfully opened CUDA library cublas64_80.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:135] successfully opened CUDA library cudnn64_5.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:135] successfully opened CUDA library cufft64_80.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:135] successfully opened CUDA library nvcuda.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:135] successfully opened CUDA library curand64_80.dll locally
Loading and formatting data...
(805, 393) (345, 393) (805, 393) (345, 393) (805, 2) (345, 2)
Loading Word2Vec model and generating embedding matrix...
Building model...
E c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\framework\op_kernel.cc:943] OpKernel ('op: "BestSplits" device_type: "CPU"') for unknown op: BestSplits
E c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\framework\op_kernel.cc:943] OpKernel ('op: "CountExtremelyRandomStats" device_type: "CPU"') for unknown op: CountExtremelyRandomStats
E c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\framework\op_kernel.cc:943] OpKernel ('op: "FinishedNodes" device_type: "CPU"') for unknown op: FinishedNodes
E c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\framework\op_kernel.cc:943] OpKernel ('op: "GrowTree" device_type: "CPU"') for unknown op: GrowTree
E c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\framework\op_kernel.cc:943] OpKernel ('op: "ReinterpretStringToFloat" device_type: "CPU"') for unknown op: ReinterpretStringToFloat
E c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\framework\op_kernel.cc:943] OpKernel ('op: "SampleInputs" device_type: "CPU"') for unknown op: SampleInputs
E c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\framework\op_kernel.cc:943] OpKernel ('op: "ScatterAddNdim" device_type: "CPU"') for unknown op: ScatterAddNdim
E c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\framework\op_kernel.cc:943] OpKernel ('op: "TopNInsert" device_type: "CPU"') for unknown op: TopNInsert
E c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\framework\op_kernel.cc:943] OpKernel ('op: "TopNRemove" device_type: "CPU"') for unknown op: TopNRemove
E c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\framework\op_kernel.cc:943] OpKernel ('op: "TreePredictions" device_type: "CPU"') for unknown op: TreePredictions
E c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\framework\op_kernel.cc:943] OpKernel ('op: "UpdateFertileSlots" device_type: "CPU"') for unknown op: UpdateFertileSlots
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:885] Found device 0 with properties:
name: GeForce GTX 1080
major: 6 minor: 1 memoryClockRate (GHz) 1.8225
pciBusID 0000:01:00.0
Total memory: 8.00GiB
Free memory: 6.63GiB
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:906] DMA: 0
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:916] 0:   Y
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:975] Creating TensorFlow device (/gpu:0) -> (device: 0, name: GeForce GTX 1080, pci bus id: 0000:01:00.0)
qa-lstm-attn.py:73: UserWarning: The `Merge` layer is deprecated and will be removed after 08/2017. Use instead layers from `keras.layers.merge`, e.g. `add`, `concatenate`, etc.
  attn.add(Merge([qenc, aenc], mode="dot", dot_axes=[1, 1]))
qa-lstm-attn.py:79: UserWarning: The `Merge` layer is deprecated and will be removed after 08/2017. Use instead layers from `keras.layers.merge`, e.g. `add`, `concatenate`, etc.
  model.add(Merge([qenc, attn], mode="sum"))
Training...
C:\Anaconda3\lib\site-packages\keras\models.py:834: UserWarning: The `nb_epoch` argument in `fit` has been renamed `epochs`.
  warnings.warn('The `nb_epoch` argument in `fit` '
Train on 724 samples, validate on 81 samples
Epoch 1/20
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\pool_allocator.cc:247] PoolAllocator: After 37467 get requests, put_count=9098 evicted_count=1000 eviction_rate=0.109914 and unsatisfied allocation rate=0.786532
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\pool_allocator.cc:259] Raising pool_size_limit_ from 100 to 110
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\pool_allocator.cc:247] PoolAllocator: After 7998 get requests, put_count=16010 evicted_count=8000 eviction_rate=0.499688 and unsatisfied allocation rate=0
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\pool_allocator.cc:247] PoolAllocator: After 17994 get requests, put_count=36006 evicted_count=18000 eviction_rate=0.499917 and unsatisfied allocation rate=0
(略)
Epoch 20/20
704/724 [============================>.] - ETA: 0s - loss: 0.0199 - acc: 0.9986Epoch 00019: val_loss did not improve
724/724 [==============================] - 21s - loss: 0.0195 - acc: 0.9986 - val_loss: 2.9519 - val_acc: 0.6543
Evaluation...
345/345 [==============================] - 1s
Test loss/accuracy final model = 2.7262, 0.6638
345/345 [==============================] - 1s
Test loss/accuracy best model = 0.6961, 0.7275

最終的なTest accuracyが、0.6638になっています。
README.mdより良い結果ですが、データセットの差と思われます。

README.mdの「QA-LSTM with Attention + Custom Embedding」ではさらに精度が上がっているので、word2vecのモデルを最適化すればさらに精度があがるようです。


この方法を応用することで、質問に回答するBotなどが作れると思います。
この実装で使用できるのは英語のみなので、日本語処理はこれとは別に対応が必要になります。