Jupyter NotebookサーバーのTensor Board対応化

Jupyter Notebookサーバーを構築して数ヶ月となりました。この間Jupyter Notebookを活用してTensor Flowの勉強をしたり、Kaggleの画像認識コンペに参加して、Tensor Flowでモデルを構築したりしてきましたが、Tensor Boardが使えない[^tensorboard]ことが分かりました。今回は`docker-compose.yml`をカスタマイズしてTensor Boardを使える様にしようと思います。

#### 過去の記事
* [Intel NUC + SSD 2TB + DRAM 32GBでハイスペックLinuxデスクトップを作ってみた](https://qiita.com/y-vectorfield/items/2fcbc669a43fe0d0cb15)
* [eGPUでハイスペックLinuxデスクトップをDeep Learning Workstation化計画(eGPUセットアップ編)](https://qiita.com/y-vectorfield/items/8960c804441d2ebd605e)
* [Deep Learning Workstation構築計画 遂に完結 (Jupyter Notebookサーバー構築編)](https://qiita.com/y-vectorfield/items/6f2989baeb388be48448)

# Tensor Boardへの対応化
localhostでTensor Boardを実行している場合は、実行時に表示されるURLをWebブラウザで開けばTensor Boardを表示させることが出来ます。しかし、Docker上で実行してるJupyter Notebookサーバー上のNotebookでTensorboardを実行可能にするためにはコンテナ上の6006[^port]ポートを公開する必要が有ります。公開したポートを介してアクセスすることでTensor Boardが利用出来る様になります。このサーバーは[過去記事](https://qiita.com/y-vectorfield/items/6f2989baeb388be48448)の方法で構築している為、Docker Composeベースで設定を行います。

## コンテナの停止
リモートサーバーにログインし、`docker-compse.yml`が格納されているディレクトリに移動します。ディレクトリに移動後、コンテナを一旦停止します。

```bash
docker-compose stop
```


## `docker-compose.yml`の改良
`docker-compose.yml`を改良して6006ポートを公開する様に設定します。

```yml:6006ポートの公開設定を追加
version: "2.4"                                                                                                                                               
services:
    tensorflow_notebook:
        build:
            context: .
            dockerfile: Dockerfile
        runtime: nvidia
        environment:
            - NVIDIA_VISIBLE_DEVICES=all
            - NVIDIA_DRIVER_CAPABILITIES=all
        ports:
            - 8888:8888
            - 6006:6006
        volumes:
            - ../work:/home/jovyan/work
        restart: always
```

## コンテナの再起動
`docker-compose.yml`の編集を終えたら、コンテナを再起動します。ここで、デフォルトのパスワードでJupyter Notebookサーバーを使いたくない場合は、[過去記事](https://qiita.com/y-vectorfield/items/6f2989baeb388be48448)の手順を参考にパスワードを変更する行程を実施して下さい。

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

## 設定確認
ポートが公開出来ているか確認します。無事`6006`ポートの公開設定が出来ました。

```bash
$ docker-compose ps
                         Name                                       Command              State                       Ports                     
-----------------------------------------------------------------------------------------------------------------------------------------------
jupyter-notebook-server-container_tensorflow_notebook_1   tini -g -- start-notebook.sh   Up      0.0.0.0:6006->6006/tcp, 0.0.0.0:8888->8888/tcp
```
$\Large{SSHによるポートフォワーディング}$

**2020/9/13訂正**： SSHによるポートフォワーディングは今回の構築方法では、不要であることが分かりました。

<details><summary>この行程は不要</summary><div>
サーバー側の設定は終わりましたので、次はクライアント側の設定を行います。サーバーの`6006`ポートをクライアントの`6006`ポートにバインドします。バインドする方法として以下のコマンドを実行します。

```bash:sshによるポートフォワーディング
ssh -fNL 6006:localhost:6006 <USER_NAME>@<SERVER_NAME>
```

* 補足説明  
  * -Lオプション: ポートフォワードの実行  
  * -fオプション: バックグラウンド実行  
  * -Nオプション: コマンド入力の無効化  

`~/.ssh/config`に以下の内容[^passwd]を追記すれば、コマンドの大部分を省略出来ます。

```config:~/.ssh/config
Host HOST_NAME
  HostName HOST_NAME
  User USER_NAME
  LocalForward 6006 127.0.0.1:6006
  IdentityFile IDENTIFY_FILE_PATH
```

ポートフォワードは以下のコマンドで実行出来ます。

```bash
ssh -fN HOST_NAME
```

実行結果を`lsof`コマンドで確認します。正しくポートフォワード出来ています。

```bash:実行結果
$ lsof -i:6006
COMMAND  PID      USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
ssh     6575 USER_NAME    8u  IPv6 0x58f4e09519d9696d      0t0  TCP localhost:6006 (LISTEN)
ssh     6575 USER_NAME    9u  IPv4 0x58f4e0952735ec85      0t0  TCP localhost:6006 (LISTEN)
```

2020/9/13追記 Windowsの場合
Windowsにはlsofコマンドが2020年9月現在有りません。netstatコマンドを上手く使うことで見える様です。

* 参考: [コマンドプロンプトで使用したいポートの状態を確認する](https://qiita.com/hirorin/items/e26d3aec0bfc97b9a173)

</div></details>

# Tensor Boardの動作確認
いよいよTensor Boardの動作確認を行います。[公式チュートリアル](https://www.tensorflow.org/tensorboard/tensorboard_in_notebooks#tensorboard_in_notebooks)の内容をそのまま利用します。今回利用するTensor FlowはVer2.3.0です。

```python:JupyterNotebookの内容
import tensorflow as tf
import datetime, os

fashion_mnist = tf.keras.datasets.fashion_mnist

(x_train, y_train), (x_test, y_test) = fashion_mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

def create_model():
    return tf.keras.models.Sequential([
        tf.keras.layers.Flatten(input_shape=(28, 28)),
        tf.keras.layers.Dense(512, activation='relu'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(10, activation='softmax')
    ])

def train_model():
    model = create_model()
    model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
    logdir = os.path.join("logs", datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))
    tensorboard_callback = tf.keras.callbacks.TensorBoard(logdir, histogram_freq=1)
    model.fit(x=x_train, y=y_train, epochs=5, validation_data=(x_test, y_test), callbacks=[tensorboard_callback])
```

```python:学習の実行
train_model()
```

Jupyter Notebook上にTensor Boardを表示させる拡張機能を読み込みます。

```python
%load_ext tensorboard
```

<font color="Red">**必ず--bind_allオプションを付けて下さい。**</font>Dockerコンテナ上で実行されているTensor Boardにクライアントからアクセス出来る様になります。

```python:TensorBoardの起動
%tensorboard --logdir logs --bind_all
```
以下の様に、Jupyter Notebook上にTensor Boardを表示出来ました。

![Tensor Board Jupyter](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469079/3507c445-0122-115f-fc25-29cff51f4048.png)

ブラウザのアドレスバーから`localhost:6006`にアクセスすることで、Webページとして表示させることも出来ます。

![Tensor Board Web](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469079/b0d84b45-3d0b-9d23-6a92-95eb1b08cdb8.png)

* 今回の検証に用いたJupyter Notebookのファイルは[こちらのリンク](https://github.com/y-vectorfield/tensorboard_test)からダウンロード出来ます。

# まとめ
Docker上でコンテナとして起動しているJupyter Notebookサーバー上で実行したTensor Boardをクライアントからアクセスして閲覧出来る様に改良しました。Tenor Boardをモデルの学習、評価に活用してきたいと思います。

# Reference
* [ノートブックでのTensorBoardの使用](https://www.tensorflow.org/tensorboard/tensorboard_in_notebooks)
* [リモートからtensorboardにアクセスする](http://miscellanea.hateblo.jp/entry/2017/07/17/231728)
* [Docker コンテナ上で起動した TensorBoard に外部からアクセスする方法](https://qrunch.net/@diatonic/entries/fPGdT6VC7FKEpCVm)
* [多段SSHやポートフォワーディングを '.ssh/config' に書く](https://qiita.com/lasta/items/41e95a2fdded18c34dae)

[^tensorboard]: Tensor Boardを実行することは出来ますが、クライアント端末のブラウザから見に行くことが出来ないという現象に見舞われています。

[^port]: Tensor Boardはデフォルトで6006ポートを使用しています。別のポートを使用することも出来ますが、明示的に指定する必要が有る為、今回はデフォルトポートを公開することにします。

[^passwd]: ~~サーバーへのログインには公開鍵方式を用いている想定です。パスワードログインの場合は`IdentityFile`の指定は不要です。~~