超初心者向け爆速BERTチュートリアル

## はじめに

自然言語処理全くワカラン人でもBERTが使えるようになるための[ノートブック](https://github.com/si1242/nagisa_bert)です。

### 対象とする人

- とにかくBERTを触ってみたい
- 環境構築が苦手


### やること

- GoogleColabでBERTを使うための爆速チュートリアル
- JPOPの歌詞を可視化してみるデモ

## BERTの簡単な説明
[BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding](https://arxiv.org/abs/1810.04805)

>We introduce a new language representation model called BERT, which stands for <font color="Black">**B**idirectional **E**ncoder **R**epresentations from **T**ransformers</font>. Unlike recent language representation models, BERT is designed to pre-train deep bidirectional representations from unlabeled text by jointly conditioning on both left and right context in all layers. As a result, the pre-trained BERT model can be fine-tuned with just one additional output layer to create state-of-the-art models for a wide range of tasks, such as question answering and language inference, without substantial task-specific architecture modifications.

この名の通り双方向のTransformerを用いた汎用的な言語モデルである。
ざっくり言うとWord2Vecのように<font color="red">**可変長の文字列を固定長の数値ベクトルで表現する**</font>ことができる。



BERTの学習は２ステージに分けられる。


![bert1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166931/9fa66d1f-87e0-df81-d048-a0b1ce10551b.png)


実践的な説明からすると図左側のPre-Trainingの部分はGoogleや偉大なる研究室、個人などから学習済みのモデルが提供されている。

- [BERT日本語Pretrainedモデル - KUROHASHI-KAWAHARA LAB](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 with SentencePiece を日本語 Wikipedia で学習してモデルを公開しました – 原理的には可能 – データ分析界隈の人のブログ、もとい雑記帳](https://yoheikikuta.github.io/bert-japanese/)
- [BERT-Base, Multilingual Cased (New, recommended)](https://storage.googleapis.com/bert_models/2018_11_23/multi_cased_L-12_H-768_A-12.zip)

※ここを自前でやろうとすると膨大な時間とGPU/TPUが必要になる。

右側のFine-Tuningは自分で実装する部分である。Pre-Trainingで取得できたベクトルを利用して、取り組んでいるタスク（「スパム検知」や「記事の要約」など）に応じた実装をする。


### 事前学習(Pre-Training)

#### Task#1: Masked LM (MLM)　穴埋め問題

>Input: the man went to the [MASK1] . he bought a [MASK2] of milk.
Labels: [MASK1] = store; [MASK2] = gallon

- ランダムに一定数の単語をマスキング
- マスクした単語を予測するように学習

従来は文脈を考慮せずにtarget wordを予測していたため、<font color="red">**プログラミング言語の"Python💻"とニシキヘビの"Python🐍"を同じものとみなしてしまう。**</font>

BERTでは穴埋め問題(Cloze test)のように前後の文脈から単語を予測させている。これによって

- DeepLearningとかクラウドが同じ文に含まれるぞ → プログラミング言語Python
- ジャングルとかコブラが同じ文に含まれるぞ → ニシキヘビPython

というように使い分けができるようになる。

#### Task#2: Next Sentence Prediction (NSP) 次文予測

>Sentence A: the man went to the store .
Sentence B: he bought a gallon of milk .
Label: IsNextSentence

>Sentence A: the man went to the store .
Sentence B: penguins are flightless .
Label: NotNextSentence

このように文Aと文Bが連続しているかどうかを予測させる。
これによって文と文の関係性や論理構造を学習できる=文脈を読み解くことができると言われているが…

このあたりは議論が多くなされているところで、実はよくわかっていない。

BERT以降様々な改良モデル[^after]では**NSPの効果はそれほどないのでは？**という説も出てきている。

[^after]: RoBERTaやALBERTなど



### 転移学習(Fine-Tuning)

Pre-Trainingモデルを利用して文や単語をベクトルとして表現できれば、あとはクラスタリングやクラス分類など好きなように応用することができる。

これが<font color="red">**BERTがここまで注目されている最大の理由**</font>だ。

ここはデモを見てもらうのが早いのでサクサク次へ行こう。

## 環境構築

今回はGoogleColaboratory上で日本語BERTの環境構築を行う。

- [BERT日本語Pretrainedモデル - KUROHASHI-KAWAHARA LAB](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)を利用
- 単語分割ではNagisaを利用
    - [taishi-i/nagisa: A Japanese tokenizer based on recurrent neural networks](https://github.com/taishi-i/nagisa)

実際のモノを見た方が早い。
[nagisa_bert/nagisa_bert.ipynb](https://github.com/si1242/nagisa_bert/blob/master/nagisa_bert.ipynb)

中央の`Open in Colab`というボタンをクリックするとGoogleColabで直接実行できるようになる。初心者でもセルを実行していけば何となく雰囲気は理解できるのでは？


<img width="1007" alt="スクリーンショット 2019-12-15 6.24.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166931/661d71eb-6dcd-1218-6099-ce431ab6cf09.png">



### なんでnagisaなの？

今回扱うPre-TrainedモデルはJUMAN++という単語分割ツールを用いているので、本来ならそちらに合わせるべきだ。

だが、しかし、but…下記の理由でnagisaを使うことにする。

- `pip install nagisa`で導入できるのでGoogleColabで使いやすい
    - GoogleColabでJUMAN++を使うのはなかなかしんどいハズ
- single_word_listで柔軟に分割に対応できる
    - SentencePieceやJUMAN++では分割の粒度が細かすぎる
    - 特にナウい文章を分析するときは「タピオカミルクティー」とか「馬
と鹿」などを分割させたくない
- nagisa使ってみたい

確かにJuman++を使った方がvacabの語彙と近いというのはゴモットモだ。
だが、こちらをご覧いただきたい。

```Python:jumanpp
from pyknp import  Juman
jumanpp = Juman()

result = jumanpp.analysis("「馬と鹿」は日本の男性ミュージシャン米津玄師の楽曲だ") 
for mrph in result.mrph_list(): # 各形態素にアクセス
    print(mrph.midasi)
```

>['「', '馬', 'と', '鹿', '」', 'は', '日本', 'の', '男性', 'ミュージシャン', '米津', '玄師', 'の', '楽曲', 'だ']

え…「馬🐴」と「鹿🦌」？

「馬と鹿」がきちんとに分割されたところで、そんな単語ベクトルに誰が興味を持つんだ。だったら未知語[UNK]扱いでもいいから、「馬と鹿」という単語でしっかりベクトル化して欲しい。

米津玄師もそう。おそらくどちらも未知語扱いされそうなんだから「米津」「玄師」ではなくて「米津玄師」でしっかり一単語として処理して欲しい。


<font color="red">**nagisaのsingle_word_listという機能を使えばこれがうまいこと柔軟に制御できるのだ。**</font>


```Python:nagisa
import nagisa

text = "「馬と鹿」は日本の男性ミュージシャン米津玄師の楽曲だ"

words = nagisa.Tagger(single_word_list=['馬と鹿', '米津玄師']).tagging(text)
words.words
```

>['「', '馬と鹿', '」', 'は', '日本', 'の', '男性', 'ミュージシャン', '米津玄師', 'の', '楽曲', 'だ']

大量のデータを扱うときは処理性能で優れているJUMAN++の方がいいかもしれない。

## デモ


今年流行ったあの曲を可視化してみる

それでは聞いてください。

```text:crontab_database～君がしでかしてくれたもの～
君とジョブの終わり　将来の夢
大きなサイズ忘れない
1年後の8月14日午前7時00分（実際の日時）
また出会えるのを信じて
最高の思い出を

出会いはふっとした瞬間
帰り道の交差点でひらめいてくれたね
「シェルで書いてcronで処理すれば当日コマンド打たなくてよくね？」
僕は照れくさそうに
crontab -e を打ちながら
本当はとてもとても
手抜きをしてた

あぁvimでシェルを綺麗に書いて
ちょっとドヤ顔
あぁログが時間とともに流れる

嬉しくって楽しくって
冒険もいろいろしたよね

二人の秘密のリポジトリ

君とジョブの終わり　将来の夢
日付でファイル作成し
1日後の朝7時
また出会えるのを信じて
痛恨の権限エラー

＜中略＞

君が最後まで
心から「消すの忘れないで」
叫んでたこと知ってたよ
涙を堪えて笑顔でrm -f
せつないよね
でかすぎるそのサイズ

突然の実行でどうしようもなく
始末書書くよログも残すよ
忘れないでね crontab -r
いつまでも
```

</div></details>

![crontab.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166931/47c0b7c7-db33-64dd-cd46-7703d039352c.gif)

なんか見てると切なくなってくる…😥

## おわりに

実際に動かしてみたい方はこちらからどうぞ

https://projector.tensorflow.org/?config=https://gist.githubusercontent.com/si1242/bdf390e1a658bf96843c55d5ad8c4b61/raw/8e3afaa1926b3954a7e7cc09868805c42d9e9fd6/crontab_config.json


## 参考

- [crontab database ～君がしでかしてくれたもの～ - Qiita](https://qiita.com/raki/items/8aab477b4d64c7e7610e)
- [nagisa: RNNによる日本語単語分割・品詞タグ付けツール - Qiita](https://qiita.com/taishi-i/items/5b9275a606b392f7f58e)