RStudio+Keras を CUDA9.0 の docker イメージを作って動作させる

こんにちは、chlochanです。Ubuntu 18.04 + CUDA10 に移行したのですが、RStudio で Keras を実行する場合、CUDA9.0 の環境が必要なため、nvidia-docker2 を使って Ubuntu16.04 + CUDA9.0 の環境を作りその上で RStudio を実行する事にします。

## docker をインストールする
まず docker をインストールします。

```bash
$ sudo apt-get update
$ sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo apt-key fingerprint 0EBFCD88
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) stable"
$ sudo apt-get update
$ sudo apt-get install docker-ce
```
インストール出来たか確認してみます。

```bash
$ sudo docker container run hello-world
$ sudo docker image
$ sudo docker ps -a
```
問題なければ コンテナとdockerイメージを削除します。

```bash
$ sudo docker rm $(sudo docker ps -aq)
$ sudo docker rmi hello-world
```
## nvidia-docker2 をインストールする
現在、GPUを使うディープラーニング用のツール類は、CUDAのバージョンに依存しています。たとえば TensorFlow Ver1.12 の コンパイル済みバイナリーイメージ は、CUDA9.0 のライブラリが無いと実行時にエラーとなります。この状況は、TensorFlow Ver2 が出るとまた変化すると思われます。また RStudio で Keras を実行する場合も、CUDA9.0 のライブラリが必要です。よって、使いたいツールが要求するバージョンの CUDA Toolkit をインストールする必要があります。nvidia-docker2 は、Docker のコンテナランタイムで、これを使うと、ホストOS の CUDA Driver を最新のものにしておけば、コンテナ毎に異なるバージョン の CUDA Toolkit を使う事ができる様になり、ディープラーニング用のツール類の運用が非常に楽になります。

以下の Quickstart に従ってインストールします。
https://github.com/NVIDIA/nvidia-docker

```bash
$ curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
$ distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
$ curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | \
  sudo tee /etc/apt/sources.list.d/nvidia-docker.list
$ sudo apt-get update
$ sudo apt-get install -y nvidia-docker2
$ sudo pkill -SIGHUP dockerd
```
以下をみたところ、9.0-cudnn7-devel が CUDA9.0 のすべてをインストールするイメージなので、それで動作確認してみます。
https://hub.docker.com/r/nvidia/cuda/

```bash
$ sudo docker container run --runtime=nvidia --rm nvidia/cuda:9.0-cudnn7-devel nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2017 NVIDIA Corporation
Built on Fri_Sep__1_21:08:03_CDT_2017
Cuda compilation tools, release 9.0, V9.0.176
```
V9.0.176 になっている事を確認します。
問題なければ コンテナとdockerイメージを削除します。

```bash
$ sudo docker rm $(sudo docker ps -aq)
$ sudo docker rmi nvidia/cuda:9.0-cudnn7-devel
```

##Rocker Project の Dockerfile を調べて作成方針を決める
nvidia/cuda:9.0-cudnn7-devel-ubuntu16.04 をベースにライブラリやアプリを追加したイメージを作ります。
ネットで検索したところ、R/RStudio の docker イメージ として、
[Rocker Project] (https://hub.docker.com/u/rocker/)
というのがありましたのでこれを流用します。githubからソースを持ってきます。

~~~
$ git clone https://github.com/rocker-org/rocker-versioned.git
$ git clone https://github.com/rocker-org/shiny.git
~~~
ここで用意されているイメージの docker file のFROM行を調べたところ、以下の様になっていました。

~~~docker:rocker/r-ver/Dockerfile
FROM debian:stretch
~~~

~~~docker:rocker/rstudio/Dockerfile 
FROM rocker/r-ver:devel
~~~

~~~docker:rocker/tidyverse/Dockerfile 
FROM rocker/rstudio:devel
~~~

~~~docker:rocker/verse/Dockerfile 
FROM rocker/tidyverse:devel
~~~

~~~docker:rocker/shiny/Dockerfile 
FROM rocker/r-ver:devel
~~~

上記より、イメージの階層は下記の様になっています。
**debian:stretch ⇒ r-ver ⇒ rstudio ⇒ tidyverse ⇒ verse**
**debian:stretch ⇒ r-ver ⇒ shiny**
rocker リポジトリでは r-ver が最下層で、debian:stretch をベースイメージとしています。
この最下層の r-ver のベースとなるイメージの debian:stretch を nvidia/cuda:9.0-cudnn7-devel-ubuntu16.04 に入れ替えたバージョンをつくり、最終的に、rocker/verse と同等となる docker イメージを作ります。階層は以下の様に、shinyを間に入れる事にします。
**cuda:9.0-cudnn7-devel-ubuntu16.04 ⇒ r-ver ⇒ shiny ⇒ rstudio ⇒ tidyverse ⇒ verse**

##Dockerfile を作成する
方針が決まったので、Dockerfile を作成していきます。まず、rocker/r-ver の FROM 行を以下の様に入れ替えたバージョンを作ります。リポジトリの名称は、**c9rocker** としてみました。

~~~docker:c9rocker/r-ver/Dockerfile 
FROM nvidia/cuda:9.0-cudnn7-devel-ubuntu16.04
... 以下は rocker/r-ver と基本的に同じなので略 (書き換え必要なライブラリが3個あり) ...
~~~
ディストリビューションを debian:stretch から ubuntu16.04 に変更するにあたり、以下の書き換えが必要でした。
**libicu57 ⇒ libicu55**
**libjpeg62-turbo ⇒ libjpeg62**
**libreadline7 ⇒ libreadline6**
Dockerfileの該当の3箇所を書き換えます。上位の階層も同様に作っていきます。shiny の変更箇所は FROM行だけです。

~~~docker:c9rocker/shiny/Dockerfile
FROM c9rocker/r-ver:latest
... 以下は rocker/shiny と全く同じなので略 ...
~~~
rstudio の変更箇所は、FROM行の変更の他に、libssl1.0.0.deb のダウンロード元の変更と、Keras の実行に必要なRUNコマンドの追加があります。

~~~docker:c9rocker/rstudio/Dockerfile 
FROM c9rocker/shiny:latest
... libssl1.0.0.deb に関係する部分までは rocker/rstudio と全く同じなので略 ...
  && wget -O libssl1.0.0.deb http://security.ubuntu.com/ubuntu/pool/main/o/openssl/\
libssl1.0.0_1.0.2g-1ubuntu4.14_amd64.deb \
... RUNコマンドを追加します ...
RUN apt-get update
RUN apt-get -y upgrade
RUN apt-get install python-pip
RUN apt-get install python-virtualenv
... 以下は rocker/rstudio と全く同じなので略 ...
~~~
上記の様に、ディストリビューションを debian:stretch から ubuntu16.04 に変更したことから、rstudio の Dockerfile 中で、libssl1.0.0.deb の入力先とファイル名を以下の様に変更しました。変更しないとエラーになります。

http://ftp.debian.org/debian/pool/main/o/openssl/libssl1.0.0_1.0.1t-1+deb8u8_amd64.deb
　　　　　　　**↓**
http://security.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.0.0_1.0.2g-1ubuntu4.14_amd64.deb

tidyverse と verse の変更箇所は FROM行だけです。

~~~docker:c9rocker/tidyverse/Dockerfile 
FROM c9rocker/rstudio:latest
... 以下は rocker/tidyverse と全く同じなので略 ...
~~~

~~~docker:c9rocker/verse/Dockerfile 
FROM c9rocker/tidyverse:latest
... 以下は rocker/tidyverse と全く同じなので略 ...
~~~

## dockerイメージをビルドする時にコピーされるファイルを用意する
GitHubからクローンした rocker-versioned/rstudio　の中の sh ファイルを ./rstudio ディレクトリに持ってきて実行属性を追加します。また、disable_auth_rserver.conf ファイルも持ってきます。下記コマンドの前提条件として、一個上のディレクトリに rocker-versioned をクローンしてあるものとします。また rstudio の Dockerfile は ./rstudio にあるものとします。

```bash
$ cp ../rocker-versioned/rstudio/disable_auth_rserver.conf ./rstudio
$ cp ../rocker-versioned/rstudio/*.sh ./rstudio
$ chmod +x ./rstudio/*sh
```
同様に、GitHubからクローンした rocker/shiny の中の shiny-server.sh ファイルを ./shiny ディレクトリに持ってきて実行属性を追加します。

```bash
$ cp ../shiny/shiny-server.sh ./shiny
$ chmod +x ./shiny/*sh
```
## dockerイメージをビルドする
dockerイメージを下層から順番にビルドします。ディレクトリ c9rocker でイメージをビルドします。tidyverse はかなり時間がかかります。

```bash
$ sudo docker image build -t c9rocker/r-ver:latest ./r-ver
$ sudo docker image build -t c9rocker/shiny:latest ./shiny
$ sudo docker image build -t c9rocker/rstudio:latest ./rstudio
$ sudo docker image build -t c9rocker/tidyverse:latest ./tidyverse
$ sudo docker image build -t c9rocker/verse:latest ./verse
```
dockerイメージが出来ていることを確認します。

```bash
$ sudo docker image ls
```
Dockerfile 中の文法ミスなどでイメージ作成の失敗を繰り返すと、中途半端なコンテナが沢山出来てしまっている事があります。よってコンテナを一旦全部削除します。

```bash
$ sudo docker rm $(sudo docker ps -aq)
```
## コンテナを起動する
c9rocker/verse:latest を起動します。なお、起動時のコマンドで RStudio にログインする時のパスワードを設定します。**`<YOUR_PASS>`** の部分を自分の好きなパスワードに置き換えて実行します。なお、'rstudio' 以外のパスワードにする必要があります。

```bash
$ chmod 777 ~/Downloads
$ sudo docker run --runtime=nvidia -v ~/Downloads:/home/rstudio/Downloads \
  -e PASSWORD=<YOUR_PASS> -d -p 3838:3838 -p 8787:8787 c9rocker/verse:latest
```
上記の例では、ホスト側のディレクトリ ~/Downloads を、コンテナ側のディレクトリ /home/rstudio/Downloads とRW可能で共有しています。また、shiny と RStudio のポートを両方そのままホスト側にフォワードしています。 "-d" を指定することでバックグラウンドで起動します。
コンテナが起動出来たか確認してみます。

```bash
$ sudo docker ps -a
```
# RStudio にログインする
起動出来ていたら、既に外部から接続できる様になっています。ファイアウォールでポート3838と8787を開けます。そして、
**`http://外部IPアドレス:8787`**
で、手元のブラウザからアクセスすると RStudioに接続できます。RStudioのログイン画面がでたら、ログインします。ユーザー名 rstudio 、パスワードはコンテナ起動時に設定したパスワードでログインします。同様に shiny にはポート3838で接続できます。

次に、c9rocker/verse:latest に、Keras と tensorflow をインストールします。

# 必要なライブラリをインストールする
RStudioにログインして、サンプルコードを実行する時に必要なライブラリをまずインストールします。

```R
install.packages("gridExtra")
install.packages("magick")
install.packages("viridis")
install.packages("hashFunction")
```
次にKerasをインストールします。

# Keras をインストールする
R版 の Keras は GitHub の、[R interface to Keras](https://github.com/rstudio/keras) で公開されています。
RStudio にログインして、Keras と tensorflow-gpu をインストールします。以下を実行します。

~~~R
library(devtools)
devtools::install_github("rstudio/keras")
library(keras)
install_keras(tensorflow = "gpu")
~~~
上記で、Keras と同時に tensorflow-gpu バックエンドもインストールしています。2018/12/9 時点で、実行後、RStudio の Packages の Version には、2.2.4 が表示されました。

## サンプルプログラムをダウンロードして実行する
GitHubから下記の教科書のサンプルプログラムをダウンロードして実行します。
[RとKerasによるディープラーニング](https://www.oreilly.co.jp/books/9784873118574/)

```bash
$ git clone https://github.com/jjallaire/deep-learning-with-r-notebooks.git
```
## GPU の utilization を確認する
サンプルコード実行中に、GPU の utilization を確認するには、別ウインドウを開いて以下を実行します。以下の例では1秒毎に表示が更新されます。

```bash
$ watch -n 1 nvidia-smi -a --display=utilization
```
## コンテナの停止
コンテナを停止します。

```bash
$ sudo docker stop $(sudo docker ps -q)
```
コンテナが停止出来たか確認してみます。

```bash
$ sudo docker ps -a
```

以上です。