BERTを使った文書分類

#はじめに
仕事で、慣れないNLP（自然言語処理）の必要が生じたため、これを機に調べたことをまとめます。
Word2Vecが出てきて以来、単語のベクトル化が簡単になり、文（センテンス）のベクトル化も、いろいろと手法が出てきているが、文章・文書（文のかたまり）のベクトル化は、まだまだ難しいだろうと勝手に思い込んでいたのですが、私が勉強不足だっただけで、様々な手法が開発されており、自分で試したところ、想定していたよりいい結果が出たので、まとめることにしました。
手法を開発した先人たちに、深く感謝します。

自分で手を動かす前に、ネットで調べた際に、参考になる優れた記事にたくさん出会いましたが、下記の記事はその中でも、特に感銘を受けました。
https://qiita.com/nekoumei/items/1f5ec09e422a4be99810

やっていることは、各手法のシンプルな比較なのですが、BOWやTF-IDFのような、古典的とも言える手法も含めて、たくさんの手法を比較しておられます。
特に感銘を受けたのは、BOWとTF-IDFが、より新しい手法を凌駕して、圧倒的に優れた結果を出したことです。
このサイトの作者様も書かれていますが、文書分類という検証の性質上、特定の単語を含むかどうかが、大きな意味を持つだろうということを考えれば、当たり前のような気もしますが、実際のデータを使って、具体的な数値で手法間の差を表したことは、（私にとっては）非常に大きい意味を持ちます。
とにかく、BOWとTF-IDFの結果に驚きました。

使われているデータセットは、トピック間の類似度が低いので、もっと類似度の高い（似通った）トピックでの分類の場合は、（他の手法との比較という意味において）また、違った結果になるだろう、ということは想像できますが、文書分類の課題をやる時には、大いに試す価値があると思いました。

#検証 1
せっかくなので、上記の記事に、少しだけ追加で検証を行いました。
内容は、下記の通りです。

##検証 1-1
分ち書きに使われているMeCabをJuman++に変更して、変化があるかを確認しました。
モデルは、元の記事で、結果が良かったものに絞って実施しました。

###結果
####BoW

||MeCab|Juman++|
|---|---|---|
|fold 1|0.95658|0.96268|
|fold 2|0.95725|0.96200|
|fold 3|0.95858|0.95587|
|fold 4|0.95247|0.95790|
|fold 5|0.96062|0.96266|

####TF-IDF

||MeCab|Juman++|
|---|---|---|
|fold 1|0.96065|0.95929|
|fold 2|0.94572|0.94436|
|fold 3|0.94908|0.95247|
|fold 4|0.95247|0.95247|
|fold 5|0.94908|0.95587|


####SWEM-MAX

||MeCab|Juman++|
|---|---|---|
|fold 1|0.92062|0.90976|
|fold 2|0.92198|0.90637|
|fold 3|0.90427|0.91174|
|fold 4|0.90291|0.90563|
|fold 5|0.92668|0.91785|

###考察
MeCabからJuman++に変更して、結果が向上したものと劣化したものがあります。
一概に、どちらがいいとは言えなさそうです。
また、どちらにしても、大きな差はなさそうですが、処理時間を考えると、（個人的には）このケースでは、MeCabの方が、総合的には優れていると言えそうです。

##検証 1-2
SWEMに、MIN+MAXとMEAN+MAXを追加
（SWEMについては、こちらの記事 https://yag-ays.github.io/project/swem/ が分かりやすいです。）

SWEMにて、MINとMAX（または、MEANとMAX）をconcatしたものを使用します。
MIN、MEAN、MAXの単体では、300次元のベクトルになりますが、300次元のMIN（または、MEAN）と300次元のMAXを結合して、600次元のベクトルとして、分類（LGBM）にかけています。
（fasttextのpretrained modelより、通常のw2vの方が結果が良かったので、そちらを使用しています。）

###SWEM-MIN+MAX
####結果

||MeCab|Juman++|
|---|---|---|
|fold 1|0.93012|0.92537|
|fold 2|0.92537|0.91926|
|fold 3|0.92803|0.92735|
|fold 4|0.92871|0.92057|
|fold 5|0.92871|0.92260|

####考察
BoWやTF-IDFには及びませんが、SWEM MAXよりは、数値が向上しました。
MeCabとJuman++の比較については、MeCabの方が優れた結果となりました。

###SWEM-MEAN+MAX
####結果

||MeCab|Juman++|
|---|---|---|
|fold 1|0.93283|0.92469|
|fold 2|0.93147|0.92469|
|fold 3|0.92124|0.92871|
|fold 4|0.92803|0.91853|
|fold 5|0.93754|0.93957|

####考察
SWEM-MIN+MAXより、わずかに数値が向上しました。
BoWやTF-IDFには及びませんが、かなりいい数値が出ています。
MeCabとJuman++の比較については、MeCabの方が優れた結果となりました。

##検証 1-3
ベクトル化された文書を分類するのに使われているLGBMを、SVMに変更して、変化があるかを確認

###結果
####SWEM-MIN+MAX-MeCab

||LGBM|SVM|
|---|---|---|
|fold 1|0.93012|0.90027|
|fold 2|0.92537|0.88941|
|fold 3|0.92803|0.89545|
|fold 4|0.92871|0.87644|
|fold 5|0.92871|0.88458|

###考察
SVMの方は、大きく数値が低下してしまいました。
やはり、LGBM強し、ということでしょうか。


#検証2
BERTを使った文書分類

さて、ここからが、いよいよ本題です。
先程までの手法は、あくまで単語のベクトルの集合を元に、文章のベクトル化を行なっていましたが、BERTでは、文をベクトル化することができます。
そこで、そのベクトル化された文の集合から、SWEMを使って、文章のベクトルを生成し、文書分類を行なってみたいと思います。
また、せっかくなので、分かち書きを、MeCabとJuman++のそれぞれで実施します。

こちらの検証を実施するにあたっては、下記のサイトの実装を参考にさせて頂きました。
https://orizuru.io/blog/machine-learning/bert/
https://yag-ays.github.io/project/pytorch_bert_japanese/
深く感謝申し上げます。


まずは、ベクトル化したものをtSNEで可視化します。

||MeCab|Juman++|
|---|---|---|
|SWEM-MAX|![MeCab_MAX.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/600629/181a7b55-d9f0-0e78-0f26-7534aab91d4a.png)|![Juman_MAX.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/600629/abbc4a19-7d00-ebf0-2a00-923827deb2b1.png)|
|SWEM-MIN|![MeCab_MIN.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/600629/d6678a20-1803-1a8e-a149-abfdfcb2abda.png)|![Juman_MIN.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/600629/f1824e2c-5458-49cf-671b-0afa209b3304.png)|
|SWEM-MEAN|![MeCab_MEAN.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/600629/6168c99c-3159-288d-26f1-d87adf261178.png)|![Juman_MEN.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/600629/5cc5db1e-d242-6482-5838-b9a9bd06bc76.png)|
|SWEM-MIN+MAX|![MeCab_MIN_MAX.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/600629/d2e0b430-9b87-484c-55fc-89bea3f6ba36.png)|![Juman_MIN_MAX.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/600629/174d3a72-fd56-ee37-91f4-2f957773489b.png)|
|SWEM-MEAN+MAX|![MeCab_avg_max.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/600629/fd61854d-04a7-df30-f965-63863c5fa0ea.png)|![Juman_avg_max.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/600629/9df9bea5-bf8c-3154-bae3-c82f3abe6274.png)|
|SWEM-MIN+MEAN+MAX|![MeCab_min_avg_max.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/600629/944ee56c-9a87-b9b1-40fd-f240af0a8276.png)|![Juman_min_avg_max.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/600629/1525405b-83ed-7b8b-4ad6-ca9b6a335898.png)|

正直、どれも、あまり違いがなさそうです。

では、数値を見てみます。

###RandamForest
（＊ハイパーパラメータはすべて指定なしです。）

||MeCab<br>（1回目）|MeCab<br>（2回目）|Juman++<br>（1回目）|Juman++<br>（2回目）|
|---|---|---|---|---|
|SWEM-MAX|0.87788|0.87290|0.88150|0.88692|
|SWEM-MIN|0.87833|0.88421|0.87743|0.87924|
|SWEM-MEAN|0.87426|0.87788|0.88104|0.89009|
|SWEM-MIN+MAX|0.88059|0.88421|0.89326|0.88240|
|SWEM-MEAN+MAX|0.89823|<font color="Red">0.90321</font>|0.90909|<font color="Red">0.91361</font>|
|SWEM-MIN+MEAN+MAX|0.90094|0.89733|0.90592|0.90954|


#### 考察
精度では、MeCabよりJuman++の方が、わずかに優っています。
このモデルの場合、Juman++、MeCabともに、SWEM-MEAN+MAXが最も数値がいいという結果になりました。

###SVM
（＊ハイパーパラメータはすべて指定なしです。カーネルはrbfです。）

||MeCab<br>（1回目）|MeCab<br>（2回目）|Juman++<br>（1回目）|Juman++<br>（2回目）|
|---|---|---|---|---|
|SWEM-MAX|0.93080|0.93939|0.94437|0.94527|
|SWEM-MIN|0.94301|0.93623|0.94166|0.94754|
|SWEM-MEAN|0.93668|0.92899|0.93578|0.93894|
|SWEM-MIN+MAX|<font color="Red">0.94889</font>| 0.94346|0.94980|0.94211|
|SWEM-MEAN+MAX|0.94799|0.94392|0.94754|0.94301|
|SWEM-MIN+MEAN+MAX|0.94708|0.94166|<font color="Red">0.95522</font>|0.94889|


#### 考察
全体の精度では、やはり、MeCabよりJuman++の方が、わずかに優っていますが、SWEMの方式によって異なるなど、一概には言えない結果です。
このモデルでは、Juman++では、SWEM-MIN+MEAN+MAX（1回目）が最も数字がよく、MeCabでは、SWEM-MIN+MAX（1回目）が最も数字がいいという結果でした。


###XGB
（＊ハイパーパラメータはすべて指定なしです。）

||MeCab<br>（1回目）|MeCab<br>（2回目）|Juman++<br>（1回目）|Juman++<br>（2回目）|
|---|---|---|---|---|
|SWEM-MAX|0.90909|0.92266|0.92130|0.92944|
|SWEM-MIN|0.91407|0.91361|0.91949|0.92266|
|SWEM-MEAN|0.89688|0.90050|0.90412|0.90683|
|SWEM-MIN+MAX|0.92854|0.92809|0.92763|0.92583|
|SWEM-MEAN+MAX|<font color="Red">0.93487</font>|0.91633|0.93216|0.93261|
|SWEM-MIN+MEAN+MAX|0.92402|0.92356|0.92763|<font color="Red">0.93894</font>|

#### 考察
ここでも、全体の精度では、MeCabよりJuman++の方が、わずかに優っていますが、上記と同様に、SWEMの方式によって異なるなど、一概には言えない結果となりました。
このモデルでは、Juman++では、SWEM-MIN+MEAN+MAX（2回目）が最も数字がよく、MeCabでは、SWEM-MEAN+MAX（1回目）が最も数字がいいという結果になりました。

###LGBM
（＊ハイパーパラメータはすべて指定なしです。）

||MeCab<br>（1回目）|MeCab<br>（2回目）|Juman++<br>（1回目）|Juman++<br>（2回目）|
|---|---|---|---|---|
|SWEM-MAX|0.91316|0.90683|0.91633|0.92628|
|SWEM-MIN|0.91045|0.91633|0.91000|0.90502|
|SWEM-MEAN|0.90005|0.91045|0.90592|0.91045|
|SWEM-MIN+MAX|0.90592|0.91995|0.92266|0.92718|
|SWEM-MEAN+MAX|0.92266|0.92221|0.92537|0.91814|
|SWEM-MIN+MEAN+MAX|<font color="Red">0.92763</font>|0.91949|<font color="Red">0.93623</font>|0.93397|

#### 考察
やはり、全体の精度では、MeCabよりJuman++の方が、わずかに優っていますが、上記と同様に、SWEMの方式によって異なるなど、一概には言えない結果となりました。
このモデルでは、Juman++では、SWEM-MIN+MEAN+MAX（1回目）が最も数字がよく、MeCabでも、SWEM-MIN+MEAN+MAX（1回目）が最も数字がいいという結果になりました。

##総論
BERTを使ったこの検証では、SVMが最も精度が高いという驚くべき結果となりました。
SVMの精度が高いということは、超平面で分離できているということなので、汎用性の高い、（比較的）理解の容易な、優れたモデルができているのではないかと推測します。

また、数値自体も、上記検証1のBoWやTF-IDFに準ずるような非常に高いものが得られていますが、BERTは、BoWやTF-IDFのような単純なモデルではなく、文脈を考慮した文のベクトル化をベースにしているので、より広範囲な状況で、有用な使い方ができるのではないかと思います。
「BERT恐るべし」です。

SWEMの方式としては、SWEM-MIN+MEAN+MAXが、最もいい数値を出すケースが多かったのですが、実施のたびに数値が変わり、必ずしも、SWEM-MIN+MEAN+MAXが最も優れているとも言えないようです。
また、SWEM-MIN+MEAN+MAXでは、次元数が多くなることを考えると、SWEM-MIN+MAXか、SWEM-MEAN+MAXがいい選択肢というケースもありそうです。

また、MeCabとJuman++の比較においては、純粋な精度だけの比較では、Juman++が優れているように見えますが、圧倒的な処理時間の差を考えると、よほど精度にこだわった場合でなければ、個人的には、MeCabの方が総合的に優れいているという印象でした。

以上、最後まで、お読み頂き、有り難うございました。