自然言語処理モデルBERTの検証(1)-予測のお試し


## はじめに

BERTを題材に自然言語処理(NLP)の検証スタートします。
BERTを上回るNLPモデルも最近は開発されているようです(XLNet、MT-DNN)。

BERT(自然言語処理モデル)の定義を、[Wikipedia](https://ja.wikipedia.org/wiki/BERT_(%E8%A8%80%E8%AA%9E%E3%83%A2%E3%83%87%E3%83%AB)) から引用:

```
Bidirectional Encoder Representations from Transformers(BERT)は、
Googleによって開発された、
自然言語処理(NLP)の事前学習用ためのTransformerベースの機械学習手法。
```

「Transformerベース」を深掘りしたい気持ちではありますが、とにかく試してみたい。

## その前に、BERTに何ができる?

- 提示された単語に対し、意味の近い別の単語を与える
- 提示された文書の内容を基に、質問に答える
- 異なる2つの表現について、同じことを言っているか判断
- 文章から重要な部分を抜き出して要約
- 別言語への翻訳(えー、すごい)

ですので、チャットボット、検索エンジンのレコメンデーション、スマートスピーカーなどに利用されているようです。

## BERTの特徴
- 文章の文脈を理解できる
- 性能がよい(従来のNLPモデルCNNやRNNと比べ)
- 学習用データの収集やラベリングコストが低い(これは大きいですね)
    - ラベルの付いていない既存の大量教師なしデータで事前学習ができる

## 早速試してみたい

学習済みBERTをさくっと試せるDockerイメージを名人が用意してくださっていました。
感謝感謝。

[すぐに試せる日本語BERTのDocker Imageを作ってみた](https://yukoishizaki.hatenablog.com/entry/2019/11/27/151018)


BERTはTransformersを使用されたようです。

## 検証環境

```
CPU: Intel(R) Core(TM) 3.30GHz(4コア8スレッド)
メモリ: 16 GB
OS: Ubuntu 20.04 LTS
```

ちなみに、Ubuntuは、Windows 10で以下のように管理者権限で導入しました。
```
> wsl --install -d Ubuntu-20.04
```

## 事前準備

### Dockerのインストール
```
sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
sudo apt update
apt-cache policy docker-ce
sudo apt install docker-ce
sudo service docker start
sudo service docker status
* Docker is running
```

dockerコマンドを実行するたびに sudoを入力しないようにする。
ユーザ名をdockerグループに追加。
```
sudo usermod -aG docker `id -un`
su - `id -un`
```

## 検証スタート

- Dockerイメージをダウンロード(2GB以上あるので待たされます)
```
docker pull ishizakiyuko/japanese_bert_trial:1.0.1
```

- Dockerコマンドを実行する
```
docker run -p 8888:8888 -d ishizakiyuko/japanese_bert_trial:1.0.1
xxx(コンテナID)
```

出力されるコンテナIDをコピーします。

- logからjupyterへのアクセスURLを調べる
```
docker logs <コンテナID>

... ...
    Or copy and paste one of these URLs:
        http://327bef4aee86:8888/?token=xxx
     or http://127.0.0.1:8888/?token=xxx
```

- ブラウザからjupyterにアクセスする
``` 
 http://127.0.0.1:8888/?token=xxx
```

- Python3のnotebookを開く

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/4d0b8174-4b96-72c9-7098-6b00ae397629.png)

## BERTの検証

- まず、BERTによる形態素解析

名人のサンプルコードで、textだけ書き換えました。

```
import os
import torch
from transformers import BertForMaskedLM, BertConfig, BertTokenizer
from pyknp import Juman

BASE_PATH = './Japanese_L-12_H-768_A-12_E-30_BPE_WWM_transformers'
BERT_CONFIG = 'config.json'
BERT_MODEL = 'pytorch_model.bin'
VOCAVULARY_LIST = 'vocab.txt'

jumanpp = Juman()

# 形態素解析
text = '世の中の美味しいものを全部食べてみたいな'
result = jumanpp.analysis(text)
tokenized_text =[mrph.midasi for mrph in result.mrph_list()]
print(tokenized_text)
```

すると、ちゃんと形態素解析できました!
```
['世の中', 'の', '美味しい', 'もの', 'を', '全部', '食べて', 'みたいな']
```

- つぎ、'美味しい'をマスク

```
# Mask 
tokenized_text.insert(0, '[CLS]')
tokenized_text.append('[SEP]')

masked_index = 3 # Maskしたいtextのindex 
tokenized_text[masked_index] = '[MASK]'
print(tokenized_text)
```

結果、
```
['[CLS]', '世の中', 'の', '[MASK]', 'もの', 'を', '全部', '食べて', 'みたいな', '[SEP]']
```

- BERTモデル作成し、トークナイズ

```
# Bert model
config = BertConfig.from_json_file(os.path.join(BASE_PATH, BERT_CONFIG))
model = BertForMaskedLM.from_pretrained(os.path.join(BASE_PATH, BERT_MODEL), config=config)
tokenizer = BertTokenizer(os.path.join(BASE_PATH, VOCAVULARY_LIST), do_lower_case=False, do_basic_tokenize=False)

# token化
indexed_tokens = tokenizer.convert_tokens_to_ids(tokenized_text)
tokens_tensor = torch.tensor([indexed_tokens])
print(tokens_tensor)
```

トークナイズの結果は
```
tensor([[    2, 21199,     5,     4,    60,    10,  7299, 13135, 24681,     3]])
```

- '美味しい'に代わる単語を予測

```
# 予測
model.eval()

tokens_tensor = tokens_tensor.to('cpu')
model.to('cpu')

with torch.no_grad():
    outputs = model(tokens_tensor)
    predictions = outputs[0]

_, predicted_indexes = torch.topk(predictions[0, masked_index], k=5)
predicted_tokens = tokenizer.convert_ids_to_tokens(predicted_indexes.tolist())
print(predicted_tokens)
```

それっぽい予測できました。
```
['あらゆる', '大事な', '好きな', '悪い', 'いい']
```

## おわりに

自然言語処理モデルBERTの概念を理解し、少し試してみました。
いろいろ面白いことができそうですので検証続けます。
お楽しみに。

##### [次回] [自然言語処理モデルBERTの検証(2)-MeCab+WordPiece](https://qiita.com/mingchun_zhao/items/33ea21f1232e69756966)