Dockerコンテナで、Keras + TensorFlow / Jupyter notebook環境を簡単にデプロイする

Kerasを活用して、デープラーニングを動作させようと考えた場合、環境整備に手間取るのがめんどくさいですよね。
今回は、Dockerコンテナから、Keras + tensorFlow / Jupyter notebook環境を簡単に整備できるようになりました。
私の個人リポジトリ"[ttsubo/study_of_deeplearning_with_keras](https://github.com/ttsubo/study_of_deeplearning_with_keras)"と、このQiita記事にて、作業履歴を共有しておきます。

#⬛︎ 事前準備
Dockerコンテナを動かすUbuntuサーバから、GPUが扱えるようにしておく必要があります。
## (1) Ubuntuサーバ側の環境セットアップ

具体的には、Qiita記事: "[Dockerで、GPU対応なコンテナ環境を整備する](https://qiita.com/ttsubo/items/c97173e1f04db3cbaeda)"のセットアップが終わっているものとします。

## (2) "study_of_deeplearning_with_keras"リポジトリの取得
Dockerコンテナから、Keras + tensorFlow / Jupyter notebook環境を整備できるように、事前にリポジトリを用意しておきましたので、以下の要領で、リポジトリを取得しておいてください。

```
$ git clone https://github.com/ttsubo/study_of_deeplearning_with_keras.git
```
## (3) cuDNNファイルを別途、入手する
Dockerコンテナ内にcnDNNパッケージをインストールする必要があるので、[cuDNN Archive](https://developer.nvidia.com/rdp/cudnn-archive#a-collapse714-9) から`cuDNN v7.6.2 (July 22, 2019), for CUDA 10.0`をダウンロードしておいてください。なお、ダウンロードするためには事前にメンバー登録が必要です。

 - cuDNN Runtime Library for Ubuntu18.04 (Deb)
 - cuDNN Developer Library for Ubuntu18.04 (Deb)
 - cuDNN Code Samples and User Guide for Ubuntu18.04 (Deb)

そして、`build/download`フォルダに配置しておいてください。

```
$ cd build/download/
$ ls -l
total 314384
-rw-r--r-- 1 ttsubo ttsubo 164426244 Sep 29 14:36 libcudnn7_7.6.2.24-1+cuda10.0_amd64.deb
-rw-r--r-- 1 ttsubo ttsubo 152045132 Sep 29 14:36 libcudnn7-dev_7.6.2.24-1+cuda10.0_amd64.deb
-rw-r--r-- 1 ttsubo ttsubo   5442884 Sep 29 14:36 libcudnn7-doc_7.6.2.24-1+cuda10.0_amd64.deb
```

## (4) Dockerイメージをビルドします
```
$ docker-compose build
```
ちなみに、Dockerfileファイルは、こんな感じです。

```text:Dockerfile
FROM nvidia/cuda:10.0-devel-ubuntu18.04
  
MAINTAINER Toshiki Tsuboi <t.tsubo2000@gmail.com>

RUN apt update \
 && apt install -y \
      git-core \
      build-essential \
      python-dev \
      python-openssl \
      libssl-dev \
      libbz2-dev \
      libffi-dev \
      libsqlite3-dev \
      libreadline-dev \
      zlib1g-dev \
      libsm6 \
      libxext6 \
      libxrender-dev \
      libblas-dev \
      curl \
      vim \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*

# install pyenv
ENV HOME /root
ENV PYENV_ROOT $HOME/.pyenv
ENV PATH $PYENV_ROOT/bin:$PATH
RUN git clone https://github.com/pyenv/pyenv.git $HOME/.pyenv
RUN echo 'eval "$(pyenv init -)"' >> $HOME/.bashrc \
 && eval "$(pyenv init -)"

# install python using pyenv
RUN apt update \
 && apt install -y libssl1.0-dev \
 && pyenv install anaconda3-5.3.1 \
 && pyenv global anaconda3-5.3.1

# install pip
WORKDIR /
ADD https://bootstrap.pypa.io/get-pip.py /
RUN python get-pip.py \
 && rm get-pip.py

# install python package
WORKDIR /root
COPY requirements.txt /root
RUN /root/.pyenv/shims/pip install -r requirements.txt

# install cuDNN
COPY download /root/debian_packages
RUN cd /root/debian_packages \
 && dpkg -i \
  libcudnn7_7.6.2.24-1+cuda10.0_amd64.deb \
  libcudnn7-dev_7.6.2.24-1+cuda10.0_amd64.deb \
  libcudnn7-doc_7.6.2.24-1+cuda10.0_amd64.deb

#setup jupyter
RUN /root/.pyenv/shims/jupyter notebook --generate-config \
 && sed -i -e "s/#c.NotebookApp.ip = 'localhost'/c.NotebookApp.ip = '0.0.0.0'/" /root/.jupyter/jupyter_notebook_config.py \
 && sed -i -e "s/#c.NotebookApp.allow_remote_access = False/c.NotebookApp.allow_remote_access = True/" /root/.jupyter/jupyter_notebook_config.py \
 && sed -i -e "s/#c.NotebookApp.token = '<generated>'/c.NotebookApp.token = ''/" /root/.jupyter/jupyter_notebook_config.py

EXPOSE 8888
ENTRYPOINT ["sh", "-c", "/root/.pyenv/shims/jupyter notebook --allow-root"]
```

#⬛︎ 実際に、動かしてみる

今回、動かしてみるデープラーニング用Pythonスクリプトは、[Keras repoが提供しているサンプルアプリ](https://github.com/keras-team/keras/blob/master/examples/mnist_cnn.py)です。

```python:keras/examples/mnist_cnn.py
'''Trains a simple convnet on the MNIST dataset.

Gets to 99.25% test accuracy after 12 epochs
(there is still a lot of margin for parameter tuning).
16 seconds per epoch on a GRID K520 GPU.
'''

from __future__ import print_function
import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K

batch_size = 128
num_classes = 10
epochs = 12

# input image dimensions
img_rows, img_cols = 28, 28

# the data, split between train and test sets
(x_train, y_train), (x_test, y_test) = mnist.load_data()

if K.image_data_format() == 'channels_first':
    x_train = x_train.reshape(x_train.shape[0], 1, img_rows, img_cols)
    x_test = x_test.reshape(x_test.shape[0], 1, img_rows, img_cols)
    input_shape = (1, img_rows, img_cols)
else:
    x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1)
    x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1)
    input_shape = (img_rows, img_cols, 1)

x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')

# convert class vectors to binary class matrices
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3),
                 activation='relu',
                 input_shape=input_shape))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))

model.compile(loss=keras.losses.categorical_crossentropy,
              optimizer=keras.optimizers.Adadelta(),
              metrics=['accuracy'])

model.fit(x_train, y_train,
          batch_size=batch_size,
          epochs=epochs,
          verbose=1,
          validation_data=(x_test, y_test))
score = model.evaluate(x_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
```

## (1) Dockerコンテナを起動する
Docker-composeを使って、Dockerコンテナを起動します。
この段階で、Dockerコンテナ内では、Jupyter notebook環境も常駐するようになっています。

```
$ docker-compose up
Creating Keras ... done
Attaching to Keras
Keras           | [I 05:43:15.263 NotebookApp] Writing notebook server cookie secret to /root/.local/share/jupyter/runtime/notebook_cookie_secret
Keras           | [W 05:43:15.379 NotebookApp] All authentication is disabled.  Anyone who can connect to this server will be able to run code.
Keras           | [I 05:43:15.403 NotebookApp] JupyterLab extension loaded from /root/.pyenv/versions/anaconda3-5.3.1/lib/python3.7/site-packages/jupyterlab
Keras           | [I 05:43:15.404 NotebookApp] JupyterLab application directory is /root/.pyenv/versions/anaconda3-5.3.1/share/jupyter/lab
Keras           | [I 05:43:15.406 NotebookApp] Serving notebooks from local directory: /root
Keras           | [I 05:43:15.406 NotebookApp] The Jupyter Notebook is running at:
Keras           | [I 05:43:15.406 NotebookApp] http://(Keras or 127.0.0.1):8888/
Keras           | [I 05:43:15.406 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
Keras           | [W 05:43:15.407 NotebookApp] No web browser found: could not locate runnable browser.
```
## (2) Dockerコンテナ内に、入って、サンプルアプリを起動する
まずは、Dockerコンテナに入ります

```
$ docker exec -it Keras bash
```
そして、サンプルアプリを起動します。`$ python examples/mnist_cnn.py`

```
root@Keras:~# python examples/mnist_cnn.py
Using TensorFlow backend.
x_train shape: (60000, 28, 28, 1)
60000 train samples
10000 test samples

... (snip)

Train on 60000 samples, validate on 10000 samples
Epoch 1/12
2019-09-29 06:00:43.489125: I tensorflow/stream_executor/dso_loader.cc:152] successfully opened CUDA library libcublas.so.10.0 locally
60000/60000 [==============================] - 8s 127us/step - loss: 0.2654 - accuracy: 0.9186 - val_loss: 0.0628 - val_accuracy: 0.9785
Epoch 2/12
60000/60000 [==============================] - 7s 115us/step - loss: 0.0888 - accuracy: 0.9736 - val_loss: 0.0430 - val_accuracy: 0.9849
Epoch 3/12
60000/60000 [==============================] - 7s 112us/step - loss: 0.0677 - accuracy: 0.9802 - val_loss: 0.0394 - val_accuracy: 0.9871
Epoch 4/12
60000/60000 [==============================] - 7s 114us/step - loss: 0.0566 - accuracy: 0.9832 - val_loss: 0.0321 - val_accuracy: 0.9885
Epoch 5/12
60000/60000 [==============================] - 7s 114us/step - loss: 0.0480 - accuracy: 0.9860 - val_loss: 0.0350 - val_accuracy: 0.9882
Epoch 6/12
60000/60000 [==============================] - 7s 110us/step - loss: 0.0450 - accuracy: 0.9864 - val_loss: 0.0274 - val_accuracy: 0.9897
Epoch 7/12
60000/60000 [==============================] - 7s 113us/step - loss: 0.0395 - accuracy: 0.9883 - val_loss: 0.0292 - val_accuracy: 0.9903
Epoch 8/12
60000/60000 [==============================] - 7s 114us/step - loss: 0.0351 - accuracy: 0.9890 - val_loss: 0.0309 - val_accuracy: 0.9893
Epoch 9/12
60000/60000 [==============================] - 7s 112us/step - loss: 0.0342 - accuracy: 0.9892 - val_loss: 0.0288 - val_accuracy: 0.9906
Epoch 10/12
60000/60000 [==============================] - 7s 111us/step - loss: 0.0328 - accuracy: 0.9900 - val_loss: 0.0264 - val_accuracy: 0.9918
Epoch 11/12
60000/60000 [==============================] - 7s 112us/step - loss: 0.0305 - accuracy: 0.9901 - val_loss: 0.0280 - val_accuracy: 0.9916
Epoch 12/12
60000/60000 [==============================] - 7s 117us/step - loss: 0.0289 - accuracy: 0.9911 - val_loss: 0.0268 - val_accuracy: 0.9917
Test loss: 0.026753987711756782
Test accuracy: 0.9916999936103821
```
GPUを使用した場合、1エポック当たり、7秒程度で、学習が進んでいく様子が確認できますね。
ちなみに、CPUを使用した場合は、1エポック当たり、41秒程度で学習が進んでいきました。
私のGPU環境`GeForce GTX 1060 3GB`だと、CPUよりも、約6倍も、学習に要する時間が短縮できるようです。

```
root@Keras:~# python examples/mnist_cnn.py
Using TensorFlow backend.
Downloading data from https://s3.amazonaws.com/img-datasets/mnist.npz
11493376/11490434 [==============================] - 8s 1us/step
x_train shape: (60000, 28, 28, 1)
60000 train samples
10000 test samples

... (snip)

Train on 60000 samples, validate on 10000 samples
Epoch 1/12
60000/60000 [==============================] - 41s 679us/step - loss: 0.2666 - accuracy: 0.9189 - val_loss: 0.0607 - val_accuracy: 0.9819
Epoch 2/12
60000/60000 [==============================] - 41s 677us/step - loss: 0.0895 - accuracy: 0.9733 - val_loss: 0.0402 - val_accuracy: 0.9866
Epoch 3/12
60000/60000 [==============================] - 41s 677us/step - loss: 0.0673 - accuracy: 0.9798 - val_loss: 0.0365 - val_accuracy: 0.9879
Epoch 4/12
60000/60000 [==============================] - 41s 677us/step - loss: 0.0550 - accuracy: 0.9835 - val_loss: 0.0341 - val_accuracy: 0.9885
Epoch 5/12
60000/60000 [==============================] - 41s 678us/step - loss: 0.0477 - accuracy: 0.9857 - val_loss: 0.0293 - val_accuracy: 0.9908
Epoch 6/12
60000/60000 [==============================] - 41s 677us/step - loss: 0.0424 - accuracy: 0.9871 - val_loss: 0.0283 - val_accuracy: 0.9913
Epoch 7/12
60000/60000 [==============================] - 41s 678us/step - loss: 0.0387 - accuracy: 0.9881 - val_loss: 0.0275 - val_accuracy: 0.9906
Epoch 8/12
60000/60000 [==============================] - 41s 678us/step - loss: 0.0340 - accuracy: 0.9897 - val_loss: 0.0261 - val_accuracy: 0.9909
Epoch 9/12
60000/60000 [==============================] - 41s 677us/step - loss: 0.0319 - accuracy: 0.9902 - val_loss: 0.0291 - val_accuracy: 0.9906
Epoch 10/12
60000/60000 [==============================] - 41s 676us/step - loss: 0.0307 - accuracy: 0.9906 - val_loss: 0.0287 - val_accuracy: 0.9918
Epoch 11/12
60000/60000 [==============================] - 41s 676us/step - loss: 0.0277 - accuracy: 0.9916 - val_loss: 0.0282 - val_accuracy: 0.9917
Epoch 12/12
60000/60000 [==============================] - 41s 676us/step - loss: 0.0271 - accuracy: 0.9915 - val_loss: 0.0267 - val_accuracy: 0.9921
Test loss: 0.026685928908488858
Test accuracy: 0.9921000003814697
```
## (3) 最後に、jupyter notebook経由で、サンプルアプリを起動してみる
Web browserから、`http://(Keras or 127.0.0.1):8888/`にアクセスします。
そして、notebookを起動すると、こんな感じの結果が確認できました。
![minist_cnn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/9d3107dc-502c-9f8f-e62c-937eb82d236d.png)

#⬛︎ 終わりに、、、
Keras + TensorFlow / Jupyter notebook環境が簡単に、デプロイできるようになりました。
デープラーニング動作検証を通じて知識を習得する場合には、デープラーニング環境を色々とスクラップ＆ビルドできるのが望ましいと思うので、まさに、Dockerコンテナ活用のわかりやすい事例だと思います。