Kaggle・機械学習実験用Docker環境構築入門

# はじめに

初めましての方は初めまして，最近Volareという学生団体に所属することになったtattakaです．
普段は関西の大学院の修士課程でロボットビジョンの研究をしていて，趣味ではKaggleの主に画像コンペに参加しています．
この記事では機械学習の研究やKaggleで使いまわせる環境の構築について書いていこうと思います．
（この記事は[Volare Advent Calendar 2019](https://qiita.com/advent-calendar/2019/volare)の24日目です．）

### この記事で書くこと
* Kaggleや機械学習系の研究室でDockerを使う利点
* ディレクトリ構成について
* DockerHubを使ったイメージの管理

### 書かないこと
* NVIDIA Docker・Docker Composeの話
    * （このあたりは筆者が~~怠慢~~諸事情により最新バージョンを追えていないので......）

# 1. Kaggleや機械学習系の研究室でDockerを使う利点
具体的な環境構築の前に，Dockerを使ってKaggleや機械学習の環境を構築することの利点について述べていきます．  

* 環境構築するときの虚無の緩和
ホスト環境に影響を与えないのでインストール方法などの試行錯誤がしやすいです．
また，既存環境との衝突をあまり考えなくてもよくなります．

* 環境汚染からの脱却
同じイメージからは同じコンテナが生成されるので環境を汚してもコンテナを作り直せば良いので直で環境構築をするよりは環境が汚れにくいです．

* 環境の共有が楽
Dockerfileを共有すれば基本的に（GPUドライバ周りを合わせていれば）同じイメージを生成できます．また、後に説明するDockerHubを使えばイメージをビルドする時間の省略のほか，`docker pull`コマンドだけで違うマシンでも同じ環境を構築できるようになります．


# 2. ディレクトリ構成について
私はKaggleや研究では深層学習を主に用いるので以下のようなディレクトリ構成で運用しています．

``` 
ml_environment
├── projects
│   ├── project_dir1
│   ├── project_dir2
│   └── project_dir...
├── docker
│   ├── base
│   │   └── Dockerfile
│   ├── framework1
│   │   └── Dockerfile
│   ├── framework2
│   │   └── Dockerfile
│   ├── framework...
│   │   └── Dockerfile
│   ├── scripts
│   │   └──initialize-docker-container.sh
│   └── docker-compose.yml
└── RUN-DOCKER-CONTAINER.sh
```
それぞれのファイル・ディレクトリの説明をしていきます．  

* `projects`
Kaggleや研究で用いるソースファイルを入れる場所です．ここに集めておくことによって`Jupyter Notebook`などを`projects`ディレクトリで開いた時にプロジェクト間の行き来がしやすくなります．

* `docker`
`Dockerfile`や`docker-compose.yml`などを入れる場所です．
フレームワークごとに`Dockerfile`を分けたディレクトリを作っていて，イメージを再利用できるようにしています．
    * `base`  
ここにはフレームワークによらず共通で使うライブラリを固めるためのイメージを作成するDockerfileを置いています．  
筆者の場合[こんな感じ](https://github.com/tattaka/ml_environment/blob/cuda10/docker/base/Dockerfile)で使っています． 
    * `framework...`  
ここにはフレームワーク別の`Dockerfile`を置いています．
例えば`PyTorch`だとこのような[ファイル](https://github.com/tattaka/ml_environment/blob/cuda10/docker/pytorch/base/Dockerfile)になります．
    * `scripts`  
ここにはDocker起動時や内部で動作させるスクリプトを配置しています．
例えば[initialize-docker-container.sh](https://github.com/tattaka/ml_environment/blob/cuda10/docker/scripts/initialize-docker-container.sh)は`dbus`の起動・`exit`をしてもバックグラウンドで動き続けさせるためのスクリプトです．
    * `docker-compose.yml`  
「はじめに」でも述べた通り，詳しくは書きませんが必須な設定とやっておくと便利な設定をいくつか述べます．
        * `volumes: - ../:/root/ml_environment/`  
ホスト上のプロジェクトディレクトリをDocker上にマウントします．

        * `command: /root/ml_environment/docker/scripts/initialize-docker-container.sh`  
上で設定した`initialize-docker-container.sh`を適用する設定です．

        * `network_mode: host`  
ネットワークデバイスをホストと共有する設定です．一般的にはポートマッピングを使った方が良いのですが，リモートでこのDocker環境を使うときSSHのポートフォワードだけでいろいろできるので便利です．

* `RUN-DOCKER-CONTAINER.sh`
上の`docker-compose.yml`を使ってコンテナを立ち上げるスクリプトです．[こんな感じ](https://github.com/tattaka/ml_environment/blob/cuda10/RUN-DOCKER-CONTAINER.sh)で書くといいと思います．

# 3. GitHubとDockerHubを使ったイメージの管理
### なぜDockerHubを使った方が良いか
まず，DockerHubを使ってイメージを管理するメリットについて簡単に説明します．

* イメージをビルドする時間の省略  
ローカルでイメージをビルドするのはマシンスペックによっては時間がかかってしまいます．また，ビルド時に大容量を要求するライブラリもありダウンロードするだけの方が時間短縮になる場合が多いです．
* 違うマシンで同じイメージを使って環境構築できる  
お気づきの方がいるかもしれませんが，上で紹介した`Dockerfile`の中のライブラリはバージョン指定をしていないものがあります．これは~~調べるのが面倒くさいから~~パッケージによってはaptのkeyが無効化されていて[ダウンロードできなくなっていたり](https://discourse.ros.org/t/new-gpg-keys-deployed-for-packages-ros-org/9454)unstableなバージョンだと消えていたりすることが考えられるからです．DockerHubを使うことでビルドする回数を最小限にして動作を担保したイメージを別のマシンに配布することが可能になります．

### DockerHubとGitHubリポジトリの連携方法
#### 0. 準備
GitHubのリポジトリを準備しましょう．
ここからは筆者のこのリポジトリ([tattaka/ml_environment](https://github.com/tattaka/ml_environment))を使って説明していきます．
また[DockerHub](https://hub.docker.com/)にリポジトリを作るためにはユーザ登録が必要なので登録しておきましょう．
#### 1. リポジトリ作成
次にこのページ([https://hub.docker.com/repositories](https://hub.docker.com/repositories))に飛んで`Create Repository+`ボタンを押しましょう．
ここでリポジトリの各種設定ができるので必須項目を順に埋めていきましょう．

1. Name: リポジトリ名を入力しましょう．筆者は`ml_environment`はもう使っているので`ml_environment2`という名前を使います．
2. Visibility: リポジトリを公開するかどうかの設定です．無料アカウントだと非公開リポジトリは1つしか作れないみたいです......
3. Build Settings: 外部のWebサービスと連携するかどうかの設定です．GitHubとBitbucketが選べますが今はおいておいて後で設定しましょう．  


こんな画面になったでしょうか？
<img width="720" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211495/31921b5c-619f-4def-6b87-03243d0a879f.png">

#### 2. GitHub連携の設定
次にGitHub連携の設定をしていきます．
上の画面の`Builds`タブをクリックして，この画面に遷移します．
<img width="720" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211495/743c4494-05c6-99d3-d4c5-c24981e55533.png">
ここで`Link to GitHub`を選択します．初めてリポジトリを作る場合は下のようなユーザ設定画面でGitHub連携の初期設定をする必要があります．2回目以降はGitHubの連携の初期設定は不要です．
<img width="720" alt="スクリーンショット 2019-12-24 22.50.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211495/2d2b32ed-7907-80ca-53c0-f425627d6456.png">
ここでGitHubの行の`Connect`ボタンをクリックします．
GitHubに遷移するので`Authorize docker`を選択します．
<img width="720" alt="スクリーンショット 2019-12-24 23.00.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211495/1307a409-dfa0-58f0-1f78-f1bea5c462cc.png">
GitHubのパスワードが求められるので入力して`Confirm password`をクリックします．
これでDockerHubアカウントとGitHubアカウントの連携ができました．

#### 3. Automation Buildの設定
後もう一息です！
次にリポジトリの`Builds`タグに戻り，もう一度`Link to GitHub`をクリックします．
ビルドの設定画面になるので`Select Organization`に自分のユーザ名を入れ、`Select Repository`で使いたいリポジトリを選択します．
<img width="720" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211495/064ce95d-d648-16fa-b2f0-1ceef239ed57.png">
最後に上の画面下部の`BUILD RULES`を追加していきます．
今回は上で説明した`base`ディレクトリのDockerイメージが`git push origin master`をすると自動でビルドされるように設定していきます．今は行いませんが`Branch`ではなく`Tag`と紐付けして自動ビルドを行うこともできます．
今回`git push origin master`した時にビルドが走ってほしいので`Source`はそのままで大丈夫です．
`Docker Tag`はこの場合`base`と名付けましょう．
`Dockerfile location`はリポジトリの1番上から見て`Dockerfile`がどこにあるかを設定します．
今回は`docker/base/Dockerfile`とします．入力したら保存しましょう．
保存が完了すると`Builds`タブに戻り設定が完了していることが確認できます．
<img width="720" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211495/c53cd673-9ff8-27cc-844f-79ab62e3dd02.png">
`git push origin master`することでもビルドが始まりますが，手動でビルドしたい場合，下部に先ほど設定した自動ビルドの項目があるので`Trigger ▶︎`をクリックするとビルドが始まります．
これで設定は完了です．お疲れ様でした！

今設定してビルドしたDockerイメージは`docker pull tattaka/ml_environment2:base`でダウンロードすることができます．ぜひ試して見てください．

# 最後に
本記事では筆者の思う使いやすい環境構築のTipsについて説明してきました．
Dockerの本来の使い方や思想とはあっていない(であろう)使い方のため，「もっとここをこうした方が良いよ」「ここおかしいんじゃない？」という意見があると思います．そのような場合はコメント欄に意見を送ってもらえるととてもありがたいです．
最後に、ここまで読んでいただき本当にありがとうございました．