
1. はじめに
AIマーケティング統括部のリードデータアナリストの安藤と申します。
普段は求人と転職希望者様のマッチングに関する推薦システムの改善などの業務に従事しています。
パーソルキャリア株式会社には多くのアナリストが在籍していますが、その実験や分析結果が散逸しやすい、同僚の実験結果を再現できないという課題があります。
今回は、その課題に対する解決策として私が実践していることをまとめました。
同じ課題を感じている皆様に何か気づきを与えることができれば幸いです。
本稿の目的
ある程度経験を積んだデータサイエンティストであれば、実験管理の重要性は身に染みて感じているはずです。しかし、私たちはつい実験管理をサボります。
納期は迫っているし、ちょっとしたアイデアを試すつもりで.ipynbファイルを開き、書き捨ての気持ちでSQLを書き、ローカル環境にデータファイルを置きっぱなしにして、print(result)を別のメモに書いて終わり。苦労して出したseaborn.histplot(y_predict)の図は永久に再現されない……。
そんなことをよくやっている人もいるのではないでしょうか。少なくとも私はよくやってしまいます。
私たちに必要なものは、できるだけ簡便で、どんなずぼらな人でもできるような管理方法です。
今回の記事のテーマは、ツールを使わない実験管理です。
MLflowやDVCのような専門ツールの使い方を解説する記事ではありません。
Notebook、テキストファイル、フォルダ、Git、スプレッドシート、そしてuv(環境管理)といった、チームの誰もが使えるツールだけで、気軽に最低限の管理が出来るようになることを目指します。
また、本稿ではあえてJupyter Notebookを題材に扱っています。
Notebookを使用した開発フローがもたらす弊害についてはよく議論がなされていますが、それでも多くのデータアナリスト/データサイエンティストがNotebookを使っています。
Notebook自体を推奨するわけではありませんが、Notebookを使わなければ管理が自動的に改善するわけでもありません。
重要なのは実行形態ではなく、実験の意図と前提と結果を残す設計です。
また、実験だけでなく分析を管理することをも射程に入れています。
実験も分析もデータアナリスト/データサイエンティストの資産ですから、同じフォーマットで管理できるようにしています。
なぜデータサイエンティストのコードは煩雑になりやすいのか
私の大学院時代の研究室には、私のようなコンピュータによる解析を主とする"ドライ"な領域と、生物実験を主とする"ウェット"な領域の両方がありました。今思えば、ウェットな領域の人たちはかなり丁寧に実験管理を行っていました。
生物実験では貴重な試薬や検体を使う必要があり、一回一回の実験にかなり手間がかかるので、自然と記録が丁寧になっていきます。一方、コンピュータを使った実験はデータとマシンさえあれば何度でも気軽に実験を繰り返すことができるので、アイデアが先行して記録はおざなりになってしまいがちです。
データサイエンティストにも同じことが言えるでしょう。ふと閃いた素敵なアイデアを試すコストは低く、記録をきちんとつけるコストは高い(と勘違いしてしまう)。データサイエンティストのコードが煩雑になりがちな所以は、おそらくここにあるのでしょう。
この記事のテーマは、まず彼彼女らがどのように実験を管理し記録していたか、というのを思い出すところから出発しています。
管理の本質はツールを使いこなすことではない
データサイエンティストに必要な管理のうち、特定のツールを使うことで解決できる部分というのはそう大きくありません。
MLflow、Weights & Biases、Hydra、DVC ― 実験管理ツールは数多くあり、きちんと使えば再現性や共有性は大きく向上します。
しかし、これらのツールは、パラメータが整理され、コードがスクリプト化され、データの所在が明確な状況が前提になっています。
ところが、実務のデータサイエンスは往々にしてそうではありません。ちょっとした仮説検証、雑な可視化、思いつきの前処理、使い捨ての特徴量……。こうした探索的な作業は、ツールが想定する整った状況には収まりません。
それに、こうしたツールは得てして学習コストが高く、チーム全員で使っていくには教育に時間を割く必要がありますし、流行り廃りも激しいです。本腰を入れたパラメータ探索が必要でもないのに多種多様なツールを組み込むのは、牛刀をもって鶏を割くようなものであるといえます。
結果、次の二択に追い込まれます。
- A. ツールを無理やり使う → 面倒で続かない
- B. ツールを使わない → 管理が消える
現実にはBが選ばれがちです。だからこそ「実験管理は大事だと知っているのにサボってしまう」という状況が生まれるのです。
重要なのは実験の情報がどこかに残っていることであり、どのツールで残すかは本質ではありません。
ツールにこだわりすぎると、「MLflowをちゃんと使うのが面倒だから、何も記録しない」といった思考に陥りがちです。
しかし実際には、管理にはグラデーションがあります。
最低限の管理から始めよう
本稿では、完璧な管理ではなく最低限の管理から始めることを提案します。
これはすなわち、1ヶ月後の自分や他人が見たときに何をやったのかがある程度わかる、再現できるようにすることを目指しています。
管理すべき対象は4つです。
- ドキュメント ― なぜこの分析/実験をしたのか
- 実行環境 ― どんなライブラリやバージョンを使ったか
- データ ― どこからどのように取得したか
- スクリプト ― どんな処理をしたのか
特に最初の3つは得てしてNotebookの外にあり、普段はほとんど意識されていません。だからこそ、ここを軽量に管理することが重要になります。
本稿が扱うのは、使い捨ての思いつき実験(scratch)と、MLflow/DVCで本格管理する正式実験の間にある領域です。MLflowを使うほどではないが、他人に見せたり後で見返したりする程度には管理される必要がある実験のための、最低限の管理方法を提案します。
なお、scratchには一切の管理を導入しませんが、「ちょっと試すだけだから」とscratchに分類したくなったときこそ、一度立ち止まるべきです。
特にドキュメントを書くことは時間がかかって手間であるように思われるかもしれませんが、これらの管理に手を付けることは分析/実験全体の工程を短縮できるものと信じています。
次章では、この4つの要素をどのような方針で管理していくかを整理し、本稿全体の見取り図を示します。
2. 本稿の全体像
この章では、まず実験の全体の見通しを伝える意味でディレクトリ構成例を示し、それを起点に第3章〜第5章で扱う内容を整理します。
ディレクトリ構成例
project/
├── pyproject.toml ← 環境定義(第4章)
├── uv.lock ← 依存管理ファイル(第4章)
├── src/ ← 共通処理(特徴量作成・評価関数など)
├── scratch/ ← 管理対象外の実験コード
└── experiments/ ← 本稿が主に扱う対象
└── 20260201_churn_model/
├── churn_model.ipynb ← Notebook/コード(第3章)
├── docs/
│ └── plan.md ← 実験計画・メモ
├── data/
│ └── .gitkeep ← 実験固有データの置き場
├── models/
│ └── .gitkeep ← 実験固有モデルの置き場
├── reports/
│ └── report.md ← 実験レポート
├── 20260201_churn_model.yml
└── query_20260201_churn_model.sql
templates/ ← 新規実験作成用テンプレート(project/ の外)
└── experiment_template/
experiment_log.xlsx ← 実験管理シート(project/ の外)
第3章ではexperiments/配下のドキュメント運用、第4章では環境とpyproject.tomlの運用、第5章ではNotebook/スクリプトのメタファイルとSQLの扱いを説明します。
また、この他に実験管理シートを作成することを推奨します(本章で解説)。
Cookiecutter Data Scienceの構成に近いですが、一つのprojectの下にモノレポのようにいくつかの実験が入るような構成にしています。
もちろんこれは一例であり、一般的なsrcレイアウトを採用しても構いません。
実験の管理レベルについて
便宜上、次のように実験の管理レベルを設定しています。
| レベル | 名称 | 管理方法 | 置き場所 |
|---|---|---|---|
| Lv.0 | scratch(使い捨て) | 管理なし | scratch/ |
| Lv.1 | ラフ実験 | フォルダ+テンプレ+uv+メタファイル | experiments/ |
| Lv.2 | 正式実験 | MLflow / DVC 等 | ツール依存 |
本稿はLv.1の実験を扱っています。uvのような環境管理ツールを除けば、特別なツールは使いません。
もちろん、最初からLv.2(MLflow/DVC等)を使っても構いません。
こちらについては今週公開された弊社浦山の記事が参考になるでしょう。
しかし、チームで開発をするとなったとき、Lv.2のツールに慣れていない人が多ければ、Lv.1の浸透から始めていくのがよいのでは、と考えています。
Lv.1で実験を続けていくうちに次のいずれかに当てはまったら、Lv.2への昇格を検討します。
- パラメータ探索が手動追跡の限界を超えたと感じた時(目安: 数十実験以上)
- モデル登録やデプロイまで含めたライフサイクル管理が必要になった時
- チーム横断で比較・監査可能な実験トラッキングをしたくなった時
実験管理シートを作る
実験を始めるときに最初に書くのは、自身もしくはチームで管理している実験管理シートです。
まだそういったシートがなければ、ここから作り始めましょう。
これは詳細ログではなく、分析や実験を検索できるようにするためのインデックスであると考えてください。
| experiment_id | objective | owner | result_summary | tags |
|---|---|---|---|---|
| 20260201_churn_model | churn予測ベースライン | aruto.ando | AUC 0.82。行動頻度が上位特徴量 | churn,baseline,lightgbm |
result_summary は1〜2行の自由記述です。「何がわかったか」をシートの一覧で比較できるようにするためのものなので、詳細はレポートに委ね、ここには結論だけを書きます。
他に必要だと思うものがあれば付け足し、不要だと思うものがあれば削って構いません。
例えば次のようなものが考えられます。
experiment_iddateobjectivehypothesisownerstatusresult_summaryrepo_pathdoc_pathtags
次章以降の見方
- 第3章:
experiments/内で、仮説駆動でNotebookとレポートを先に設計する - 第4章: ルートの
pyproject.tomlを基準に、実験差分を最小化して環境管理する - 第5章: Notebook/スクリプトのIOをメタファイルで残し、データの来歴を追跡できるようにする
ここで紹介するディレクトリ構成やシートの項目はあくまで出発点です。
チームの規模や分析スタイルに合わせて項目を増減させていくことで、長く続く自分たちの型になっていきます。
この章のまとめ
- ディレクトリ構成を先に決めることで、第3〜5章の運用の置き場所が明確になる
- Lv.0(scratch)/ Lv.1(ラフ実験)/ Lv.2(正式実験)で管理レベルを分け、本稿はLv.1を対象にする
- 実験管理シートは詳細ログではなく「実験を検索するためのインデックス」として使う
3. ドキュメント管理
この章では、ドキュメントの管理を扱います。
何よりも重要なのは、実装を始める前にレポートのひな型を作ることです。
レポートの形式
ドキュメントの形式は何でも構いませんが、特にこだわりがなければIMRADを採用するとよいでしょう。
IMRADとは、Introduction / Methods / Results / Discussion の4部構成です。
分析計画をこの構造で書くだけで、読み手にとっての解像度が大きく上がります。
この場合、ドキュメントは次の順で設計します。
- RQ(Research Question)/仮説を立てる
- その仮説を検証する方法を決める
- 結果の見せ方と考察項目を先に決める
- その設計に沿って実装する
- 結果を流し込んでレポートを完成させる
これは科学実験の作法に則ったフローであり、AIコーディング時代に重要性を増してきた仕様駆動開発やドキュメント駆動開発の考え方と接続できる部分でもあります。
仕様駆動開発について
通常の分析では「とりあえずコードを動かしてみて、何が出たかを記録する」という流れになりがちです。しかし、この順序では試行錯誤の過程が残りませんし、出てきた結果に引っ張られて考察を書いてしまいがちです。出てきた結果によって思考過程にバイアスが入ってしまいますし、網羅的に条件を考慮できているかどうかも見通しにくいです。
先にIMRADの骨格を埋めることは、実装の仕様書を書くことに他なりません。例えばResultsセクションに「データのサマリ」「主指標」「比較する条件」「出力する図」を先に書いておくと、コードがその出力を作る処理として設計され、余分な脱線が減ります。実装の途中で「これを出力すれば仮説が確認できる」という問いに立ち返りやすくなるのも利点です。
また、AIコーディングエージェントツールとの相性という点でも効果的です。「このResultsセクションを埋める処理を実装してほしい」という仕様書を書いておくことで、白紙の状態から思い付きのプロンプトだけで実装させるよりもずっと精度の高い実装を引き出せます。
まずは自分の中にあるアイデアを書き出し、それをAIに相談しながら要件に落とす。AIコーディングエージェントツールをうまく使いながら実装し、結果を人間が確認して解釈する。こうしたサイクルを高速で回すことが実験/分析の高速化につながるはずです。
レポート項目の具体例
実装に入る前に、レポートのひな型を作ります。
一例としては次のようなものになります。
# report_20260201_churn_model.md
## Introduction
- 背景:
- OBJECTIVE:
- HYPOTHESIS/RESEARCH QUESTION:
## Methods
- データ:
- 前処理:
- モデル:
- 評価指標:
## Results
- データのサマリー(df.shape, df["user_id"].n_unique()など)
- 主指標:
- 比較表:
- 図:
## Discussion
- 仮説は支持されたか:
- 解釈:
- 次アクション:
これを埋めることは、
- 自分が何を検証しようとしているのか
- 何を後回しにしているのか
- どう実装すればよいのか
を明らかにすることにほかなりません。
自分自身にとっても、コーディングエージェントにとっても思考がクリアになるはずです。
AIでレポートを書く
こうしたレポートを作るのには労力がかかります。
従来であれば、分析/実験要件を定めるのにも書くのにも時間がかかってしまっていたため、レポートのないNotebookファイルが生まれやすくなっていました。
しかし、AIの登場により、分析のアイデアを要件化すること、そしてフォーマットに落とすことのハードルはかなり下がりました。
実際の実験結果をAIに読ませることにはセキュリティリスクがありますが、ここまでの作業には個人情報や機密情報が入るおそれはかなり少ないといえるでしょう。
この章のまとめ
- レポートは実験後に作るものではなく、実装前にIMRAD形式でひな型を作る
- Resultsを先に書くことで実装の仕様書になり、コーディングエージェントへの指示書としても機能する
- AIを使って仮説の要件化とドキュメント化を加速し、人間が解釈・確認に集中できるフローを作る
4. 環境管理
この章では、実行環境の管理を扱います。
ポイントは、プロジェクトルートのpyproject.tomlを基準にしつつ、実験ごとの差分や個人設定の差を安全に吸収することです。
PEP 735 dependency groupに従って依存関係を管理する
PEP 735は、pyproject.tomlに[dependency-groups]テーブルを定義する仕様で、2024年10月に採択されました。
従来、開発用の依存は[project.optional-dependencies](extras)で管理されることもありましたが、extrasはパッケージとして配布するユーザー向けの仕組みです。ビルド成果物に含まれるパブリックなメタデータであり、「開発チーム内での用途別の分離」という目的には本来そぐわないものでした。
[dependency-groups]はビルド配布に含まれない非公開の定義であり、パッケージとして公開しないスクリプトリポジトリでも使えます。グループ間の依存関係(あるグループが別のグループを含む)も記述できます。
分析リポジトリでの基本方針
分析を主とするリポジトリでは、基本的にグループを分ける必要はないと考えています。
ほとんどの依存はルートの[project.dependencies]にまとめて管理するので十分でしょう。
ただし、ルートとは異なるバージョンのライブラリを使いたい実験がある場合、あるいは特定の実験にのみ重いライブラリ(GPU関連、大規模モデルなど)が必要な場合のみ、実験単位でグループを切ります。
例えば実験exp001でのみ大規模なライブラリが必要な場合、そのグループを実験名で切ります。
[dependency-groups]
exp001 = ["torch", "transformers"]
インストールする際は必要なグループだけを指定します。グループ名は実験IDなど、チームで識別しやすい名前で自由に決めて構いません。
uvを使ったdependency groupの追加・同期の具体的な操作は公式ドキュメントを参照してください。
基本的な流れとしては、依存追加時に--groupフラグでグループを指定し、同期時に必要なグループを組み合わせるだけです。
共通設定と個人設定について
チーム開発では、誰の環境でも同じ品質チェックが動くことと、個人の好みを強制しないことを両立させる必要があります。
そのために、設定を「プロジェクト全体で共有するもの」と「個人に委ねるもの」に明確に分けます。
ruffやmypyのようなエディタ非依存の設定はpyproject.tomlの[tool.*]セクションに記述します。
[tool.ruff]
line-length = 88
select = ["E", "F", "I"]
[tool.mypy]
python_version = "3.13"
strict = true
さらに、環境にpre-commitがインストールされた状態で.pre-commit-config.yamlをリポジトリにコミットしておくと、チーム全員が同じhookを使えます。
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.2
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.19.1
hooks:
- id: mypy
一方、例えばvscodeのエディタの設定など、個人の好みに任せる部分は.gitignoreに入れて管理するようにしましょう。
この章のまとめ
- プロジェクトルートの
pyproject.tomlを基準に依存関係を管理し、実験ごとの差分はdependency groupで分離する - ruffやmypyの設定は集約しつつ、エディタの設定などの個人設定はignoreして再現性と自由度を両立する
- pre-commitをリポジトリに置くことで、チーム全員が同じ品質チェックを自動で実行できる
5.データ管理
この章では、データとその処理の管理を扱います。
SQLをファイル化する
少し面倒でも、データ抽出に使ったSQLは必ずファイル化しましょう。
query_YYYYMMDD_XXX.sql
-- query_20260201_churn_model.sql
-- 目的: churn予測モデル用データ抽出
-- 主要フィルタ: premium, 2024/1-2026/1
-- プレースホルダ: {start_date : '2024-01-01'}, {end_date : '2026-01-31'}
-- 出力先: data/churn_features_20260201.parquet
この程度のコメントがあれば最低限SQLの意図が伝わるはずです。
IOをメタファイルで管理する
実験が積み重なると、複数のインプットデータや中間生成物、アウトプットが発生します。
データの出入りを後から追跡できるようにするため、Notebookやスクリプトごとにメタファイルを作成してIOを管理します。
YYYYMMDD_XXX.yml
# 20260201_churn_model.yml
experiment_id: "20260201_churn_model"
script: "churn_model.ipynb"
run_at: "2026-02-01"
inputs:
features:
path: "data/churn_features_20260201.parquet"
description: "退会予測用特徴量データ"
source: "BigQuery: project.dataset.user_actions"
query: "query_20260201_churn_model.sql"
outputs:
model:
path: "models/churn_model_20260201.pkl"
description: "LightGBM退会予測モデル"
predictions:
path: "data/churn_predictions_20260201.csv"
description: "検証用予測結果"
note: "ベースラインモデル。AUC 0.82"
このような形でyamlファイルを書いておくとinputsとoutputsをリストではなく辞書にしておくと、Notebookやスクリプトからメタファイルを読み込んでそのまま参照できます。
import yaml
import polars as pl
with open("20260201_churn_model.yml") as f:
meta: dict[str, str | dict] = yaml.safe_load(f)
inputs: dict[str, dict[str, str]] = meta["inputs"]
outputs: dict[str, dict[str, str]] = meta["outputs"]
df: pl.DataFrame = pl.read_parquet(inputs["features"]["path"])
# ...
model.save(outputs["model"]["path"])
pl.DataFrame(preds).write_csv(outputs["predictions"]["path"])
パスをハードコードせずYAMLから取得するので、ファイル名の変更があってもYAML側を直すだけで済みます。
IOの記録から始めつつ、将来的には実験のハイパーパラメータや設定値もこのファイルに追記していくことで、実験全体の設定ファイルとして育てていくことができます。
S3やGCSなどのクラウドストレージを使っている場合は、パスにs3://bucket/path/...やgs://bucket/path/...の形式でURIを記載しておくと、保存先をファイルシステムの場合と同じように追跡できます。
特殊な前処理はコメントを書いてwhyを伝えやすくする
データ作成時に、通常と違う前処理を入れた場合は必ずコメントを残します。
例えば次のようなケースです。
- 特定セグメントだけ除外した
- 外れ値をドメイン知識でクリップした
- ラベルリーク回避のために期間をずらした
「なぜこの処理を入れたか」が後から読めないと、再現時に同じ判断ができません。
コメントにはwhyを書くことを意識しましょう。
polars.Exprを変数化して命名することでwhatをコードに残す
かなり長い間Pythonでテーブルデータを扱う際のスタンダードはPandasでしたが、より高速かつメモリ効率の良いテーブルデータ用のライブラリとしてPolarsが注目されています。
パフォーマンス上のアドバンテージがあるだけでなく、Polarsではexprを変数として切り出せるため、前処理の意図を名前で残しやすいのも利点です。
import polars as pl
clip_high_purchase: pl.Expr = pl.when(pl.col("purchase_amount") > 1000000).then(1000000).otherwise(pl.col("purchase_amount"))
premium_only: pl.Expr = pl.col("user_type") == "premium"
df: pl.DataFrame = (
df.filter(premium_only)
.with_columns(clip_high_purchase.alias("purchase_amount_clipped"))
)
このように処理単位で名前を付けると、コメントがなくともwhatが伝わりやすくなります。
data/やmodels/の下には.gitkeepを置く
重いデータやモデルを無視するために、次のような指定をすることがあります。
**/data/**
**/models/**
この指定だけだと、clone直後にdata/やmodels/が存在せず、読み書き時にエラーになることがよくあります。対策として、空ディレクトリを維持するために.gitkeepを置きます。
project/
├── data/
│ └── .gitkeep
└── models/
└── .gitkeep
この章のまとめ
- Notebook/スクリプトのIOを実験IDのymlファイルで記録し、パスのハードコードをなくす(まずはinputs/outputsから始める)
- SQLは
query_*.sqlとして保存し、冒頭コメントで目的・フィルタ・出力先を明示する - 特殊な前処理にはwhyをコメントで残し、Polarsのexpr変数名でwhatをコードに表現する
data/やmodels/をignoreする場合は.gitkeepでディレクトリ構造を維持する
6.まとめ
ここまで、ツールを"使わない"実験/分析管理について見てきました。
- 第2章: 全体ディレクトリ構成と実験管理シート
- 第3章: 仮説先行のドキュメント運用
- 第4章: PEP 735 dependency groupベースの環境管理
- 第5章: Notebook/スクリプトのIOメタファイルとSQL管理
分析開始時にまずやること
ここまでの内容をまとめると、実験や分析は次のように開始できるはずです。
- テンプレートから実験ディレクトリを作る
- 実験管理シートにこれから始める実験の内容を追加する
- レポートファイル
reports/report.mdを作成し、実験の骨子を固める - スクリプトのメタファイル
YYYYMMDD_XXX.ymlを作成する - 必要なdependency groupを
uvで同期する - 実装を開始する
プロジェクト内外に置くべきファイル
project/
├── pyproject.toml
├── uv.lock
├── src/
├── scratch/
└── experiments/
└── 20260201_churn_model/
├── churn_model.ipynb
├── docs/
│ └── plan.md
├── data/
│ └── .gitkeep
├── models/
│ └── .gitkeep
├── reports/
│ └── report.md
├── 20260201_churn_model.yml
└── query_20260201_churn_model.sql
templates/ ← project/ の外
└── experiment_template/
experiment_log.xlsx ← 実験管理シート(project/ の外)
また、上記のproject/以下の構造をあらかじめtemplates/experiment_template/として持っておくと毎回の初期化作業で漏れが出にくくなります。
ここではローカルファイルとしていますが、リポジトリで管理しても構いません。
新規実験では、このテンプレートをexperiments/YYYYMMDD_{experiment_id}/にコピー/cloneしてから着手します。
自分たちに合った管理方法を見つけよう
ここで紹介したテンプレートはあくまで出発点であり、プロジェクトの規模やチームの文化によって、合うものと合わないものがあるはずです。
項目を増やしたり削ったりしながら、自分たちに合った管理方法を見つけていってください。

安藤 有瑠聡 Ando Aruto
データ・AIソリューション本部 AIマーケティング統括部
マッチング基盤部 マッチンググループ
2022年パーソルキャリア株式会社に新卒入社。推薦システムの開発・分析ほか多数の業務に従事。趣味はゲームと映画と和装。
※2026年3月現在の情報です。