はじめに
研究のために Python 環境について色々調べたのでまとめました.
タイトルは機械学習ですが,Python プロジェクト全般で使えると思います.
開発環境
Python 開発で必須なものの一つが仮想環境ですが,これは uv で構築しています.
こちらは今回の Advent Calendar 含め沢山記事があるので割愛します.
フォーマッタは VSCode 拡張の Ruff を使用しています.
あと,私は使っていませんが型チェックなどを行ってくれる mypy なんかも有用だと思います.
ディレクトリ構成
言わずもがなファイル,ディレクトリ整理はどんなプロジェクトでもしましょう.
ディレクトリテンプレートの扱いに優れる Cookiecutter などを使用すれば,複数プロジェクトで同じ構成を使用する場合に一貫性を持たせられます.
今のディレクトリ構成 (概略) はこんな感じです ( 参考はこちらの記事 + ChatGPT ) .
project/
├─ src/
│ ├─ config/
│ ├─ models/
│ ├─ train/
│ └─ visualizer/
├─ outputs/
│ ├─ logs/
│ ├─ models/
│ └─ pred/
└─ main.py
Python ライブラリ
最後にコーディングの話です.
汚いコードは罪ですので可読性が高く保守しやすいコードを書きたいですよね.
個人的に「Python 書きづれ~」と思ったときにライブラリを調べたもののまとめです.
こういうものは枚挙に暇が無いですし,ここより詳しいことが書いてある記事がたくさんあるので,「へ~こんなのもあるのか」くらいの温度感で見てもらえると嬉しいです.
標準ライブラリ
pathlib
パスを変数管理するときは os モジュールではなく pathlib が便利です.
os.path では,関数の入れ子の発生が多いため,可読性が下がります.
pathlib であればメソッドチェーンが効くし,パスの結合に関数を使う必要もないため美しく書けます.
例えば
# os
os.path.exists(os.path.join("path", "to", "file"))
# pathlib
from pathlib import Path
Path("path", "to", "file").exists()
## or
(Path("path") / "to" / "file").exists()
こんな感じで使えます.
多くのライブラリのパスを引数に取る関数では Path オブジェクトも扱ってくれるため,わざわざ str(path) とする必要は少ないです.
dataclasses
データを文字列をキーにして dict で保存していませんか?
このように保存すると
- 外からアクセスするときにキーが分かりにくい
- 保存されているデータの型が分かりにくい
という欠点があります.
代わりに dataclass を使いましょう.
例えば以下のように実装すればいいです.
from dataclasses import dataclass
@dataclass
class Game:
name: str # requires type annotation
price: int
review: float
is_released: bool
これを使えば
-
Game.nameのようにドット演算子でアクセスできる- 多くのエディタで予測が効く (vim 派は知らない)
- 型が宣言されているため処理が書きやすい
ということです.
logging
なんらかのログを取るとき,毎回
with open("log_file.log", "a") as f:
を書きたくはないですよね.
ログ出力はそれ専用の logging を使って取ると処理を書くのも楽です.
from logging import getLogger, FileHandler
# 最低限のセットアップ
logger = getLogger(__name__)
logger.setLevel(INFO)
logger.setLevel(DEBUG)
filehandler = FileHandler(log_file_path, encoding="utf-8")
logger.addHandler(filehandler)
# logger.level() で level に合う出力
logger.debug("starg logging")
メッセージのフォーマットや出力先を柔軟に設定することもできます.
外部ライブラリ
PyYAML + dacite
学習パラメータなどは YAML や JSON で保存するとモデルを動かすときに取得しやすいです.
今回は dataclass を YAML で扱うことを考えます.
-
dataclassをYAMLで保存
from dataclasses import asdict
import yaml
game = Game(name="mine sweeper", ...) # dataclass
with Path.open(yaml_path, "w") as f:
yaml.safe_dump(asdict(game), f)
-
YAMLからdataclassを読み込み
yaml.safe_load() するだけでは dict になってしまうため,dacite.from_dict() を使用して dataclass の型に合うように変換します.
import yaml
from dacite import from_dict
with Path.open(yaml_path, "r") as f:
# game: dict
game = yaml.safe_load(f)
# game: Game
game = from_dict(data_class=Game, data=game)
これでモデルのバージョン管理もしやすいですね.
参考にした記事
Ruff の設定
Cookiecutter
logging
dacite