Tensorflowのdockerを使ってみる（docker入門） 

初めてdockerを使った時のメモ(初心者向け)。[docker hub](https://hub.docker.com)で配布されているdockerの中身は使ってみないとよくわからないので、Tensorflowのdocker環境についてもメモ。Tensorflowの[公式ページ](https://www.tensorflow.org/install/?hl=ja)で配布（リンク）されているdockerイメージを使ってみた。（2018/11/24現在）


* 2019/3/9: Tensorflow 2.0 Alpha版向けに更新。
* 2021/2/21: 現状のTensorflow 2.0 に合わせて1章、２章を更新。DockerのUbuntuが16.04から18.04になり（May 20 2019以降に切り替わった）、デフォルトのpythonが2系から3系になっている。


## 動作環境

ホスト側はmacOS High Sierra。

```bash
$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.15.7
BuildVersion:	19H114
```

## 1. dockerのインストール
[dockerのホームページ](https://store.docker.com/editions/community/docker-ce-desktop-mac)からDocker.dmgをダウンロードする。このときdocker storeのアカウントを作る必要があったので作ってからダウンロード。dmgを開いてApplicationフォルダに移動してインストール。Docker.appを起動する。メニューバーにクジラのアイコンが出て、dockerコマンドが使えるようになる。

```bash
$ docker -v
Docker version 20.10.2, build 2291f61
```
（補足：メモリ）デフォルトではメモリを2GB割り当てるようになっている。Tensorflowを使って学習させると、2GBでは不足するので、メニューバーのクジラアイコンから"Preferences..."→"Resources"→"Advanced"→"Memory"で割り当てを増やしておくと良い。CPUの割り当て数も適宜変更する。
（補足：ディスクサイズ）dockerが使用するディスク領域として、"Preferences..."→"Resources"→"Disk image location"で場所、"Disk image size"で大きさが設定できる。デフォルトでは`~/Library/Containers/com.docker.docker/Data/vms/{int}/data/Docker.raw`という60GBのファイルができて、利用される。

## 2. Tensorflowのdockerを動かす。

### 2-1. dockerの起動
Tensorflowの[公式ページ](https://www.tensorflow.org/install/?hl=ja)を参考にdockerを起動してみる。[TensorflowのDocker Hub](https://hub.docker.com/r/tensorflow/tensorflow/tags?page=1&ordering=last_updated)に行くと多くのタグがあることがわかる。ここでは（従来通りの）CPU+Jupyter版が欲しかったので、`latest-jupyter`を使う。

```bash
$ docker pull tensorflow/tensorflow:latest-jupyter #最新イメージ(Jupyter付き)をダウンロード
$ docker run -it -p 8081:8888 tensorflow/tensorflow:latest-jupyter # Start a Jupyter notebook server
```

起動すると以下のように表示される。

```bash
[I 15:54:34.087 NotebookApp] Writing notebook server cookie secret to /root/.local/share/jupyter/runtime/notebook_cookie_secret
jupyter_http_over_ws extension initialized. Listening on /http_over_websocket
[I 15:54:34.268 NotebookApp] Serving notebooks from local directory: /tf
[I 15:54:34.269 NotebookApp] Jupyter Notebook 6.2.0 is running at:
[I 15:54:34.269 NotebookApp] http://2d5f2a274659:8888/?token=3a7bd5d12e8861bb64e008064170ed0648e4197e21feeed7
[I 15:54:34.269 NotebookApp]  or http://127.0.0.1:8888/?token=3a7bd5d12e8861bb64e008064170ed0648e4197e21feeed7
[I 15:54:34.269 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 15:54:34.274 NotebookApp] 
    
    To access the notebook, open this file in a browser:
        file:///root/.local/share/jupyter/runtime/nbserver-1-open.html
    Or copy and paste one of these URLs:
        http://2d5f2a274659:8888/?token=3a7bd5d12e8861bb64e008064170ed0648e4197e21feeed7
     or http://127.0.0.1:8888/?token=3a7bd5d12e8861bb64e008064170ed0648e4197e21feeed7
```
これでjupyterが動いたので、Safariから、
http://localhost:8081/
にアクセスしてみる。docker run -it -p <font color="Red">8081</font>:8888 tensorflow/tensorflow としたので、8081ポートからアクセスする。Macでもjupyterを稼働していたので変更した。Password or token: を聞かれるので、runした時に表示されていたtoken=<font color="Red">3a7bd5d12e8861bb64e008064170ed0648e4197e21feeed7</font>を入れて、アクセスする。
<img width=50% alt="jupyter.png" src="https://qiita-image-store.s3.amazonaws.com/0/123458/6e484fd1-c15e-9329-0fd1-3e8d1d76ece4.png">

Tensorflowのバージョンを確認すると、2.4.1だった。

```python
import tensorflow as tf
print(tf.__version__)
```


### 2-2. dockerの端末で作業する
#### 2-2-1. dockerの状態を確認する
dockerの起動で一つのターミナルが使われている(runコマンドのデフォルトではフォアグラウンドで動作する)ので、もう一つターミナルを開いて、稼働中のコンテナの状況を確認する。

```bash
$ docker ps
CONTAINER ID   IMAGE                                  COMMAND                  CREATED         STATUS         PORTS                    NAMES
2d5f2a274659   tensorflow/tensorflow:latest-jupyter   "bash -c 'source /et…"   6 minutes ago   Up 6 minutes   0.0.0.0:8081->8888/tcp   mystifying_khorana
```



ここで表示される<font color="Red">mystifying_khorana</font>というコンテナ名を使う。最初にrunする時に名前を--nameで指定しない時は、勝手に名前がつけられる。今回はmystifying_khoranaという名前になった。

#### 2-2-2. dockerの端末にログインする
dockerで作業をするため、bashでdockerに入る。

```bash
$ docker exec -it mystifying_khorana bash
```

OSを確認する。Tensorflowの提供しているdockerのOSはUbuntu 18.04だった。

```bash
# cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.5 LTS"
```

python3で実行することが想定されている。以前の`-py3`オプションはUbuntu18.04版から廃止された([docker hubのDescription](https://hub.docker.com/r/tensorflow/tensorflow))。

```bash
# python --version
Python 3.6.9
```

<details><summary>pip list 結果</summary><div>

```bash
# pip list
Package                Version
---------------------- ---------
absl-py                0.11.0
argon2-cffi            20.1.0
asn1crypto             0.24.0
astunparse             1.6.3
async-generator        1.10
attrs                  20.3.0
backcall               0.2.0
bleach                 3.2.2
cachetools             4.2.0
certifi                2020.12.5
cffi                   1.14.4
chardet                4.0.0
cryptography           2.1.4
cycler                 0.10.0
decorator              4.4.2
defusedxml             0.6.0
entrypoints            0.3
flatbuffers            1.12
gast                   0.3.3
google-auth            1.24.0
google-auth-oauthlib   0.4.2
google-pasta           0.2.0
grpcio                 1.32.0
h5py                   2.10.0
idna                   2.6
importlib-metadata     3.4.0
ipykernel              5.1.1
ipython                7.16.1
ipython-genutils       0.2.0
ipywidgets             7.6.3
jedi                   0.18.0
Jinja2                 2.11.2
jsonschema             3.2.0
jupyter                1.0.0
jupyter-client         6.1.11
jupyter-console        6.2.0
jupyter-core           4.7.0
jupyter-http-over-ws   0.0.8
jupyterlab-pygments    0.1.2
jupyterlab-widgets     1.0.0
Keras-Preprocessing    1.1.2
keyring                10.6.0
keyrings.alt           3.0
kiwisolver             1.3.1
Markdown               3.3.3
MarkupSafe             1.1.1
matplotlib             3.3.3
mistune                0.8.4
nbclient               0.5.1
nbconvert              6.0.7
nbformat               4.4.0
nest-asyncio           1.4.3
notebook               6.2.0
numpy                  1.19.5
oauthlib               3.1.0
opt-einsum             3.3.0
packaging              20.8
pandocfilters          1.4.3
parso                  0.8.1
pexpect                4.8.0
pickleshare            0.7.5
Pillow                 8.1.0
pip                    20.2.4
prometheus-client      0.9.0
prompt-toolkit         3.0.13
protobuf               3.14.0
ptyprocess             0.7.0
pyasn1                 0.4.8
pyasn1-modules         0.2.8
pycparser              2.20
pycrypto               2.6.1
Pygments               2.7.4
pygobject              3.26.1
pyparsing              2.4.7
pyrsistent             0.17.3
python-dateutil        2.8.1
pyxdg                  0.25
pyzmq                  21.0.1
qtconsole              5.0.2
QtPy                   1.9.0
requests               2.25.1
requests-oauthlib      1.3.0
rsa                    4.7
SecretStorage          2.3.1
Send2Trash             1.5.0
setuptools             51.3.3
six                    1.15.0
tensorboard            2.4.1
tensorboard-plugin-wit 1.8.0
tensorflow             2.4.1
tensorflow-estimator   2.4.0
termcolor              1.1.0
terminado              0.9.2
testpath               0.4.4
tornado                6.1
traitlets              4.3.3
typing-extensions      3.7.4.3
urllib3                1.26.2
wcwidth                0.2.5
webencodings           0.5.1
Werkzeug               1.0.1
wheel                  0.36.2
widgetsnbextension     3.5.1
wrapt                  1.12.1
zipp                   3.4.0
```
</div></details>

以前、この記事でubuntu16.04時代に出していたapt list結果は、ubuntu18.04になってインストールされているものが大幅に増えて、リストが巨大になるので省略。

##### 余談：dockerでGPUを使う
TensorflowはGPUとともに使うことが多く、tensorflowのdockerでは`-gpu`を設定してdocker runする必要があるが、その場合は[nvidia-docker](https://github.com/NVIDIA/nvidia-docker)が必要となる。macOS(High Sierra, 10.13.6時点)ではnVidiaのGPUが正式サポートされていない。WindowsではWindows Insider ProgramのDevチャンネルで最新のWindows10(2021-02-21現在、OS Build:21318.1000)にし、DockerでWSL2バックエンドを選択することで--gpus allオプションが利用できるようになる。~~nvidia-dockerが使えないことから、結局、dockerの場合はホストOSにUbuntuなどLinux系を使わないとGPUが使えない~~。macOSやWindows上で、CPUベースでのTensorflowを使った動作確認する程度であれば、dockerは便利かもしれない。TensorflowをGPU有効にして使う場合は、Windowsの場合はGPU対応されている(Dockerではなく)NativeなTensorflowを使う必要がある。macOSは今のところGPUを使った機械学習には不利な印象。
**2020.06.01追記**: Linuxの場合、[Docker ver.19.03（2019-07-22）](https://github.com/docker/docker-ce/releases/tag/v19.03.0) 以降と[nvidia-container-toolkit](https://github.com/NVIDIA/nvidia-docker/tree/master#quickstart)を利用することで、`docker run --gpus all --rm nvidia/cuda nvidia-smi`のように--gpusオプションで、GPUが利用できるため、nvidia-dockerは不要になったようです。また、[Singularity](https://sylabs.io)を使う方法もあります。
**2021.02.21追記**: Windowsの場合、Dockerではなく直接Tensorflowを使うか、Windows 10 Pro Insider Preview DevチャンネルとWSL2を使うとGPUを使うことができます。

##### 余談：macOSでGPUを使う（Intel:[PlaidML](https://github.com/plaidml/plaidml)とAMD:[ROCm](https://rocm.github.io)）
macOSではiGPUとしてIntelのGPU、dGPUとしてAMDのGPUのサポートがされているため、これらを使う方法が考えられる。AMDでは[ROCm](https://github.com/RadeonOpenCompute/ROCm)とよばれる仕組みがサポートされており、これがnVidiaのCUDAに相当する。CUDAを使った[`tensorflow-gpu`](https://www.tensorflow.org/install/gpu)と同様に、ROCmを使った[`tensorflow-rocm`](https://github.com/ROCmSoftwarePlatform/tensorflow-upstream)が用意されている。しかし、ROCmはUbuntuなどLinux系のみの対応であり、macOSでは使えない。一方、macOSでGPUを使う方法としてIntel（旧Vertex.ai）の[PlaidML](https://ai.intel.com/plaidml/)を使う方法がある。Kerasのバックエンドを置き換える形（pythonのコードで、`os.environ["KERAS_BACKEND"] = "plaidml.keras.backend"` を `import keras` の前に設定するとKerasのバックエンドがデフォルトのTensorflowから置換）で容易に利用できる。今のところ、公式(?)にmacOSでGPUに対応する唯一の方法かもしれない。また、Windowsを含めて内蔵GPUを使う方法としてもplaidMLは有効と思われる。Intelの傘下となり、今後の発展に期待。


#### 2-2-3. dockerの端末を環境設定する（OpenCVのセットアップ）
dockerの中で作業する。
デフォルトではOpenCVが使えなかったので、インストールする。

```bash
# apt update
# apt install -y libsm6 libxext6 libxrender-dev python-pip python3-pip
# pip install opencv-python
# pip3 install opencv-python
```

##### 余談：Dockerfileを使う
dockerのセットアップはDockerfileという名前のファイルにスクリプトとして書いてもセットアップできる。Dockerfileを使う場合は、以下のようなDockerfileを用意して、`docker build -t tf:1 .`のように呼び出して（同じフォルダ"."にDockefileを置く）、イメージ"tf:1"を作る。`docker run -it -p 8081:8888 tf:1` のようにして起動できる。Dockerfileは他人の作業内容を知る上で便利。ただ、 `FROM ubuntu:xenial` などベーシックなものをベースにしないと、結局どんな環境設定がされているのかわからないという印象。

```dockerfile:Dockefile
FROM tensorflow/tensorflow
RUN apt update && apt install -y libsm6 libxext6 libxrender-dev python-pip python3-pip
RUN pip install opencv-python
RUN pip3 install opencv-python
```

### 2-3. dockerを終了する
dockerの端末での作業を終える。

```bash
# exit
```

dockerを終了する。

```bash
$ docker stop mystifying_khorana
```

### 2-4. 再びdockerで作業を行う
環境を保存して、再度実行させるには、作業したコンテナを利用すれば良い。
再度、dockerのコンテナを起動して、jupyterのtokenを知る。

```bash
$ docker start mystifying_khorana
$ docker exec -it mystifying_khorana bash -c "source /etc/bash.bashrc && jupyter notebook --notebook-dir=/tf --ip 0.0.0.0 --no-browser --allow-root"
```

もしも、最初のコマンドと同じように
`$ docker run -it -p 8082:8888 tensorflow/tensorflow:latest-jupyter`
とすると、履歴の保存されていない新しいコンテナができる。
生成された全てのコンテナを知るには以下のコマンド(docker ps -a)でわかる。

```bash
$ docker ps -a
CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS                       PORTS                              NAMES
8b33acd60c67   tensorflow/tensorflow:latest-jupyter   "bash -c 'source /et…"   35 seconds ago   Up 34 seconds               0.0.0.0:8082->8888/tcp   elegant_mahavira
2d5f2a274659   tensorflow/tensorflow:latest-jupyter   "bash -c 'source /et…"   2 hours ago      Up 3 minutes                0.0.0.0:8081->8888/tcp   mystifying_khorana
```
不要なコンテナ（ここではCONTAINER ID=8b33acd60c67）は、コンテナ名(elegant_mahavira)を使って、stopした後に、

```bash
$ docker stop elegant_mahavira
$ docker rm elegant_mahavira
```
で削除できる。

## 3. コンテナを保存して別のマシンで動作させる
### 3-1. コンテナを停止する
```bash
$ docker stop mystifying_khorana
```
### 3-2. イメージを作成する
ここでは、"gallant:0.0"という名前でイメージを作る。

```bash
$ docker commit -m "Test" mystifying_khorana gallant:0.0
```
Macに保存されているイメージは`docker images`でわかる。

```bash
$ docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
gallant                 0.0                 67e9ba8e53c8        9 seconds ago       1.37GB
tensorflow/tensorflow   latest              2054925f3b43        2 weeks ago         1.34GB
```

### 3-3. イメージをファイルに保存する
以下のコマンドでimageを"~/gallant.tar.gz"に保存する。

```bash
$ docker save gallant:0.0 > ~/gallant.tar.gz
```

### 3-4. イメージをファイルからロードする
別のマシンにgallant.tar.gzをコピーして、以下のコマンドでrunさせる。
ここでは一旦イメージを削除して、ロードしてみる。

```bash
$ docker rmi gallant:0.0 #削除
$ docker images #削除されたことを確認
$ docker load < gallant.tar.gz #ロード
$ docker images #追加されたことを確認
```

### 3-5. イメージから起動する
最初にTensorflowのdockerを起動した時と同様に、runで起動する。今回は、`gallant`という名前をつけてコンテナを作る。また、`-d`オプションをつけてバックグラウンドで起動させる。

```bash
$ docker run --name gallant -it -d -p 8081:8888 gallant:0.0 # Start a Jupyter notebook server
```
psコマンドで起動中のコンテナを確認すると、gallantが動作していることがわかる。

```bash
$ docker ps
CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS              PORTS                              NAMES
de4373702c8a        gallant:0.0             "/run_jupyter.sh --a…"   24 seconds ago      Up 22 seconds       6006/tcp, 0.0.0.0:8081->8888/tcp   gallant
```
あとは上述のexecコマンド(`docker exec -it gallant bash`)で端末での作業を継続できる。


## まとめ
Tensorflowのサイトにあるdockerを使って、Jupyterでの動作環境を構築した。OpenCVを使えるように設定し、docker imageを作成して保存した。他のマシンでも使えるようにimageをファイルとして出力し、そのファイルを用いてロード、開発環境を再現できることを確認した。