diff --git a/.claude/rules/coding-style.md b/.claude/rules/coding-style.md new file mode 100644 index 0000000..5859e20 --- /dev/null +++ b/.claude/rules/coding-style.md @@ -0,0 +1,116 @@ +--- +paths: ["src/**", "scripts/**"] +--- + +# コーディング規約 (Coding Standards) + +Python コード(`src/**`・`scripts/**`)を編集するときは本ルールに従うこと. + +## 基本方針 (Basic Policy) + +- ruff を唯一の lint / format ツールとして使用する.設定は `pyproject.toml` の `[tool.ruff]` に集約する. +- 標準(PEP 8)から逸脱しない範囲で ruff の自動修正に従う. +- プロジェクト固有のルールは本ファイルに最小限で書く. +- 別ツール(black・isort・mypy 等)は導入しない(ruff で完結させる). + +## 適用 ruff ルール (Ruff Rules) + +`pyproject.toml` の `[tool.ruff.lint]` で `select = ["E", "F", "W", "I", "B", "UP", "N"]` を選択する. + +| ルール | 内容 | +| --- | --- | +| E (pycodestyle) | PEP 8 形式エラー | +| F (pyflakes) | 未使用変数・未定義シンボル等 | +| W (pycodestyle) | 形式警告 | +| I (isort) | import 文の並び順 | +| B (flake8-bugbear) | バグになりやすいパターン検出 | +| UP (pyupgrade) | Python 3.12 の機能で書き換え可能なコードの提案 | +| N (pep8-naming) | クラス・関数・変数の命名 | + +行長は 100 文字とする.実行コマンドは [ENV_04 開発コマンド](../../docs/02_ENV/ENV_04_開発コマンド.md)を参照する. + +## 臨床データを扱うコードの規則 (Clinical Data Rules) + +本プロジェクトの最重要規則.[PLAN_01 要件定義書](../../docs/03_PLAN/PLAN_01_要件定義書.md)の制約に対応する. + +- **データのパスをコードに書かない**.データの場所は設定 YAML と環境変数から受け取る +- **ログ・例外メッセージに患者 ID・画像ファイル名を出さない**.個票が手元のログに流れるのを防ぐため,件数・行番号・クラス名で示す(例外処理の詳細は `.claude/rules/error-handling.md`) +- `print` ではなく `logging` を使う(サーバ実行のログが `runs/<実験ID>/log.txt` に残るため) + +## 機械学習コードの規則 (ML Code Rules) + +- **乱数はグローバル状態に依存しない**.seed を引数・設定から受け取り,実際に使った値を `config.yaml` に記録する +- **デバイスをハードコードしない**.`cuda:0` を直書きせず設定から受け取る(共用サーバのため GPU 指定が必要) +- **テンソルの形状を docstring に書く**(例: `(B, C, H, W)`).形状の取り違えは ML コードの主要なバグ源である +- 学習のハイパーパラメータは設定 YAML に置き,コードに既定値を散らさない + +## 型ヒント (Type Hints) + +- **公開関数(モジュールから export される関数・クラス)**: 引数と戻り値に型ヒント必須 +- **内部ヘルパ・テストコード**: 任意.書ける所は書く +- `torch.Tensor`・`np.ndarray` は型だけでは情報が足りないため,要素型と形状を docstring で補う + +静的型チェック(mypy / pyright)は当面導入しない.必要が出てから追加する. + +## docstring + +- **公開関数・クラス**: docstring 必須 +- **書式**: Google スタイル(`Args:` / `Returns:` / `Raises:`)に統一 +- **言語**: 日本語可 + +例: + +```python +import torch + + +def normalize_batch(images: torch.Tensor, mean: torch.Tensor, std: torch.Tensor) -> torch.Tensor: + """バッチ画像を ImageNet の統計で正規化する. + + Args: + images: 入力画像(`(B, 3, H, W)`, float32, 値域 0.0〜1.0). + mean: チャンネルごとの平均(`(3,)`). + std: チャンネルごとの標準偏差(`(3,)`). + + Returns: + 正規化後の画像(`(B, 3, H, W)`, float32). + """ +``` + +## 命名規則 (Naming) + +ruff `N` ルールでチェックする. + +| 対象 | スタイル | 例 | +| --- | --- | --- | +| モジュール | snake_case | `manifest.py`・`patient_split.py` | +| パッケージ | snake_case | `tias_judge_model` | +| 関数・変数 | snake_case | `load_manifest`・`macro_f1` | +| クラス | PascalCase | `TongueColorDataset`・`TrainConfig` | +| 定数 | UPPER_SNAKE_CASE | `CLASS_SLUGS` | +| プライベート | 接頭辞 `_` | `_validate_manifest` | + +## コメント (Comments) + +- **WHY を書く**: 何をしているかは識別子で表現し,コメントは「なぜこの実装か」「制約」「出典」に限定する +- **TODO** は `# TODO(rintoHasegawa): 内容` 形式で記述する + +## import 順序 (Import Order) + +ruff `I` で自動整列する.手動整列は不要. + +1. 標準ライブラリ +2. サードパーティ(`torch`・`timm`・`numpy` 等) +3. プロジェクト内(`from tias_judge_model import ...`) + +## CLI 引数 (CLI Arguments) + +- 短縮形より長い形を優先する(`--config` を主とする) +- `argparse` 標準を使用する +- ヘルプ文は日本語可 + +## 参考 (References) + +- [PEP 8 — Style Guide for Python Code](https://peps.python.org/pep-0008/) +- [Google Python Style Guide — Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) +- [ruff documentation](https://docs.astral.sh/ruff/) diff --git a/.claude/rules/error-handling.md b/.claude/rules/error-handling.md new file mode 100644 index 0000000..2609399 --- /dev/null +++ b/.claude/rules/error-handling.md @@ -0,0 +1,43 @@ +--- +paths: ["src/**", "scripts/**"] +--- + +# エラーハンドリング方針 (Error Handling) + +例外を raise / 捕捉するコード(`src/**`・`scripts/**`)を書くときは本ルールに従うこと.コーディング全般の規約は `.claude/rules/coding-style.md` を参照する. + +## 例外の使い分け (Exception Types) + +| 状況 | 送出する例外 | +| --- | --- | +| ユーザー入力(設定 YAML・manifest・画像ファイル等)の内容・形式の問題 | `ValueError` に統一して raise | +| 内部論理エラー(到達しないはずの分岐・不変条件違反等) | `RuntimeError` | +| pydantic を使うバリデーション | `pydantic.ValidationError` をそのまま伝播(握りつぶさない) | + +## データ整合性は fail fast (Fail Fast on Data) + +機械学習では**静かな除外が評価を歪める**ため,データの問題は黙って飛ばさず止める. + +- `patient_id` の欠落・未知のラベル値・重複行・読めない画像は raise する +- 意図的に除外する行は manifest の `note` 列に理由を明示し,除外件数をログに出す(人間が判断できるようにする) +- 「とりあえず `try` で囲んで `continue`」は禁止.クラス分布が静かに変わり,結果の解釈を誤る + +## 個人情報を出さない (No PII in Messages) + +例外メッセージ・ログに患者 ID・画像ファイル名を含めない.位置は行番号・件数で示す. + +```python +raise ValueError(f"manifest の {row_number} 行目: patient_id が空である") +``` + +## 方針 (Policy) + +- **最小限主義**: 独自例外クラスの階層は導入しない.上表の標準例外で表現できる範囲に留め,足りなくなった時点で検討する +- **メッセージは日本語可**: 原因と対処が分かる具体的な内容にする(どのフィールド・何行目が不正か) +- **握りつぶさない**: `except:` で例外を無言で捨てない.捕捉するなら再送出するか,ログを残した上で明示的に処理する +- **境界で検証する**: 入力の検証は入口(CLI・スキーマ読込・manifest 読込)で行い,学習ループ・指標計算は「検証済みの正しい入力が来る」前提で書く(`.claude/rules/project-structure.md` の依存方向・純関数優先と対応する) + +## 参考 (References) + +- [Python 公式ドキュメント — 組み込み例外](https://docs.python.org/3/library/exceptions.html) +- [Pydantic — Error Handling](https://docs.pydantic.dev/latest/errors/errors/) diff --git a/.claude/rules/project-structure.md b/.claude/rules/project-structure.md new file mode 100644 index 0000000..20d2aca --- /dev/null +++ b/.claude/rules/project-structure.md @@ -0,0 +1,35 @@ +--- +paths: ["src/**", "tests/**", "scripts/**"] +--- + +# プロジェクト構成ルール (Project Structure) + +ソース・テスト・スクリプトを追加・移動するときは本ルールに従い,責務ごとのサブパッケージ配置を保つこと.設計の背景は [SPEC_01 アーキテクチャ設計](../../docs/04_SPEC/SPEC_01_アーキテクチャ設計.md)を参照する(本ルールは配置規則のみを定める). + +## ディレクトリ構成 (Layout) + +```text +src/tias_judge_model/ +├── __init__.py +├── schema.py # pydantic 設定スキーマ +├── data/ # manifest 読み込み・Dataset・患者単位分割・前処理 +├── models/ # timm ラッパ(アーキテクチャ差し替え点) +├── training/ # 学習ループ・損失・スケジューラ +├── evaluation/ # 指標・混同行列・レポート生成 +├── baseline/ # ルールベース比較(TIASjudge 相当) +├── remote/ # リモート実行(転送・ジョブ実行・回収) +└── cli/ # CLI エントリポイント + +tests/ # src のサブパッケージ構造をミラーする +scripts/ # 単発の調査・合成データ生成・manifest 生成 +configs/ # 実験設定 YAML(コード外の設定) +``` + +## 配置ルール (Placement Rules) + +- **1 モジュール 1 責務**: 新しい機能は責務に合うサブパッケージに置く.該当が無ければ新サブパッケージを作る(既存モジュールを肥大化させない) +- **依存方向**: `cli` → `training` / `evaluation` → `data` / `models` の一方向を保つ.`data`・`models` から上位(`cli`・`training`)を import しない +- **純関数を優先**: 副作用(ファイル I/O・ssh・GPU 実行)は `cli`・`remote`・`training` の縁に寄せ,`data` の変換処理・`evaluation` の指標計算は入力 → 出力の純関数として書く(テスト容易性のため.`.claude/rules/testing.md` の必須テスト対象と対応する) +- **設定・データ**: 実験条件は `configs/*.yaml` に置き,閾値・パスをコードに埋め込まない.臨床データはリポジトリに置かない(`data/` は `.gitignore` 済み) +- **調査・使い捨てコード**: 恒久的に使わない単発スクリプトは `src/` ではなく `scripts/` に置く(パッケージの公開 API を汚さない) +- **テストの配置**: `tests/` は `src/tias_judge_model/` の構造をミラーする(詳細は `.claude/rules/testing.md`) diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md new file mode 100644 index 0000000..3604523 --- /dev/null +++ b/.claude/rules/testing.md @@ -0,0 +1,78 @@ +--- +paths: ["tests/**", "**/test_*.py", "**/conftest.py"] +--- + +# テスト方針 (Test Policy) + +テストコード(`tests/**`・`test_*.py`・`conftest.py`)を追加・編集するときは本ルールに従うこと. + +## 基本方針 (Basic Policy) + +- フレームワーク: **pytest** +- 配置: `tests/` 配下に `src/tias_judge_model/` の構造をミラーする +- カバレッジ目標: **数値目標なし**.代わりに「下記の必須対象は必ず単体テストを書く」を徹底する +- **臨床データを使うテストは書かない**.テストは合成データ(小さいランダム画像と合成 manifest)のみで完結させる.手元でもサーバでも同じテストが通ること +- 実行コマンドは [ENV_04 開発コマンド](../../docs/02_ENV/ENV_04_開発コマンド.md)を参照する + +## ファイル・関数命名 (Naming) + +- ファイル: `test_<対象モジュール>.py` +- 関数: `test_<振る舞い>`(例: `test_split_keeps_patients_disjoint`) +- クラス(任意): `Test<クラス名>` で振る舞いごとにグループ化してよい + +## 必ずテストを書く対象 (Mandatory) + +1. **患者単位分割**: 同一 `patient_id` が train / val / test に跨らないこと.**最重要**(破れると評価が無意味になる).同じ seed で分割が再現することも確認する +2. **manifest のバリデーション**: `patient_id` 欠落・未知ラベル・重複行で停止すること(黙って除外しないこと) +3. **指標計算**: 既知の混同行列に対して macro F1・クラス別 precision / recall が期待値と一致すること.クラス ID の順序が固定されていること +4. **回収ホワイトリスト**: 画像・`manifest.csv`・個票の予測結果が回収対象に入らないこと([SPEC_01](../../docs/04_SPEC/SPEC_01_アーキテクチャ設計.md)の制約をテストで固定する) +5. **設定スキーマ**: 不正な YAML が `ValidationError` になること.必須フィールドの欠落を検出すること + +## テストを必須としない対象 (Optional) + +- **CLI のエントリポイント**: 統合テスト 1 本でカバーする +- **図の生成**: 見栄えはテストしにくいため「ファイルが生成されたか」の smoke テストで十分 + +## モック方針 (Mocking) + +- **ssh・リモート実行はモックする**.テストが実サーバに接続してはならない(転送コマンドの組み立て・回収対象の判定など,純粋なロジックを検証する) +- **ファイル I/O はモックしない**.`tmp_path` フィクスチャで実ファイルを使う +- **モデルの学習はモックしない**が,合成データ・極小モデル・1 エポックで回す(下記 slow マーカー) +- **乱数を含むテストは必ず seed を固定する** + +## 重いテストの扱い (Slow Tests) + +学習を回すテストは `@pytest.mark.slow` を付けて区別する. + +```python +import pytest + + +@pytest.mark.slow +def test_train_one_epoch_on_synthetic_data(): + ... +``` + +- 通常実行(高速): `uv run pytest -m "not slow"` +- 全テスト実行: `uv run pytest` + +マーカーは `pyproject.toml` の `[tool.pytest.ini_options]` に登録する. + +## /implement パイプラインでのテスト (Pipeline) + +1. **Phase 1(実装 / coder)**: 機能実装 → 人間が動作確認 +2. **Phase 2(テスト / tester)**: 本方針に従いテストを追加 +3. **Phase 3(リファクタ / refactorer)**: テストを safety net として品質改善 +4. **Phase 4**: ドキュメント更新 + +Phase 2 でテストが書きにくいと感じたら設計が悪いシグナルである.Phase 3 でモジュールを分割するか責務を見直す. + +## テスト失敗時の運用 (On Failure) + +- 仕様変更による失敗: 先に SPEC を更新してからテストを書き直す +- バグ起因の失敗: 修正してから再実行する +- flaky なテスト: seed 固定漏れか,学習の非決定性に依存した期待値を疑う(学習結果の数値を直接期待値にしない) + +## 参考 (References) + +- [pytest documentation](https://docs.pytest.org/) diff --git a/CLAUDE.md b/CLAUDE.md index 7c83e46..96348a0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,12 +5,18 @@ ## 開発進捗 -最新: 立ち上げフェーズ 3(環境構築)完了.サーバ側の CUDA 疎通まで確認(手元の依存インストールは継続中) +最新: 立ち上げ完了(フェーズ 1〜6).次は PLAN_02 の Step 1(基盤: 設定スキーマ・合成データ・manifest・患者単位分割) ※ 本欄は**最新ステップ 1 行のみ上書き更新**.詳細な進捗履歴(動機・設計判断・失敗パターン)は [docs/PROGRESS.md](docs/PROGRESS.md) に追記する.運用ルールは `.claude/rules/progress-log.md` を参照(該当ファイル編集時に自動ロードされる). ## 必須ルール(コード実装時) - +- **臨床データを手元 PC に置かない.リポジトリにも入れない**.実データを触るコードはサーバでのみ動かし,手元は合成データで確認する([PLAN_01](docs/03_PLAN/PLAN_01_要件定義書.md)) +- ログ・例外メッセージに患者 ID・画像ファイル名を出さない(件数・行番号で示す) +- 手元へ回収するのは指標・図・ログ・重みのみ(画像・manifest・個票は回収しない.[SPEC_01](docs/04_SPEC/SPEC_01_アーキテクチャ設計.md)「回収ホワイトリスト」) +- データ分割は必ず患者単位で行う([SPEC_02](docs/04_SPEC/SPEC_02_学習・評価仕様.md)) +- 舌色そのものがラベルであるため,色を変えるデータ拡張は既定で使わない(SPEC_02) +- パッケージ管理は uv のみ(`uv add`).絶対パスのハードコード禁止(ENV_02「環境再現性のルール」) +- コーディング規約・構成・テスト方針・エラー処理は `.claude/rules/`(coding-style / project-structure / testing / error-handling)が該当ファイル編集時に自動ロードされる ### Git 運用 @@ -66,7 +72,7 @@ - プロジェクト立ち上げフロー: docs/01_GUIDE/GUIDE_01_プロジェクト立ち上げフロー.md - エージェント運用ルール: docs/01_GUIDE/GUIDE_02_エージェント運用ルール.md - ※ Git 規約・ドキュメント書式・命名規則・進捗記録・テンプレート改変記録は `.claude/rules/`(git-conventions / markdown-style / docs-naming / progress-log / template-customization)に定義されている(git-conventions は常時,他は該当ファイル編集時に自動ロード) -- ※ コーディング規約,テスト方針等はプロジェクト立ち上げ時に path-scoped rules(`.claude/rules/`)として作成する(GUIDE_01 参照) +- ※ コーディング規約・プロジェクト構成・テスト方針・エラー処理は path-scoped rules(`.claude/rules/` の coding-style / project-structure / testing / error-handling)として定義済み(該当ファイル編集時に自動ロード) ### 02_ENV(環境) @@ -78,10 +84,12 @@ ### 03_PLAN(計画) - 要件定義書: docs/03_PLAN/PLAN_01_要件定義書.md +- 開発ステップ: docs/03_PLAN/PLAN_02_開発ステップ.md ### 04_SPEC(仕様・設計) - +- アーキテクチャ設計: docs/04_SPEC/SPEC_01_アーキテクチャ設計.md +- 学習・評価仕様: docs/04_SPEC/SPEC_02_学習・評価仕様.md ### 05_TECH(技術設計) diff --git "a/docs/02_ENV/ENV_04_\351\226\213\347\231\272\343\202\263\343\203\236\343\203\263\343\203\211.md" "b/docs/02_ENV/ENV_04_\351\226\213\347\231\272\343\202\263\343\203\236\343\203\263\343\203\211.md" index a355e2e..16988fe 100644 --- "a/docs/02_ENV/ENV_04_\351\226\213\347\231\272\343\202\263\343\203\236\343\203\263\343\203\211.md" +++ "b/docs/02_ENV/ENV_04_\351\226\213\347\231\272\343\202\263\343\203\236\343\203\263\343\203\211.md" @@ -31,6 +31,8 @@ | Lint(自動修正) | `uv run ruff check --fix .` | | Format | `uv run ruff format .` | +※ `.claude/` はテンプレート所有のため ruff の対象外(`pyproject.toml` の `extend-exclude`).テンプレート由来のフックのテストは `uv run pytest .claude/hooks/tests` で実行する. + ## フック (Hooks) | 目的 | コマンド | 備考 | diff --git "a/docs/03_PLAN/PLAN_02_\351\226\213\347\231\272\343\202\271\343\203\206\343\203\203\343\203\227.md" "b/docs/03_PLAN/PLAN_02_\351\226\213\347\231\272\343\202\271\343\203\206\343\203\203\343\203\227.md" new file mode 100644 index 0000000..ac37eb8 --- /dev/null +++ "b/docs/03_PLAN/PLAN_02_\351\226\213\347\231\272\343\202\271\343\203\206\343\203\203\343\203\227.md" @@ -0,0 +1,70 @@ +# 開発ステップ (Development Steps) + +[PLAN_01 要件定義書](PLAN_01_要件定義書.md)の目的を満たすための実装順序.各ステップは `/implement <タスク>` で進める(実装 → テスト → リファクタリング → ドキュメント更新). + +## 全体像 (Overview) + +| Step | 内容 | 前提 | +| --- | --- | --- | +| 1 | 基盤: 設定スキーマ・合成データ生成・manifest 読み込み・患者単位分割 | なし(手元で完結) | +| 2 | リモート実行ツール(送信・実行・回収・GPU 確認,回収ホワイトリスト) | Step 1 | +| 3 | データセット集計(実データの素性把握) | **臨床データのコピー完了**・倫理確認 | +| 4 | 学習パイプライン最小版(ConvNeXt-Tiny を 1 本通す) | Step 2・3 | +| 5 | 評価・レポート生成(指標・混同行列・`runs/` 出力) | Step 4 | +| 6 | ベースライン比較(TIASjudge のルールベース) | Step 5 | +| 7 | 実験反復(不均衡対策・モデル比較・解像度検討) | Step 6 | +| 8 | 結論(5-fold 交差検証・評価レポート・配布形式の判断) | Step 7 | + +Step 1・2 はデータのコピー待ちの間に進められる. + +## Step 1: 基盤 (Foundation) + +- **成果物**: `schema.py`(pydantic 設定),`data/manifest.py`(読み込み・検証),`data/split.py`(患者単位分割),`scripts/make_synthetic_dataset.py` +- **完了条件**: 合成 manifest で分割が患者単位に保たれ,同一 seed で再現することがテストで確認できる.不正な manifest が停止する +- **理由**: 手元だけで完結し,以降すべての土台になる.最重要の「患者単位分割」をここで固める + +## Step 2: リモート実行ツール (Remote Execution) + +- **成果物**: `remote/`・`cli/remote.py`(コード送信 / ジョブ実行 / 結果回収 / GPU 空き確認) +- **完了条件**: コードを送って合成データの短い学習をサーバで実行し,結果を回収できる.**回収ホワイトリスト**が効いており,画像・manifest が回収対象にならないことがテストで確認できる +- **注意**: 非対話 ssh では `uv` が PATH に無いため,ツール側で `export PATH="$HOME/.local/bin:$PATH"` を行う([ENV_02](../02_ENV/ENV_02_環境構築手順.md)トラブルシューティング) + +## Step 3: データセット集計 (Dataset Survey) + +- **成果物**: データセット集計コマンド,`scripts/build_manifest.py`,集計レポート(`docs/06_TEST/` に記録) +- **完了条件**: 枚数・患者数・クラス分布・画像サイズ分布・破損件数が数値で判明し,[SPEC_01](../04_SPEC/SPEC_01_アーキテクチャ設計.md)・[SPEC_02](../04_SPEC/SPEC_02_学習・評価仕様.md)の未確定事項(マスク外領域の扱い・入力解像度・患者 ID の追跡可否・ラベルの保存形式)が確定する +- **前提**: 研究室 PC → サーバのデータコピーと,倫理審査・データ利用許諾の確認が済んでいること + +## Step 4: 学習パイプライン最小版 (Minimal Training) + +- **成果物**: `models/`・`training/`・学習コマンド,設定 YAML 1 本 +- **完了条件**: 手元で合成データ 1 エポックが通り,サーバで実データの学習が完走して `runs/<実験ID>/` が生成される +- **方針**: 精度は問わない.「落ちずに回り,記録が残る」ことだけを見る + +## Step 5: 評価・レポート生成 (Evaluation) + +- **成果物**: `evaluation/`・評価コマンド(macro F1・クラス別指標・混同行列・学習曲線) +- **完了条件**: val で選び test を最後に一度だけ見る運用が実装され,指標が既知の混同行列に対してテストで期待値と一致する + +## Step 6: ベースライン比較 (Baseline) + +- **成果物**: `baseline/`(TIASjudge のルールベース診断),比較レポート +- **完了条件**: 同一テストセットでルールベースと機械学習モデルの指標が並べて出る +- **ここで決める**: ルールベースを移植するか別リポジトリを参照するか(SPEC_02 の未確定事項) + +## Step 7: 実験反復 (Experiments) + +- **成果物**: 実験群(クラス不均衡対策・モデル比較・入力解像度・マスク外領域の埋め方)とその記録 +- **完了条件**: 各要因の効果が**独立した実験として**切り分けて記録されている +- **注意**: 共用サーバのため使用する GPU を明示的に指定する + +## Step 8: 結論 (Conclusion) + +- **成果物**: 5-fold 交差検証の結果,評価レポート,TIASjudge への引き渡し方針(配布形式) +- **完了条件**: PLAN_01 の成功基準 4 項目が満たされ,「組み込むか・手法を変えるか」の判断材料が揃う + +## 進め方の原則 (Process Rules) + +- Step 3 の結果で SPEC_01・SPEC_02 を更新してから Step 4 以降に進む(仕様変更は先にドキュメントへ反映する) +- 実験は 1 回の変更で 1 要因のみ変える(効果の切り分けができなくなるため) +- 各ステップ完了時に進捗を記録する(`.claude/rules/progress-log.md`) diff --git "a/docs/04_SPEC/SPEC_01_\343\202\242\343\203\274\343\202\255\343\203\206\343\202\257\343\203\201\343\203\243\350\250\255\350\250\210.md" "b/docs/04_SPEC/SPEC_01_\343\202\242\343\203\274\343\202\255\343\203\206\343\202\257\343\203\201\343\203\243\350\250\255\350\250\210.md" new file mode 100644 index 0000000..5d8a4b1 --- /dev/null +++ "b/docs/04_SPEC/SPEC_01_\343\202\242\343\203\274\343\202\255\343\203\206\343\202\257\343\203\201\343\203\243\350\250\255\350\250\210.md" @@ -0,0 +1,103 @@ +# アーキテクチャ設計 (Architecture) + +## 基本方針 (Principles) + +1. **実データに触るコードはサーバでしか動かさない**.手元では合成データで同じコードパスを流す +2. **手元への回収はホワイトリスト方式**.回収対象を明示的に列挙し,列挙外(画像・manifest・患者 ID)は持ち帰れないようにする +3. 設定は YAML に集約し,pydantic で検証する.コードに絶対パス・閾値を書かない +4. 実験は `runs/<実験ID>/` に閉じる.同じ設定と seed で同じ結果に辿れるようにする + +## ディレクトリ構成 (Directory Layout) + +```text +TIASJudgeModel/ +├── configs/ # 実験設定 YAML +├── src/tias_judge_model/ +│ ├── schema.py # pydantic 設定スキーマ +│ ├── data/ # manifest 読み込み・Dataset・分割・前処理 +│ ├── models/ # timm ラッパ(アーキテクチャ差し替え点) +│ ├── training/ # 学習ループ・損失・スケジューラ +│ ├── evaluation/ # 指標・混同行列・レポート生成 +│ ├── baseline/ # ルールベース比較(扱いは未定) +│ ├── remote/ # リモート実行(tar over ssh・tmux・回収) +│ └── cli/ # エントリポイント +├── scripts/ # 合成データ生成・manifest 生成 +├── tests/ +├── data/ # .gitignore(合成データ.実データは置かない) +└── runs/ # .gitignore(実験結果) +``` + +依存方向は `cli` → `training` / `evaluation` → `data` / `models` の一方向とする.`data` と `models` は CLI・学習ループを import しない. + +## データ契約 (Data Contract) + +サーバ上のデータ配置(`~/data/TIASJudgeModel/`): + +```text +~/data/TIASJudgeModel/ +├── images/ # 舌領域セグメンテーション済み画像 +└── manifest.csv # 学習が読む唯一の入口 +``` + +`manifest.csv` の列: + +| 列名 | 内容 | 備考 | +| --- | --- | --- | +| `image_path` | `images/` からの相対パス | — | +| `patient_id` | 患者を一意に識別する ID | **必須**.分割の単位になる | +| `label` | 舌色 5 分類のいずれか | 医師の診断記録に由来 | +| `note` | 任意の備考 | 除外理由等 | + +- manifest はサーバ上の生成スクリプトで作る(実データの構造を確認してから実装する) +- `patient_id` が取得できない場合,**画像 ID を患者 ID と見なす代替はしない**.生成を止めて人間が判断する(同一患者が分割を跨ぐと評価が壊れるため) + +## クラス定義 (Class Definition) + +内部表現は英字 slug,表示は日本語とし,整数 ID を固定して混同行列の軸をぶらさない. + +| ID | slug | 表示 | +| --- | --- | --- | +| 0 | `pale` | 淡白 | +| 1 | `light_red` | 淡紅 | +| 2 | `red` | 紅 | +| 3 | `dark_red` | 暗赤 | +| 4 | `purple` | 紫 | + +※ 並び順は TIASjudge の定義に合わせて確定する(順序尺度として扱えるかは未確定.[SPEC_02](SPEC_02_学習・評価仕様.md) 参照). + +## 実験の出力 (Run Artifacts) + +```text +runs/<実験ID>/ +├── config.yaml # 解決済み設定 + git コミットハッシュ + seed +├── history.csv # エポックごとの損失・指標 +├── metrics.json # 最終指標(macro F1・accuracy・クラス別) +├── confusion_matrix.png # 図 +├── confusion_matrix.csv # 数値 +├── log.txt +└── checkpoints/best.pt +``` + +実験 ID の書式: `YYYYmmdd-HHMMSS_<設定ファイル名>_` + +## 回収ホワイトリスト (Pull Whitelist) + +手元へ回収できるのは以下のみ.リモート実行ツールにこの制約を実装する. + +| 回収する | 回収しない | +| --- | --- | +| `config.yaml`・`history.csv`・`metrics.json`・`*.png`・集計のみの `*.csv`・`log.txt` | 画像・`manifest.csv`・患者 ID を含むファイル・個票の予測結果 | + +- `checkpoints/` は既定では回収せず,明示的に指定したときのみ回収する +- 個票(画像ごとの予測)が必要な分析はサーバ上で行い,集計結果だけを持ち帰る + +## CLI (Entry Points) + +| コマンド | 役割 | 実行場所 | +| --- | --- | --- | +| データセット集計 | 枚数・患者数・クラス分布・画像サイズ・破損チェック | サーバ(結果の要約のみ手元へ) | +| 学習 | 設定 YAML を受け取って学習する | サーバ | +| 評価 | 学習済み run の評価・レポート生成 | サーバ | +| リモート実行 | コード送信・ジョブ実行・結果回収・GPU 空き確認 | 手元 | + +コマンド名は実装時に確定し,[ENV_04 開発コマンド](../02_ENV/ENV_04_開発コマンド.md)に記載する. diff --git "a/docs/04_SPEC/SPEC_02_\345\255\246\347\277\222\343\203\273\350\251\225\344\276\241\344\273\225\346\247\230.md" "b/docs/04_SPEC/SPEC_02_\345\255\246\347\277\222\343\203\273\350\251\225\344\276\241\344\273\225\346\247\230.md" new file mode 100644 index 0000000..290385f --- /dev/null +++ "b/docs/04_SPEC/SPEC_02_\345\255\246\347\277\222\343\203\273\350\251\225\344\276\241\344\273\225\346\247\230.md" @@ -0,0 +1,76 @@ +# 学習・評価仕様 (Training & Evaluation) + +## 前処理 (Preprocessing) + +| 項目 | 方針 | +| --- | --- | +| リサイズ | アスペクト比を保って 224×224 に収める(事前学習モデルの入力に合わせる) | +| 正規化 | ImageNet の mean / std(事前学習の統計に合わせる) | +| マスク外領域 | データ確認後に確定(黒埋めか平均色埋めか.黒は強いエッジを作るため要検証) | + +## データ拡張 (Augmentation) + +**色を変える拡張は既定で無効にする.** 舌の色そのものが正解ラベルであり,color jitter・彩度変更・ホワイトバランス変更はラベルを壊す. + +| 使う | 使わない(既定) | +| --- | --- | +| 左右反転・微小回転・微小スケール・ランダムクロップ | 色相・彩度・明度ジッタ・グレースケール化・強い正規化 | + +※ 撮影条件差の吸収のために色系の拡張を試す場合は,**独立した実験**として効果を測る. + +## モデル (Model) + +| 項目 | 初期設定 | +| --- | --- | +| アーキテクチャ | ConvNeXt-Tiny(timm・ImageNet 事前学習) | +| 出力 | 5 クラス | +| 学習方式 | 全層ファインチューニング(データが少ないと判断した場合は段階的凍結を試す) | +| 最適化 | AdamW + cosine annealing(warmup 付き) | +| 早期終了 | 検証セットの macro F1 を基準にする | + +まず 1 本を通すことを優先し,比較は timm での差し替えで後から行う. + +## データ分割 (Data Split) + +**すべての分割は患者単位で行う.** 同一患者の画像が複数の分割に跨ってはならない. + +| 局面 | 方式 | +| --- | --- | +| 日常の試行 | 患者単位の単一分割(train / val / test).クラス分布を層化する | +| 結論を出すとき | 患者単位の 5-fold 交差検証 | + +- 分割は `patient_id` と seed から決定的に生成し,`config.yaml` に記録する +- **test セットはモデル選択に使わない**(val で選び,test は最後に一度だけ見る) + +## クラス不均衡 (Class Imbalance) + +初回は対策を入れずに素の結果を見る.混同行列でどのクラスが落ちているかを確認してから,クラス重み付き損失等を**独立した実験**として追加し,効果を切り分けて評価する. + +## 評価指標 (Metrics) + +| 指標 | 役割 | +| --- | --- | +| **macro F1** | 主指標(クラス不均衡下で少数クラスを無視できないため) | +| accuracy | 参考(クラス分布に引っ張られる) | +| クラス別 precision / recall | どのクラスが弱いかの特定 | +| 混同行列 | 誤りの方向(隣接クラスとの混同か,飛んだ誤りか)の確認 | + +- 舌色が順序尺度として扱える(淡白 → 淡紅 → 紅 → 暗赤 → 紫 のような並び)なら,**quadratic weighted kappa** を追加する.順序の妥当性は TIASjudge の定義と医学的な解釈を確認してから決める(未確定) +- 医師ラベルの評価者間一致率が分かる場合は,モデル性能の上限の目安として併記する + +## ベースライン (Baseline) + +既存 TIASjudge のルールベース診断を同一テストセットで実行し,同じ指標で並べる.実装の取り込み方(移植 / 別リポジトリ参照)は未定とし,ベースライン実装に着手する時点で決める. + +## 再現性 (Reproducibility) + +- seed を設定ファイルで固定し,`config.yaml` に git コミットハッシュとともに記録する +- 完全なビット再現性(`torch.use_deterministic_algorithms`)は速度とのトレードオフのため求めない.代わりに**同一設定で複数 seed を回して振れ幅を確認する** +- 共用サーバのため,使用する GPU は明示的に指定する + +## 未確定事項 (Open Items) + +- マスク外領域の扱い(黒埋め / 平均色埋め) +- 舌色 5 分類を順序尺度として扱えるか(quadratic weighted kappa の採用可否) +- 入力解像度 224 で足りるか(舌の微細な色ムラが失われないか) +- ルールベース診断の取り込み方 diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index af694dc..1b4d66b 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -27,3 +27,15 @@ - 失敗パターン: ① 手元の `uv sync` が PyPI からのダウンロードで connection reset(`safetensors`)→ 再試行中 / ② 非対話 ssh では `uv` が PATH に無く `command not found`(`export PATH="$HOME/.local/bin:$PATH"` が必要)/ ③ 踏み台経由で `Newton.local` の名前解決が一時的に失敗(再試行で復帰).② ③ は ENV_02 のトラブルシューティングに記録した - 未実施: push・PR,臨床データの初回コピー(研究室 PC → Newton),倫理審査・データ利用許諾の確認.仕様設計(フェーズ 4)はドラフトを提示済みで保存保留 +## 立ち上げ(フェーズ 4〜6: 仕様設計・規約整備・開発計画) + +- [x] **仕様設計**: [SPEC_01 アーキテクチャ設計](04_SPEC/SPEC_01_アーキテクチャ設計.md)と [SPEC_02 学習・評価仕様](04_SPEC/SPEC_02_学習・評価仕様.md)を作成した. + - 設計判断 ①: 手元へ持ち帰れるものを**回収ホワイトリスト**として仕様に固定した(画像・manifest・個票は回収しない).「回収しないよう気を付ける」では守れないため,ツールの制約として実装しテストで固定する + - 設計判断 ②: `manifest.csv` を学習の唯一の入口とし,`patient_id` を必須にした.取得できない場合に画像 ID を患者 ID と見なす代替はしない(同一患者が分割を跨ぐと評価が壊れる) + - 設計判断 ③: **色を変えるデータ拡張を既定で禁止**した.舌の色そのものが正解ラベルであり,color jitter 等はラベルを壊す + - 設計判断 ④: クラス ID を固定表(0=淡白 … 4=紫)にして混同行列の軸をぶらさないようにした +- [x] **規約整備**: `.claude/rules/` に coding-style・project-structure・testing・error-handling を作成し(path-scoped rule),`pyproject.toml` に ruff の選択ルールと pytest の `slow` マーカーを追加した.CLAUDE.md の必須ルール欄にプロジェクト固有の制約(データの持ち出し禁止・患者単位分割・色拡張の禁止等)を明記した. + - 方針: 書式・命名・docstring 等の標準部分は SmTIAS-LightSim と同一にし,本プロジェクト固有の規則(臨床データの扱い・個人情報を含むログの禁止・データ整合性の fail fast)だけを追加した +- [x] **開発計画**: [PLAN_02 開発ステップ](03_PLAN/PLAN_02_開発ステップ.md)を作成した(Step 1 基盤 → 2 リモート実行 → 3 データセット集計 → 4 学習最小版 → 5 評価 → 6 ベースライン比較 → 7 実験反復 → 8 結論). + - 順序の理由: Step 1・2 は手元で完結しデータのコピー待ちの間に進められる.実データの素性が判明する Step 3 の後に SPEC を更新してから学習に入る + diff --git a/pyproject.toml b/pyproject.toml index 93f4432..4cb1ce0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,19 @@ [tool.ruff] line-length = 100 target-version = "py312" +# .claude/ はテンプレート所有のファイル(フック・テンプレートのテスト)であり, +# このプロジェクトの規約で整形しないため lint 対象から外す +extend-exclude = [".claude"] + +[tool.ruff.lint] +select = ["E", "F", "W", "I", "B", "UP", "N"] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" [tool.pytest.ini_options] testpaths = ["tests"] +markers = [ + "slow: 学習を回す等の実行時間が長いテスト(通常実行では -m \"not slow\" で除外する)", +]