【実装解説】日本語版BERTをGoogle Colaboratoryで使う方法（PyTorch）

本記事では、Google Colaboratoryで日本語版のBERTを使用する方法について解説します。

BERTそのものについては、昨年執筆した書籍

[「つくりながら学ぶ！PyTorchによる発展ディープラーニング」](https://www.amazon.co.jp/dp/4839970254/)

で詳細に解説しています。

BERTの仕組みを知りたい方は上記の書籍をご覧ください。

書籍では英語版しか扱っていなかったので、本投稿では日本語版でのBERTの使用方法の解説を行います。
（この記事のあと、2つほど書きたいと思っています。）

なお本投稿内容の実装コードは以下のGitHubリポジトリに置いています。

[GitHub：日本語版BERTのGoogle Colaboratoryでの使用方法：実装コード](https://github.com/YutaroOgawa/BERT_Japanese_Google_Colaboratory)
の、1_Japanese_BERT_on_Google_Colaboratory.ipynbです。


**連載一覧**
[[1] ※本記事【実装解説】日本語版BERTをGoogle Colaboratoryで使う方法（PyTorch）](https://qiita.com/sugulu/items/e522a38b812b8edb8a54)
[[2]【実装解説】日本語版BERTでlivedoorニュース分類：Google Colaboratoryで（PyTorch）](https://qiita.com/sugulu/items/697bd03499c1de9cf082)
[[3]【実装解説】脳科学と教師なし学習。情報量最大化クラスタリングでMNISTを分類](https://qiita.com/sugulu/items/e3c01a0776a5df3ad552)
[[4]【実装解説】日本語BERT × 教師なし学習（情報量最大化クラスタリング）でlivedoorニュースを分類](https://qiita.com/sugulu/items/6a887e612edda9e35c0b)

---

## 準備1：MeCabをGoogle Colaboratoryにインストール

分かち書き（形態素解析）のツールであるMeCabをインストールします。
pipではインストールできないので、aptでインストールです。

```python
!apt install aptitude swig
!aptitude install mecab libmecab-dev mecab-ipadic-utf8 git make curl xz-utils file -y
```

PythonからMeCabを使用できるように、mecab-python3をpipでインストールします。

```python
!pip install mecab-python3
```

MeCabで新語（最近の新しい言葉）を使えるように、辞書であるNEologdをインストールします。
（ただし、学習済みBERTでは使用しません）

```python
!git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
!echo yes | mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n -a

```

新語辞書NEologdへのpathを取得しておきます。

```python
import subprocess

cmd='echo `mecab-config --dicdir`"/mecab-ipadic-neologd"'
path_neologd = (subprocess.Popen(cmd, stdout=subprocess.PIPE,
                           shell=True).communicate()[0]).decode('utf-8')
```

以上で、MeCabの仕様準備は完了です。

#### 備考（IPAdicとUniDic）

新語辞書のmecab-ipadic-neologdの、**ipadic**とは「IPA辞書」のことです。
IPA辞書は「IPA品詞体系」で整理されてます。

IPAdic以外に**UniDic**という表現を見たことがあるかもしれません。
UniDicは「国語研短単位自動解析用辞書」で整理されている体系です。

例えば、形態素解析のSudachiの場合、デフォルトの辞書はUniDicです。

UniDicとIPAdicでは品詞体系が異なります。
例えばUniDicでは形容動詞というものはなく、**形状詞**となります（[UniDicの品詞体系](https://www.ogiso.net/wiki/index.php?%BC%F8%B6%C8%BB%F1%CE%C1/UniDic%A4%CE%C9%CA%BB%EC%C2%CE%B7%CF)）。


私自身、新卒入社してITを学び始めて3年、機械学習・ディープラーニングを学びはじめて2年半になります。

新入社員の頃から私のメンターをしてくださっている先輩のFさんはインドネシアの方ですが、私より日本語が上手です。

sudachiのデフォルトは、形容動詞でなく形状詞である点も、インドネシア人のメンターのFさんから教えていただき、「そんな文法、日本人は学校で習っていないです、形状詞って初めて聞ききました」と驚きでした。



## 準備2：MeCabの動作確認

それでは実際に文章を分かち書き（形態素解析）して、MeCabの動作を確認します。

文章は
「私は機械学習が好きです。」
にしましょう。

まずは新語辞書NEologdを使用しない場合です。

```python
import MeCab

m=MeCab.Tagger("-Ochasen")

text = "私は機械学習が好きです。"

text_segmented = m.parse(text)
print(text_segmented)
```
（出力）

```python
私	ワタシ	私	名詞-代名詞-一般		
は	ハ	は	助詞-係助詞		
機械	キカイ	機械	名詞-一般		
学習	ガクシュウ	学習	名詞-サ変接続		
が	ガ	が	助詞-格助詞-一般		
好き	スキ	好き	名詞-形容動詞語幹		
です	デス	です	助動詞	特殊・デス	基本形
。	。	。	記号-句点		
EOS
```

MeCab.Tagger("-Ochasen")の、**-Ochasen**は出力オプションです。
これを、
**-Owakati**にすると、分かち書きのみを出力します。
**-Oyomi**にすると、読みのみを出力します。

```python
m=MeCab.Tagger("-Owakati")
text_segmented = m.parse(text)
print(text_segmented)
```

出力は
`私 は 機械 学習 が 好き です 。 `
です。

```python
m=MeCab.Tagger("-Oyomi")
text_segmented = m.parse(text)
print(text_segmented)
```

の場合、出力は
`ワタシハキカイガクシュウガスキデス。`
です。


続いて、新語辞書NEologdを使用する場合です。

```python
m=MeCab.Tagger("-Ochasen -d "+str(path_neologd))  # NEologdへのパスを追加

text = "私は機械学習が好きです。"

text_segmented = m.parse(text)
print(text_segmented)
```

（出力）

```python
私	ワタシ	私	名詞-代名詞-一般		
は	ハ	は	助詞-係助詞		
機械学習	キカイガクシュウ	機械学習	名詞-固有名詞-一般		
が	ガ	が	助詞-格助詞-一般		
好き	スキ	好き	名詞-形容動詞語幹		
です	デス	です	助動詞	特殊・デス	基本形
。	。	。	記号-句点		
EOS
```

NEologdを使用するために、MeCab.Taggerに対して、-dでオプションを加え、NEologdへのパスを指定しています。

新語辞書を使用していないときには、”機械学習”という単語が、”機械”と”学習”に分離されていましたが、
新語辞書を使用すると、”機械学習”（固有名詞）と1単語になっています。

これは”機械学習”という専門用語が新語辞書に登録されているからです。

新語辞書バージョンでも、**-Owakati**にすると、分かち書きのみを出力します。


```python
m=MeCab.Tagger("-Owakati -d "+str(path_neologd))  # NEologdへのパスを追加
text_segmented = m.parse(text)
print(text_segmented)
```

出力は
`私 は 機械学習 が 好き です 。 `
となります。


## 準備3：日本語版BERTの学習済みモデルと形態素解析を用意

それでは日本語版BERTの学習済みモデルと形態素解析を用意します。

拙著、[「つくりながら学ぶ！PyTorchによる発展ディープラーニング」](https://www.amazon.co.jp/dp/4839970254/)のBERTモデルも使用できるのですが、ここでは最近スタンダードに使用されている、HuggingFaceさんのモデルを使用します。

Huggingとは日本語でハグする（抱きしめる）の意味です。


BERTモデルはHuggingFaceさんのものを使用し、日本語での学習済みパラメータ、および
学習時の形態素解析（Tokenizer）は
[東北大学（乾・鈴木先生研究室）で鈴木正敏さんが公開してくださったもの](https://www.nlp.ecei.tohoku.ac.jp/news-release/3284/)
を使用させていただきます。

この東北大学の日本語版学習済みモデルはHuggingFaceさんのOSSである**transformers**に取り込まれているので、transformersから直接、利用できます。


まずはpipでtransformersのバージョン2.9をインストールします。

```python
!pip install transformers==2.9.0
```

**注意**
transformersは2020年5月8日にバージョンが2.8から2.9にアップされました。
バージョン2.8だと、日本語データへのファイルパスがエラーになるので、2.9をインストールするように注意してください。


それでは、PyTorch、BERTモデル、そして日本語のBERT用tokenizer（分かち書きするクラス）をimportします。


```python
import torch
from transformers.modeling_bert import BertModel
from transformers.tokenization_bert_japanese import BertJapaneseTokenizer
```

日本語用のtokenizerを用意します。引数に'bert-base-japanese-whole-word-masking'を指定します。

```python
# 分かち書きをするtokenizerです
tokenizer = BertJapaneseTokenizer.from_pretrained('bert-base-japanese-whole-word-masking')
```

日本語での学習済みモデルを用意します。

```python
# BERTの日本語学習済みパラメータのモデルです
model = BertModel.from_pretrained('bert-base-japanese-whole-word-masking')
print(model)
```

出力されたモデルの結果を簡単に確認すると以下の通りです。

```
BertModel(
  (embeddings): BertEmbeddings(
    (word_embeddings): Embedding(32000, 768, padding_idx=0)
    (position_embeddings): Embedding(512, 768)
    (token_type_embeddings): Embedding(2, 768)
    (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
    (dropout): Dropout(p=0.1, inplace=False)
  )
  (encoder): BertEncoder(

・・・
```

ここで、日本語版モデルの設定（config）を確認しておきます。

```python
from transformers import BertConfig

# 東北大学_日本語版の設定を確認
config_japanese = BertConfig.from_pretrained('bert-base-japanese-whole-word-masking')
print(config_japanese)
```

出力は以下です。

```
BertConfig {
  "architectures": [
    "BertForMaskedLM"
  ],
  "attention_probs_dropout_prob": 0.1,
  "hidden_act": "gelu",
  "hidden_dropout_prob": 0.1,
  "hidden_size": 768,
  "initializer_range": 0.02,
  "intermediate_size": 3072,
  "layer_norm_eps": 1e-12,
  "max_position_embeddings": 512,
  "model_type": "bert",
  "num_attention_heads": 12,
  "num_hidden_layers": 12,
  "pad_token_id": 0,
  "type_vocab_size": 2,
  "vocab_size": 32000
}
```

設定を見ると、単語ベクトルは768次元、最大の単語数（サブワード数）は512、BERTのレイヤー数は12層、ボキャブラリのサイズは32,000であることが分かります。

以上で、日本語学習済みモデル、モデルに投入する前に使用する日本語用tokenizerが用意できました。


## 日本語版BERTで文章を扱う

それでは最後に、文章を日本語版BERTで扱いましょう。

「会社をクビになった。」
「テレワークばかりでクビが痛い。」
「会社を解雇された。」

という3つの文章を用意します。

そしてそれぞれの文章の、**”クビ”**、**”クビ”**、**"解雇"**の3単語のベクトルを比較します。

BERTは文脈に応じて単語ベクトルが変化するのが特徴なので、1つ目と2つ目の文章では
**”クビ”**という同じ単語でも768次元のベクトル表現は異なるものになります。

**そして、1つ目の文章の”クビ”が、2つ目の文章の”クビ”よりも、3つ目の文章の"解雇"に近いと嬉しいです。**

単語ベクトルの類似度はコサイン類似度で計測しましょう。

それでは実装します。
まず文章を用意します。

```python
text1 = "会社をクビになった。"
text2 = "テレワークばかりでクビが痛い。"
text3 = "会社を解雇された。"
```

text1を日本語版BERTの分かち書きtokenizerで処理します。

```python
# 分かち書きをして、idに変換
input_ids1 = tokenizer.encode(text1, return_tensors='pt')  # ptはPyTorchの略

print(tokenizer.convert_ids_to_tokens(input_ids1[0].tolist()))  # 文章
print(input_ids1)  # id
```
出力は

```
['[CLS]', '会社', 'を', 'クビ', 'に', 'なっ', 'た', '。', '[SEP]']
tensor([[    2,   811,    11, 13700,     7,    58,    10,     8,     3]])
```
となります。

”クビ”という単語は3番目で、idは13700と分かりました。

2つ目、3つ目の文章も同様に処理します。それぞれの出力は、以下のようになります。

```
['[CLS]', 'テレ', '##ワーク', 'ばかり', 'で', 'クビ', 'が', '痛', '##い', '。', '[SEP]']
tensor([[    2,  5521,  3118,  4027,    12, 13700,    14,  4897, 28457,     8,
             3]])

['[CLS]', '会社', 'を', '解雇', 'さ', 'れ', 'た', '。', '[SEP]']
tensor([[   2,  811,   11, 7279,   26,   20,   10,    8,    3]])
```

2つ目の文章の”クビ”は5番目、3つ目の文章の”解雇”は3番目と分かりました。


それではこのid化された内容を日本語BERTモデルに入力し、出力ベクトルを計算します。

```python
# 日本語BERTモデルに入力
result1 = model(input_ids1)

print(result1[0].shape)
print(result1[1].shape)

# reult は、sequence_output, pooled_output, (hidden_states), (attentions)です。
# ただし、hidden_statesとattentionsはoptionalであり、標準では出力されません。
```

出力は
`
torch.Size([1, 9, 768])
torch.Size([1, 768])
`
となります。

9は、1つ目の文章の単語数（サブワード数）を表します。
768は単語の埋め込み次元です。
よって、1つ目の文章の"クビ"は3番目にあったので、その単語ベクトルは
`result1[0][0][3][:]`
となります。

なお、BERTモデルの計算で出力されるのは
`outputs  # sequence_output, pooled_output, (hidden_states), (attentions)`
です（ただし、hidden_statesとattentionsはoptionalであり、標準では出力されません）。

参考 [BerTModelのforwardの解説](https://huggingface.co/transformers/model_doc/bert.html#bertmodel)



同様に2つ目の文章の”クビ”（5番目）、3つ目の文章の”解雇”（3番目）の単語ベクトルも求めます。

```python
# 日本語BERTモデルに入力
result2 = model(input_ids2)
result3 = model(input_ids3)

word_vec1 = result1[0][0][3][:]  # 1つ目の文章の”クビ”（3番目）
word_vec2 = result2[0][0][5][:]  # 2つ目の文章の”クビ”（5番目）
word_vec3 = result3[0][0][3][:]  # 3つ目の文章の”解雇”（3番目）
```

最後に類似度を求めてみます。

```python
# コサイン類似度を求める
cos = torch.nn.CosineSimilarity(dim=0)
cos_sim_12 = cos(word_vec1, word_vec2)
cos_sim_13 = cos(word_vec1, word_vec3)

print(cos_sim_12)
print(cos_sim_13)
```

出力は
`
tensor(0.6647, grad_fn=<DivBackward0>)
tensor(0.7841, grad_fn=<DivBackward0>)
`

となりました。

よって、BERTで処理された単語表現は、
1つ目の文章の”クビ”と2つ目の文章の”クビ”の類似度は0.66
1つ目の文章の”クビ”と3つ目の文章の"解雇"の類似度は0.78

と、1つ目の文章の”クビ”は3つ目の文章の"解雇"に近い（類似度が高い）と分かります。

同じ"クビ"という単語でも、BERTを使うことで、文脈に応じた意味に変化した単語ベクトルになっていることが確認できました。

以上、【実装解説】日本語版BERTをGoogle Colaboratoryで使う方法（PyTorch）でした。


---

【備考】[私がリードする、AIテクノロジー部開発チームはメンバ募集中です。興味がある方はこちらから](https://www.isidgroup.com/u/job.phtml?job_code=430)


【免責】本記事の内容そのものは著者の意見/発信であり、著者が属する企業等の公式見解ではございません

---

参考文献
● [mecab ipadic-NEologd を Google Colaboratory で使う](https://qiita.com/Fulltea/items/90f6ebe6dcceaf64eaef)