自然言語処理の深遠

Deep Dive Into Natural Language Processing

文字レベルの畳込みニューラルネットワークによる文書分類

はじめに

自然言語処理で文書分類は最も基本的なタスクの一つです。 文書分類は、SNSに対する評判分析、ニュースのジャンル分類、メールのスパムフィルタや文書の著者推定といった問題の解決に使われています。 このように基本的なタスクである文書分類は広く使われています。

最近では、文書分類に対するニューラルベースのアプローチとして、単語レベルのRNNが広く使われています。 RNNを使うことで、広い文脈と単語の出現順序を自然な形で考慮することが可能になります。 この性質により、RNNベースの手法は文書分類で良い性能を示してきました。

しかし、単語レベルのRNNには2つの問題が挙げられます。 一つ目は、RNNは計算を並列化し難いため、計算の高速化が難しいということです。 二つ目は、単語レベルの入力がSNS等のユーザ生成コンテンツ(UGC: User-Generated Content)に対して弱いということです。

これらの問題を解決するために、文字レベルのCNNを使って文書分類する手法が提案されてきました。 CNNを使うことで、GPUで計算を高速化できるという利点があります。 また、文字レベルの特徴を使うことで、UGCに対してロバストになることを期待できます。

今回はそんな文字ベースのCNNで文書分類を行う論文をKerasで実装してみました。 コードは以下に置いてあります:

手法

入力の特徴

モデルの入力として、各文字をone-hotエンコードしたベクトルを与えます。 文字列の長さは固定していて、論文では1014文字としています。 なぜ1014文字なのかというと、これくらいの長さを取れば重要な情報は入っているだろうということです。 (それにしても1014の意味はわからんけど)

one-hotエンコードするために用いる語彙としては以下の70文字を使用しています。 具体的に言うと、a〜zまでのアルファベット26文字、0〜9まで数字10文字、その他の文字が34文字です。 この語彙に含まれていない文字はすべて0ベクトルに変換します。

abcdefghijklmnopqrstuvwxyz0123456789
-,;.!?:’’’/\|_@#$%ˆ&*˜‘+-=<>()[]{}

モデルのアーキテクチャ

モデルとしては、全9層のネットワークになっています。 具体的には、最初に6層の畳み込み層、その後に3層の全結合層が接続されます。 アーキテクチャを図にすると以下のようになっています:

f:id:Hironsan:20170919092728p:plain

入力は語彙数と等しくなります。今回の場合70です。ここはどういう文字を定義するかに依存します。 また、入力の長さは1014です。 図には書かれていませんが、正則化のために3層の全結合層の間に2層のドロップアウトが入っています。

畳み込み層とプーリングのハイパーパラメータについては以下の表の通りです:

Layer Large Feature Small Feature Kernel Pool
1 1024 256 7 3
2 1024 256 7 3
3 1024 256 3 N/A
4 1024 256 3 N/A
5 1024 256 3 N/A
6 1024 256 3 3

最後の全結合層のユニット数はタスク依存になります。たとえば、10クラス分類ならユニット数は10にします。 全結合層のハイパーパラメータは以下のとおりです:

Layer Output Units Large Output Units Small
7 2048 1024
8 2048 1024
9 タスク依存 タスク依存

実装

上記のネットワークを Keras を使って実装してみました。 一番メインとなるモデルの構築部分のコードは以下のように書けます:

from keras.layers import Input, Conv1D, Dense, MaxPool1D, Flatten, Dropout
from keras.models import Model


def build_model(kernel_sizes, dense_units, vocab_size,
                nb_filter, nb_class, keep_prob, maxlen=1014):
    inputs = Input(batch_shape=(None, maxlen, vocab_size))

    conv1 = Conv1D(nb_filter, kernel_sizes[0], activation='relu')(inputs)
    pool1 = MaxPool1D(pool_size=3)(conv1)

    conv2 = Conv1D(nb_filter, kernel_sizes[1], activation='relu')(pool1)
    pool2 = MaxPool1D(pool_size=3)(conv2)

    conv3 = Conv1D(nb_filter, kernel_sizes[2], activation='relu')(pool2)
    conv4 = Conv1D(nb_filter, kernel_sizes[3], activation='relu')(conv3)
    conv5 = Conv1D(nb_filter, kernel_sizes[4], activation='relu')(conv4)
    conv6 = Conv1D(nb_filter, kernel_sizes[5], activation='relu')(conv5)
    pool3 = MaxPool1D(pool_size=3)(conv6)
    pool3 = Flatten()(pool3)

    fc1 = Dense(dense_units[0], activation='relu')(pool3)
    fc1 = Dropout(keep_prob)(fc1)
    fc2 = Dense(dense_units[1], activation='relu')(fc1)
    fc2 = Dropout(keep_prob)(fc2)
    pred = Dense(nb_class, activation='softmax')(fc2)

    model = Model(inputs=[inputs], outputs=[pred])

    return model

単純に実装しているだけなので、特に言うことはないのですが、Keras に用意されている Conv1DMaxPool1D を使って実装することで、簡単に書くことができます。

学習用のコードを含めた完全な実装は以下にあります:

実行結果

論文と同じ Yelp のデータセットを使って実験してみました。 実験環境としては、AWS EC2のg2.2xlargeインスタンスを使用しています。 時間的には、1エポックに約2500秒(≒40分)かかっています。

Epoch 1/15
7000/7000 [==============================] - 2513s - loss: 0.2202 - acc: 0.9061 - val_loss: 0.1565 - val_acc: 0.9396
Epoch 2/15
7000/7000 [==============================] - 2490s - loss: 0.1527 - acc: 0.9413 - val_loss: 0.1532 - val_acc: 0.9411
Epoch 3/15
 304/7000 [>.............................] - ETA: 2260s - loss: 0.1334 - acc: 0.9514

ちなみに、手元のCPUで計算しようとしたら、1エポックに10万秒(約27時間)くらいかかりそうだったのでやめました。 GPUで計算することをおすすめします。

おわりに

今回は、 Character-level Convolutional Networks for Text Classification を基に、文字レベルの畳込みニューラルネットワークによる文書分類をやってみました。Kerasを使って実装することで、かなりシンプルな形でモデルを構築することができました。本記事が皆様のお役に立てば幸いです。

私のTwitterアカウントでも機械学習自然言語処理に関する情報をつぶやいています。

@Hironsan

この分野にご興味のある方のフォローをお待ちしています。

参考資料