【TPUを使いたい人向け】Tensorflow Object Detection API を使ってみた 2

# はじめに
RHEMS技研のIchiLabです。
（※タイトルのTPUはTensor Processing Unitの略であり、熱可塑性ポリウレタンの事ではありませんのでご注意を。）

前回記事はこちらです。
[【初心者向け】Tensorflow Object Detection API を使ってみた](https://qiita.com/IchiLab/items/fd99bcd92670607f8f9b)

TensorFlowのObject Detection API（以下、API）は物体検出のAIを作るのにとても便利です。
一方、以下のような悩みを持つ方は少なくないと思います。
* 学習時間が長すぎる
* 高性能なGPU(Tensorコアが搭載されている等)は高価すぎて買えない
* TPUというディープラーニングに最適なものがあるらしいがどう使えば良いかよく分からない
* Cloud TPUはめちゃくちゃ使用料が高すぎる

今回は、個人レベルでもお金が出せそうな範囲でCloud TPUを使用できた方法について、この場を借りて備忘録として残しておきます。
本記事の方法でAPIを使用すると、GCPのCloud TPUを最初から最後まで使うよりもずっとずっと安く済ませることが可能になります。

皆様にも少しでもお役に立てれば幸いです。

# 前置き
## 大まかな方法
結論から書くと、できるだけ安く抑えてTPUを用いてAPIを使用する方法は以下の内容がベストだと考えます。
1. Google Colaboratoryの無料枠で学習させる
2. 無料枠から弾かれている間に学習させたい場合は、GCPのVMとCloud TPU（いずれもプリエンプティブ）で続きを行う

Google Colaboratoryは他記事でもたくさん紹介されているので詳細は割愛しますが、
* 無料で使える（12時間未満の制限あり）
* ブラウザからPythonを動かせる
* Jupyter Notebookのような操作性
* 高価なGPU・TPUが無料で使える
といった特徴の素晴らしいサービスです。

素晴らしい上に高性能な環境を無料で手に入れられる代償として、使いすぎるとしばらくGPUまたはTPUを使わせてもらえないことがあります。

そのような時に、自前でも同様の環境を用意できるようにしておくと、お金はかかりますが再び使えるまで待つ時間は節約できますよね。

[Google Colaboratory Pro](https://colab.research.google.com/signup)という月額9.99ドルの素敵なサービスもありますが、本記事執筆時点(2020/06)では米国のみのサービスとなっております。
（日本からでも登録できたという他記事もありますが、規約違反になる可能性もありますので自己責任で & 私は試していません）

## 前提条件
ここでの説明は、以下の条件を前提として記載しております。
* APIを使用したことがある（推奨）
* GCPを触ったことがある（推奨）
* アノテーション済みの教師用データが存在する（必須）
* Googleアカウントを持っている（必須）
* GCPの課金設定を終えている（クレジットカード等の登録）（必須）


## 注意事項（お金の話）
ここで紹介する方法は、必ず[GCP](https://console.cloud.google.com/?hl=ja)のサービスを使用します。
そしていずれの方法でも[Cloud Storage](https://cloud.google.com/storage?hl=ja)の利用料などで必ずお金がかかってきます。
GCPは初回だと[300ドルの無料枠](https://cloud.google.com/free/docs/gcp-free-tier?hl=ja)が用意されていますが、
無料枠にTPUの使用料は含まれていなかったり、Cloud Storageの無料枠にもいくつか制限がありますので、必ずご自身で内容を確認してから行ってください。
(Cloud MLには無料枠が用意されていますが、私は試しておりません）

# 共通の前準備
TPUを使用して学習させるためには、[Cloud Storage](https://cloud.google.com/storage?hl=ja)に保管している必要があります。

ここでは、以下の名称であることと仮定して説明していきます。
`プロジェクトID : ` gcp-project-123
`バケット名 : ` my-bucket-123

ローカルPCから手早くバケットにコピーしたいなら、`-m`オプションを忘れずに！

```console:Macのzshで今いるディレクトリのフォルダをバケットに送り込むコマンド例
gsutil -m cp -r \* gs://my-bucket-123/
```

バケット内のフォルダ構成は、以下のようにしました。
（※以降の説明もこの構成を前提に話を進めていきます）

```console
gs://my-bucket-123/
├── models
│     ├── ssd_mobilenet_v1_fpn (転移学習元のモデルデータ)
│     　　　　　　　　└──　.ckpt 他いろいろ
├── data
│     ├── save (学習データ保管用ディレクトリ)
│     ├── train (教師用データ保管用 ~ tfrecord)
│     └── val (検証用データ保管用 ~ tfrecord)
├── hoge.config (コンフィグデータ)
└── tf_label_map.pbtxt (ラベルデータ)
```

今回は転移学習元に、[ssd_mobilenet_v1_fpn_coco](http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v1_fpn_shared_box_predictor_640x640_coco14_sync_2018_07_03.tar.gz)を使用しました。
なお、[Tensorflow detection model zoo](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md)のページには、TPUをサポートしたtrained modelに☆マークがついています。

コンフィグの内容については[前記事](https://qiita.com/IchiLab/items/fd99bcd92670607f8f9b#%E3%82%B3%E3%83%B3%E3%83%95%E3%82%A3%E3%82%B0%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E7%B7%A8%E9%9B%86)で詳しく説明しましたので割愛しますが、
上記ファイルがCloud Storageになったことで、以下の項目もそれに合わせる必要があります。

```console
fine_tune_checkpoint: "gs://my-bucket-123/models/ssd_mobilenet_v1_fpn/model.ckpt"
label_map_path: "gs://my-bucket-123/tf_label_map.pbtxt"
input_path: "gs://my-bucket-123/data/train/{filename}.tfrecord
input_path: "gs://my-bucket-123/data/val/{filename}.tfrecord
```
（※{filename}の部分の書き方は[前記事](https://qiita.com/IchiLab/items/fd99bcd92670607f8f9b#%E3%82%B3%E3%83%B3%E3%83%95%E3%82%A3%E3%82%B0%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E7%B7%A8%E9%9B%86)参照）

# 1. Google Colaboratoryを使って行う方法
無料枠で行えるうちは、こちらで行いましょう。
ただし、先述しましたがCloud Storageの料金はかかってきます。

## 1-1. APIのソースをドライブに置く
ローカルでもコンテナでも良いので、一度`git clone`したものを、自分のGoogleドライブ内に保管します。

ちなみに最新のmasterでやるとうまく出来たものが色々と出来なくなったりとトラブルが多かったので、
私の試したおすすめは以下のブランチです。

```console
git clone -b tf_2_1_reference https://github.com/tensorflow/models.git
```

cocoAPIも忘れずに。

```console
git clone --depth 1 https://github.com/cocodataset/cocoapi.git
```

なお、ここではソースコードは以下のディレクトリに置くと仮定します。

```console
/content/drive/My Drive/models/research
/content/drive/My Drive/cocoapi/PythonAPI
```

## 1-2. 新しいノートブックを作る

ブラウザ上で、Googleドライブから、
「新規」→「その他」→「Google Colaboratory」を選択します。

タイトルを、「Untitled0.ipynb」から任意の名前に変更します。（推奨）

上のメニューから、「ランタイム」→「ランタイムのタイプを変更」→ハードウェア アクセラレータを「TPU」に指定し、「保存」します。
次に、「接続」を選択します。

## 1-3. Googleドライブのマウント
まずはAPIのソースが読めないと始まらないのでマウントします。

```python
from google.colab import drive
drive.mount('/content/drive')
```

## 1-4. GCPプロジェクトの設定
Cloud Storageと連携するために、gcloudコマンドでプロジェクトの設定を行います。

```python
from google.colab import auth
auth.authenticate_user()
project_id = 'gcp-project-123'
!gcloud config set project {project_id}
!gsutil ls my-bucket-123
```

Googleドライブと同様の認証です。
成功すると、バケットの中身が`ls`コマンドによって確認出来ます。

## 1-5. cocoAPIのインストール（初回のみ）

```console
%cd /content/drive/My\ Drive/cocoapi/PythonAPI
!make
!cp -r pycocotools /content/drive/My\ Drive/models/research/
```

## 1-6. protocの実行（初回のみ）
.protoを.pyに変換します。

```console
%cd /content/drive/My\ Drive/models/research
!protoc object_detection/protos/*.proto --python_out=.
```

## 1-7. tensorflowのバージョンを変更する
Tensorflow Object Detection APIはtensorflowの2.Xに対応していません。
一方、Google Colaboratoryは初めから2.X系がインストールされています。
そのため、バージョンを確認してインストールし直す必要があります。

```console
!pip list | grep tensor
```

```console
!pip install tensorflow==1.15.0rc3
```

## 1-8. 環境変数の設定

```console
%env PYTHONPATH=/env/python:/content/drive/My Drive/models/research:/content/drive/My Drive/models/research/slim
```

## 1-9. APIのテストコードの実行
無事に環境構築を終えているかをテストしましょう。
うまくいっていれば「OK」と複数行にわたって表示されます。

ちなみに本記事では少し古いブランチのソースを使用していますが、最新では`model_builder_tf1_test.py`という名前に変更されています。

```
%cd /content/drive/My Drive/models/research
!python object_detection/builders/model_builder_test.py
```

## 1-10. Tensorboard起動（必須ではない）
以下のように学習データを保存するディレクトリを指定して起動しておくと、lossの動きや1秒あたりの学習step数などが確認できます。

```
%load_ext tensorboard
%tensorboard --logdir gs://my-bucket-123/data/save
```

## 1-11. 学習開始
学習には、`model_main.py`ではなく、`model_tpu_main.py`を使用します。
オプションにGCPプロジェクトIDやTPU名を指定することができますが、Google Colaboratoryの環境内では不要でした。
おそらく環境変数に元からTPUのアドレスが登録されているからなのではないかと思われます（推測）。
（`%env`で確認すると`TPU_NAME`という名称で`grpc://`に続くTPUのアドレスが登録されています）

```python
%cd /content/drive/My Drive/models/research
pipeline = 'gs://my-bucket-123/hoge.config'
save = 'gs://my-bucket-123/data/save'
train_step = 1000
mode = 'train'
batch_size = 64

!python object_detection/model_tpu_main.py \
 --pipeline_config_path={pipeline} \
 --mode={mode} \
 --num_train_steps={train_step} \
 --eval_training_data=True \
 --train_batch_size={batch_size} \
 --model_dir={save} \
 --alsologtostderra
```

## 1-12. おまけ（Colabratoryの使用時間を確認する方法）
Google Colaboratoryは12時間未満の使用制限がありますが、
実際にあと何時間使えるのかがどこかに書いているわけではありません。
下記のコードを実行すると、それを知ることが出来ます。

pythonだとdatetimeモジュールが便利ですが、あえて使用せず書いてみました。

```python
import time, psutil

COLAB_LIMIT_HOUR = 12 # Google Colabratoryの無料枠は約12時間

def time_conversion(second):
  h = second // 3600
  m = second % 3600 // 60
  s = second % 60
  return h, m, s

now = time.time() # 今の時間
boottime = psutil.boot_time() # 起動した時間

elapsed_time = now - boottime # 経過時間
remaining_time = COLAB_LIMIT_HOUR * 60 * 60 - elapsed_time # 残り時間

print("経過時間は %d 時間 %d 分 %d 秒です" % (time_conversion(elapsed_time)))
print("残り時間は %d 時間 %d 分 %d 秒です" % (time_conversion(remaining_time)))
```

学習を始めてしまうと終わるまで他の実行は待機になってしまうので、
これをやるときは何かの処理が完了している場合にしましょう。

# 2. 自分でGCPのVMとCloud TPUを立ち上げて行う方法
さて、もしもGoogle Colaboratoryで「しばらく使えないから待ってね」と言われてしまい、どうしても待てない場合はこちらの手段で実施してみましょう。

## 2-1. VMとCloud TPUを立ち上げる
はじめに、Compute EngineとCloud TPUを有効にしておく必要があります。
初回は下記のように表示されます（執筆時点の画像）
`Compute Engineの場合`
左上の「ナビゲーションメニュー」→「Compute Engine」→「VMとインスタンス」
![ss001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499855/975e4bde-607b-31fd-65b5-f9170821cb8f.png)
自動で準備が始まる。

`Cloud TPUの場合`
左上の「ナビゲーションメニュー」→「Compute Engine」→「TPU」

![ss002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499855/2d6abb73-b720-c6b1-8836-c08ca3cfd4d8.png)
初回は「APIを有効にする」を選択する必要がある。
（これだけでTPUの課金は始まらないのでご安心を）

これらが準備出来た方、もしくは既に有効になっている方は、Cloud Shellを開きます。
Cloud Shellは右上の方に下のようなアイコンがあります。
![ss003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499855/daedc6fa-3117-0a06-dae0-0f619523e01f.png)
少し待って開いたら、`ctpuコマンド`でVMとTPUを同時に立ち上げます。

```console
ctpu up --zone=us-central1-b --tf-version=1.15 --machine-type=n1-standard-4 --name=mytpu --preemptible --preemptible-vm
```

ここでの重要なポイントは、VMとTPUのオプションに`preemptible`を入れること、
つまりプリエンプティブを使用することです。

次の表は、公式の料金計算ツールで、TPU V2のLocationがus-central1の場合で計算した結果です。

| TPU Class |  Regular | Preemptible |
| --- | --- | --- |
| 1時間あたり | 約485円 | 約146円 |

プリエンプティブに関しては、[公式ドキュメント](https://cloud.google.com/tpu/docs/preemptible?hl=ja#tpu-vm-preemptible)をご参照ください。

コンソールやgcloudコマンドからでも同様の操作は可能です。
詳しくは公式ドキュメントの[TPUの作成と削除](https://cloud.google.com/tpu/docs/creating-deleting-tpus?hl=ja)に記載されています。

コマンドを実行すると、下記のように確認が表示されます。

```console
  Name:                 mytpu
  Zone:                 us-central1-b
  GCP Project:          gcp-project-123
  TensorFlow Version:   1.15
  VM:
      Machine Type:     n1-standard-4
      Disk Size:        250 GB
      Preemptible:      true
  Cloud TPU:
      Size:             v2-8
      Preemptible:      true
      Reserved:         false
OK to create your Cloud TPU resources with the above configuration? [Yn]:
```
`y`を入力して`Enter / return`でそれぞれの作成が始まります。
machine typeに`n1-standard-4`を選択した理由は、Google Colaboratory環境のメモリに近いからというだけですので、必要に応じて変更してください。

ちなみに、もしも誤ってCompute Engineのデフォルトサービスアカウントを消してしまっていると、上記のctpuコマンドでの作成が不可能になってしまいます。

```
2020/06/20 00:00:00 Creating Compute Engine VM mytpu (this may take a minute)...
2020/06/20 00:00:07 TPU operation still running...
2020/06/20 00:00:07 error retrieving Compute Engine zone operation: 
```
（こんな感じのエラーに... いつ消してしまったのでしょうか？）
解決策が分からなかったので私は新しいプロジェクトを作り直しました。


「・・・時を戻そう。」
## 2-2. 起動したばかりのTPUを停止させる
Cloud TPUは秒単位で課金されていきます。
無事起動が確認できたら、とりあえず速攻「停止」させましょう。

## 2-3. SSHで作成したVMインスタンスの中に入る
インスタンスが無事起動したら、下記の「SSH」から中に入りましょう。

![ss004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499855/0516df86-bdf8-a88f-adb6-23acc4f467b6.png)

接続が完了すると、下記のようにコンソール画面が開きました。
![ss005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499855/084fb86d-927a-2d2b-d538-a39923fedb2c.png)

ここからはこの中で作業していきます。

なお、ここで`gcloudコマンド`でTPUの状態を確認することも可能です。

```console

gcloud config set compute/zone us-central1-b
Updated property [compute/zone].

gcloud compute tpus list
NAME   ZONE           ACCELERATOR_TYPE  NETWORK_ENDPOINTS  NETWORK  RANGE          STATUS
mytpu  us-central1-b  v2-8              10.240.1.2:8470    default  10.240.1.0/29  STOPPING
```

余談ですがTPUのステータスは次のように表示されます。

| 作成中  |  起動中  |  起動  |  停止中  |  停止  |
| ---- | ---- | ---- | ---- | ---- |
| CREATING | STARTING | READY | STOPPING | STOPPED |

## 2-4. aliasを設定（必須ではない）
pythonなのかpython3なのか、はっきり統一させたいのでこの項目を加えました。
常にpythonは3.Xを使っていきたいので、以下のように設定を変更します。

`.bashrcを開く`

```
vi ~/.bashrc
```

`1番最後の行に設定を追加する`
※`I`押して`Shift+G`で1番下へ移動して下記入力して`esc`押して`:wq`で上書き！

```
alias python="python3" 
alias pip='pip3'
```

`設定を反映`

```
source ~/.bashrc
```

これで`python`は`python3`ということになりました。

## 2-5. 必要なライブラリのインストール
ここからはAPIの環境構築とほとんど同じになりますが、割愛せず記載していきます。

```
sudo apt-get update
sudo apt-get install -y protobuf-compiler python-pil python-lxml python-tk
pip install -U pip && pip install Cython contextlib2 jupyter matplotlib tf_slim pillow
```

次にAPIのソースコードとcocoAPIを持ってきます。

```
git clone -b tf_2_1_reference https://github.com/tensorflow/models.git
git clone --depth 1 https://github.com/cocodataset/cocoapi.git
```

繰り返しますが本記事の説明で使用しているAPIのソースコードは上記のブランチです。

## 2-6. cocoAPIのインストール
続いてcocoAPIのインストール。

`make`で以下のように失敗してしまいました。

```
x86_64-linux-gnu-gcc: error: pycocotools/_mask.c: No such file or directory
```

これを回避するために、Makefileを少し変更します。

```
cd cocoapi/PythonAPI
vi Makefile
```

Makefileを開いたら、`python`の部分を`python3`に変更します（2箇所あります）。

```
make
cp -r pycocotools /home/ichilab/models/research && cd ../../ && rm -rf cocoapi
```
※ichilabの部分はあなたのユーザー名に変更してください。

## 2-7. protocの実行
.protoを.pyに変換します。

```
cd models/research
protoc object_detection/protos/*.proto --python_out=.
```

## 2-8. 環境変数の設定
ここの部分は一度SSHの画面を閉じてしまうと、再度行う必要がありました。

```
(pwd = models/research)
export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim
source ~/.bashrc
```

## 2-9. APIのテストコードの実行
無事に環境構築を終えているかをテストしましょう。
成功なら「OK」と複数行にわたって表示されます。

```
python object_detection/builders/model_builder_test.py
```

一応バケットの中身も見えるか確かめておくと良いかもしれませんね。

```
gsutil ls gs://my-bucket-123
```

## 2-10. 止めていたTPUを再開させる
停止させたままでは学習できませんので、ここで再び起動させましょう。
起動が確認できたら次です。

## 2-11. 学習開始
学習開始です。

```console
python object_detection/model_tpu_main.py \ 
--tpu_name=mytpu \ 
--model_dir=gs://my-bucket-123/data/save \ 
--mode=train \ 
--pipeline_config_path=gs://my-bucket-123/hoge.config \ 
--alsologtostderra
```

オプションの説明を簡単に書きます。
* `--gcp_project` : プロジェクトIDです。省略可能でした。
* `--tpu_name` : ctpuコマンドで立ち上げた時に名付けたものです。
* `--tpu_zone` : TPUのタイムゾーンです。省略可能でした。
* `--model_dir` : 学習させたckptファイルたちの保存先を指定します。
* `--pipeline_config_path` : 設定（コンフィグ）ファイルの保存場所を指定します。

ちなみに私はここで最新のソースを使用した場合、

```console
tensorflow.python.framework.errors_impl.InvalidArgumentError: From /job:tpu_worker/replica:0/task:0:
```
のエラーに相当悩まされました。
これが先述したブランチのソースコードを使用している唯一の理由です。
コンフィグもその他のファイルも全く同じ条件下でしたので、今のところ原因は分かっておりません。

## 2-12. 学習が終わったらTPUとVMを停止・削除
用が済んだら、TPUを優先的に停止・削除しましょう。


# 料金はどれくらいになるのか
一通り環境構築の説明を終えたところで、結局どれくらい金がかかるのかが気になりますよね。

ちゃんとした比較を載せられないのは申し訳ないのですが、
Google Colaboratoryで100,000Step学習させたときは、400円もいかないくらいで済んでいます。
自分のプロジェクトでVMとTPUを立ち上げて使用した場合、さすがに100,000Stepも実施したことはないのですが、
TPUは先述した料金を目安に考えていただいて、Compute Engineが約6時間使用して4円、External IPの使用料に1円で合計10円未満でした。

この辺は私の記事よりも公式の料金計算ツールを使用していただくのが限りなく正解に近いでしょう。

# おわりに
いかがでしたでしょうか？

意外と`Cloud TPU × Tensorflow Object Detection API`の環境構築についてのまとめ記事が見つからなかったりするので、これを機にTPUで学習させてみようと思う方や、GCPに興味を持つ方が増えれば幸いです。

学習速度の加速とともに皆様の物体検出AIの研究も加速されることを心より祈っております。