医療従事者出身、未経験でデータ職を目指して転職活動中のshimodasと申します。
初学者なりにMLOpsについて学び、試行錯誤の末に出来上がったものたちを発信していきたいと思います。
シリーズ「ノートブックを卒業した日 ── 指南書で学んだMLOps技術をKaggleで全部試してみた」
- uv(パッケージ管理)← 今ここ
- Pandera(データバリデーション)
- Hydra(設定管理)
- MLflow(実験管理)
- Streamlit(アプリ化・デプロイ)
- Dev Container(開発環境コンテナ)
- Cookiecutter × リポジトリ設計
はじめに
正直に言うと、つい最近まで Python の依存関係管理を「なんとなく pip でやってた」ままでした。
pip install hogehoge → requirements.txt に手書き追記(それすらもしない) → なんか動かなくなる → 作り直し。この流れを何度繰り返したかわかりません。Kaggle のコンペでノートブック一本で戦っているうちはそれでよかったんですが、あるときふと「本物のエンジニアリングとのギャップ」を感じる瞬間が増えてきました。
Kaggle のコードはノートブック一本で完結している。でもどうやら実務で使われている ML システムはそうじゃない——未経験からデータサイエンティストへの転職を目指して勉強している自分は、そのギャップがずっと気になっていました。「どうやって整理すればいいんだろう」とぼんやり思っていたころに手に取ったのが、「先輩データサイエンティストからの指南書」 でした。
読んでみて正直に言うと、知らないことだらけでした。uv、Hydra、MLflow、Streamlit、Dev Container、Cookiecutter……「そういうツールがあるんだ」というところから始まった感じです。名前を見ても何をするものかよくわからないものも多くて、「これ全部使いこなす人が実務のデータサイエンティストなのか~」という気持ちになりました。
指南書は一通りしっかり読んで、自分なりにNotionに内容をまとめてから実装に入りました。実装する題材として選んだのが Kaggle Playground Series S6E5(F1 ピットストップ予測) です。ちょうどその月の Playground Series のテーマが F1 だっただけで、特別な理由があったわけではないのですが、テーブルデータとしてのサイズ感がちょうどよく、特徴量エンジニアリングの試行錯誤もしやすかったので、MLOps の実装題材として採用しました。
このシリーズでは、LightGBM × 4-fold CV でピットストップ予測モデルを作り、最終的に Streamlit アプリとして Streamlit Community Cloud にデプロイするところまでの MLOps 実装を、全6回で書いていきます。
この記事ではそのうち「環境管理」、つまり uv を使ってどうプロジェクトをセットアップしたかをまとめます。
そもそも「パッケージ管理」って何を解決したいのか
「パッケージ管理ツールが必要な理由」を聞かれたとき、最初はピンとこなかったです。pip で入れればいいじゃないか、と。
でも実際にはこういう問題が起きるようです:
- A さんの環境では動くのに B さんの環境では動かない(バージョン違い)
- pandas 2.0 に上げたら scikit-learn が壊れた(依存関係の衝突)
- 半年後に同じプロジェクトを再現しようとしたら requirements.txt が古くなっていた
つまり、パッケージ管理が解決したいのはある種の「再現性」なんじゃないかと思います。いつでも、誰でも、どこでも同じ環境を作り直せること。これを当たり前に担保するためのツールが必要になってくる、ということかもしれません。
pip・Poetry・uv ── 三者三様の管理方針
主要なパッケージマネージャーを整理すると以下の通りです。
| ツール | 特徴 | 管理ファイル |
|---|---|---|
| pip | 最もシンプル。Python 付属 | requirements.txt |
| Poetry | 仮想環境・依存解決・パッケージ公開が一体 | pyproject.toml + poetry.lock |
| uv | Poetry に近い機能 + Rust 製で超高速 | pyproject.toml + uv.lock |
pip は「手軽さ」が強みなんですが、仮想環境の管理・依存関係の解決・再現性の担保は全部自分でやる必要があります。Poetry はそれらを一手に引き受けてくれる優れもので、実際に多くのプロジェクトで使われているみたいです。
uv は後発ながら Poetry と同等の機能を持ちつつ、Rust 製ゆえのインストール速度が圧倒的という特徴があります。公式ベンチマークでは pip より数十倍速いとされています。
私見ですが、速度が速いということは「試行錯誤のコストが下がる」ということでもあるかもしれません。パッケージ構成を変えるたびに1分待つのと5秒で済むのとでは、心理的障壁が全く違います。POC を小さく素早く試したい自分のスタイルと、uv の速度感はかなり相性がよかったです。
今回 F1 ピットストップ予測プロジェクトで uv を選んだ理由もここにあります。LightGBM・MLflow・Hydra・Streamlit など依存パッケージが10本を超える構成だったので、「どのツールで管理するか」の選択が地味に重要でした。Poetry も候補だったんですが、指南書で uv が紹介されていたこともあり、せっかくだから乗り換えてみることにしました。
uv の基本コマンド
コマンドの対応表を見ておくと、既存の知識と接続しやすいと思います。指南書から引用いたします:
| 操作 | pip | Poetry | uv |
|---|---|---|---|
| 初期化 | — |
poetry new / poetry init
|
uv init |
| パッケージ追加 | pip install X |
poetry add X |
uv add X |
| パッケージ削除 | pip uninstall X |
poetry remove X |
uv remove X |
| 環境の同期 | pip install -r requirements.txt |
poetry install |
uv sync |
| スクリプト実行 | python X.py |
poetry run python X.py |
uv run python X.py |
uv init でプロジェクトを初期化すると、主にpyproject.toml と .python-version が自動生成されます。.python-version はそのプロジェクトで使う Python バージョンを固定するファイルで、これが生成されるのは地味に嬉しかったです。「あれ、このプロジェクトどのバージョンで動かすんだっけ」が確実になくなります。
uv add pandas を叩くと:
- pandas をインストールします
-
pyproject.tomlの dependencies を更新します -
uv.lockを更新します
この3ステップが 1コマンドで完了します。pip だと pip install pandas → pip freeze(、どのパッケージが不要になるのかを手動で判断) >> requirements.txt(または手動追記)の2ステップが必要だったことを考えると、かなりスッキリする気がします。
実際のコマンド例:
# プロジェクト初期化
uv init
# パッケージ追加(バリエーション)
uv add pandas # 最新版
uv add "pandas==2.0.0" # バージョン固定
uv add "pandas>=2.0.0" # バージョン範囲指定
# 依存関係の同期(他環境での再現)
uv sync
# スクリプト実行
uv run python src/train.py
uv run jupyter notebook
# パッケージ削除
uv remove pandas
pyproject.toml と uv.lock の役割分担
この2ファイルの使い分けを理解するとグッと整理される気がします。
pyproject.toml ← 人間が書く「宣言」
uv.lock ← uv が生成する「記録」
pyproject.toml は「このプロジェクトには pandas と lightgbm が必要です」という宣言です。バージョン範囲指定(>=2.0.0)で書くことが多く、柔軟性を保つのが目的だと思います。
uv.lock は「現在インストールされている全パッケージの正確なバージョンと依存関係ツリー」の記録です。uv が自動生成するファイルで、環境の完全再現に使います。
if-then で整理すると:
-
新環境でプロジェクトを動かしたい →
uv sync(uv.lock から完全再現) -
新しいパッケージを追加したい →
uv add X(pyproject.toml と uv.lock を両方更新) -
パッケージのバージョンを最新に上げたい →
uv add "X>=新バージョン"で範囲指定を更新
つまり、uv.lock さえリポジトリに含めておけば「あの環境もう一回作って」が確実に再現できます。これが再現性の核心なんじゃないかと思います。
実際のプロジェクトで使った pyproject.toml
F1 ピットストップ予測プロジェクト(f1-pit-stop-predictor)の実際の pyproject.toml を載せておきます。
[project]
name = "f1-pit-stop-predictor"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"lightgbm>=4.6.0",
"mlflow>=2.22.0",
"hydra-core>=1.3.2",
"streamlit>=1.45.0",
"pandera>=0.23.1",
"pandas>=2.2.3",
"numpy>=2.2.5",
"scikit-learn>=1.6.1",
"matplotlib>=3.10.1",
"seaborn>=0.13.2",
"plotly>=6.0.1",
"jupyter>=1.1.1",
]
[tool.uv]
dev-dependencies = [
"pytest>=8.3.5",
]
[tool.uv] セクションの dev-dependencies が便利で、pytest など本番環境には不要なパッケージを dev 扱いに分けられます。uv sync --no-dev とすれば本番用の軽量環境だけ再現できます。
ちなみにパッケージは10本以上ありますが、uv add を繰り返すだけで全部 pyproject.toml に追加されます。pip 時代のように「requirements.txt を手で直した後に pip install し直す」みたいな作業はゼロです。
追記 : [tool.uv] セクションの dev-dependencies キー
https://github.com/astral-sh/uv/issues/15406
2025年8月に uv の GitHub Issue(#15406)で非推奨化(deprecation)の方針が示されました。現在の推奨は PEP 735 準拠の [dependency-groups] セクションを使う書き方です。
[dependency-groups]
dev = [
"pytest>=8.3.5",
]
uv add --dev pytest を実行すると、現在のバージョンの uv では自動的にこちらの形式で書き込まれます。記事のサンプルは一応動作はしますが、将来のバージョンで警告が出る可能性があります。注釈を添えるか、推奨形式に書き換えることをお勧めします。
ハマりポイントと実装で気づいたこと
実際に使ってみて詰まったことをメモしておきます。
1. Windows では uv のインストールから始める必要があった
最初に uv init を打ったら無反応で、uv version も同様でした。uv がそもそもインストールされていなかったというオチで、公式の手順でインストールしてから改めてセットアップしました。
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
pip は Python に付属しているので意識しなかったんですが、uv は別途インストールが必要です。「なぜ動かないんだろう」と数分悩みましたが、原因はシンプルでした。
2. uv init が "already initialized" でエラーになった
すでに pyproject.toml があるディレクトリで uv init を実行したら error: Project is already initialized と怒られました。pyproject.toml が存在するかどうかを判定基準にしているようで、既存プロジェクトには uv init は不要みたいです。この場合は uv sync から始めれば大丈夫でした。
3. devcontainer 環境との組み合わせが強力
今回のプロジェクトは devcontainer(Docker ベースの開発環境コンテナ、詳細は後の回で扱います)を使って開発環境を統一しました。コンテナ起動後に uv sync を走らせるだけで、pyproject.toml に書いたパッケージが全部入った状態になります。
# devcontainer の postCreateCommand に設定
uv sync
これにより「環境構築の手順書」が事実上不要になります。uv.lock と pyproject.toml をリポジトリに含めておけば、git clone → コンテナ起動 → uv sync で即開発開始できます。指南書を読んで初めて「再現性」という概念を意識するようになったばかりだったので、それが具体的な仕組みとして動いているのを見たときは、思ったより感動しました。
4. uv run の使い所
仮想環境を activate しなくても uv run python X.py でプロジェクトの仮想環境を使ってスクリプトを走らせられます。Jupyter も uv run jupyter notebook で起動できます。activate し忘れによる「あれ、なんで入ってないの」事故がほぼゼロになりました。自分はこれで何度か詰まっていたので、早めに知れてよかったです。
5. 速すぎてありがたみがわからなかった
正直に言うと、セットアップが終わって「あ、もう終わり?」という感じでした。インストールが速いのはわかるんですが、pip と比べて何が嬉しいのかを体感として掴むには、もう少し大きなプロジェクトや、依存関係が複雑な場面を経験しないといけないのかもしれません。「ありがたみは後から来るタイプのツールなのか??」という印象を持ちました。
まとめ
「pip でなんとなくやっていた」自分が uv に移行して変わったことを振り返ると:
-
uv addでインストールとpyproject.toml更新が1コマンドになりました -
uv syncで環境の完全再現が保証されるようになりました - Rust 製の速度で、パッケージ操作のストレスが大幅に下がりました
- devcontainer との組み合わせで「環境差異ゼロ」の開発体験が手に入りました
結局、uv が解決しているのは「再現性」と「速度」の2点に尽きる気がします。どちらも「POC を小さく素早く試す」というスタイルと相性がいいんじゃないかと思っています。
一方で、uv はまだ比較的新しいツールなので、エコシステムのサポートが追いついていない場面も稀にあるかもしれません。「安定第一」の本番プロダクトでは Poetry を選ぶ判断もあり得るとは思います。
次回は Hydra を扱います。設定ファイルの管理をどうやって整理するかという話です。
参考文献・リンク
- 浅野純季ほか『先輩データサイエンティストからの指南書 ―実務で生き抜くためのエンジニアリングスキル』技術評論社、2025年(ISBN: 978-4-297-15100-3)- https://gihyo.jp/book/2025/978-4-297-15100-3
- Kaggle Playground Series S6E5 – Predicting F1 Pit Stops
- uv 公式ドキュメント
- astral-sh/uv(GitHub)