BERTの日本語事前学習済みモデルをGoogle Colaboratoryで手軽に試す方法


## BERTとは
　最近少し自然言語処理を勉強しています（仕事とかは、全く関係なくて個人的興味です）。本としては、以下の本がとても良かったです。

[「Python自然言語処理入門」が自然言語処理の基礎にとても良かった](https://karaage.hatenadiary.jp/entry/2020/01/31/073000)

　本の最後の方に、最新のディープラーニングを使った手法である「BERT」の解説があります。なので、BERTのモデル詳細に関しては本や他のネット記事など参照ください。

　本では、理論面に関しては詳しく解説あるのですが、残念ながらBERTの発表が発刊直前だったとのことで、サンプルまでは本に盛り込まれていませんでした。というわけで、この記事ではBERTを使って簡単な自然言語処理を実践してみたいと思います。

# BERTをGoogle Colaboratoryで手軽に試す
　手っ取り早く試すために、この記事ではGoogle Colaboratory(Google Colab）を活用します。Google Colabに関しての説明は以下記事参照下さい。

[Google Colaboratoryを使えば環境構築不要・無料でPythonの機械学習ができて最高](https://karaage.hatenadiary.jp/entry/2018/03/21/073000)

　ここから以下はGoogle Colabが使える前提で記載していきます。また、ディープラーニングのフレームワークはPyTorchを使用します。

　この記事のコードを動作確認したGoogle Colabのノートブックは以下です。参考にしたブログ記事やドキュメントに関しては、本記事の最後に参考リンクとして挙げさせていただいています。先人の知恵に助けられました、感謝です。

[BERT_example.ipynb(Google Colab Notebook)](https://colab.research.google.com/drive/1LFi_aJx7xh86qsneedapKbgGZsJ2FG7C)

### 必要なライブラリのセットアップ
　Transformersをセットアップします。

```sh
!pip install transformers
```

　本来ならこれで良いはずなのですが、現在Bertの日本語モデルのリンクが変わってしまい、まだ修正がpipで入るバージョンでは間に合っていないようです。そのため、以下で最新のソースを使ってセットアップします。

```sh
!git clone https://github.com/huggingface/transformers
%cd transformers
!pip install .
```

　実行後は再起動します。

　前処理（分かち書き）に必要なMeCabとその関係ライブラリをインストールします。

```sh
!apt install aptitude
!aptitude install mecab libmecab-dev mecab-ipadic-utf8 git make curl xz-utils file -y
!pip install mecab-python3==0.7
!pip install fugashi ipadic
```

　分かち書きとかMeCabに関しての説明は省略します。

### BERTで単語の穴埋め
　最初にBERTの日本語事前学習済みモデルを使って単語の穴埋めをしてみましょう。

　必要なライブラリをインポートします。

```python
import torch
from transformers import BertJapaneseTokenizer, BertForMaskedLM
```

　分かち書きをします。サンプルは「父の父は、祖父」です。学習済みモデルは勝手にインストールされます。

```python
tokenizer = BertJapaneseTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')

text = '父の父は、祖父。'
tokenized_text = tokenizer.tokenize(text)
print(tokenized_text)
```

　前処理をします。分かち書きとマスク処理とテンソル化をしています。

```python
# Mask a token that we will try to predict back with `BertForMaskedLM`
masked_index = 2
tokenized_text[masked_index] = '[MASK]'
print(tokenized_text)

# Convert token to vocabulary indices
indexed_tokens = tokenizer.convert_tokens_to_ids(tokenized_text)
print(indexed_tokens)

# Convert inputs to PyTorch tensors
tokens_tensor = torch.tensor([indexed_tokens])
print(tokens_tensor)
```

　以下が実行結果です。「父の父は、祖父」の真ん中の父をマスクしています。

```
['父', 'の', '父', 'は', '、', '祖父', '。']
['父', 'の', '父', 'は', '、', '祖父', '。']
['父', 'の', '[MASK]', 'は', '、', '祖父', '。']
[800, 5, 4, 9, 6, 5235, 8]
tensor([[ 800,    5,    4,    9,    6, 5235,    8]])
```

　モデルをロードします。

```python
# Load pre-trained model
model = BertForMaskedLM.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')
model.eval()
```


　予想して結果を当てます。


```python
# Predict
with torch.no_grad():
    outputs = model(tokens_tensor)
    predictions = outputs[0][0, masked_index].topk(5) # 予測結果の上位5件を抽出

# Show results
for i, index_t in enumerate(predictions.indices):
    index = index_t.item()
    token = tokenizer.convert_ids_to_tokens([index])[0]
    print(i, token)
```

　結果です。4番目に父がでてきました。ギリギリ正解？といったところでしょうか。


```
0 の
1 、
2 と
3 父
4 母
```


### BERTで文章の特徴量抽出
　次にBERTで文章の特徴量を抽出してみます。

　まずは、必要なライブラリをインポートして、BERTのモデルを定義します。学習済みモデルが自動でダウンロードされます。

```python
from transformers import BertJapaneseTokenizer, BertModel
tokenizer = BertJapaneseTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')
model_bert = BertModel.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')
model_bert.eval()
```

　特徴量を抽出する関数を定義します。

```python
import numpy as np

def calc_embedding(text):
  bert_tokens = tokenizer.tokenize(text)
  ids = tokenizer.convert_tokens_to_ids(["[CLS]"] + bert_tokens[:126] + ["[SEP]"])
  tokens_tensor = torch.tensor(ids).reshape(1, -1)

  with torch.no_grad():
    output = model_bert(tokens_tensor)

  return output[1].numpy()
```

　特徴量を抽出します。

```python
text = '私はからあげです'
calc_embedding(text)
```

　以下のような768次元の特徴量が抽出されます。

```
array([[-4.80725728e-02,  4.65253592e-01, -1.83069140e-01,
        -3.77458245e-01,  9.60656032e-02,  9.98048246e-01,
        -7.86745101e-02, -1.10579640e-01, -2.16161519e-01,
         2.70328492e-01,  6.12610817e-01, -5.22116981e-02,

　　　　　（中略）

         2.64560562e-02,  9.74305570e-02,  3.00148994e-01,
        -3.19734991e-01,  1.47917762e-01,  3.07653695e-01,
         4.50628586e-02, -1.08084820e-01, -4.28770244e-01]], dtype=float32)
```

　ただ、transformersの公式ドキュメントによると、これは最終層(Last layer)の出力だけど、特徴量として使うのはあまりよく無いから、隠れ層を平均するなりプーリングするなりして使ってくれ、みたいなことが書いてあります。

　隠れ層での計算結果との比較とかもしてみたいところですが、パッとできなかったので今回はここまでにします（誰か教えてくれると嬉しいです）。

参考：[transformersの公式ドキュメント](https://huggingface.co/transformers/model_doc/bert.html#transformers.BertModel)


　この特徴量を使って、可視化とかすると文章の類似度とかが分かるのではないかなと思います。以前、Word2Vecで単語で可視化したものの文章版ができるのかなと思っていますが、可視化も今回は省略します。

　単語版の可視化の方法は、以前ブログ記事に書いたので興味ある方はどうぞ。

[機械学習手法を用いてブログの文章を分析・可視化（テキストマイニング）](https://karaage.hatenadiary.jp/entry/2018/02/19/073000)

　他は、例えばニュースのタイトルの特徴量を計算して、適当な機械学習の手法（SVMとか）で分類すれば、タイトルを元に、カテゴリーの自動分類とかができるのかなと思っています。

## Transformer/Attentionの画像認識への応用
　BERTのベースとなるTransformer/Attentionという技術は、今画像認識にも応用されつつあります。ここでは詳細説明しません（できません）が、以下の動画や記事が分かりやすいと思います。興味ある人は参考にしてみてください。

- [Attention解説(YouTube)](https://www.youtube.com/watch?v=dE4Z3Ru-2OY)
- [深層学習界の大前提Transformerの論文解説！](https://qiita.com/omiita/items/07e69aef6c156d23c538)
- [画像認識の大革命。AI界で話題爆発中の「Vision Transformer」を解説！](https://qiita.com/omiita/items/0049ade809c4817670d7)

## まとめ
　自然言語処理のBERTに関して、手を動かすことで理解を深めてみました。自然言語処理に興味ある方は、最初に紹介した「Python自然言語処理入門」がおすすめかなと思っています。


[「Python自然言語処理入門」が自然言語処理の基礎にとても良かった](https://karaage.hatenadiary.jp/entry/2020/01/31/073000)

　自然言語処理の基礎に関しては、言語処理100本ノックがおすすめです（自分も挑戦中）。こちらもGoogle Colabで手軽にチャレンジできます。

[言語処理100本ノック 2020を「Google Colaboratory」で楽々学習](https://qiita.com/karaage0703/items/e4694d184bc0c6c6684d)

## 参考リンク
- https://huggingface.co/transformers/model_doc/bert.html#transformers.BertModel
- https://yag-ays.github.io/project/pytorch_bert_japanese/
- https://dev.classmethod.jp/articles/bert-text-embedding/
- https://qiita.com/kenta1984/items/7f3a5d859a15b20657f3
- https://qiita.com/yuki_uchida/items/09fda4c5c608a9f53d2f

## 変更履歴
- 2020/10/16 Transformer/Attentionの画像認識への応用に関して追記