[Docker入門]機械学習用のDockerイメージを作成してJupyter notebookを使用する

# この記事について
機械学習の学習環境をDockerに移行したので、Dockerの使い方を忘れたときのための備忘録用記事です。

# 環境
基本Mac前提なのでWindowsの場合のコマンドなどは書いていないです。

- MacOS Mojave

# GitHub
この記事で紹介しているDockerfileはGitHubに公開しています。
[GitHub:/kuboshu/pythonml](https://github.com/kuboshu/pythonml)

# この記事でわかること
機械学習用ライブラリをインストールしたDockerイメージを構築し、コンテナのJupyter-notebookを外部から使用して機械学習で遊ぶ方法。

# Dockerイメージにインストールされているライブラリ
Pythonを使用するのでPythonの機械学習関連のライブラリを適当に入れてみました。

- python 3.8.2
- scikit-learn 0.23.2
- pandas 1.1.2
- numpy 1.18.5
- jupyterlab 2.2.8
- pycaret 2.1.2
- lightgbm 3.0.0
- xgboost 1.2.0
- scipyt 1.5.2
- matplotlib 3.3.2
- tensorflow 2.3.1
- pytorch 1.6.0
- pyocr 0.7.2
- opencv-python 4.4.0.44
- optuna 2.1.0
- mecab

# 作成したDockerfileの内容

Ubuntu20.04をベースに作成しました。基本的にはpipでPythonのパッケージをインストールしているだけなので、特別なことは何もしていないです。

```dockerfile
FROM ubuntu:20.04
LABEL maintainer="kuboshu83"
ENV DEBIAN_FRONTEND noninteractive
ARG INSTALLDIR_PYOCR="/app/ocr"
RUN apt-get update && \
    apt-get -y upgrade && \
    apt-get install -y git \
                       make \
                       cmake \
                       gcc \
                       g++ \
                       wget \
                       zip \
                       curl && \
    # ~~~~~Pythonのインストール~~~~~
    apt-get install -y python3 python3-pip && \
    ln -s $(which python3) $(dirname $(which python3))/python  && \
    ln -s $(which pip3) $(dirname $(which python3))/pip && \
    # ~~~~~Python用ML関連ライブラリのインストール~~~~~
    # TensorflowとPytorchは容量が大きいので必要なければコメントアウトしてください。
    # 容量の目安はtensorflow=1.2GB, pytorch=2GBです。
    # Tensorflow, Pytorch以外のML様ライブラリで約2GBです。
    pip install pystache \
                numpy==1.18.5 \
                pandas \
                scikit-learn \
                matplotlib \
                jupyterlab \
                pycaret \
                lightgbm \ 
                alembic==1.4.1 \ 
                sqlalchemy==1.3.13 \
                optuna && \
    pip install tensorflow && \
    pip install torch torchvision && \
    # ~~~~~OpenCVのインストール~~~~~
    pip install opencv-python && \
    apt-get install -y libgl1-mesa-dev && \
    # ~~~~Tesseractのインストール~~~~~
    apt-get install -y libleptonica-dev tesseract-ocr && \
    # ~~~~PyOCRのインストール~~~~~
    pip install pyocr && \
    mkdir -p /usr/local/share/tessdata/ && \
    curl https://raw.githubusercontent.com/tesseract-ocr/tessdata_best/master/jpn.traineddata -sS -L -o /usr/share/tesseract-ocr/4.00/tessdata/jpn.traineddata && \
    # ~~~~MeCabのインストール~~~~
    apt-get install -y mecab libmecab-dev mecab-ipadic && \
    pip install --no-binary :all: mecab-python3 && \
    pip install neologdn && \
    #~~~~作業ディレクトリの作成~~~~
    mkdir -p /home/share

# デフォルトでPythonシェルを起動
CMD ["python"]
```


# Dockerイメージのビルド方法
Dockerイメージは以下のコマンドでビルドできます。また、Githubにあるbuild.shに同様のコマンドが記載されているので、build.shを実行してもイメージがビルドできます。

```bash
docker build -t イメージ名:バージョン Dockerfileの場所
```

# Jupyter-notebookを起動する方法
カレントディレクトリにshare/を作成して以下のコマンドを実行すると、コンテナに次いでJupyter-notebookが起動します。あとは表示されたURLをブラウザで開けばJupyter-notebookを使用することができます。
Dockerイメージのバージョンは適当なものを指定してください。以下の例ではv0.1.0を使用した場合になります。

```bash
# コンテナの/home/shareと共有するためのディレクトリを作成する
> mkdir share

# コンテナを起動する。
# -rm: コンテナ停止と同時にコンテナを削除する。
# -it: コンテナでターミナルを使用するために必要。
#      今回みたいにJupyter-notebookを使用するだけなら不要だけど何となく入れてある。
# -p : ホストの8888番ポートをコンテナの8888番ポートに割り当てる。
# -v : ホストの(カレントディレクトリ)/share/をコンテナの/home/share/にマウントする。
# -w : コンテナ起動時のコンテナのカレントディレクトリを/home/share/にする。
# Jupyter-labは8888番ポートで起動しています。
> docker run --rm -it -p 8888:8888 -w /home/share -v $(pwd)/share:/home/share pythonml:v0.1.0 /usr/local/bin/jupyter lab --ip=0.0.0.0 --port 8888 --allow-root
```

コンテナを起動すると作業用として用意してある/home/share/がカレントディレクトリになるので、これをホスト側のディレクトリと共有すると使いやすいです。
# Dockerfileを書くときに調べたこと

### [インタラクティブなインストールを避ける]

- 参考にしました([DEBIAN_FRONTEND=noninteractive ってなんだ:qiita@udzura様](https://qiita.com/udzura/items/576c2c782adb241070bc))

完全に自動でDockerイメージをビルドしたいので、パッケージなどのインストール時に手動での設定を求められたくないので、インストール時のインタラクティブな設定を無効化したかったので環境変数として以下を設定した。

```dockerfile
ENV DEBIAN_FRONTEND noninteractive
```

### [RUNの使用回数を減らす]

- 参考にしました([Dockerイメージの理解を目指すチュートリアル:qiita@zembutsu様](https://qiita.com/zembutsu/items/24558f9d0d254e33088f))

最初は何も考えずにRUN命令を多用していたが、docker image ls -aでイメージを確認すると下の様に中間レイヤーのイメージが量産されていた。どうやら、DockerはDockerfileで命令を使用する毎に中間レイヤーを作成して、最終的に中間レイヤーを合成して最終イメージを作成するらしい。よって、なるべく使用する命令数を少なくしました。

中間レイヤーが多いと問題があるのかどうかは、まだ理解していないのでわかりません。ただ、イメージ一覧を表示した際に\<none\>が大量にあるのが気持ち悪かったので、中間レイヤーを減らす様に記述してみました。

```bash
REPOSITORY          TAG       IMAGE ID      
pythonml            v0.1.0    xxxxxx        
<none>              <none>    xxxxxx        <= これとか
<none>              <none>    xxxxxx        <= これとか
<none>              <none>    xxxxxx        <= これとか
<none>              <none>    xxxxxx        <= これとか
<none>              <none>    xxxxxx        <= これとか
<none>              <none>    xxxxxx        <= これとか
<none>              <none>    xxxxxx        <= これもか
ubuntu              20.04     xxxxxx        
```

# まとめ
今回は単にPythonのパッケージをインストールしたDockerイメージを構築して、ホストからJupyter-notebookを使用して機械学習で遊ぶ環境の構築方法をメモしました。まだ、遊んでみたいライブラリとかがあるので今後入れていきたいです。

また、今回はDockerfileの見た目を優先させ、ライブラリは全てapt-getかpipで入れているため古いバージョンのものもあります。なので、今後ソースコードからビルドして最新のバージョンのインストールも時間があればやっていきたいです。

# 参考にさせていただきました

- [DEBIAN_FRONTEND=noninteractive ってなんだ:qiita@udzura様](https://qiita.com/udzura/items/576c2c782adb241070bc)
- [Dockerイメージの理解を目指すチュートリアル:qiita@zembutsu様](https://qiita.com/zembutsu/items/24558f9d0d254e33088f)