hiramekunのブログ

プログラミングと読書と

畳み込みニューラルネットワークを用いた文章分類をkerasで実装する

概要

機械学習を用いた文章分類(Text Classification)タスクは、主にRNN(Recurrent Neural Networks)で実装されることが多いです。それは、文章というものの特徴に由来しています。従来のCNNのように入力が独立ではなく、文脈や前後関係があって初めて文章の意味が成立するからです。

しかし近年では文章分類タスクに適していないと考えられていたCNNを用いた文章分類の論文がいくつか出ています。
それに興味を持って実装、検証した結果を書いていきたいと思います。
自分の実装はこちらにアップしています。 github.com

分類対象

分類の対象にしたデータセットは、pubmed-rct PubMed 200k RCT datasetです。
github.com

a Dataset for Sequential Sentence Classification inMedical Abstracts

とあるように、医学分野論文のabstractの文脈中で、20万個ある各文がどのような役割を担っているのかを分類するためのデータセットです。データセットの内容に関する論文はこちらにあります。
[1710.06071] PubMed 200k RCT: a Dataset for Sequential Sentence Classification in Medical Abstracts

headコマンドで一部をのぞいてみると、こんな感じに各ラベルと文がテキストファイルになって入っています。

>> head data/train.txt
OBJECTIVE   To investigate the efficacy of 6 weeks of daily ...
METHODS A total of 125 patients with primary knee OA were ...
METHODS Outcome measures included pain reduction ...
METHODS Pain was assessed using the visual analog pain ...
METHODS Secondary outcome measures included the Western Ontario ...
METHODS Serum levels of interleukin 1 ( IL-1 ) , IL-6 , tumor necrosis factor ...
RESULTS There was a clinically relevant reduction in the intervention ...
RESULTS The mean difference between treatment arms ( 95 % CI ) ...
RESULTS Further , there was a clinically relevant reduction in the serum ...

今回はこのデータセットのうち、学習時間を考慮して文が2万件ほどの縮小版のデータセット、Pub Med 20k RCT datsetを用いました。

実装

kerasのgithubにあった、imdbデータセットをCNNを用いて分類するコードを参考にしました。
keras/imdb_cnn.py at master · keras-team/keras · GitHub

train/test split

元のデータセットでは、trainが180,000件。testが30,000件と、かなり偏った比率になっていたので、一回trainとtestを混ぜ合わせてから2:1に分割し直すことをしました。x_all.reshape((len(x_all)), 1))などとしているのは、tokenizerでベクトルに変換した場合はshapeが(300, )のようになっているのに対して、train_test_split()では(300, 1)としないとダメなためです。

# data_loader.py

from sklearn.model_selection import train_test_split
# 中略
x_train, x_test, y_train, y_test = train_test_split(x_all.reshape((len(x_all), 1)),
                                                       y_all.reshape(
                                                           (len(y_all[:]), len(y_all[0][:]), 1)),
                                                       test_size=0.33,
                                                       random_state=0)

ベクトル変換

文字列を機械学習で扱うためには、まずは文字をベクトル表現しなければなりません。kerasのTokenizerを用いて、fit_on_tests→texts_to_sequencesでベクトル表現に変換します。

# data_loader.py

from keras.preprocessing.text import Tokenizer
tokenizer_train = Tokenizer(filters="")
tokenizer_test = Tokenizer(filters="")

tokenizer_train.fit_on_texts(x_train)
tokenizer_test.fit_on_texts(x_test)

x_train = tokenizer_train.texts_to_sequences(x_train)
x_test = tokenizer_test.texts_to_sequences(x_test)

ラベル

categorycal_crossentropyを損失関数に使いたいので、今回はラベルをone-hot表現にする必要があります。 例えば、(りんご, バナナ, もも)があった時に、[2]と表現して配列の3番目の"もも"を表しているとします。これはone-hot表現にすると、[0, 0, 1]となります。つまりラベルの順番での表現から、「表したいラベルを1、それ以外を0」として表現するということです。

kerasの to_categorical で変換してくれます。

# data_loader.py

from keras.utils.np_utils import to_categorical
categorical_labels = to_categorical(whole_labels, num_classes=5)

train.py

そして、変換したベクトルを埋め込み表現にします。kerasのEmbeddingでそれを実装できます。

  • input_dim: 単語の数
  • output_dim: 埋め込みの次元
  • input_length: 入力配列の長さ
# imdb_sample.py

embedding_dims = 128
vocab_size_train = len(tokenizer_train.word_index) + 1
X_train = sequence.pad_sequences(X_train, padding='post')  # 一番長い文に合わせてベクトルの長さを合わせます
seqX_len = len(X_train[0])

model = Sequential()
model.add(Embedding(input_dim=vocab_size_train, output_dim=embedding_dims, input_length=seqX_len))

学習

学習率が0.001がデフォルトでしたが、学習がうまく進みませんでした。そこで徐々に小さくしていくと、0.00001でようやく学習がうまく進むようになりました。

optimizer = Adam(lr=0.000001)

こちらの記事などを参考にして、Google ColaboratoryでGPUを使いました。
qiita.com

20epoch回した様子です。 f:id:thescript1210:20180326113813p:plain
f:id:thescript1210:20180326113817p:plain

結果

最終的には、validation_loss 1.062, validation_accuracy 57.4%となりました。abstractの文脈を無視して一つ一つの文単体のみで分類を行ったからでしょうか、あまり精度は高く出ていないのが残念です。
より良い分類の実装ができたらまた書きたいと思います、ありがとうございました。