公開！週末研究ノート０３ ー 形態素解析と正答率、実行時間の関係

# はじめに　ー　週末研究ノートとは？

個人的に研究的な活動をやるにあたり、オープンにしてみたら面白いかもと思い、自分が興味を持っている ざっくりテーマについて、これから、ゆるい週末研究を公開していこうと思います。（有識者の方のアドバイスも、ちょっとというかかなり期待してます！笑）

どこかの権威的な学会やジャーナルなどで発表する予定はないため、万が一、私の記事を利用する際には自己責任でお願いします。そんな人はいないと思いますが、念のため。



# 今回のサマリ （TL; DR）

数年ぐらい前に、共著本を書く時に作ったモジュールを VSCode でもメンテできるように構成を整理しました。その流れでトークナイザの違いによる正答率、実行時間の違いを評価したのでシェアします。


# 環境

- Docker Desktop: 4.0.1(68347)
    - コンテナのメモリ： 12G
    - 自分の環境では、コンテナのメモリを 12GiB まで拡張しておかないと落ちました笑
- Docker: version 20.10.8
- docker-compose: version 1.29.2
- Python: 3.8.10
- [GitHub リポジトリ](https://github.com/tkosht/experiment/tree/text-classification)


# 今回の週末研究ノート

---

## 概要

[青空文庫](http://aozora-word.hahasoha.net/aozora_word_list_utf8.csv.gz)、[livedoor ニュースコーパス](http://www.rondhuit.com/download.html#ldcc)をデータセットとして、MeCab, Janome, Sudachi, Sentence Piece の４つの形態素解析器を用いて、精度（正答率）を評価しました。


コード上では、Nagisa でも評価できるようにコメントアウトしていますが、あまりに実行時間がかかるので、今回の評価対象から除外しました。


---

## パイプライン

作ったパイプラインは、JpTokenizer クラスを継承した、JpTokenizerMeCab, JpTokenizerJanome, JpTokenizerSudachi, JpTokenizerSentencePiece クラス（のインスタンス）でトークナイズした後、tf-idf でベクトル化、その後、PCA で次元削減したベクトルと結合して、LGBM でクラス分類するものです。図に示すと以下の通りになります。

![model-design-pipeline-classify.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226427/a440756e-8be1-89ca-eb6d-d857096825d1.png)

データをシャッフルしてデータ分割した後、同じデータセットでトークナイザを入れ替えて正答率を評価しました。

---

## 実行環境の準備

### リポジトリのクローン

```zsh
~/p/1 ❯❯❯ git clone https://github.com/tkosht/experiment.git
Cloning into 'experiment'...
remote: Enumerating objects: 275, done.
remote: Counting objects: 100% (275/275), done.
remote: Compressing objects: 100% (202/202), done.
remote: Total 275 (delta 85), reused 194 (delta 46), pack-reused 0
Receiving objects: 100% (275/275), 299.37 KiB | 499.00 KiB/s, done.
Resolving deltas: 100% (85/85), done.
~/p/1 ❯❯❯ ls
experiment/
```

### ブランチ切り替え

```zsh
~/p/1 ❯❯❯ cd experiment
~/p/1/experiment ❯❯❯ git switch text-classification
Branch 'text-classification' set up to track remote branch 'text-classification' from 'origin'.
Switched to a new branch 'text-classification'
~/p/1/experiment ❯❯❯ git branch
  main
* text-classification
~/p/1/experiment ❯❯❯ 
```

### コンテナのビルド＆起動

```zsh
~/p/experiment ❯❯❯ make
docker-compose build
Building app
[+] Building 6.3s (5/29)                                                                                                                  
 => [internal] load build definition from Dockerfile.cpu                                                                             0.1s
:
```

### コンテナの起動確認

```zsh
~/p/experiment ❯❯❯ make ps
docker-compose ps
   Name      Command   State                                                      Ports                                                   
------------------------------------------------------------------------------------------------------------------------------------------
experiment   bash      Up      0.0.0.0:5000->5000/tcp,:::5000->5000/tcp, 0.0.0.0:6006->6006/tcp,:::6006->6006/tcp,                        
                               0.0.0.0:8000->8000/tcp,:::8000->8000/tcp, 0.0.0.0:8080->8080/tcp,:::8080->8080/tcp,                        
                               0.0.0.0:9229->9229/tcp,:::9229->9229/tcp                                                                   
~/p/experiment ❯❯❯ 
```

### 辞書のインストール

MeCab, Sudachi の辞書をインストールするには、コンテナ内で bash を起動し、backend ディレクトリ直下で `make install` を実行します。

```zsh
~/p/experiment ❯❯❯ make bash                                                                                                              
docker-compose up -d app                                                                                                                  
experiment is up-to-date                                                                                                                  
docker-compose exec app bash                                                                                                              
dsuser@c6f99ce288f0:~/workspace$ cd backend/
dsuser@c6f99ce288f0:~/workspace/backend$ make install 
sh bin/morph/install_mecabdic.sh
Cloning into 'mecab-ipadic-neologd'...
remote: Enumerating objects: 9210, done.
Receiving objects:  63% (5891/9210), 35.52 MiB | 246.00 KiB/s 
:
```


---

## 実行

### livedoor ニュースコーパス（ldcc）

```zsh
~/p/experiment ❯❯❯ cd backend
~/p/e/backend ❯❯❯ make run-ldcc
```

### 青空文庫（aozora）

```zsh
~/p/experiment ❯❯❯ cd backend
~/p/e/backend ❯❯❯ make run-aozora
```


---

## 実行結果

上記環境の準備ができたら、早速実行結果を見ていきます。

### livedoor ニュースコーパス （ldcc）

```zsh
~/p/e/backend ❯❯❯ cat log/run.log.ldcc | egrep '(datetime|Jp.*,)'
datetime, tokenizer, train_acc, valid_acc, elapsed_time, cpu_time
2021/10/18 22:53:16 , JpTokenizerMeCab, 1.0, 0.9561284486657621, 295.2752750000218, 451.85172472799997
2021/10/18 23:12:33 , JpTokenizerJanome, 1.0, 0.9547715965626413, 1155.6335971000372, 1299.311036286
2021/10/18 23:27:25 , JpTokenizerSudachi, 1.0, 0.9579375848032564, 892.0077779999701, 1033.869282912
2021/10/18 23:30:46 , JpTokenizerSentencePiece, 1.0, 0.9588421528720036, 200.03009699995164, 475.91284866800015
2021/10/18 23:34:40 , JpTokenizerMeCab, 1.0, 0.947535052012664, 231.71136680000927, 362.2309905950001
2021/10/18 23:54:15 , JpTokenizerJanome, 1.0, 0.9507010402532791, 1174.1650601000292, 1329.868560463
2021/10/19 00:12:01 , JpTokenizerSudachi, 1.0, 0.9538670284938942, 1065.5431799000362, 1220.2210928059994
2021/10/19 00:16:09 , JpTokenizerSentencePiece, 1.0, 0.9556761646313885, 245.5751178000355, 576.70874533
2021/10/19 00:19:28 , JpTokenizerMeCab, 1.0, 0.9611035730438715, 197.11452950001694, 347.50039318700055
2021/10/19 00:39:45 , JpTokenizerJanome, 1.0, 0.9574853007688828, 1216.471751700039, 1357.8573210659997
2021/10/19 00:54:54 , JpTokenizerSudachi, 1.0, 0.9611035730438715, 908.6285593999783, 1045.788085096001
2021/10/19 00:58:16 , JpTokenizerSentencePiece, 1.0, 0.9611035730438715, 200.6965769000817, 481.8085457010002
```

上のファイルをcsv に変換して、簡易分析してみましょう。


#### 結果整形

以下のようにスクリプトを実行すると `data/result/` 配下に csv ファイルとして結果ログを整形した結果を出力します。

```zsh
~/p/e/backend ❯❯❯ ls data/result
ls: data/result: No such file or directory
~/p/e/backend ❯❯❯ sh bin/morph/to_csv.sh
~/p/e/backend ❯❯❯ ls data/result
aozora.csv      ldcc.csv
~/p/e/backend ❯❯❯ 
```

#### 正答率

検証セットの正答率をみてみます。

```python
>>> import pandas
>>> df = pandas.read_csv("data/result/ldcc.csv", skipinitialspace=True)
>>> df.groupby("tokenizer").describe()["valid_acc"]
                          count      mean       std       min       25%       50%       75%       max
tokenizer                                                                                            
JpTokenizerJanome           3.0  0.954319  0.003415  0.950701  0.952736  0.954772  0.956128  0.957485
JpTokenizerMeCab            3.0  0.954922  0.006864  0.947535  0.951832  0.956128  0.958616  0.961104
JpTokenizerSentencePiece    3.0  0.958541  0.002726  0.955676  0.957259  0.958842  0.959973  0.961104
JpTokenizerSudachi          3.0  0.957636  0.003628  0.953867  0.955902  0.957938  0.959521  0.961104
```

livedoor ニュースコーパスを使ったクラス分類では、ほぼ、どのトークナイザを使っても 平均約95%の正答率で大きな差がないように見えます。標準偏差は、MeCab を除いて約0.3%程度で、SentencePiece の標準偏差が一番小さいようです。ただ、標準偏差が一番大きい MeCab　でも 約0.7%と低いので大きく正答率はぶれないと推定できます。
また、若干、SentencePiece の正答率が高いように見えます。

#### 実行時間

次は、実行時間をみてみましょう。

```python
>>> df = pandas.read_csv("data/result/ldcc.csv", skipinitialspace=True)
>>> df.groupby("tokenizer").describe()["elapsed_time"]
                          count         mean        std          min          25%          50%          75%          max
tokenizer                                                                                                               
JpTokenizerJanome           3.0  1182.090136  31.183736  1155.633597  1164.899329  1174.165060  1195.318406  1216.471752
JpTokenizerMeCab            3.0   241.367057  49.787622   197.114530   214.412948   231.711367   263.493321   295.275275
JpTokenizerSentencePiece    3.0   215.433931  26.105161   200.030097   200.363337   200.696577   223.135847   245.575118
JpTokenizerSudachi          3.0   955.393172  95.754011   892.007778   900.318169   908.628559   987.085870  1065.543180
>>> df.groupby("tokenizer").describe()["cpu_time"]
                          count         mean         std          min          25%          50%          75%          max
tokenizer                                                                                                                
JpTokenizerJanome           3.0  1329.012306   29.282533  1299.311036  1314.589798  1329.868560  1343.862941  1357.857321
JpTokenizerMeCab            3.0   387.194370   56.477233   347.500393   354.865692   362.230991   407.041358   451.851725
JpTokenizerSentencePiece    3.0   511.476713   56.569456   475.912849   478.860697   481.808546   529.258646   576.708745
JpTokenizerSudachi          3.0  1099.959487  104.319964  1033.869283  1039.828684  1045.788085  1133.004589  1220.221093
>>> 
```

経過時間（elapsed_time）、CPU時間（cpu_time） の平均を比べてみると、MeCab が一番小さく、最も高速であるとわかります。
正答率が良かった SentencePiece　は、経過時間（学習時間を含む）もMeCab　の次に小さく、それなりに高速であることがわかります。



### 青空文庫（aozora）

青空文庫に対しても、比較してみます。

#### 正答率

```python
>>> df = pandas.read_csv("data/result/aozora.csv", skipinitialspace=True)
>>> df.groupby("tokenizer").describe()["valid_acc"]
                          count      mean       std       min       25%       50%       75%       max
tokenizer                                                                                            
JpTokenizerJanome           3.0  0.792593  0.018900  0.771111  0.785556  0.800000  0.803333  0.806667
JpTokenizerMeCab            3.0  0.795556  0.021886  0.771111  0.786667  0.802222  0.807778  0.813333
JpTokenizerSentencePiece    3.0  0.829630  0.017824  0.811111  0.821111  0.831111  0.838889  0.846667
JpTokenizerSudachi          3.0  0.808889  0.032049  0.773333  0.795556  0.817778  0.826667  0.835556
>>> 
```

青空文庫を使ったデータについては、SentencePiece 以外は、約 80% の正答率とほぼ同等ですが、SentencePiece は 約 83% と他のトークナイザよりもよい結果になりました。また、標準偏差についても1.8% と正答率のばらつきも一番小さく、より安定する正答率が期待できる結果になりました。


#### 実行時間

```python
>>> df = pandas.read_csv("data/result/aozora.csv", skipinitialspace=True)
>>> df.groupby("tokenizer").describe()["elapsed_time"]
                          count         mean         std          min          25%          50%          75%          max
tokenizer                                                                                                                
JpTokenizerJanome           3.0  2413.819408   49.390590  2363.523425  2389.603184  2415.682944  2438.967400  2462.251857
JpTokenizerMeCab            3.0    88.271292    8.092714    83.573667    83.598984    83.624301    90.620104    97.615907
JpTokenizerSentencePiece    3.0   340.076074    6.913173   333.385537   336.518002   339.650467   343.421342   347.192218
JpTokenizerSudachi          3.0  1620.522197  114.059864  1545.804074  1554.878440  1563.952807  1657.881259  1751.809711
>>> df.groupby("tokenizer").describe()["cpu_time"]
                          count         mean         std          min          25%          50%          75%          max
tokenizer                                                                                                                
JpTokenizerJanome           3.0  2474.472940   52.376899  2421.456101  2448.616641  2475.777180  2500.981360  2526.185539
JpTokenizerMeCab            3.0   153.270915    5.526774   148.152682   150.340750   152.528819   155.830031   159.131243
JpTokenizerSentencePiece    3.0   724.512431    9.514062   713.611898   721.195443   728.778988   729.962697   731.146407
JpTokenizerSudachi          3.0  1683.941731  119.669483  1605.085597  1615.092507  1625.099417  1723.369797  1821.640178
>>> 
```

livedoor ニュースコーパスと同じく、経過時間（elapsed_time）、CPU時間（cpu_time） の平均を比べてみると、MeCab が一番小さく、次に SentencePiece　という結果になりました。

以前比較評価したときは、Sudachi が結構時間がかかる印象でしたが、Janome の処理速度にかなり近づき実用上でも十分な速度になったのは、驚きでした。


## データセットとモデルの保存

評価に使ったデータは、`data/dataset` 配下に保存され、モデル・パイプラインは、`data/model` 配下に保存されます。正答率や実行時間以外の指標値で評価したい場合は、これらの保存された結果を用いて分析することができます。

```zsh
~/p/e/backend ❯❯❯ ls data/dataset
aozoraset.gz            aozoraset_iter01.gz     ldccset.gz              ldccset_iter01.gz
aozoraset_iter00.gz     aozoraset_iter02.gz     ldccset_iter00.gz       ldccset_iter02.gz
~/p/e/backend ❯❯❯ ls data/model
pipe-jptokenizerjanome.gz               pipe-jptokenizersentencepiece.gz
pipe-jptokenizermecab.gz                pipe-jptokenizersudachi.gz
~/p/e/backend ❯❯❯ 
```




# まとめ

- VSCode を使ってメンテできるように、リポジトリを整備
- 整備ついでに、トークナイザの違いによる正答率、実行時間を評価
    - 正答率で比較すると、SentencePiece がほんの少しですが平均が高く、標準偏差が小さく安定した正答率が望める結果になった
    - 実行時間で比較すると、経過時間、CPU時間、ともに MeCab が最も小さく、高速である結果になった
- Sudachi がかなり高速化された様子で、今後の更なる高速化に期待感が湧き上がった
- 比較評価するために使ったデータ、モデル（パイプライン）はファイルとして保存されるように作っている
    - 正答率、実行時間以外で評価したいときは、これらを用いて評価することができる


次回は、保存した結果を用いて、可視化などの分析をやってみたいと思います


# 参考文献

- 今回は、過去に作成したものを掘り起こして再評価したものなので、参考文献はなし