Proxyの影響下のUbuntu 18.04でKeras on TensorFlow with GPU on NVIDIA Docker !!

# 前書き

このドキュメントはhttp://geotaru.hatenablog.com/entry/2018/07/06/153208 の改正版です。

20180803現在、Ubuntu 18.04ではCUDA9.1をインストールできますが、下記リンクによるとTensorFlowの最新版はCUDA 9.0に対応しています。Ubuntu18.04にCUDA 9.0を入れるのはなんだかめんどくさそう...

https://www.tensorflow.org/install/install_linux#NVIDIARequirements

TensorFlowのアップデートを待とうと考えていますが、Dockerを使って楽に環境構築する方法を確立したので共有します。Dockerのオーバヘッドが小さいなら、環境が汚れないからこの方法を維持するのがいいのではないでしょうか?

# Keras on TensorFlow with GPUを使えるようにする

## Overview
手順を簡単に説明すると

* NVIDIA DriverをホストマシンのUbuntu　18.04にインストール
* Dockerをインストール
* NVIDIA Dockerをインストール
* GitHubまたはBitbucketのリポジトリにDockerfileを置く
* DockerHubと上のリポジトリを連携させる(Automated Build)
* Dockerfileからイメージをダウンロードして起動

## Pros/Cons

### 利点
*　安定したバージョンのCUDAを使える
*　環境を持ち運べる
*　CUDAやcudnnをホストマシンにインストールする手間が省ける
### 欠点
* Dockerによるオーバーヘッドの発生

## Requirements

* Docker 18.06.0-ce
* [NVIDIA Docker](https://github.com/NVIDIA/nvidia-docker)
* Ubuntu 18.04
* NVIDIA Driver 390.48, 390.77で動作確認済み
* ネットワーク環境: Proxyの影響下の環境であればProxyの設定をホストマシンにしてください

NVIDIA DriverはUbuntu 18.04なら

```bash

$ sudo ubuntu-drivers autoinstall
```
で簡単にインストールできます

### Dockerグループにユーザを追加

Dockerをsudoなしで使うためのコマンドを実行  
しなくても良いがすると楽。  

```bash

$ sudo gpasswd -a $USER docker
```

## Quick Start

下記のようなシェルスクリプトを書いて実行!
実行するとJupyter Notebookが動いているインスタンスが生成されます。

```bash

#!/bin/bash

docker run --runtime=nvidia \
           -e NVIDIA_VISIBLE_DEVICES=0 \
           --rm \
           -p 60000:8888 \
           -v `pwd`:/notebooks \
           -v /home/geotaru/data/:/notebooks/data \
           -it \
           geotaru/keras-notebook
```

tokenをコピーしてブラウザを開き、  
"GPUサーバのURL":60000/?token="上のコマンドを実行して表示されたtoken"
にアクセスすれば親の顔より見たJupyter Notebookが!!

## 上のシェルスクリプトの説明

若干の語弊があるかもしれないですが、だいたいコメントのような意味です。

```bash

#!/bin/bash

docker run --runtime=nvidia \  # NVIDIAのドライバを使用する
           -e NVIDIA_VISIBLE_DEVICES=0 \  # GPUのID "0,1"とやるとGPUを2本使える(GPUが2本あれば)
           --rm \  # Dockerのインスタンスをstop時に消す
           -p 60000:8888 \  # ホストの60000ポートとDockerの8888ポートを繋ぐ
           -v `pwd`:/notebooks \  # カレントディレクトリをDockerの中の/notebooksディレクトリとする
           -v /home/geotaru/data/:/notebooks/data \  # 上と同様に
           -it \  # コマンドラインをインタラクティブにする
           geotaru/keras-notebook  # DockerHubのリポジトリ　geotaru/keras-notebookのイメージを使用 
```

## Dockerfile
私のDocker imageを使用してもいいですが、自分でカスタマイズしたくなるでしょう。
Dockerfileを書き換えればキーバインドをEmacsにしたりすることもできる上に、好きなライブラリを入れることもできます。
まず、GitHubまたはBitbucketにリポジトリを作成してそこに下記のようなDockerfileを置きます。

```Docker
FROM nvidia/cuda:9.0-cudnn7-devel-ubuntu16.04
MAINTAINER geotaru

# 一部のライブラリをインストールするときに
# スクリプトが止まらないように下記の環境変数を設定する
ENV DEBIAN_FRONTEND=noninteractive

# Pythonをソースからコンパイルするのに必要なライブラリをインストール
RUN apt-get update -y && apt-get upgrade -y && \
apt-get install -y git \
build-essential \
graphviz \
make \
build-essential \
libssl-dev \
zlib1g-dev \
libbz2-dev \
libreadline-dev \
libsqlite3-dev \
wget \
curl \
llvm \
libncurses5-dev \
libncursesw5-dev \
xz-utils \
tk-dev \
libffi-dev

# Python
WORKDIR /opt
RUN wget https://www.python.org/ftp/python/3.6.6/Python-3.6.6.tgz && \
tar xf Python-3.6.6.tgz

WORKDIR /opt/Python-3.6.6
RUN ./configure && \
make && \
make install && \
rm /opt/Python-3.6.6.tgz

# 欲しいライブラリをインストール
RUN pip3 install --upgrade pip && \
pip3 install numpy \
scipy \
pandas \
h5py \
scikit-learn \
imbalanced-learn \
nose \
xgboost \
lightgbm \
tensorflow-gpu \
keras \
seaborn \
matplotlib \
plotly \
jupyter \ 
tqdm \
cython \
jupyter_contrib_nbextensions \
pydot \
graphviz \
pydot3 \
pydot-ng \
folium \
autopep8 \
line_profiler \
memory_profiler \
rise

# Jupyter NotebookのExtensionの設定
RUN jupyter contrib nbextension install --user && \
: "Jupyter NotebookのキーバインドをVim風に設定" && \
mkdir -p $(jupyter --data-dir)/nbextensions && \
cd $(jupyter --data-dir)/nbextensions && \
git clone https://github.com/lambdalisue/jupyter-vim-binding vim_binding &&  \
jupyter nbextension enable vim_binding/vim_binding && \
: "Jupyter Notebookでプレゼンをするためのライブラリ" && \
jupyter-nbextension install rise --py --sys-prefix && \
jupyter-nbextension enable rise --py --sys-prefix && \
: "セルごとに実行時間を測定" && \
jupyter-nbextension enable execute_time/ExecuteTime  && \
jupyter nbextension enable move_selected_cells/main && \
jupyter nbextension enable toggle_all_line_numbers/main && \
jupyter nbextension enable code_prettify/code_prettify && \
jupyter nbextension enable scratchpad/main

WORKDIR /notebooks
EXPOSE 8888
ENTRYPOINT ["jupyter", "notebook", "--no-browser", "--ip=0.0.0.0", "--allow-root"]
```

### このDockerfileの問題点
* バージョン固定
    pip install hogeのところは本当はバージョン固定した方がよいとおもいます。面倒なのでここではやっていませんが。 

* Dockerイメージが大きいのでビルドにしか使用していないライブラリは削除したい...


## DockerHubを利用する

Proxyの影響下の環境ではProxyの設定が必要で面倒なので(コンテナの中からも通信できるように設定する必要がある)、DockerHub上でビルドすると楽でしょう。Proxyの影響下ではなくても、ビルドが楽になるのでおすすめです。

DockerHubのアカウントを作成して、ホーム画面の右上 create -> Create Automated Buildでさっき作成したリポジトリを指定してください。  

詳しくは[Docker docs](http://docs.docker.jp/docker-hub/builds.html)を参照してください。
ビルドには時間がかかります。
うまく設定できれば、GitHubやGitBucketのリポジトリが更新されるたびに自動でDockerHub上でDocker imageがビルドされるようになります。

DockerHubをどうしても使用したくないなら、家などProxyのない場所でDocker imageをビルドしてハードディスクに入れて持って来るとか、scpで持って来るとかすれば良いでしょう。

### ビルドできたら...


```bash

$ docker pull "DockerHubアカウント名"/"リポジトリ名"
```

で自分が作ったイメージをダウンロードできる。

最初に書いたようなスクリプトを書いて実行してください。

```run.sh
#!/bin/bash

docker run --runtime=nvidia \
           -e NVIDIA_VISIBLE_DEVICES=0 \
           --rm \
           -p 60000:8888 \
           -v `pwd`:/notebooks \
           -v /home/geotaru/data/:/notebooks/data \
           -it \
           geotaru/keras-notebook
```

```bash

geotaru@gpu-server:~/project/interactive$ chmod +x ./run.sh  # 実行権限の付与
geotaru@gpu-server:~/project/interactive$ ./run.sh
[I 22:24:12.442 NotebookApp] Writing notebook server cookie secret to /root/.local/share/jupyter/runtime/notebook_cookie_secret
[I 22:24:12.568 NotebookApp] [jupyter_nbextensions_configurator] enabled 0.4.0
[I 22:24:12.568 NotebookApp] Serving notebooks from local directory: /notebooks
[I 22:24:12.568 NotebookApp] The Jupyter Notebook is running at:
[I 22:24:12.568 NotebookApp] http://(f11eb68ebfa1 or 127.0.0.1):8888/?token=01bdb4d77d7f893f35423007968035149360374a6d30e48d
[I 22:24:12.568 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 22:24:12.568 NotebookApp]

    Copy/paste this URL into your browser when you connect for the first time,
    to login with a token:
        http://(f11eb68ebfa1 or 127.0.0.1):8888/?token=01bdb4d77d7f893f35423007912345649362361b6d30e48d
```

tokenをコピーしてブラウザを開き、  

```
"GPUサーバのURL":60000/?token=01bdb4d77d7f893f35423007912345649362361b6d30e48d
```

をURL欄に書いてアクセスするとJupyter Notebookの画面がでます!

# まとめ

NVIDIA DockerとDocker Hubを活用して、Proxy環境下であろうと機械学習の環境構築を簡単にできるようにした。

# おまけ

## 稼動中のDockerのインスタンスを止める方法

```
$ docker ps -a  # どんなインスタンスがあるか確認
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS                     NAMES
f9bf6bfc6e68        geotaru/keras-notebook   "jupyter notebook --…"   10 seconds ago      Up 7 seconds        0.0.0.0:60000->8888/tcp   ecstatic_meninsky
```

上記のインスタンスは--rmオプションで実行しているため、stopするだけでDockerインスタンスが停止し削除される。

```bash

$ docker stop ecstatic_meninsky
ecstatic_meninsky
$ docker ps -a  # インスタンスがなくなったことを確認
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```

## 既存のDocker imageを削除する方法

```bash

$ docker ps -a
```

で削除したいイメージのインスタンス確認する。
使っているものがあったら、

```bash

$ docker stop "インスタンス名"
$ docker rm "インスタンス名"
````

とする。
削除したいイメージのインスタンスがなければ

```bash

$ docker rmi "イメージの名前"
```

で破棄できる