BERTをお手軽に試すための環境

# はじめに
いつも参考にさせていただいている[Developers.IO](https://dev.classmethod.jp/)さんのサイトに掲載されている記事[BERTの日本語事前学習済みモデルでテキスト埋め込みをやってみる](https://dev.classmethod.jp/machine-learning/bert-text-embedding/)を見ると、いつものように試したくなるのが人情というもの。そして、色々とつまづいた点もあり、ちょっと修正しなければならなかったり、環境を揃えなければいけない点もありました。これらをできるだけ簡単に揃えて試すところまでできたので紹介します。

# ベースイメージ
やっぱりお手軽に試すには、サイズは小さい方が正義ということで、alpineベースにしようとしたけれど、そもそもtensorflowが対応していなかった（←:joy:）。ということで、tensorflowと親和性が良いUbuntuベースにpython環境があるものを利用する方針。
ただ、[python公式](https://hub.docker.com/_/python)を探しても基本debianベースのイメージしかなかったため、[3.7.5-slim-buster](https://github.com/docker-library/python/blob/0b1fb9529c79ea85b8c80ff3dd85a32a935b0346/3.7/buster/slim/Dockerfile)を参考にubuntuベースのイメージを用意して利用することとした。サイズ的にはまぁまぁな感じです。

```bash
0bara/python                   3.7.5-ubuntu18.04   167MB
python                         3.7.5-slim-buster   178MB
```

# juman++ & tensorflow
ただ、ベースイメージを小さくしても、結局形態素解析（とその辞書）やtensorflow動作環境を入れると大きくなってしまいます。

```dockerfile
# docker/Dockerfile
FROM 0bara/python:3.7.5-ubuntu18.04

MAINTAINER t.obara

RUN apt update -y && apt upgrade -y && \
        apt install -y --no-install-recommends \
        build-essential cmake git curl

WORKDIR /opt
COPY requirements.txt /opt
COPY bin /opt/bin
RUN curl -L https://github.com/ku-nlp/jumanpp/releases/download/v2.0.0-rc3/jumanpp-2.0.0-rc3.tar.xz -o /tmp/j.txz \
        && cd /tmp && tar xJfv j.txz \
        && cd jumanpp-2.0.0-rc3 \
        && mkdir build && cd build \
        && cmake .. -DCMAKE_BUILD_TYPE=Release \
        && make install \
        && cd /opt \
        && git clone https://github.com/0bara/bert.git \
        && pip install -r requirements.txt \
        && rm -rf /tmp/* \
        && apt autoremove -y build-essential cmake git curl \
        && apt -y clean \
        && rm -rf /var/cache/* \
        && rm -rf /var/lib/apt/lists/*

CMD ["/bin/bash"]
```

これでサイズが1.5G近く:scream:

```bash
bert_env                       latest              1.51GB
```
ちなみに、元の[BERTコード](https://github.com/google-research/bert)は、TensorFlow1.11.0で確認したとのことで、新しいTensorFlowだとエラーやワーニングが出てきます。既にdeprecatedなメソッドなどがエラー、もうすぐでdeprecatedだよというワーニングがある。基本的には`import tensorflow as tf`を、`import tensorflow.compat.v1 as tf`とするとかなりの軽減される。ワーニングも全て解消したいとは思ったのだけれど、それなりにあるLayerをEager modeに変換するのは厄介なので、その部分のワーニングは放置したけど、それなりに動くようにした[コード](https://github.com/0bara/bert)を置いたので、Docker Image内で取り込むようにしている。修正するのであれば、「[Migrate your TensorFlow 1 code to TensorFlow 2](https://www.tensorflow.org/guide/migrate)」とか、「[Writing custom layers and models with Keras](https://www.tensorflow.org/guide/keras/custom_layers_and_models)」を読むのが良さそうです。
あ、肝心な事を忘れていましたが、Juman++を利用して日本語をトークン化する部分も同時に追加しています。この部分は[元のページ](https://dev.classmethod.jp/machine-learning/bert-text-embedding/)に詳しく記述されているので、ここでは特に触れない。

# 事前学習モデル
さらに事前学習モデルがあるともっと大きくなるのですが、このモデル自体は色々と出てくるでしょうし、ここに入れてより簡単にするか手間を増やすか考えたのですが、複数のモデルを同時に利用することもないだろうということから、利用したいモデルを選んで（自分でダウンロードして）利用していただく方法とした。（比較したい人は同梱の方が楽でしょうけど。）
一応、ダウンロードして動く事を確認したのは以下3つのモデルです。

  1. [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)
  1. [BERT日本語Pretrainedモデル Whole Word Masking版](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)
  1. [日本語ビジネスニュース記事(300万記事)](https://qiita.com/mkt3/items/3c1278339ff1bcc0187f)

1、2は同じサイトで、それぞれtensorflow用のzipファイルをダウンロードし、所定の場所に配置してください。3については「ダウンロードリンク」が示されているので、そこを辿り、GoogleDriveにあるtensorflow版に個々のファイルがあるので、それぞれダウンロードして所定の場所においてください。所定の場所については、以降に記載する手順の中にあります。

# 環境構築準備
それほど難しい作業ではないけれど、手軽と言いつつ、それなりに面倒かもしれない。
以下に手順を示す。

```bash
# 作成したファイルのダウンロード
$ git clone https://github.com/0bara/bert_env.git
$ cd bert_env
# Dockerイメージの作成
$ docker-compose build
# extract_features.pyを実行する際に入力するテキストをinput.txtを配置する場所としてworkディレクトリを用意する。ここには実行結果（output.jsonlと、output.tsv)が出力される。binディレクトリに入力例のテキストを配置しているので、binをworkにシンボリックリンクしている。自分で入力テキストを用意するのであれば、workディレクトリを作成するだけで良い。
$ ln -s docker/bin work
$ cd work
$ ln -s input_ex1.txt input.txt
$ cd ..
# モデルデータを用意する
$ mkdir model
# このディレクトリにダウンロードしたファイルを配置する
$ curl http://nlp.ist.i.kyoto-u.ac.jp/nl-resource/JapaneseBertPretrainedModel/Japanese_L-12_H-768_A-12_E-30_BPE.zip -o model/Japanese_L-12_H-768_A-12_E-30_BPE.zip
$ cd model
$ unzip Japanese_L-12_H-768_A-12_E-30_BPE.zip
$ cd ..
```
上記の作業により、以下のようなファイル構成となっていれば良い。

```bash
        $ tree
        .
        ├── README.md
        ├── docker
        │   ├── Dockerfile
        │   ├── bin
        │   │   ├── btest.sh
        │   │   ├── conv_tsv.py
        │   │   ├── elmo.env
        │   │   ├── input.txt -> input_ex1.txt
        │   │   ├── input_ex1.txt
        │   │   ├── input_ex2.txt
        │   │   ├── norm.env
        │   │   ├── output.jsonl
        │   │   ├── output.tsv
        │   │   └── wwm.env
        │   ├── jumanpp-2.0.0-rc3.tar.xz
        │   └── requirements.txt
        ├── docker-compose.yml
        ├── model
        │   ├── Japanese_L-12_H-768_A-12_E-30_BPE
        │   │   ├── README.txt
        │   │   ├── bert_config.json
        │   │   ├── bert_model.ckpt.data-00000-of-00001
        │   │   ├── bert_model.ckpt.index
        │   │   ├── bert_model.ckpt.meta
        │   │   └── vocab.txt
        │   └── Japanese_L-12_H-768_A-12_E-30_BPE.zip
        └── work -> docker/bin
```

# 実行してみる
```bash
$ docker-compose up
```
これを行うと、workディレクトリにoutput.jsonlとoutput.tsvが出力される。

# 可視化
これも元のページと同じなので軽く
[Embedding Projector](http://projector.tensorflow.org/)を利用して可視化できるようにしています。利用するのは、入力で利用したinput.txtと出力されたoutput.tsvです。

 1. 上記[Embedding Projector](http://projector.tensorflow.org/)を開く
 1. 左側ペインにあるLoadボタンを押すとダイヤログが開く
 1. 上部[Step 1: Load a TSV file of vectors]に対してoutput.tsvを指定
 1. 下部[Step 2 (optional): Load a TSV file of metadata]にinput.txtを指定
 ダイヤログ以外をクリックすると結果が表示される

# 別のモデルを利用する場合
## Whole Word Masking model
tensorflow版のJapanese_L-12_H-768_A-12_E-30_BPE_WWM.zip をダウンロードし、modelディレクトリに置いてunzipする
以下のような配置になれば良いかと。

```bash
        ├── model
        │   ├── Japanese_L-12_H-768_A-12_E-30_BPE_WWM
```
そして、以下のコマンドで実行する

```bash
$ docker-compose run bert /bin/sh bin/btest.sh bin/wwm.env
```
ちなみに、bin/wwm.envファイルは環境変数を設定しているファイルで、各モデルがあるディレクトリに応じて変更すれば良いが、上記の配置にしておけば、そのまま修正する必要はない。
これは次のモデルでも同様である。

## 日本語ビジネスニュース記事(300万記事)

modelディレクトリ直下にELMoディレクトリを作成し、ダウンロードリンクからダウンロードした各ファイルを配置する。

```bash
├── ELMo
│   ├── bert_config.json
│   ├── output_model.ckpt.data-00000-of-00001
│   ├── output_model.ckpt.index
│   ├── output_model.ckpt.meta
│   └── vocab.txt

```
そして、以下のコマンドで実行する

```bash
$ docker-compose run bert /bin/sh bin/btest.sh bin/elmo.env
```

# 参考ページ
- https://dev.classmethod.jp/machine-learning/bert-text-embedding/
- 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
- https://qiita.com/mkt3/items/3c1278339ff1bcc0187f

# 最後に
本当はGPUを利用した場合も試したかったのだけれど、すぐ使えるモノがなかったのと、AWSで試そうかと調べたんだけど、そこまでの元気がありませんでした。。
また、「機械学習ツールを掘り下げる」のAdventCalendarに参加している割に、機械学習という単語がタイトルにもタグも入れないという記事ですが、生暖かい感じで流し読みいただけたら幸いです。