🧩

機械学習プロジェクトのソースコード構造パターン

2024/04/13に公開

機械学習プロジェクトのソースコード構造パターンを紹介します。

対象読者

  • 機械学習の実験〜運用をする際の、軽量なテンプレ構成を探している方

TL;DR

以下の構造を解説していきます。
実装例はこちらのリポジトリにあります。

.
├── README.md
├── data
│   └── README.md
├── experiment
│   ├── README.md
│   ├── poetry.lock
│   ├── pyproject.toml
│   ├── runners
│   │   ├── aws
│   │   │   ├── connection_config.yaml
│   │   │   ├── cpu
│   │   │   │   ├── Dockerfile
│   │   │   │   └── run.sh
│   │   │   ├── gpu
│   │   │   │   ├── Dockerfile
│   │   │   │   └── run.sh
│   │   │   ├── poetry.lock
│   │   │   └── pyproject.toml
│   │   └── local
│   │       └── run.sh
│   └── src
│       ├── config
│       │   ├── data
│       │   │   └── dataset_v1_0_0.yaml
│       │   └── experiment_1.yaml
│       ├── entrypoint.py
│       ├── methods
│       │   └── lgbm.py
│       └── util
│           ├── dataset.py
│           └── evaluate.py
└── report
    ├── poetry.lock
    └── pyproject.toml

基本構造

まず、大まかなディレクトリ構成は以下の通り。

.
├── README.md
├── data
├── experiment
└── report
  1. dataディレクトリのコードを使用してデータセット作成を行う
    • 実際にはlocal実行とは限らず、他環境に計算自体は投げることもある
    • 作成されたデータセットは何らかのストレージに保存する
    • 複数のデータセットや複数バージョンを作成して使うことがある
  2. experimentディレクトリのコードで実験を行う
    • 実際にはlocal実行とは限らず、他環境に計算自体は投げることもある
    • データセットを(クラウド)ストレージから読み込み、実験を行い、結果をストレージに保存する
  3. reportディレクトリのコードで結果の閲覧を行う
    • 実験結果をデータベースに置いているなら、単にそれを読むためのツールやコードを置く場合もある
    • MLFlowのような実験フレームワークを使用しているなら、その実験結果viewerを起動するコードを置けばよい
    • その他のレポート作業が発生するなら、異なる実験の結果を簡単にまとめるためのスクリプトを置く

続いて、それぞれ細かく拡大すると以下のような構造になっている。
各モジュールにrunners/があり、それらの中に実行環境ごとに必要なスクリプトやDockerfileなどを分けている。

1. data/の詳細

データセット作成に関連する処理を担う。
ここでいう"データセット"とは、各種学習アルゴリズムに渡す手前の段階まで簡単な処理をしてある状態のデータのこと。
データセットはセマンティックバージョンでバージョン管理をする(例えば特徴量を増やすときはバージョンを上げるなどする)。

ここに置くもの
  • データセット作成処理(例えば、データレイクにある生データを加工して、特徴量とラベルが並んだ状態のテーブルデータを作るなど)
    • 大規模なデータなら、DWH上にデータセットを生成する dbt のプロジェクトをここに入れるとよい。生成結果はクラウド上のデータベースに出力する。
    • 小規模なデータなら、実際のデータをダウンロードして加工をここで行うというのでもよい。
  • 人手で作成したラベルを付加する処理
  • モデルに依存せず行うような、データのクリーニング処理(外れ値の削除など)
ここに置かないもの
  • One-hot化、欠損値補完などの、モデル依存な処理(最近のGBDTなどよく使われるモデルでは、欠損値も学習アルゴリズムレベルで扱い方が決まっているため)
  • 正規化などの、実験の中で試しながら変更する可能性の高い特徴量加工の処理
存在理由

プロジェクトの進展に伴って、データセットは拡充することになる(例えば外部データの追加など)。
そのため、モデリングの作業と行ったり来たりすることになる。
モデルとは別にバージョンを管理しながら、データセットも育てていくので、分離してあると管理しやすい。

2. experiment/の詳細

学習〜評価の実行の処理を担う。
詳細はあとで詳しく説明する。

ここに置くもの
  • 一連の実験コード:データの読み込み〜手法ごとの前処理〜モデルの訓練〜モデルの保存〜評価〜評価結果の保存
    • 評価結果としては、数値のjsonや、プロットなども含む。
  • 実験の設定ファイル
  • 実験デプロイ用のコード
ここに置かないもの
  • データセット自体に関する処理
  • 評価結果を読みやすい形に整形する処理

3. report/の詳細

実験結果を比較するために、見たい部分をまとめたものを生成する処理を担う。

ここに置くもの
  • レポートのテンプレートや生成のためのスクリプト
存在理由

実験結果は、フォルダを分けたり色々するはず。
その結果となる各種のプロットをわざわざ見に行くのは大変。
まとめて見えるようにしよう。

追加の説明: experiment/のさらなる詳細

.
├── README.md
├── poetry.lock
├── pyproject.toml
├── runners
│   ├── aws
│   └── local
└── src
    ├── config
    ├── entrypoint.py
    ├── methods
    └── util
  1. src/config
    • hydraで読み込むconfig
  2. src/methods
    • Torch-LightningのModuleのような方式で、手法ごとのデータ前処理〜学習〜評価などの処理をポータブルにまとめた実装(entrypointから読み込み)
  3. src/entrypoint.py
    • エントリポイント
    • データセット保存先のストレージの情報や、データベースとの接続情報、結果の保存先の場所など、事前の処理を行う
    • hydra等を呼び出してconfigのパースを行い、そのconfigに従ってどのmethods/を呼び出すかなどを決定する
    • ここからfrom src.methods.lgbm import run_experimentなどのように、実験〜評価の処理を呼び出す。
  4. src/util
    • 評価用のメソッドなど、手法に依存しない処理をまとめる

Discussion