Dockerを使って機械学習の環境を作ろうとした話

## はじめに

Docker を使用して機械学習の環境を構築してみます。
構築を通して Docker に慣れて貰えれば幸いです。 
質問や指摘、感想は大歓迎です。よろしくお願いします。

## 目標

- Docker を実際に使用してみる
- Docker を使って機械学習の環境構築をしてみる

## どんな環境作るの？？

今回は Docker を使用して環境構築をすることが目標なので
そこまで厳密に環境にこだわっていません。
ディレクトリの構成は以下です。
機械学習の環境っぽくしたいので、Kaggle からタイタニックのデータセットを拝借しています。
以下のイメージでディレクトリを分けています。

- `input`:検証対象のデータを保存するディレクトリ
- `output`:検証結果を保存するディレクトリ
- `src`:データ分析のスクリプトを配置するディレクトリ

```lang:bash
.
├── Dockerfile
├── README.md
├── docker-compose.yml
├── input
│   └── titanic
│       ├── gender_submission.csv
│       ├── test.csv
│       └── train.csv
├── output
├── requirements.txt
└── src
    └── main.py
```

requirements.txt の中身は以下です。
本当は ver の指定とかをするべきなんでしょうが省略。

```lang:requirements.txt

numpy
pandas
matplotlib
scikit-learn
```

## 環境の準備

### Docker の用語解説

ここからは実際に Docker を使用していきます。
簡単に用語の解説をしていきます。

- イメージ:仮想環境の設計図
- コンテナ:イメージを基に作成される仮想環境
- Dockerfile:イメージの設計図

以下が作成した Dockerfile です。
python3.8.0 のイメージを基に

- OS のパッケージのアップデート
- 機械学習に使用するライブラリのインストール  

を行なっています。

```lang:Dockerfile
# pythonの3.8.0をベースにする
FROM python:3.8.0

RUN apt-get update \
    && apt-get upgrade -y \
    # imageのサイズを小さくするためにキャッシュ削除
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* \
    # pipのアップデート
    && pip install --upgrade pip

# 作業するディレクトリを変更
WORKDIR /home/DeepLearning

COPY requirements.txt ${PWD}

# pythonのパッケージをインストール
RUN pip install -r requirements.txt

# 作業するディレクトリを変更
# コンテナの内部には入った際のディレクトリの位置を変更している
WORKDIR /home/DeepLearning/src
```

今回は image のサイズの削減にも取り組んだので、以下のことをしています。

- -slim を使用
- Run コマンドは一つにまとめる
- キャッシュの削除

詳しくは、こちらに載っています。

http://docs.docker.jp/engine/userguide/eng-image/dockerfile_best-practice.html#build-cache

## イメージを作成,コンテナを起動して潜入してみる

build で用意した Dockerfile からイメージを作成します。  
作成したイメージが確認しやすいように、名前を付けて build しています。

`docker build --rm -t {イメージ名:タグ} {Dockerfileのパス}`

- --rm : 構築に成功したら、全ての中間コンテナを削除
- -t : '名前:タグ' 形式で名前とオプションのタグを指定

```lang:bash
　docker build --rm -t deeplearning_env:latest .
```

イメージが作成されているか確認
`docker images`でイメージの一覧を確認できます。

```lang:bash
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
python              3.8.0               0a3a95c81a2b        3 weeks ago         932MB
deeplearning_env    latest              6a4aaef11001        7 minutes ago       1.33GB
```

イメージが作成されていることが確認出来たので
これを基にしてコンテナを作成します。
今回はコンテナの作成と同時にコンテナの内部に入っています。

```lang:bash
$ docker run --rm -it -v .:/home/DeepLearning --name deeplearning_container deeplearning_env:latest /bin/bash
```

使用しているオプションの説明

- -i:ホストマシンとコンテナの双方向に接続できるようにするため
- --rm:コンテナから抜けるとコンテナを削除
- -t:コンテナ内に擬似的なターミナルを割り当て
- -name:コンテナに名前を付ける
- -v:ホストにあるファイルをコンテナ内にマウント {ホストのパス}:{コンテナのパス}

最後の/bin/bas はターミナルの起動のために追加
コンテナの内部に入ると以下のように表示されます。

```lang:bash
$ docker run --rm -it --name deeplearning_container deeplearning_env:latest /bin/bash
root@21d326d93525:/home/DeepLearning/src# 
```

この状態で別のターミナルを開いてコンテナの確認をしましょう。
`docker ps`で現在稼働中のコンテナのみを確認ができます。

```lang:bash
$ docker ps
CONTAINER ID        IMAGE                            COMMAND                  CREATED             STATUS              PORTS                    NAMES
21d326d93525        deeplearning_env:latest          "/bin/bash"              25 seconds ago      Up 24 seconds                                deeplearning_container
```

コンテナ内部に潜入できたので、実際にpythonを動かしてみましょう！
動かすスクリプトは以下です。
Kaggleからデータセットを読み込んで、それっぽくデータを整形して
実際に分析するところ位までを書いています。

```lang:python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# フォルダのパスを定数化
INPUT_FOLDER_PATH = '../input'
OUTPUT_FOLDER_PATH = '../output'

# CSVファイルの読み込み
gender_submission = pd.read_csv("{}/titanic/gender_submission.csv".format(INPUT_FOLDER_PATH))
train = pd.read_csv("{}/titanic/train.csv".format(INPUT_FOLDER_PATH))
test = pd.read_csv("{}/titanic/test.csv".format(INPUT_FOLDER_PATH))

# データの形を確認
print(train.shape)

# 欠損値を処理
train['Age'].fillna(train['Age'].median(), inplace=True) 

train.drop("Cabin", axis=1, inplace=True)

train.dropna(subset = ['Embarked'], inplace=True)

# データを確認
print(train.head())

# 以降データ解析

```

```lang:bash
root@8a559713801e:/home/DeepLearning/src# python main.py 
(891, 12)
   PassengerId  Survived  Pclass                                               Name     Sex   Age  SibSp  Parch            Ticket     Fare Embarked
0            1         0       3                            Braund, Mr. Owen Harris    male  22.0      1      0         A/5 21171   7.2500        S
1            2         1       1  Cumings, Mrs. John Bradley (Florence Briggs Th...  female  38.0      1      0          PC 17599  71.2833        C
2            3         1       3                             Heikkinen, Miss. Laina  female  26.0      0      0  STON/O2. 3101282   7.9250        S
3            4         1       1       Futrelle, Mrs. Jacques Heath (Lily May Peel)  female  35.0      1      0            113803  53.1000        S
4            5         0       3                           Allen, Mr. William Henry    male  35.0      0      0            373450   8.0500        S
```

ここで、もう少し楽にしたいと思います。
具体的には、runコマンドの時にオプションの指定が複雑だし、毎回打つのが大変です。
`docker run --rm -it --name deeplearning_container deeplearning_env:latest /bin/bash`
これ、もう少し楽に記述できないかな？？  

## docker-compose を利用

開発環境を楽に立ち上げる為に以下のファイルを用意します。

```lang:docker-compose
version: "3"
services:
  app:
    container_name: "python_machine_learning"
    build:
      context: .
      dockerfile: ./Dockerfile
    image: python_machine_learning
    volumes: 
        - $PWD:/home/DeepLearning
    tty: true
```

### docker-compose ？

複数のコンテナを同時に立ち上げてくれるもの。
オプションを記述することで各コンテナの起動時の設定などができる。
今回は複数のコンテナではないですが、起動時のオプションをこちらに持たせておくことにしましょう。
docker-compose のあるディレクトリに移動して
以下のコマンドでイメージの作成からコンテナの起動まで全て行ってくれます。

```lang:bash
$ docker-compose up -d
```

* -d:バックグラウンドでコンテナを起動

起動したコンテナに入ります。  
`docker exec -it python_machine_learning /bin/sh -c "[ -e /bin/bash ] && /bin/bash || /bin/sh"`

## まとめ

Docker を使って、機械学習用の環境が構築できました。