コンテンツにスキップ

開発の流れ

ここからは、「PyTorch の Basic MNIST Example をモジュール化する」というシナリオで、モジュール開発の流れを説明します。 MNIST とは、0 - 9 の手書き数字の描かれた画像データセットで、Basic MNIST Example (下記リンク参照)は、このデータセットを学習して描かれた数値を予測するプログラムです。

  • https://github.com/pytorch/examples/tree/main/mnist

必要な機能をモジュールとして開発する際のベースとして、本手順を参考にしてください。
このドキュメントを含めたサンプル一式はこちらからダウンロード可能です。

仕様検討および外部設計

1. モジュール名を決める

他のモジュールと重複しない、モジュールの名称を決めます。 ここでは mnist_module とします。

2. メソッドの構成を決める

モジュールに持たせるメソッドを決めます。 ここでは学習処理と予測処理を持たせたいので、以下の 2 メソッド構成とします。

  • train: モデル学習
  • predict: 学習済みモデルによる予測

3. メソッドの入出力を決める

各メソッドの入出力(input/output)を決めます。 MNIST の学習処理と予測処理の内容を考えると、入出力は以下のようになります。

  • train
    • input: ラベル付き画像データ(学習用)
    • output: 学習済みモデル (CNN)
  • predict
    • input: ラベル付き画像データ(予測用)
    • input: 学習済みモデル (CNN)
    • output: 予測結果データ

4. 入出力データのスキーマを決める

モジュール機能の入出力(input/output)は、ともに EvWH 内のテーブルまたはモデルストアに保存されることになります。 うち、テーブルに保存されるデータについてはスキーマを決める必要があります。 モデルストアに保存されるデータ(学習済みモデル)については、スキーマは検討不要です。

  • ラベル付き画像データ
    • image_id integer
    • label integer
    • image text (画像バイナリデータを base64 encode した文字列)
  • 予測結果データ
    • image_id integer
    • label integer
  • 学習済みモデル (CNN)
    • (スキーマ検討不要)

開発工程

1. ディレクトリ構成を作成する

まず、モジュールのルートディレクトリ直下に MNIST モジュールのディレクトリを作成します。 今回は下記の構成とします。 ファイルについては、記述内容をこれから説明しますので現段階では作成しておく必要はありません。

- mnist_module/
  - docker-compose.yml
  - Dockerfile_train
  - Dockerfile_predict
  - Dockerfile_get-definition
  - src/
    - train.sh
    - predict.sh
    - definitions/
      - definition.json
    - mnist/
      - train.py
      - predict.py
      - requirements.txt
      - ...

2. メソッドの構成に沿ってファイルの雛形を作成する

まず、ひな形として何もしない train, predict メソッドを Dataproc API で動かすための最小構成を作成します。

最小構成の作成

最小構成を作成するために、

  • docker compose コマンドが動作するために必要な docker-compose.yml, Dockerfile, エントリーポイント (train.sh, predict.sh)
  • Dataproc API でモジュールが動作するために必要な definition.json

の内容を記述してゆきます。

docker-compose.yml(完成)

services:
  train:
    build:
      context: .
      dockerfile: Dockerfile_train
    entrypoint: ["/work/src/train.sh"]

  predict:
    build:
      context: .
      dockerfile: Dockerfile_predict
    entrypoint: ["/work/src/predict.sh"]

  get-definition:
    build:
      context: .
      dockerfile: Dockerfile_get-definition
    entrypoint: ["cat", "/work/src/definitions/definition.json"]

※ get-definition は、実装対象のメソッドとは別に、Dataproc API で動かすモジュールには必ず含める必要のあるメソッド(サービス)です。

Dockerfile_train(未完成)

FROM python:3

WORKDIR /work
COPY ./src /work/src

RUN chmod +x /work/src/train.sh

Dockerfile_predict(未完成)

FROM python:3

WORKDIR /work
COPY ./src /work/src

RUN chmod +x /work/src/predict.sh

Dockerfile_get-definition(完成)

FROM alpine:latest

WORKDIR /work
COPY ./src /work/src

train.sh, predict.sh(未完成)

#!/bin/sh

definition.json (未完成, input/output の定義はまだ無し)

{
  "name": "module.mnist_module",
  "description": "MNIST module",
  "supports": "",
  "definitions": {},
  "scripts": {
    "train": {
      "description": "Train",
      "path": "train",
      "type": "training",
      "input": {},
      "output": {}
    },
    "predict": {
      "description": "Predict",
      "path": "predict",
      "type": "prediction",
      "input": {},
      "output": {}
    }
  }
}

動作確認

次に、動作確認として何もしないモジュールを Dataproc API で呼び出します。

docker compose build

を実行してコンテナをビルドし、ビルドエラーが無ければサンプルクライアント(mnist_module.zip/samples/sample_client_1.py)を実行してください。 サンプルクライアントもエラーなく完了したら、ひな形としては完成です。

3. 各メソッドの input/output を用意する

前項では definition.json に学習・予測処理の入出力の定義をしませんでしたが、 本項では入出力を定義することで、入力データを EvWH から取得し、出力データを EvWH に格納することができるようにします。 train, predict はまだ何もしないままです。

入出力の定義では、definition.json の definitions, scripts の 2 箇所へ定義の記述が必要となります。

definitions の設定

まず definitions には input, output の各種データの形式を定義します。 今回は、ラベル付き画像データ(学習用、予測用) / 学習済みモデル / 予測結果データの 3 種類の形式が必要なので、これらの定義を definitions に記述します。

  • ラベル付き画像データ(学習用、予測用) "mnist_dataset": ラベル付き画像データを定義します。学習用、予測用の両方で共通に使用される定義です。 この形式のデータは EvWH のテーブルとの間で CSV 形式で入出力するようにします。 そのために "form": "directory" とし、その下に CSV ファイル(dataset.csv)の定義をします。 CSV のスキーマに start_datetime, end_datetime, location がありますが、これらはデータローダでロードしたデータにデフォルトで作成されるカラムであり、MNIST では使用しません。
  • 学習済みモデル "mnist_model": 学習済みモデルを定義します。 この形式のデータは EvWH のモデルストアに入出力をしたいので、モデルファイルとそのメタデータ (JSON) のセットとして扱う必要があります。 モデルファイルは PyTorch の torch.save() で保存できる state_dict 形式に対応しています。 "form": "directory" とし、その下に モデル(mnist.pth) とメタデータ(mnist.xdata-meta.json) のファイル定義をします。
  • 予測結果データ "mnist_predict_result": 予測結果データを定義します。 この形式のデータは EvWH のテーブルとの間で CSV 形式で入出力するようにします。 そのために "form": "file" として CSV ファイルの定義をします。

scripts の設定

一方 scripts では、train / predict のそれぞれにつき input / output の定義を記述します。 設計で定めたメソッドの入出力に従い、以下の内容とします。

  • train.input
    • ラベル付き画像データ(学習用) "train_dataset": 学習に使用するラベル付き画像データの入力を設定します。 paramtype は definitions で定義した "mnist_dataset" を指定します。 path 指定により、DDC から取得したデータが /work/runs/input/train_dataset/dataset.csv に予め配置されるようになります。
  • train.output
    • 学習済みモデル "trained_model": 学習済みモデルの出力を設定します。 paramtype は definitions で定義した "mnist_model" を指定します。 path 指定により、/work/runs/output/models/mnist.pth, /work/runs/output/models/mnist.xdata-meta.json にモデルとそのメタデータを出力すれば自動的にモデルストアに収納されるようになります。
  • predict.input
    • ラベル付き画像データ(予測用) "predict_dataset": 予測に使用するラベル付き画像データの入力を設定します。 paramtype は definitions で定義した "mnist_dataset" を指定します。 簡単のために学習用データと共通の定義を流用していますが、予測処理ではラベルは正解率の計算のみに使われます。 path 指定により、DDC から取得したデータが /work/runs/input/predict_dataset/dataset.csv に予め配置されるようになります。
    • 学習済みモデル "trained_model": 学習済みモデルの入力を設定します。 paramtype は definitions で定義した "mnist_model" を指定します。 path 指定により、モデルストアから取得したモデルとそのメタデータが /work/runs/input/models/mnist.pth, /work/runs/input/models/mnist.xdata-meta.json に予め配置されるようになります。
  • predict.output
    • 予測結果データ "predict_result": 予測結果データの出力を設定します。 paramtype は definitions で定義した "mnist_predict_result" を指定します。 path 指定により、/work/runs/output/predict_result/result.csv に予測結果データを出力すれば自動的に DDC に収納されるようになります。

ここまでの手順で、input, output を含むモジュールの準備が完了しました。 ここからは、作成したモジュールの動作確認をします。

動作確認の下準備: EvWH 側に input, output の入れ物(DDC, モデルストア)を作る

definition.json で定義した input, output の各種データは DDC またはモデルストアに格納されます。 動作確認をするためには DDC とモデルストアをあらかじめ作成しておく必要があります。

以下の curl コマンドにより各種 API を実行し予測結果データの DDC とモデルストアを作成します。 なお、これ以降の説明では、xData Edge の初期インストール状態で導入される APIKey と Secret を利用します。環境に合わせて設定してください。

curl \
-H "Content-type:application/json" \
-H "APIKey: a4e16fab-9077-4a13-a7d5-0f1fda63cd16" \
-H "Secret: Do8feigevo8OhCi7OchoSan8zid7Fie3Aez4" \
-X POST -d \
'
{
  "jsonrpc": "2.0",
  "method": "fl.initialize_model_store",
  "params": {
    "model_store_ddc":"ddc:mnist_models"
  },
  "id": "mnist_module"
}
' \
http://localhost/api/v1/fl/jsonrpc

curl \
-H "Content-type:application/json" \
-H "APIKey: 0df23f0d-44cb-4085-8ac9-158e6ced3056" \
-H "Secret: 4AzbY3pvfes33aow49nhjn3aUphv9zFWctcT" \
-X POST -d \
'
{
  "jsonrpc": "2.0",
  "method": "prov.new_ddc",
  "params": {
    "ddc_label": "ddc:mnist_predict_result",
    "columns": [
      {"column_name":"image_id", "data_type":"int"},
      {"column_name":"predicted", "data_type":"int"}
    ],
    "key": "image_id"
  },
  "id": "mnist_module"
}
' \
http://localhost/api/v1/provenance/jsonrpc

更に、ラベル付き画像データは添付のスクリプト(mnist_module.zip/tools/mnist_loader.py)によってテーブル作成 & データ投入し、

python mnist_loader.py ~/data_dir --kind train --end 6000
python mnist_loader.py ~/data_dir --kind test --end 1000

さらに、対象のテーブルに DDC を紐づけます。

curl \
-H "Content-type:application/json" \
-H "APIKey: 0df23f0d-44cb-4085-8ac9-158e6ced3056" \
-H "Secret: 4AzbY3pvfes33aow49nhjn3aUphv9zFWctcT" \
-X POST -d \
'
{
  "jsonrpc": "2.0",
  "method": "prov.set_ddc",
  "params": {
    "ddc_label": "ddc:mnist_train",
    "source": "event.mnist_train_tbl_0",
    "ddc_type":"link"
  },
  "id": "mnist_module"
}
' \
http://localhost/api/v1/provenance/jsonrpc

curl \
-H "Content-type:application/json" \
-H "APIKey: 0df23f0d-44cb-4085-8ac9-158e6ced3056" \
-H "Secret: 4AzbY3pvfes33aow49nhjn3aUphv9zFWctcT" \
-X POST -d \
'
{
  "jsonrpc": "2.0",
  "method": "prov.set_ddc",
  "params": {
    "ddc_label": "ddc:mnist_test",
    "source": "event.mnist_test_tbl_0",
    "ddc_type":"link"
  },
  "id": "mnist_module"
}
' \
http://localhost/api/v1/provenance/jsonrpc

動作確認の下準備: output のダミーデータを作る

現段階では train, predict ともに学習・予測処理の実装がまだ無いため、動作確認では output のダミーデータを作ることとします。 train.sh, predict.sh で以下のように記述することで、それぞれ最後の echo でダミーデータを出力します。

train.sh(未完成)

#!/bin/sh

mkdir "/work/runs/output/models"

MODEL_FILE="/work/runs/output/models/mnist.pth"
MODEL_META_FILE="/work/runs/output/models/mnist.xdata-meta.json"

echo 'dummy_model_data' > "$MODEL_FILE"
echo '{"model_kind": "mnist", "model_state": 10, "round": 1}' > "$MODEL_META_FILE"

predict.sh(未完成)

#!/bin/sh

mkdir "/work/runs/output/predict_result"

RESULT_FILE="/work/runs/output/predict_result/result.csv"

echo 'image_id,label\n1,1' > "$RESULT_FILE"

動作確認

動作確認の準備ができましたので、

docker compose build

を実行してコンテナをビルドし、ビルドエラーが無ければサンプルクライアント(mnist_module.zip/samples/sample_client_2.py)を実行してください。 サンプルクライアントもエラーなく完了したら、

curl \
-H "Content-type:application/json" \
-H "APIKey: a4e16fab-9077-4a13-a7d5-0f1fda63cd16" \
-H "Secret: Do8feigevo8OhCi7OchoSan8zid7Fie3Aez4" \
-X POST -d \
'
{
  "jsonrpc": "2.0",
  "method": "fl.get_models",
  "params": {
    "model_store_ddc":"ddc:mnist_models"
  },
  "id": "mnist_module"
}
' \
http://localhost/api/v1/fl/jsonrpc

curl \
-H "Content-type:application/json" \
-H "APIKey: 0df23f0d-44cb-4085-8ac9-158e6ced3056" \
-H "Secret: 4AzbY3pvfes33aow49nhjn3aUphv9zFWctcT" \
-X POST -d \
'
{
  "jsonrpc": "2.0",
  "method": "prov.get_ddc_records",
  "params": {
    "ddc_label": "ddc:mnist_predict_result"
  },
  "id": "mnist_module"
}
' \
http://localhost/api/v1/provenance/jsonrpc

を実行して、

  • モデルストアにモデルが追加されたこと(train の output)
  • 予測結果にレコードが追加されたこと(predict の output)

を確認できます。

また、モジュールを実行すると環境設定の WORKER_DATA_DIR で設定したフォルダ(例: ~/work/runs/)下に実行フォルダ(ハッシュフォルダ名)が生成されます。 学習処理、予測処理で 2 つのフォルダが生成されているはずなので、その中の input, output が正しく生成されているか確認してください。 また、

  • [学習処理の実行フォルダ]/input/train_dataset/dataset.csv
  • [予測処理の実行フォルダ]/input/predict_dataset/dataset.csv

は次項の動作確認に使用します。

4. 処理の本体を開発する

ここからは、MNIST データセットの学習、予測処理を実装してゆきます。 今回は GitHub で公開されている Basic MNIST Example (下記リンク参照)をベースとして、入力と出力の形式をモジュール用に調整します。

  • https://github.com/pytorch/examples/tree/main/mnist

学習・予測処理の実装(train.py, predict.py)

学習・予測処理の実装では、src/mnist/ 下に以下の 4 ファイルを作成します。

  • train.py
  • predict.py
  • common.py
  • requirements.txt

完成形のコードは添付ファイル(mnist_module.zip/src/mnist/)を参照ください。ここではポイントとなる事項のみ説明します。

  • 学習処理(train.py)
    • 引数 dataset によりラベル付き画像データ(学習用)の入力元 CSV ファイルパスを指定できるようにします。
    • 元の実装では、学習用データをその場でダウンロードしていますが、引数で指定した CSV ファイルから読み込むように変更します。 その際、画像データは base64 デコードして取得します。
    • 引数 --model により学習済みモデルの出力先ファイルパスを指定できるようにします。
  • 予測処理(predict.py)
    • 引数 dataset によりラベル付き画像データ(予測用)の入力元 CSV ファイルパスを指定できるようにします。
    • 元の実装は、学習用データセットをその場でダウンロードしていますが、引数で指定した CSV ファイルから読み込むように変更します。 その際、画像データは base64 デコードして取得します。
    • 引数 --model により学習済みモデルの入力元ファイルパスを指定できるようにします。
    • 引数 dataset により予測結果の出力先 CSV ファイルパスを指定できるようにします。

エントリーポイントの作成(train.sh, predict.sh)

次に、train.py, predict.py を実行するためのシェルスクリプトを作成します。 シェルスクリプトでは train.py, predict.py で定めた引数に、具体的な値(ファイルパス)を渡します。

train.sh(完成)

#!/bin/sh

mkdir "/work/runs/output/models"

SCRIPT_FILE="/work/src/mnist/train.py"
MODEL_FILE="/work/runs/output/models/mnist.pth"
MODEL_META_FILE="/work/runs/output/models/mnist.xdata-meta.json"
DATASET_FILE="/work/runs/input/train_dataset/dataset.csv"

python "$SCRIPT_FILE" --model "$MODEL_FILE" --epoch 2 "$DATASET_FILE"

echo '{"model_kind": "mnist", "model_state": 10, "round": 1}' > "$MODEL_META_FILE"

predict.sh(完成)

#!/bin/sh

mkdir "/work/runs/output/predict_result"

SCRIPT_FILE="/work/src/mnist/predict.py"
MODEL_FILE="/work/runs/input/models/mnist.pth"
DATASET_FILE="/work/runs/input/predict_dataset/dataset.csv"
RESULT_FILE="/work/runs/output/predict_result/result.csv"

python "$SCRIPT_FILE" --model "$MODEL_FILE" "$DATASET_FILE" "$RESULT_FILE"

動作確認

作成した学習・予測処理をローカル環境でを動作確認します。まずは、

pip install -r requirements.txt

を実行し、お使いの Python 環境に必要なパッケージをインストールします。 (仮想環境を利用したい場合は必要に応じ venv などを利用ください)

学習処理の動作確認では、train.sh の DATASET_FILE で指定したファイルパスに予めラベル付き画像データ(学習用)の CSV ファイルを配置します。 CSV ファイルは前項の動作確認で生成された [学習処理の実行フォルダ]/input/train_dataset/dataset.csv を使用できます。 その後シェルスクリプトを実行し、

train.sh

同シェルスクリプトの MODEL_FILE で指定した場所にモデルファイルが生成されていれば、成功です。

予測処理の動作確認では、predict.sh の DATASET_FILE で指定したファイルパスに予めラベル付き画像データ(予測用)の CSV ファイルを配置します。 CSV ファイルは前項の動作確認で生成された [予測処理の実行フォルダ]/input/predict_dataset/dataset.csv を使用できます。 更に、学習処理で出力されたモデルファイルを predict.sh の MODEL_FILE で指定されたパスに移動します。 その後シェルスクリプトを実行し、

predict.sh

同シェルスクリプトの RESULT_FILE で指定した場所に予測結果が生成されていれば、成功です。

5. 処理の本体をコンテナ化する

次に、前項で実装した学習、予測処理を Docker コンテナとして実行できる形にします。 Dockerfile_train, Dockerfile_predict に、学習・予測処理を実行するために必要なパッケージをインストールする記述を追加します。

Dockerfile_train(完成)

FROM python:3

COPY ./src/mnist/requirements.txt /tmp/

RUN pip install --upgrade pip
RUN pip install --upgrade setuptools
RUN pip install --no-cache-dir -r /tmp/requirements.txt

RUN rm -f /tmp/requirements.txt

WORKDIR /work
COPY ./src /work/src

RUN chmod +x /work/src/train.sh

Dockerfile_predict(完成)

FROM python:3

COPY ./src/mnist/requirements.txt /tmp/

RUN pip install --upgrade pip
RUN pip install --upgrade setuptools
RUN pip install --no-cache-dir -r /tmp/requirements.txt

RUN rm -f /tmp/requirements.txt

WORKDIR /work
COPY ./src /work/src

RUN chmod +x /work/src/predict.sh

(補足) requirements.txt だけを先に単独でコピーしてパッケージをインストールするように記述しています。 このようにすることで、src 下に変更を入れて再ビルドする際、キャッシュを利用してパッケージのインストールをスキップできるため、ビルド時間を短縮できます。

続いて、

docker compose build

を実行してコンテナをビルドし、ビルドエラーが無ければ、モジュールの実行準備が完了です。

6. モジュールとして Dataproc API 経由で動作確認する

最後に、実装した機能をモジュールとして Dataproc API 経由で実行します。 ここまでの手順で準備は既にできているので、サンプルクライアント(mnist_module.zip/samples/sample_client_2.py)を実行してください。 エラーなく完了したら、

curl \
-H "Content-type:application/json" \
-H "APIKey: a4e16fab-9077-4a13-a7d5-0f1fda63cd16" \
-H "Secret: Do8feigevo8OhCi7OchoSan8zid7Fie3Aez4" \
-X POST -d \
'
{
  "jsonrpc": "2.0",
  "method": "fl.get_models",
  "params": {
    "model_store_ddc":"ddc:mnist_models"
  },
  "id": "mnist_module"
}
' \
http://localhost/api/v1/fl/jsonrpc

curl \
-H "Content-type:application/json" \
-H "APIKey: 0df23f0d-44cb-4085-8ac9-158e6ced3056" \
-H "Secret: 4AzbY3pvfes33aow49nhjn3aUphv9zFWctcT" \
-X POST -d \
'
{
  "jsonrpc": "2.0",
  "method": "prov.get_ddc_records",
  "params": {
    "ddc_label": "ddc:mnist_predict_result"
  },
  "id": "mnist_module"
}
' \
http://localhost/api/v1/provenance/jsonrpc

を実行して、

  • モデルストアにモデルが追加されたこと(train の output)
  • 予測結果にレコードが追加されたこと(predict の output)

を確認できます。途中で動作確認したときとは異なり、ダミーではない実際のモデルと予測結果です。

Basic MNIST Example をベースとしたモジュール開発の流れは以上となります。 処理本体の実装と input/output の設定を、必要な機能に合わせて置き換えることで、モジュールを開発することができます。