予測モデル生成エージェント(MODEL_GENERATE_AGENT) 作業まとめ
作成日: 2026-10-21
リポジトリ: Uranos8685/MODEL_GENERATE_AGENT
作業ブランチ: claude/youthful-cray-a9ync2
======================================================================
1. ツールの改善点の分析
【結論】
LLM は計画・特徴量コード・レポートだけを担当し、学習は決まった手順のコードで行う、
という全体設計は堅実。大きな改善点は、評価と推論の正しさに関わる次の3点。
優先度:高(結果の正しさに影響する)
(1) XGBoost / LightGBM の交差検証スコアが実力より良く出て、選定がこの2つに偏る
– agent/modeling.py の _fit_one_fold で、早期終了の判定に検証分割を使い、
そのあと同じ検証分割で採点している。
– HistGradientBoosting は内部で別の検証データ(validation_fraction)を使うので
この問題はなく、候補間で条件がそろっていない。
– 「チューニング結果がベースライン以下なら既定値を採用」の比較相手は
早期終了なしのベースラインなので、チューニング側がほぼ必ず勝つ。
– 対策: 学習用の分割をさらに分けて、早期終了用のデータを作る(HGB と同じ方式)。
※ 詳しい説明は「4.」を参照。
(2) 特徴量コードが「そのデータ自身の統計値」を使うため、学習時と推論時で変換がずれる
– 列ごとの処理指示(agent/data_utils.py)が、LLM に
df[“x”].fillna(df[“x”].median()) と書かせている。
– 学習時は train/test 分割の前に全データで実行されるため、評価用データの値が
中央値に混ざる(軽いリーク)。
– 推論時は新しいデータの中央値で補完し直す。1行だけ予測すると中央値そのものが
欠損のままになる。
– 欠損補完はもともと後段のパイプラインが担っている(線形モデル・RF は
SimpleImputer、勾配ブースティング系は欠損をそのまま扱える)ので、
特徴量コードでの補完はやめるよう指示するのが一番簡単。
(3) 推論時、足りない列を何も知らせずに 0 で埋める
– agent/predict.py では、特徴量の列が無いと黙って 0 を入れる。
列名の変更などで列が消えても、予測値はそれらしく出てしまう。
– 欠けた列を画面で警告するか、ダミー列以外ではエラーにするのが安全。
優先度:中(評価の設計)
- 評価指標が決め打ち(分類は f1_weighted、回帰は r2)。クラスが偏ったデータでは weighted F1 は多数派クラスに引っ張られる。ROC-AUC・PR-AUC・macro-F1・RMSE などを選べるようにするとよい。 - 時系列・グループ構造を考えていない。分割は常にシャッフルしてからの KFold / train_test_split。日付列があるときの TimeSeriesSplit やグループ単位の分割を 選べるようにすることを提案。 - タスク種別の推定が粗い(ユニーク値が 20 以下の float 列を分類とみなす)。 - LLM が返した目的変数が存在しないと、黙って末尾の列にする。ログには警告が 出るが画面には出ない。 - 特徴量重要度は推奨モデルの不純度ベースの値だけ。評価用データでの permutation importance の方が解釈しやすい。
優先度:中(安全性・堅牢性)
- サンドボックスは静的チェックと監査フックの二段構えでよくできているが、 OS レベルの隔離ではない(ドキュメント記載どおり)。 ・読み込みは実行時に制限されていない ・scipy.io.loadmat などは禁止リストに入っていない ・子プロセスに環境変数がすべて引き継がれる(HF トークンなど) → 渡す環境変数を最小限にし、許可するサブモジュールを限定すると、さらに固くなる。 - LLM 生成にタイムアウトがない。モデルが止まらなくなるとワークフロー全体が止まる。
優先度:低(保守性・使い勝手)
- テストはモック LLM を使った疎通テスト3本だけで、CI もない。 sandbox・leakage・predict などは単体テストにしやすい。 - グラフ描画に使う matplotlib が requirements.txt にない。入っていないと ROC 曲線・残差プロットの画像が何も表示されずに出なくなる。 - リポジトリに、コードから参照されていない qwen3vl_meta/(約 15MB)と MODEL_GENERATE_AGENT_atlas.html(約 2MB)がある。 .claude/settings.local.json は本来ローカル専用のファイル。 - config.py の既定のモデルパスが個人環境の H:\... になっている。 WORKDIR.mkdir が import しただけで実行される。 MODEL_SCRIPT_NAME は使われていない定数。 - modeling.py は 1,200 行あり、分ければテストも書きやすくなる。
【進め方の提案(費用対効果が高い順)】
1. (1) 早期終了用データの分離
2. (2) 特徴量コードでの欠損補完の指示をやめる
3. (3) 列の欠落の警告
4. matplotlib を requirements に追加
5. 上記の回帰テストを pytest で追加
======================================================================
2. 実施した変更
(a) 元データのコピーをやめた【反映済み】
– 実行時に選んだ CSV / Excel を run フォルダへ original.
(b) input.csv を作らないようにした → 取り消し済み
– データをメモリに保持し、特徴量スクリプトは一時フォルダで実行する方式に
一度変更したが、ユーザーの判断で取り消した(revert コミット)。
– input.csv は従来どおり run フォルダに作られる。
(c) main ブランチへの結合 → 未完了
– プルリクエストを作成:
https://github.com/Uranos8685/MODEL_GENERATE_AGENT/pull/1
(claude/youthful-cray-a9ync2 → main)
– マージ操作が自動承認の安全チェックで止められたため、まだ結合されていない。
– GitHub 上で「Merge pull request」を押せば結合できる。「Squash and merge」を
選ぶと、取り消し済みのコミットが main の履歴に残らず1コミットにまとまる。
– (b) の変更と取り消しは打ち消し合うため、main のコードに入る差分は (a) のみ。テスト: python tests/test_agent_smoke.py が全件成功。
======================================================================
3. 学習データとテストデータの分割割合
学習データ 75% : テストデータ 25%(agent/modeling.py の train_test_split) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.25, random_state=42, stratify=stratify ) - 乱数を固定(random_state=42)。同じデータなら毎回同じ分け方になる。 - 分類では層化(クラスの比率が学習・テストでそろう)。回帰はただランダムに分ける。 - 目的変数が欠損している行は、分割の前に削除される。 学習データ(75%)の使い道 - モデルの比較とチューニングは、学習データの中だけで交差検証して行う。 ・学習データが 250 行以上なら 5 分割、それ未満なら 3 分割 ・分類では、一番少ないクラスの件数より多く分割しないよう減らす(最低 2 分割) - 最終モデルは、決まったハイパーパラメータで学習データ全体を使って学習し直す。 テストデータ(25%)の使い道 - 最後の評価(指標・ROC 曲線・残差プロット)にだけ使う。モデル選びには使わない。 割合は現在固定で、画面や環境変数からは変えられない。
======================================================================
4. XGBoost / LightGBM のスコアが甘く出る件の説明
【ひとことで】
XGBoost と LightGBM だけ、採点に使うデータを見ながら学習を止める位置を決めているので、
点数がほかのモデルより甘く出る。
【何が起きているか】
交差検証では学習データを例えば5つに分け、毎回「4つで学習 → 残り1つ(検証用)で採点」を
繰り返す。勾配ブースティング系は木を1本ずつ足して学習するので、「何本で止めるか
(早期終了)」を決める必要がある。今のコードでは XGBoost / LightGBM がこうなっている
(agent/modeling.py の _fit_one_fold と _cv_score)。1. 木を足すたびに、検証用データで成績を確認する 2. 検証用データでの成績が一番良かった本数で止める 3. その本数のモデルを、同じ検証用データで採点する
本数を「採点用データで一番点が取れる位置」に合わせてから、そのデータで採点している。
【たとえ話】
模試の過去問を1問ずつ解きながら、「どこまで勉強すれば一番点が取れたか」を本番の答案を
見て決め、その本番の点数を実力として報告するようなもの。答えを見て調整しているので、
初めて見る問題で取れる点より高めに出る。
【なぜ選定が偏るのか】
ほかのモデルはこの有利さを持っていない。モデル 本数の決め方 採点 ----------------------------- ----------------------------------- ---------------------- XGBoost / LightGBM 検証用データを見て決める 同じ検証用データ → 甘い HistGradientBoosting 学習データ内で 10% を取り分けて決める 検証用データ → 公平 RF / ロジスティック回帰 / Ridge 早期終了なし 検証用データ → 公平
推奨モデルは「チューニング後の交差検証スコアが一番高いもの」で決まるため、実力が同じ
くらいなら XGBoost / LightGBM がわずかに有利になり、選ばれやすくなる。Optuna で何十回も
試してその最高点を採用するので、運良く高く出た回が選ばれやすく、差がさらに広がる。
同じ理由で、「チューニング結果がベースライン以下なら既定値を採用する」安全策も
うまく働かない。ベースラインは早期終了なしの公平な点数なので、甘く出るチューニング後の
点数がほぼ必ず勝つ。
【影響の範囲】
– 影響あり: どのモデルを推奨にするか、レポートに出る交差検証スコア
(XGBoost / LightGBM の分が高めに出る)
– 影響なし: 最後に出すテストデータ(25%)での評価指標。テストデータは学習にも
本数決めにも使っていないので、正直な数字。
差の大きさはデータによる。決めているのは「木の本数」1つだけなので普通は大差にならないが、
データが少ないときや検証用データの点数がばらつきやすいときは目立つ。
【直し方】
HistGradientBoosting と同じ方式にそろえる。各分割の学習用データから 10% ほどを
取り分け、それを見て本数を決める。採点用の検証データは本数決めに使わない。
こうすると全モデルが同じ条件で比べられる。変更は _fit_one_fold まわりの数行で済む。