Deep Learning Workstation構築計画 遂に完結 (Jupyter Notebookサーバー構築編)

[eGPUでハイスペックLinuxデスクトップをDeep Learning Workstation化計画(eGPUセットアップ編)](https://qiita.com/y-vectorfield/items/8960c804441d2ebd605e)でeGPUのセットアップを実施しました。これでNVIDIAドライバーやCUDA 10.2のインストールが完了し、GPUが利用出来る様になりました。今回はDocker及びNVIDIA DockerをインストールしてGPUが利用出来るDocker環境を構築し、最終的にJupyter Notebookサーバーを構築する所までをやってみようと思います。構築マシンはeGPUをThundebolt 3接続という特殊構成のため、セットアップ中に怒濤のエラーが出て断念:scream:という恐れも十分に有り得ますが、折角作り上げたマシーンなので何事も無く完成[^egpu]となることを願いつつセットアップを行います。

* Deep Learning Workstation構築計画の変遷  
  * [PC構築編](https://qiita.com/y-vectorfield/items/2fcbc669a43fe0d0cb15)  
  * [eGPU導入編](https://qiita.com/y-vectorfield/items/8960c804441d2ebd605e)  
  * 完結編(本記事)

:warning: 本記事で紹介するセットアップ方法は2022/5/22現在最新の情報に基づいていますが、今後のアップグレードでより簡便な方法が可能となる可能性は十分有ります。その時は内容を随時更新していこうと思います。

# Dockerのインストール
まずはDockerをインストールします。今回は新規に環境構築する為、最新版をインストールします。

Docker本体のインストール前に必要なアプリケーションをインストールします。

```bash
sudo apt install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common
```
リポジトリキーを追加します。

```bash
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
```

### 参考情報

https://docs.docker.com/engine/install/ubuntu/

<details><summary>apt-keyを用いる方法(旧情報：非推奨)</summary><div>

```bash
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
```

<details><summary>実行結果</summary><div>

```bash
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
OK
```

</div></details>

```bash
sudo apt-key fingerprint 0EBFCD88
```

<details><summary>実行結果</summary><div>

```bash
$ sudo apt-key fingerprint 0EBFCD88
pub   rsa4096 2017-02-22 [SCEA]
      9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88
uid           [  不明  ] Docker Release (CE deb) <docker@docker.com>
sub   rsa4096 2017-02-22 [S]
```

</div></details>

リポジトリ情報を追加します。

```bash
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
```

</div></details>

本体のインストールを実行します。

```bash
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io
```

* 参考: [Install Docker Engine-Community](https://docs.docker.com/install/linux/docker-ce/ubuntu/#install-docker-engine---community)

正常にインストール出来たか確認します。

```bash
$ docker -v
Docker version 19.03.8, build afacb8b7f0
```

sudo無しでDockerを実行出来る様に設定します。

`docker`グループを追加します。

```bash
sudo groupadd docker
```

<details><summary>既に存在する場合の表示</summary><div>

```bash
$ sudo groupadd docker
groupadd: グループ 'docker' は既に存在します
```

</div></details>

現在ログイン中のユーザーを`docker`グループに追加します。

```bash
sudo gpasswd -a $USER docker
```

<details><summary>実行結果</summary><div>

```bash
$ sudo gpasswd -a $USER docker
ユーザ USER をグループ docker に追加
```

</div></details>

一旦ログアウトし、再ログインすることで有効になります。

```bash
exit
```

* 参考: [Dockerコマンドをsudoなしで実行する方法](https://qiita.com/DQNEO/items/da5df074c48b012152ee)

## Hello Worldコンテナの起動と実行
Hello Worldコンテナを起動し実行します。正常にコンテナが起動されるかの確認の為に用意されています。

```bash
docker container run hello-world
```

<details><summary>正常に起動出来ている場合</summary><div>

```bash
$ docker container run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
1b930d010525: Pull complete 
Digest: sha256:f9dfddf63636d84ef479d645ab5885156ae030f611a56f3a7ac7f2fdd86d7e4e
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
```

</div></details>

# Docker Composeのインストール
Docker Composeは`apt`コマンドでインストールが可能になりました。

```bash
sudo apt update
sudo apt install -y docker-compose-plugin
```

インストール完了後、一旦ログアウトし、再ログインします。

```bash
exit
```

これまで`docker-compose`で実行が必要でしたが、`docker compose`という風に`docker`コマンドのマネジメントコマンドとして実行出来る様になりました。

```bash
$ docker compose version 
Docker Compose version v2.12.2
```

<details><summary>従来のインストール方法</summary><div>

Docker Composeをインストールします。最新版のバージョンは[公式のGitHub](https://github.com/docker/compose/releases)で確認出来ます。現在の最新安定版は`v2.2.3`です。まずは実行ファイルをダウンロードします。

```bash
export compose_version="v2.3.3"
sudo curl -L "https://github.com/docker/compose/releases/download/${compose_version}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
```

ダウンロード後、実行権限を付与します。

```bash
sudo chmod +x /usr/local/bin/docker-compose
```

正常に動作するか確認します。

```bash
$ docker-compose -v
docker-compose version 1.25.4, build 8d51620a
```

* 参考: [Install Docker Compose](https://docs.docker.com/compose/install/#install-compose)

</div></details>

# nvidia-container-runtimeのインストール
ここからいよいよDockerからGPUを動作させるために必要なセットアップを行います。使用するGPUがNVIDIAのGPUの場合はnvidia-container-runtimeというランタイムが用意されています。AMDのGPU[^AMD]では以降の行程は実行出来ません。

```bash
curl -s -L https://nvidia.github.io/nvidia-container-runtime/gpgkey | sudo gpg --dearmor -o /etc/apt/keyrings/nvidia-container-runtime.gpg
```

<details><summary>apt-keyを用いた方法(旧情報：非推奨)</summary><div>

```bash
curl -s -L https://nvidia.github.io/nvidia-container-runtime/gpgkey | sudo apt-key add -
```

<details><summary>実行結果</summary><div>

```bash
$ curl -s -L https://nvidia.github.io/nvidia-container-runtime/gpgkey | sudo apt-key add - 
OK
```

</div></details>

</div></details>

```bash
export distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
```
```bash
curl -s -L https://nvidia.github.io/nvidia-container-runtime/$distribution/nvidia-container-runtime.list | sudo tee /etc/apt/sources.list.d/nvidia-container-runtime.list
```

```bash
sudoedit /etc/apt/sources.list.d/nvidia-container-runtime.list
```


<details><summary>実行結果</summary><div>

```bash
$ curl -s -L https://nvidia.github.io/nvidia-container-runtime/$distribution/nvidia-container-runtime.list | sudo tee /etc/apt/sources.list.d/nvidia-container-runtime.list
deb https://nvidia.github.io/libnvidia-container/ubuntu18.04/$(ARCH) /
deb https://nvidia.github.io/nvidia-container-runtime/ubuntu18.04/$(ARCH) /
```
</div></details>

以下のように修正します。

```bash
deb [signed-by=/etc/apt/keyrings/nvidia-container-runtime.gpg] https://nvidia.github.io/libnvidia-container/stable/ubuntu18.04/$(ARCH) /                             
#deb https://nvidia.github.io/libnvidia-container/experimental/ubuntu18.04/$(ARCH) /
deb [signed-by=/etc/apt/keyrings/nvidia-container-runtime.gpg] https://nvidia.github.io/nvidia-container-runtime/stable/ubuntu18.04/$(ARCH) /
#deb https://nvidia.github.io/nvidia-container-runtime/experimental/ubuntu18.04/$(ARCH) /
```

```bash
sudo apt update && sudo apt install -y nvidia-container-runtime
```

```bash
sudo reboot
```

* 参考: [nvidia-container-runtime](https://nvidia.github.io/nvidia-container-runtime/)

## Docker上でnvidia-smiの動作確認
セットアップが完了したらDocker上でnvidia-smiが動作するか確認します。今回はGPUが1台なのでオプションに`--gpus all`を渡しています。(nvida/cudaイメージでは現在latestタグが廃止されています。([参考](https://hub.docker.com/r/nvidia/cuda))

```bash
docker container run --gpus all --rm nvidia/cuda:11.4.0-base-ubuntu20.04 nvidia-smi
```

nvidia-container-runtimeをインストール後は必ず再起動します。再起動していない場合はエラーが出ます。

```bash
Digest: sha256:31e2a1ca7b0e1f678fb1dd0c985b4223273f7c0f3dbde60053b371e2a1aee2cd
Status: Downloaded newer image for nvidia/cuda:latest
docker: Error response from daemon: could not select device driver "" with capabilities: [[gpu]].
```

以下の様な表示が出ればインストールに成功しています。

```bash
$ docker container run --gpus all --rm nvidia/cuda nvidia-smi
Mon Apr  6 00:51:45 2020       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.64       Driver Version: 440.64       CUDA Version: 10.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce RTX 208...  Off  | 00000000:07:00.0 Off |                  N/A |
|  0%   31C    P8    16W / 250W |      0MiB /  7982MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+
```

# 2022/5/22改訂: Docker ComposeでＧＰＵ対応コンテナを起動する
初版を執筆してから2年以上が経過し，Docker Composeで特別な設定をしなくてもGPU対応コンテナが起動可能となりました．よって内容を改訂します．(旧来の方法も後半に残しています．)今回利用する環境は以下の様な環境です．(参考とした記事は失念してしまいました．．．:scream:)

* Docker version 20.10.16, build aa7e414
* Docker Compose version v2.5.1

## Dockerfileの作成
Dokcerfileを以下の様に作成しました．jovyanアカウントでもapt等をコンテナ内で利用出来る様に，sudoの設定もbuild時に実行します．

```dockerfile
FROM jupyter/tensorflow-notebook:latest

USER root
RUN apt-get update && apt-get upgrade -y

# Install visualization tools for Python
RUN apt-get install -y graphviz
USER jovyan
RUN pip install graphviz

USER root
RUN apt-get install -y build-essential curl wget vim git bash-completion cmake
RUN echo "jovyan ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers
RUN gpasswd -a jovyan sudo

USER jovyan
# Install basic ML libralies
RUN pip install torch torchvision scikit-learn janome
```

* 参考

https://qiita.com/DS27/items/7301cce7a2c1dec107ad

https://mukiudo.dev/post/docker/0001-debian-frontend/

http://docs.docker.jp/engine/faq.html#dockerfile-debian-frontend-noninteractive

https://www.debian.org/releases/jessie/s390x/ch05s02.html.ja

http://l.github.io/debian-handbook/html/ja-JP/sect.automatic-upgrades.html

https://qiita.com/munky69rock/items/4c3a076fe496045de5f1

http://docs.docker.jp/v1.11/engine/reference/builder.html

https://github.com/moby/moby/issues/4032#issuecomment-34597177

https://developer.nvidia.com/cuda-toolkit-archive

## docker-compose.ymlの作成
docker-compose.yml内でgpuオプションが利用出来る様になりました．解放しているポートはJupyter Notebookサーバーへのアクセス用のポートとTensorBoardへのアクセス用のポートです．

```yml
version: "3.3"                                                                                                                                               
services:
    tensorflow_notebook:
        build:
            context: .
            dockerfile: Dockerfile
        deploy:
            resources:
                reservations:
                    devices:
                        - capabilities:
                            - gpu
        environment:
            - NVIDIA_VISIBLE_DEVICES=all
            - NVIDIA_DRIVER_CAPABILITIES=all
        ports:
            - 8888:8888
            - 6006:6006
        volumes:
            - ../work:/home/jovyan/work
        restart: always
```

<details><summary>旧来の方法</summary><div>

# Docker ComposeでGPU対応コンテナを起動するための設定
[Issue情報](https://github.com/docker/compose/issues/6691)より、2020/4現在Dockerの最新版で導入された`--gpus`オプションにDocker Composeは対応出来ていない様です。その為、旧来の`--runtime=nvidia`オプションを有効にする設定が必要です。最初は`nvidia-docker`というコマンドで起動していたGPU対応コンテナが`--runtime=nvidia`オプションとなり、`--gpus`オプションへ進化し簡単になりました。Docker Composeも早く対応して欲しいところです。

まず`nvidia-container-runtime`の所在地を調べます。

```bash
$ which nvidia-container-runtime 
/usr/bin/nvidia-container-runtime
```

`/etc/docker/daemon.json`ファイルを作成します。

```bash
$ sudo vim /etc/docker/daemon.json
```

```json:/etc/docker/daemon.json
{
    "runtimes": {
        "nvidia": {
            "path": "/usr/bin/nvidia-container-runtime",
            "runtimeArgs": []
        }
    }
}
```

```bash
sudo reboot
```

この操作によって以下の2種類のコマンドが有効になります。

```bash:新方式のコマンド
docker container run --gpus all  --rm nvidia/cuda nvidia-smi
```

```bash:旧形式のコマンド
docker container run --runtime=nvidia --rm nvidia/cuda nvidia-smi
```

* 参考: [docker-composeでgpuを使う方法](https://qiita.com/hgoj/items/1ba050c7d73e0dd5ef90)

# Jupyte Notebookサーバー化に向けた設定
`TensorFlow`や`PyTorch`を実行する環境としてマシーンを活用したいと予てから思っていました。しかし、ローカルに環境構築すると管理が煩雑になります。そこで、`Docker`及び`Jupyter Notebook`の出番です。

## Dockerfileの作成
Jupyter公式の`tensorflo-notebook`というイメージをベースに環境を構築します。基本的な機械学習、Deep LearningのPythonライブラリ及びLinuxのアプリケーションをインストールします。自然言語処理の勉強もしたいと考えているので`Janome`もインストールしています。`MeCab`は頻繁に更新される為、敢えて初期設定ではインストールしないこととします。

```dockerfile:Dockerfile
FROM jupyter/tensorflow-notebook

# Install basic ML libralies
RUN pip install torch torchvision keras scikit-learn janome
USER root
RUN apt update && apt upgrade -y
RUN apt install -y build-essential curl wget vim git bash-completion
```

## 2020/5/22追記: CUDA10への対応
先日PyCUDAの勉強のためにソースをNotebook上で実行した所、以下の様なエラーが出ました。

```
CompileError: nvcc preprocessing of /tmp/tmp_9qpp46j.cu failed
[command: nvcc --preprocess -arch sm_75 -I/opt/conda/lib/python3.7/site-packages/pycuda/cuda /tmp/tmp_9qpp46j.cu --compiler-options -P]
[stderr:
b"nvcc fatal   : Value 'sm_75' is not defined for option 'gpu-architecture'\n"]
```

これはCUDA Toolkitのバージョンが古い時に、リリース以後に追加されたGPUのアーキテクチャ情報が見つからず、コンパイル出来ず出力されるエラーです。`nvcc -V`コマンドでCUDAのCUDA Toolkitのバージョンを確認しました。

```bash
$ nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2017 NVIDIA Corporation
Built on Fri_Nov__3_21:07:56_CDT_2017
Cuda compilation tools, release 9.1, V9.1.85
```

何とCUDA 9.1...。どうやらtensorflow-notebookイメージをビルドする時にデフォルトでインストールされるCUDA Toolkitがバージョン9.1の様です。

## 改訂版: Dockerfile
DockerfileにCUDA Toolkit10のインストールを追加します。2020/5/22現在PyCUDAは最新のCUBA 10.2には対応していません。故にCUDA 10.1をインストールします。

```dockerfile:Dockerfile(改訂版)
FROM jupyter/tensorflow-notebook

ARG ROOT_PASSWD
ARG PASSWD
ENV CUDA_VERSION=10.1

# Install CUDA10
USER root
RUN apt update && apt upgrade -y
RUN apt install -y gnupg2
RUN wget https://developer.download.nvidia.com/compute/cuda/repos/\
ubuntu1810/x86_64/cuda-repo-ubuntu1810_10.1.105-1_amd64.deb
RUN dpkg -i cuda-repo-ubuntu1810_10.1.105-1_amd64.deb
RUN apt-key adv --fetch-keys https://developer.download.nvidia.com/\
compute/cuda/repos/ubuntu1810/x86_64/7fa2af80.pub
RUN apt update
RUN apt install -y cuda
RUN rm cuda-repo-ubuntu1810_10.1.105-1_amd64.deb

# Install PyCUDA
ENV PATH=/usr/local/cuda-$CUDA_VERSION/bin:$PATH
ENV CPATH=/usr/local/cuda-$CUDA_VERSION/include:$CPATH
ENV LIBRARY_PATH=/usr/local/cuda-$CUDA_VERSION/lib64:$LIBRARY_PATH
RUN pip install pycuda

# Install basic ML libralies
RUN pip install torch torchvision keras scikit-learn janome

USER root
RUN apt install -y build-essential curl wget vim git bash-completion
```

## PATHの追加
`.bashrc`に以下の内容を追記します。

```bash
export cuda_version=$(/usr/local/cuda/bin/nvcc -V | grep "Cuda compilation" | awk '{print $5}' | awk '{sub(",.*","");print $1;}')
export PATH=/usr/local/cuda-${cuda_version}/bin:$PATH
export CPATH=/usr/local/cuda-${cuda_version}/include:$CPATH
export LIBRARY_PATH=/usr/local/cuda-${cuda_version}/lib64:$LIBRARY_PATH
```

以下の様に再確認するとCUDA 10.1が無事インストールされていることが分かります。

```bash
$ nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2019 NVIDIA Corporation
Built on Wed_Apr_24_19:10:27_PDT_2019
Cuda compilation tools, release 10.1, V10.1.168
```

* 参考: https://pytorch.org/
* 参考: [Keras Documentation](https://keras.io/ja/#_2)
* 参考: [Installing scikit-learn](https://scikit-learn.org/stable/install.html)
* 参考: [CUDA Toolkit 10.1 Original Archive](https://developer.nvidia.com/cuda-10.1-download-archive-base?target_os=Linux&target_arch=x86_64&target_distro=Ubuntu&target_version=1810&target_type=debnetwork)
* 参考: [pycuda 2019.1.2](https://pypi.org/project/pycuda/#files)

## docker-compose.ymlの作成
コンテナの管理を容易にする為に、docker-compose.ymlを作成しました。注意として、<font color="RED">Version3.Xは現在`runtime`オプションに非対応</font>です。故に<font color="RED">Version2.Xを指定する</font>必要が有ります。

```yml:docker-compose.yml
version: "2.4"
services:
    tensorflow_notebook:
        build:
            context: .
            dockerfile: Dockerfile
        runtime: nvidia
        environment:
            - NVIDIA_VISIBLE_DEVICES=all
            - NVIDIA_DRIVER_CAPABILITIES=all
        ports:
            - 8888:8888
        volumes:
            - ../work:/home/jovyan/work
        restart: always
```

* 参考: [Docker Hub](https://hub.docker.com/r/jupyter/tensorflow-notebook)
* 参考: [jupyter/docker-stacks](https://github.com/jupyter/docker-stacks)
* 参考: [Support \`runtime\` in v3.x compose files #6239](https://github.com/docker/compose/issues/6239)

</div></details>

## コンテナの起動
Docker Composeでコンテナを起動します。最初の起動では`-d`(バックグラウンド実行オプション)は付けません。これはJupyter Notebookへのログイン用トークンを確認する為です。ターミナル上に`http://127.0.0.1:8888/?token=(48桁の英数字の羅列:アクセストークン)`という表示が出ます。英数字の羅列を控えておきます。また、ターミナルを閉じるとコンテナが落ちてしまうため、そのままにしておきます。

```bash
docker-compose up -d
```

```bash
$ docker compose ps
NAME                                                      IMAGE                                                   COMMAND                  SERVICE               CREATED             STATUS                             PORTS
jupyter-notebook-server-container-tensorflow_notebook-1   jupyter-notebook-server-container-tensorflow_notebook   "tini -g -- start-no…"   tensorflow_notebook   11 seconds ago      Up 10 seconds (health: starting)   0.0.0.0:6006->6006/tcp, :::6006->6006/tcp, 0.0.0.0:8888->8888/tcp, :::8888->8888/tcp
```

コンテナに乗り込みJupyter Notebookのパスワードを任意の物に変更します。

```bash
docker compose exec -it tensorflow_notebook bash
```

```bash
(base) jovyan@e6af7c1fe937:~$ jupyter notebook password
Enter password: 
Verify password: 
[NotebookPasswordApp] Wrote hashed password to /home/jovyan/.jupyter/jupyter_notebook_config.json
```

変更後、コンテナからログアウトします。

```bash
(base) jovyan@e6af7c1fe937:~$ exit
```

ログアウトした後、コンテナを再起動します。再起動後に、Webブラウザで`http://サーバーIP:8888`にアクセスします。

```bash
docker compose restart 
```

<details><summary>旧情報</summary><div>

Webブラウザを開き`コンテナを稼働させているマシーンのIP:8888`でアクセスします。以下の画面が表示されれば一先ず成功です。

<img width="1437" alt="jupyter login.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469079/51d2ebe7-fcf9-b6d9-161d-1448e9045e7e.png">

ここで`Password or token:`欄に先程控えておいた英数字を入力すればログインが完了し、利用可能になります。しかし、今回は所望のパスワードでログイン出来る様にしたいので下の`Setup a Password`欄を利用します。`Token`欄に先程控えた英数字を入力し、`New Password`欄に所望のパスワードを入力します。`Log in and set new password`をクリックすれば設定完了です。下図の様な画面に遷移すれば成功です。

<img width="1437" alt="jupyter.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469079/685ecef0-0caa-3028-cd33-7aca0d5f0982.png">

## 設定したパスワードで再ログイン
画面右上の`Logout`をクリックし、一旦終了します。これからコンテナの起動を永続化します。コンテナを起動したターミナル上で`control + C`を打鍵しコンテナを終了します。`Stopping コンテナ名 ... done`と表示されます。以下のコマンドを実行し、今度はバックグラウンドでコンテナを起動します。`Starting コンテナ名 ... done`と出たら、再度Webブラウザで`コンテナを稼働させていマシーンのIP:8888`にアクセスします。

```bash
docker-compose up -d
```

</div></details>

以下の画面が出たら、設定したパスワードを入力しログインします。

<img width="1437" alt="pwlogin.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469079/5e4f150e-58e1-f861-4e38-0c405a4ccdef.png">

下図の画面に遷移し、無事再ログインが出来ました。

<img width="1437" alt="jupyter completed.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469079/f32e10e4-7ec2-e29a-214c-5d8a20e9becc.png">

## 2022/10/24追記: jupyterコマンドでの設定
jupyterコマンドで設定する方法も有ります．

https://jupyter-server.readthedocs.io/en/latest/operators/public-server.html

https://qiita.com/SaitoTsutomu/items/aee41edf1a990cad5be6

https://qiita.com/hiiragi1104/items/c2e9042bc6170873a859#jupyter-notebook%E3%81%AE%E8%A8%AD%E5%AE%9A

## 実際にTensorFlowやPyTorchがGPUモードで動作するかの確認
そもそも巨費を投じてPC自作を行ったのはTensorFlowやPyTorchでDeep Learningの学習や推論の実験をやってみたいと思っていた為です。故に両者がGPUモードで動作しなければ今回の構築は水泡に帰します。最後に動作確認を行って終了です。GPUで動作するかの確認方法は以下のコードを実行します。適当なNotebook上で実行するだけで確認出来ます。

```python:TensorFlow
from tensorflow.python.client import device_lib

device_lib.list_local_devices()
```

<details><summary>実行結果</summary><div>
GPUに関する情報が出力されれば成功です。

```bash
[name: "/device:CPU:0"
 device_type: "CPU"
 memory_limit: 268435456
 locality {
 }
 incarnation: 845859908487858210,
 name: "/device:XLA_CPU:0"
 device_type: "XLA_CPU"
 memory_limit: 17179869184
 locality {
 }
 incarnation: 2134477200705340707
 physical_device_desc: "device: XLA_CPU device",
 name: "/device:XLA_GPU:0"
 device_type: "XLA_GPU"
 memory_limit: 17179869184
 locality {
 }
 incarnation: 6110340117972168618
 physical_device_desc: "device: XLA_GPU device"]
```

</div></details>

* 参考: [TensorFlowからGPUが認識できているかを2行コードで確認する](https://thr3a.hatenablog.com/entry/20180113/1515820265)

```python:PyTorch
import torch

print(torch.cuda.is_available())
```

<details><summary>実行結果</summary><div>
`True`と表示されれば成功です。

```bash
True
```

</div></details>

* 参考: [PytorchでGPUが使えているか確認する方法](https://aniharu.hatenablog.com/entry/2019/11/15/145506)

# KerasでMNIST
この世界のHello World的な位置付けのプログラムは矢張りKerasでMNISTを学習、推論させるプログラムでは無いでしょうか？動作テストも兼ねて以下のコードをNotebook上で実行してみます。

```python:モデル本体
from keras.datasets import mnist
from keras.utils import to_categorical
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense
from keras import models

model = models.Sequential()
model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(Flatten())
model.add(Dense(64, activation='relu'))
model.add(Dense(10, activation='softmax'))

(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

train_images = train_images.reshape((60000, 28, 28, 1))
train_images = train_images.astype('float32') / 255

test_images = test_images.reshape((10000, 28, 28, 1))
test_images = test_images.astype('float32') / 255

train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)

model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
model.fit(train_images, train_labels, epochs=10, batch_size=64)

test_loss, test_acc = model.evaluate(test_images, test_labels)
```

```python:モデルの構造を表示
model.summary()
```

<details><summary>モデルの構造</summary><div>

```
Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_2 (Conv2D)            (None, 26, 26, 32)        320       
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 13, 13, 32)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 11, 11, 64)        18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 5, 5, 64)          0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 3, 3, 64)          36928     
_________________________________________________________________
flatten_1 (Flatten)          (None, 576)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 64)                36928     
_________________________________________________________________
dense_2 (Dense)              (None, 10)                650       
=================================================================
Total params: 93,322
Trainable params: 93,322
Non-trainable params: 0
_________________________________________________________________
```
</div></details>

## 実行結果
下図の様に約99.1%の正答率を達成しました。

<img width="417" alt="result.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469079/95c2bc89-8452-63eb-5b2a-b1d7f7034449.png">

* 参考: [PythonとKerasによるディープラーニング](https://book.mynavi.jp/ec/products/detail/id=90124)

# ＜おまけ＞ JupyterLabを使いたい場合
デフォルト状態はJupter Notebook Serverで動作しますが、`JupyterLab`も使用することが出来ます。方法は簡単でURL(`コンテナが稼働しているマシーンのIP:8888`)の末尾に`/lab`を追加するだけです。下図がJupyterLabが起動したスタートページです。小洒落たUIになりました。(昨今流行のダークモードも利用可)タブに表示されているタイトルも`Home Page - Select or create a notebook`から`JupyterLab`に変わります。

<img width="1437" alt="jupyterlab.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469079/c38e27b9-13ef-f780-df21-629ac6fbc2bb.png">

# 2020/9/12追記 Tensor Boardへの対応化
Tensor Boardを使える様にする為の改良方法を記事にまとめました。以下の記事に設定方法をまとめました。

* [Jupyter NotebookサーバーのTensor Board対応化](https://qiita.com/y-vectorfield/items/700d517befdb979c6364)

# まとめ
過去記事でeGPUのセットアップを実施し、GPUマシーンとして動作するマシーンとなりました。今回、DockerやNvidia Dockerをインストールしたことで、Deep Learning Workstationとして使えるマシーンが完成しました。今後投じた予算の元を取って更にお釣りが来るぐらいに活用したいと思います。

# Reference
* [NVIDIA Docker って今どうなってるの？ (19.11版)](https://qiita.com/ksasaki/items/b20a785e1a0f610efa08)
* [Dockerで、GPU対応なコンテナ環境を整備する](https://qiita.com/ttsubo/items/c97173e1f04db3cbaeda)
* [docker-composeでgpuを使う方法](https://qiita.com/hgoj/items/1ba050c7d73e0dd5ef90)
* [nVIDIA-docker2 を dockerから導入してみよう。](https://qiita.com/maloney-san/items/29608297d0c33b457ac3)

[^egpu]: 結論から言ってしまうと何事も無く無事完了しました。eGPU自体のセットアップを終えてOSから認識させることに成功すれば後はPCIExpress接続のGPUと操作は全く変わりません。寧ろDockerの進化にDocker Composeの開発が追い付いていないと痛感したセットアップになりました。

[^AMD]: 割り切りが必要かもしれませんが、AMDのGPUが機械学習の分野でNVIDIAと互角に渡り合う為の課題として、CUDAやcuDNNに匹敵するライブラリの開発、周辺環境の充実が挙げられると思います。TensorFlowは近々AMD GPUに完全対応との噂が出てますが、現状NVIDIAの独占市場であることは否めません。