GCP+DockerでKaggle用GPU環境構築

# はじめに
本記事ではGCPでKaggle用の計算・開発環境を構築した手順を公開する。
以前はKaggleのKernelで計算していたのだが、以下の理由からKaggle Kernelでは物足りなくなってきた。

- KernelからScriptを呼び出すのがめんどくさい（都度インポートが必要）
- Kernelのメモリ不足
- Gitでソースのバージョン管理をしたい（実験の再現性）

# 要件
機械学習環境についての要件は以下の通り。

- pythonスクリプトをバッチ処理で実行できること
- Gitでバージョン管理ができること
    - 実験の再現性を担保するため
- 計算リソースがスケーラブルであること
    - コンペの段階に応じて、計算リソースを自由に変更したいため
- Kaggle Kernelで実行結果を再現できること
    - Kernel Onlyのコンペに参加することもあるため

加えて、できるだけ安く（できれば無料で！）利用できること。←重要


# 計算環境

## GCP
クラウドの計算環境はGCPの他にAmazon Web Service, Microsoft Azureなどがある。今回はKaggleやTensorflowと相性の良さそうなGoogle Cloud Platform(GCP)を使用した。
GCPへ登録すると最初に30000円分のクレジットがもらえるので、無料でもしばらく使える。

### GPUの有効化
無課金状態ではGPUが利用できない。GPUを利用できるようにするには、以下の2手順を実施する。
この時点ではまだ課金は発生しない。

- クレジットカードを登録する
- GCPのコンソールより IAMと管理 > 割り当てを選択し、フィルタを指標：なしでクリアしてからNVIDIA P100など適当なGPUを選択する。しばらくするとメールが届き、インスタンス作成時にGPUが選択可能となる


## インスタンスの作成
GCPのCompute Engine > VM インスタンス > インスタンスを作成　を選択。

```:bash
# リモート接続方法(gcloudコマンドをインストールしておくこと)
$ gcloud compute ssh INSTANCE --zone ZONE
```

### OS
Dockerで環境を構築するので何でもよいのだが、Debian系で基本的なコマンドが入っていること、GPUを使う前提のイメージなので相性問題が起きにくそうなDeep Learning VMを選択。
Container Optimized OSも使ってみたのだが、デフォルトで入っているコマンドが最小限なのがネックだった。例えば```apt```は入っているが```yum```は入っていないので注意。基本的にすべて```wget```を使わないといけず、コンテナに入っていないちょこっとしたアプリのインストールが面倒になる。
Ubuntu OSはデフォルトでDockerが入っていないので最初のインストールがやや面倒。

[2020/3/20追記]
CentOS8で環境を構築してみた。yumコマンドで大抵のパッケージはインストールできるので、非常におすすめできる。
初期状態ではgit, dockerがインストールされていないが```sudo yum install [package]```すればよい。


### ディスク容量
デフォルトは10GBだが、30GBまでは無料で増やせる。
インスタンス作成後でも容量は変更できるのでそこまで気にする必要はないが、KaggleのDockerイメージに24GB使ってしまうため画像コンペに参加する場合は30GBだと足りなくなると思われる。

## rootになる
```
sudo su
```
## Docker

Kaggle公式のDockerを使う。CPU版とGPU版があり、CPU版はimageが公開されているがGPU版はimageが公開されていない。Deep Learningの実行にはGPUが不可欠なため、GPU版をbuildする。
CPUのみのインスタンスでGPU版を使っても特に問題は発生しなかったので、GPU版の利用をおすすめする。
イメージが非常に大きい。空き容量を24GB確保しておく必要がある。

### GPUイメージのビルド
```bash:
# clone
git clone https://github.com/Kaggle/docker-python.git
cd docker-python

# --gpuオプションでビルド
./build --gpu

# イメージを確認
docker images
```

### コンテナの生成
```bash:
# コンテナを生成
$ docker run -itd \
  -p 8080:8080 \
  -v HOSTDIR:LOCALDIR \
  --name CONTAINER \
  -h HOSTNAME \
  IMAGE /bin/bash

# 例
$ docker run -itd \
  -p 8080:8080 \
  -v /home:/home \
  --name cont-1 \
  -h host \
  kaggle-env:20190815-1 /bin/bash

# オプションの指定
# -it : 標準出力をコンソールに繋げる
# -d : 起動時にコンテナに入らない
# -p : hostとコンテナ間のポートフォワーディング。後述のJupyter Notebookを使う場合は必要
# -v : インスタンス上のディレクトリをコンテナにマウントする
# docker run [オプション] [イメージ]の順でないといけないので注意。

# コンテナを起動する
$ docker container start CONTAINER

# コンテナに入る
$ docker container attach CONTAINER

# コンテナを停止せずにデタッチ
ctrl-p,q

# コンテナを停止してデタッチ
$ exit

# コンテナを削除
$ docker container rm CONTAINER
```

## コンテナ環境のセットアップ

コンテナ上でプログラムを編集、実行する際に必要な環境設定を行う。
私のリポジトリに設定をまとめたスクリプトを置いている。
https://github.com/yota-p/kaggle-env

設定するには、コンテナ上で以下を実行する。
```bash:
$ git clone https://github.com/yota-p/kaggle-env.git
$ cd kaggle-env
$ ./setup.sh```

上記リポジトリの```.bashrc```に記載しているが、以下を実行しておかないとコンテナからGPUを使えないので注意。

```bash:
export LD_LIBRARY_PATH=/usr/local/cuda/lib64
```

## タイムゾーンの変更
デフォルトはUTCだが、自分のタイムゾーンに合わせておいた方がわかりやすい。
以下はDebian9の手順。
```bash
$ rm /etc/localtime # シンボリックリンクを削除
$ echo Asia/Tokyo > /etc/timezone # timezone上書き
$ dpkg-reconfigure -f noninteractive tzdata # シンボリックリンク再作成

Current default time zone: 'Asia/Tokyo'
Local time is now:      Wed Oct 17 01:40:34 JST 2018.
Universal Time is now:  Tue Oct 16 16:40:34 UTC 2018.
```

## GPUの動作確認

コンテナからGPUが認識できていることを確認する。
```$ nvidia-smi```でインスタンス生成時に指定したGPUが表示されればOK。

TensorflowでGPUの動作確認を行う。
```python```でPythonクライアントを立ち上げ、以下を実行。同じくインスタンス生成時に指定したGPUが表示されればOK。

```python:
from tensorflow.python.client import device_lib
device_lib.list_local_devices()
```

## Jupyter Notebookサーバの疎通確認

自分のPC - インスタンス - Dockerコンテナの接続経路を確保することで、自分のPCからサーバにあるJupyter Notebookを実行できるようにする。

```bash:
$ nohup jupyter notebook --port 8888 --ip=0.0.0.0 --allow-root >> notebooks/jupyter.log 2>&1 &
```

自分のPCからポートフォワーディングssh。portはインスタンス側のポート番号を指定する。

```bash:
$ gcloud compute ssh instance-copt-1 --zone asia-northeast1-b -- -N -L 8888:localhost:8888
```

Jupyterサーバを立ち上げる。
jupyter.logにtokenの入ったURLが出力されるのでコピー。portはインスタンス生成時に指定したコンテナ側のポート番号を指定する。
```nohup```コマンドを使用することでバックグラウンドで実行される。

コピーしたURLへブラウザでアクセスすれば、Jupyter Notebookが利用できる。jupyter.logに出力されるURLそのままではなく、"localhost"にする。
http://localhost:8888/?token=xxxxxx

M-x package-install -> solarized-themeを実行


## 環境構築完了

お疲れ様でした！ガンガンGCPで計算を回していきましょう。

## その他

以下は自分の備忘録代わりです。

### CookieCutter
様々なプロジェクト用のディレクトリのテンプレートが使えるライブラリ。
ディレクトリ構成に悩む必要がなくなる。
```$ pip install cookiecutter```

機械学習界隈だとcookiecutter-data-scienceが有名なようで、自分もこれをカスタマイズして使っている。
https://github.com/drivendata/cookiecutter-data-science

### git cloneしたらpermission denied
GitHubに認証鍵を登録していない状態でリポジトリオーナ用のgit:~から始まるURLをcloneしようとしたため。基本的にhttps:~で始まるURLを利用する。プライベートリポジトリを利用している場合のみgit:~をcloneできる。

### Torqueをインストールする
ジョブスケジューラ。まだそこまでJOBを投入しないので必要ないが、いずれ導入するかも。