TextCNNによる日本語評判文書分類

本記事は[scouty Advent Calendar](https://qiita.com/advent-calendar/2018/scouty)の23日目の記事になります。

前回は[コーパス内の単語共起関係を用いた文書分類](https://qiita.com/donaldchi/items/839f328730b6843fe0d0)というタイトルで Graph Neural Networkを使った文書分類手法を紹介しました。

今回はやや古いですが、ものすごい引用数を持つ [Convolutional Neural Networks for Sentence Classification](http://emnlp2014.org/papers/pdf/EMNLP2014181.pdf)で提案した**TextCNN法**について紹介します。

*源論文では本手法に特に名前をつけていないですが、なぜかネット上でTextCNNと読んでいるようなので、本記事でもこの名前で紹介します。*

# TextCNN法について
TextCNN法はCNNを使ってテキスト分類に取り込んだ手法です。論文タイトルからもわかるようにこの手法は**文分類(sentence classification)**を目的としています。

従って、長い文書の分類よりは、一個の文まだは複数の文からなる短い文書の分類に適していて、感情分析、評判分析や質問タイプの分類などの短い文書を対象とするタスクに適しています。

本手法に関して、以下のような英語記事や日本語記事があります。まだ、実は実装したコードもGitHubで検索すると山ほどあります。
[Implementing a CNN for Text Classification in TensorFlow](http://www.wildml.com/2015/12/implementing-a-cnn-for-text-classification-in-tensorflow/)
[自然言語処理における畳み込みニューラルネットワークを用いたモデル](https://qiita.com/Hironsan/items/63d255fd038acbcdf95b)
[【Chainer】畳み込みニューラルネットワークによる文書分類](https://qiita.com/ichiroex/items/f225f6d8eceb6796cc7e)

今回は

- 実装は多いものの、TensorFlowベースで完全実装したものがない
- 実験はやっているものの、日本語テキスト分類タスクでためしたものはない

といった理由から練習も重ねて以下のようなことをやってみました。

- [cnn-text-classification-tf](https://github.com/dennybritz/cnn-text-classification-tf)のソースコードをベースにTensorFlowでの実装を充実した
- [Twitter日本語評判分析データセット](http://bigdata.naist.jp/~ysuzuki/data/twitter/)を用いて実験をやってみた

# TextCNN法の詳細

TextCNN法のモデル構造
<img width="1148" alt="スクリーンショット 2018-12-22 21.40.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/77744/96675753-b3c4-4dad-4078-dd8486a13ab0.png">

TextCNNは図のようにembedding layer, convolutional layer, max-over-time pooling layer, output layer 四層からなります。

## 前処理
各層にデータを投入する前に、TextCNN法は前処理として学習対象となる文書を単語単位に分割した後、全文書の内最長の文書を基準に **padding処理**を行います。 

最長文書が```n```個の単語からなる文書だった場合、padding処理は学習対象の全文書が```n```次元の単語ベクトルだと見なし、要素のない部分には意味のない単語で(例えば、```<UNK>```)埋め込むことにします。

例えば、```n = 5```の場合、
```['私'、'学生']```
といった文書はpadding処理後、
```['私'、'学生', <UNK>, <UNK>, <UNK>]```
になります。

padding処理により、投入するデータのサイズを揃えることで、batch処理が可能になります。

## embedding layer
embedding layerでは、前処理で単語単位で区切った単語ベクトルを投入し、各単語を該当する**分散表現(単語埋め込み)**に置き換える作業を行います。

分散表現は初期値として他のタスクで事前に学習済みのword2vec modelの結果を引用することもできれば、ランダムな値を設定することもできます。

TextCNNはembedding layerで二つchannelを用意し、一つ目のchannelの分散表現はモデルの学習に連れてtuningされるもの(**non-static**)とし、二つ目のchannelの分散表現は変わらないもの(**static**)とします。

上図では、
　　["wait", "for", "the", "video", "and", "do", "n't", "rent", "it"]
という文書に対して、 $n \times k$次元の二つのchannelを用意しています。(ただし、$k$は単語分散表現の次元を表します。)

## convolutional layer
convolutional layerではいつくかの**フィルター(filter)**を用意し、フィルターサイズ分の単語に対して**畳み込み処理**を行います。

単語$w_i$の分散表現を$\vec{v_i}$、 フィルターのサイズを$h$と表記すると、畳み込み処理は以下のように定義されます。

$$
c_i = f(w \cdot (\vec {v_i} \oplus \vec {v_{i+1}} \oplus ... \oplus \vec {v_{i+h-1}} ) + b)
$$

ただし、$f$は活性化関数、$\oplus$はベクトルの連結演算子(concatenation operator)を指します。$c_i$は$i$番目の単語から$h$個の単語を畳み込んで得た新しい特徴量となります。

従って、長さ$n$の文書にサイズ$h$のフィルターを掛けた場合、$(n-h+1)$次元新しい特徴量ベクトル$c_h$が生成されます。
$$
c_h = [c_1, c_2, ..., c_{n-h+1}]
$$

TextCNNは同時に複数のフィルターを文書にかけることにより、$(フィルターの数 \times (n-h+1))$次元の特徴量を出力します。

上図では、四つのフィルターを文書に書け、$4 \times (n-h+1)$次元の特徴量を出力しています。

## max-over-time pooling layer
max-over-time pooling layerでは、各フィルターで生成された特徴量に対してpooling処理を行い、$(フィルターの数 \times 1)$次元の特徴量$c_h \prime$を出力します。
$$
c_h \prime = max \\{ c_h \\}
$$

## output layer
output layerでは、max-over-time pooling layerの出力にsoftmax関数をかけた結果を出力します。

# 実装
TextCNN法は過学習を抑制するために、L2ノルムとDropoutを採用します。

今回は、[cnn-text-classification-tf](https://github.com/dennybritz/cnn-text-classification-tf) by dennybritzを参考に実装を充実しました。

追加した部分
- 日本語前処理の部分
- multi channel対応部分
- cross-validation部分
- 学習済み分散表現導入部分

学習済み分散表現導入部分では、[日本語 Wikipedia エンティティベクトル](http://www.cl.ecei.tohoku.ac.jp/~m-suzuki/jawiki_vector/)から日本語分散表現を取得しています。

ソースコードは[こちら](https://github.com/donaldchi/TextCNN)で共有しました。

# Twitterデータを使った実験
[Twitter日本語評判分析データセット](http://bigdata.naist.jp/~ysuzuki/data/twitter/)を用いて、ある特定ジャンルのスマホに関するツイート文の分類をやって見ました。

上記データセットの内、ジャンル10000のツイートデータ51,088件を取得し、 **(ポジティブ&ネガティブ、ポジティブ、ネガティブ、ニュートラル、無関係)**と5クラスからなる多値分類モデルを構築しました。

**データセットの内訳**

|クラス|データ数|
|---|---|
|ポジティブ&ネガティブ|262|
|ポジティブ|3,028|
|ネガティブ|5,174|
|ニュートラル|31,492|
|無関係|11,132|

それぞれのパラメータ組み合わせに対して、デフォルトの設定で10 ephochsぐらいずつ実行し、以下のような結果になりました。

|パラメータ <br> 組み合わせ|Accuracy(Train)|Accuracy(Test)|
|---|---:|---:|
|single channel, <br> random(non-static) init|0.94|0.71|
|single channel, <br> pretrained(non-static) init|0.74|0.72|
|multi channel, <br> pretrained(static) & <br> random(non-static) init|0.82|0.74|
|multi channel, <br> pretrained(static) & <br> pretrained(non-static) init|0.73|0.71|

四つの場合に置いて、(multi channel、random initialization)でモデルを構築した場合、一番良い精度を出しています。

random initializationでモデル構築を行った場合、やや過学習が起きています。

理由について検証はしていないですが、ツイートの場合非定型的な表現が多く、学習済み分散表現で初期値設置をする場合は、非定型的な表現は```<UNK>```扱いをしている一方、ランダムの場合は意味のある単語扱いになるので、学習を通して非定型的な表現が学習されたのではないかと思われます。

single channelの場合、pretrained initializationは精度向上に繋がっていますが、multi channelの場合は逆に精度が下がっています。理由としては、（multi channel、random initialization）の方が、より非定型的な表現と定型的な表現に対するバランスが取れたモデルが学習されたからではないかと思います。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/77744/198fd184-4126-e50f-ded9-b8dec6a597e7.png)
最後に、10 epoches実行でのテストデータ精度変化を見ると、epochesを増やすことでまだ精度が上がる余地があると思われます。

# まとめ
練習を重ねてTextCNN法を使って日本語ツイート分類モデルを作って見ました。データセットにもよると思いますが、今回の実験に限っては以下のようなことがわかりました。

- 事前学習済み分散表現の初期化が確かに精度向上に繋がる
- 非定型的な単語が多い文書の場合、事前学習済み分散表現初期化は精度向上だけでなく、過学習を抑える役割も果たす

# 最後に
本記事で何か見違ったことが書かれていたり、気になることがあったりする場合、コメントで指摘してくださると嬉しいです。


# 参考文献
[Implementing a CNN for Text Classification in TensorFlow](http://www.wildml.com/2015/12/implementing-a-cnn-for-text-classification-in-tensorflow/)
[自然言語処理における畳み込みニューラルネットワークを用いたモデル](https://qiita.com/Hironsan/items/63d255fd038acbcdf95b)
[【Chainer】畳み込みニューラルネットワークによる文書分類](https://qiita.com/ichiroex/items/f225f6d8eceb6796cc7e)