BERT日本語Pretrainedモデルを利用した日本語文章の尤度

# 概要
[BERT日本語Pretrainedモデル(京大 黒橋・河原研)](http://nlp.ist.i.kyoto-u.ac.jp/index.php?BERT%E6%97%A5%E6%9C%AC%E8%AA%9EPretrained%E3%83%A2%E3%83%87%E3%83%AB)を利用して、日本語文章に含まれるトークン(単語)の尤度を出力します。
[「BERTの学習済みモデルを利用して日本語文章の尤度を測る」](https://qiita.com/KTaskn/items/86a25932d706ae1d772b)を参考に、こちらの記事では[SentencePieceによるモデル](https://github.com/yoheikikuta/bert-japanese)を利用されていたところを、上記の京大のモデル(Juman++)に対応させた形です。

# コード
- https://github.com/ohchun/bert-as-language-model
 - fork: https://github.com/xu-song/bert-as-language-model

# 環境
- Ubuntu 16.04.4 LTS
- Python 3.6.0
  - tensorflow==1.14.0
  - pyknp==0.4.1
- Juman++ Version: 2.0.0-rc2

# 参考サイト
- [BERT日本語Pretrainedモデル](http://nlp.ist.i.kyoto-u.ac.jp/index.php?BERT%E6%97%A5%E6%9C%AC%E8%AA%9EPretrained%E3%83%A2%E3%83%87%E3%83%AB)
- [BERTの日本語事前学習済みモデルでテキスト埋め込みをやってみる](https://dev.classmethod.jp/machine-learning/bert-text-embedding/)
- [BERTの学習済みモデルを利用して日本語文章の尤度を測る](https://qiita.com/KTaskn/items/86a25932d706ae1d772b)

# 利用手順

+ Juman++のインストール
+ Pythonモジュールのインストール
+ 上記コードを clone
    + modelsディレクトリを作成
+ BERT日本語Pretrainedモデルのダウンロード
    + 解凍後、モデルファイル群が入ったディレクトリを models に配置
+ 以下のコマンドを実行

```bash
export BERT_BASE_DIR=models/Japanese_L-12_H-768_A-12_E-30_BPE
python run_lm_predict.py \
  --input_file=./data/lm/test.ja.tsv \
  --vocab_file=$BERT_BASE_DIR/vocab.txt \
  --bert_config_file=$BERT_BASE_DIR/bert_config.json \
  --init_checkpoint=$BERT_BASE_DIR/bert_model.ckpt \
  --do_lower_case False \
  --max_seq_length=128 \
  --output_dir=./data/lm/output/ \
  --jp_tokenizer=True
```

# 実行結果

## 入力内容

誤変換を含む文章を用意し、尤度が低くなるか確認してみます。

```bash
cat ./data/lm/test.ja.tsv 
機械学習で処理する
機会学習で処理する
世間では人工知能が流行している
世間では人口知能が流行している
```

## 結果

誤変換に該当する単語およびその周辺単語の尤度が低くなりました。
なお、先頭と最後の単語も尤度が低くなりがちです。
この理由は、先頭と最後には様々な単語が現れる可能性があり、bi-directional とはいえ確率がばらけてしまうためと考えています。

```yml
# prob: probability
# ppl:  perplexity
[
  {
    "tokens": [
      {
        "token": "機械",
        "prob": 0.7197790145874023
      },
      {
        "token": "学習",
        "prob": 0.0011436253553256392
      },
      {
        "token": "で",
        "prob": 0.4158846437931061
      },
      {
        "token": "処理",
        "prob": 0.00014628272037953138
      },
      {
        "token": "する",
        "prob": 0.00011202425957890227
      }
    ],
    "ppl": 177.91305425305157
  },
  {
    "tokens": [
      {
        "token": "機会",
        "prob": 4.273203558113892e-06  # low probability
      },
      {
        "token": "学習",
        "prob": 0.00048818811774253845
      },
      {
        "token": "で",
        "prob": 0.14289069175720215
      },
      {
        "token": "処理",
        "prob": 0.0002504551666788757
      },
      {
        "token": "する",
        "prob": 8.453470945823938e-05
      }
    ],
    "ppl": 2754.0894828429246
  },
  {
    "tokens": [
      {
        "token": "世間",
        "prob": 0.00029105637804605067
      },
      {
        "token": "で",
        "prob": 0.8351001739501953
      },
      {
        "token": "は",
        "prob": 0.9587864875793457
      },
      {
        "token": "人工",
        "prob": 0.986724317073822
      },
      {
        "token": "知能",
        "prob": 0.5667852759361267
      },
      {
        "token": "が",
        "prob": 0.9329909086227417
      },
      {
        "token": "流行",
        "prob": 0.1348113864660263
      },
      {
        "token": "して",
        "prob": 0.9265641570091248
      },
      {
        "token": "いる",
        "prob": 1.2146945664426312e-05
      }
    ],
    "ppl": 12.065788660694167
  },
  {
    "tokens": [
      {
        "token": "世間",
        "prob": 0.00021123532496858388
      },
      {
        "token": "で",
        "prob": 0.8677905201911926
      },
      {
        "token": "は",
        "prob": 0.949567437171936
      },
      {
        "token": "人口",
        "prob": 1.7458520233049057e-05  # low probability
      },
      {
        "token": "知能",
        "prob": 3.086066135438159e-05  # low probability
      },
      {
        "token": "が",
        "prob": 0.914918839931488
      },
      {
        "token": "流行",
        "prob": 0.051112908869981766
      },
      {
        "token": "して",
        "prob": 0.927436888217926
      },
      {
        "token": "いる",
        "prob": 1.0879161891352851e-05
      }
    ],
    "ppl": 141.40153489831755
  }
]
```

# コードの修正内容

ほぼ、参考サイト様の通りです。

- Juman++ でトークン化できるように、tokenization.py に JumanPPTokenizer クラスを追加
  - [BERTの日本語事前学習済みモデルでテキスト埋め込みをやってみる](https://dev.classmethod.jp/machine-learning/bert-text-embedding/) を参考にしました。
- 日本語トークナイズ処理のオン/オフを切り替えられるように、run_lm_predict.py に jp_tokenizer オプションを追加
  - [BERTの学習済みモデルを利用して日本語文章の尤度を測る](https://qiita.com/KTaskn/items/86a25932d706ae1d772b) を参考にしました。
  - 日本語トークナイズを行わない場合(=オリジナルのトークナイズを行う場合)は、別途、BERTモデルを用意しておきます。
- 出力ファイル名が固定だったため、入力ファイル名_result.json となるように変更しました。

# 所感

- 思いのほか簡単に導入でき、結果が期待通りで良かったです。
- 結果が分かりやすいので、色々と活用できそうです。
- 各トークン位置の候補語も出せそうな気がするので、覗いていきたいところです。