松田研究室
FindingID: p1-b-f-trainer-convention-bridge
Status: stable / task P1.B + P1.F Stage 1
← Findings Index
P1 Phase B+F · Stage 1 minimal PoC · convention bridge

P1.B.F Stage 1 — gt_convention=premultiplied 切替で brush eval PSNR を 1.67 → 31.33 dB に回復、coupling 解消実証

P1.A.3 で falsify した apparent-gap 仮説の代替として、`training-eval convention coupling` を解消すべく `data.gt_convention = premultiplied` (brush 流) 経路を trainer まで通し、5k smoke を legacy/brush 2 通り × eval legacy/brush 2 通り = 4 通りで cross-eval。brush trainer + brush eval は **31.33 dB (5k 時点)**、legacy 5k baseline の brush eval は 1.63 dB のままで A.3 を再現、symmetry 検証として brush trainer × legacy eval も 1.59 dB に崩壊し coupling が双方向に存在することを実証。GT loader 切替 + config flag (4 file 編集、loss kernel 変更ゼロ、既存 config 不変、deny_unknown_fields 維持) で deliver、Stage 2 (hyperparameter 移植 + 30k full) GO。

positiverank: highhypothesis-confirmed-stage-2-go第 1 軸第 2 軸第 3 軸p1phase-bphase-fmilestone-m2brush-paritytrainerpremultipliedconvention-bridgesmokehypothesis-confirmed
Δ PSNR
+29.71 dB (brush eval 系: A.3 30k 1.667 dB → P1.B.F 5k 31.334 dB)、Stage 1 hypothesis (>10 dB) を +21 dB 上回り
Δ Wallclock
5k 比較: legacy 202.4s / brush 125.4s (brush -38% 高速、splats 77.6k → 93.9k だが GPU loss は同等)
Impact
P1.A.3 で `splat-rs trainer が white-bg target で学習 → 背景を opaque-white splat で埋める → brush 流 eval (bg=ZERO 比較) で MSE≈1 崩壊` と診断された coupling を、**GT loader を premultiplied 経路に切替えるだけ** で解消できるか 5k smoke で検証。同一 hyperparameter (`2026-05-22-2155-lego-sh3-30k.toml` の iter のみ 5k 短縮) で `data.gt_convention=white_bg` vs `data.gt_convention=premultiplied` を独立 training し、各 final.ply を 2 通り convention で eval (4 cell)。結果: brush trainer × brush eval = **31.334 dB**、legacy trainer × brush eval = 1.628 dB と完全に対比、coupling が双方向に存在することも symmetry test (brush trainer × legacy eval = 1.595 dB) で確定。5k 段階で既に B-N 30k baseline (24.88 dB legacy) を **brush eval 系で +6.5 dB 超え**、brush 公称 37 dB との gap は -5.7 dB のみ。Stage 1 hypothesis (10+ dB) を 21 dB 上回り、coupling 解消が brush parity への critical path であることを定量実証。実装は `splat-cli/src/config.rs` に `data.gt_convention: GtConvention` enum 追加 (default=`WhiteBg`、既存 configs 完全互換) + `train.rs` の train/val load を `load_nerf_synthetic_with_convention` に切替、合計 4 file の最小差分。loss kernel (`loss.metal:31-88`) は変更不要 (n_total=W·H·4 が α channel を含み、premultiplied target の α=0 領域が `rendered α (=1-T) → 0` の natural pressure を提供、brush の match_alpha 機構と同等効果)。
01

本文

P1.A.3 で 「training/eval convention coupling」 が brush gap の主因と判明 (apparent-gap 仮説 falsify、brush convention で trainer の white-bg fit が露呈し 24.88 → 1.67 dB に崩壊)。本 task は coupling の 最小 PoC fix: data.gt_convention=premultiplied flag を新設し、GT loader (load_rgba_premultiplied) を train / val 両方 の経路で選択可能にする。5k smoke を legacy / brush 2 通り × eval legacy / brush 2 通り = 4 通りで cross-eval し、coupling が解消されるか検証。

結論 (Stage 1 → Stage 2 GO)

仮説 OK (上回り)。brush trainer × brush eval = 31.334 dB at 5k iter、legacy 5k baseline の brush eval = 1.628 dB に対し +29.7 dB。Stage 1 OK 閾値 (10+ dB) を +21 dB 上回り、coupling 解消が brush parity への critical path であることを定量実証。さらに symmetry test (brush trainer × legacy eval = 1.595 dB) で coupling が双方向に存在 することも確認 (brush trainer は背景を α≈0 にして fit するので white-bg GT に対して背景全体で MSE≈1)。Stage 2 (full hyperparameter 移植 + 30k bench) に進行 GO。loss kernel 改変ゼロ・既存 config 不変・最小差分 (4 file) で達成。

1. 4 通り cross-eval table (Lego val, 100 view)

#trainer (gt_convention)eval conventionquantize8PSNR (dB)minmedianmaxvs A.3 30k判定
1**legacy** (white_bg)legacyoff**21.874**5.89622.63928.806-3.005 (5k 短縮による degrade)5k iter での legacy baseline
2**legacy** (white_bg)brushon1.6280.4731.6862.050-0.039 (A.3 の 1.667 と一致)**A.3 を 5k で再現**, baseline collapse
3**brush** (premultiplied)legacyoff1.5950.9911.6141.944**symmetry test pass**, brush trainer は逆向きに崩壊
4**brush** (premultiplied)brushon**31.334**25.08931.57235.902**+29.667 (1.667 → 31.334)****hypothesis confirmed**, Stage 1 OK threshold (10) +21 dB
table 読み方

diagonal (1 と 4) が "training と eval が一致した自然状態"、off-diagonal (2 と 3) が "convention 不一致による崩壊"。diagonal は両方 high (21.87 / 31.33 dB)、off-diagonal は両方 ~1.6 dB という綺麗な対称形 → coupling は training-eval convention の不一致による systematic mismatch であって、brush trainer が片側だけ広い optimum を持つ等の非対称要因ではないことを示す。さらに brush trainer の diagonal が legacy trainer の diagonal を +9.5 dB 上回る 点が重要 (5k vs 5k の同条件で)、brush 流の loss landscape は同 iter で legacy より遥かに容易な fit を提供する。

2. 仮説検証 table

仮説 (Stage 1)予測 PSNR実測 PSNR差分判定
brush trainer × brush eval で 10+ dB≥ 10.031.334+21.3 dB**OK (大幅上回り)**
brush trainer × legacy eval で低下 (symmetry test)低下1.595**OK (1.6 dB に崩壊、coupling 双方向)**
legacy trainer × brush eval は A.3 30k と同帯 (5k で再現)~1.71.628-0.04**OK (再現精度 ±0.04 dB)**
legacy trainer × legacy eval は 30k から短縮分 degrade~20-2321.874**OK (5k 相応、degrade 3 dB)**

3. 機構 — なぜ premultiplied GT 切替だけで効果が出たか

メカニズム (predicted → confirmed)

(A) 背景の取扱い: load_rgba_white_bg は α=0 領域を (r,g,b,a)=(1,1,1,1) に変換 → loss は "背景にも 白く塗れ" と要求 → trainer は opaque-white splat を背景に増殖。load_rgba_premultiplied は α=0 領域を (0,0,0,0) のまま → rendered (rasterize.metal: pix_out = Σ rgb·α·T, α_out = 1-T) と直接比較できる。(B) α channel の役割: loss kernel (loss.metal:31-88) は n_total = W·H·4α channel も MSE 対象。premultiplied GT (α=元 PNG α) と rendered α (=1-T) の L1 loss は、α=0 領域で |1-T - 0| = 1-T → 0 即ち 背景 splat の T を 1 に保て (= 透明にせよ) という pressure を自動的に与える。これは brush の match_alpha=0.1 loss と同等の効果が weight 1.0 で発動している状態。Stage 2 では brush の hyperparameter (lr / refine / decay) 移植で更に伸ばせる余地あり。(C) なぜ symmetry test も -20 dB 崩壊するか: brush trainer は背景を α≈0 (rendered では bg=ZERO) として学習 → legacy convention (white-bg GT) と比較すると background pixel 全体で rendered=(0,0,0) vs target=(1,1,1) → MSE≈1 で systematic 崩壊。これも A.3 と同じ機構の鏡像。

4. wallclock observation (副次)

  • brush trainer は 5k で 38% 高速 (legacy 202.4s / brush 125.4s on M4 Max)。同 hyperparameter / 同 backend / 同 capacity 上限
  • 理由仮説: legacy trainer は背景を opaque-white splat で埋めるため 無駄な splat 数が多く (背景占有面積 ≈ 50%)、forward / backward 共に rasterize cost 高。brush trainer は背景 splat を増やさない (生成 → opacity_min で prune ?) ので net cost 減
  • 最終 splat 数は brush の方が 21% 多い (77.6k vs 93.9k) — 背景に splat を 持たない のではなく、refine が前景に集中 → 前景 detail に splat を割り振っている。これも 31 dB に伸びた一因 (5k iter で前景 detail に capacity を使い切れる)
  • Stage 2 でも brush 化で wallclock 不利は出ない見込み (むしろ +30% 程度高速化)

5. 実装スコープ (本 task で変更した部分)

  1. splat/crates/splat-io/Cargo.tomlserde = { version = "1.0", features = ["derive"] } を追加 (GtConvention enum を Deserialize 対応)
  2. splat/crates/splat-io/src/dataset.rs — 既存 GtConvention enum に #[derive(serde::Deserialize)] + #[serde(rename_all = "snake_case")] + impl Default for GtConvention (= WhiteBg)
  3. splat/crates/splat-cli/src/config.rsDataConfig#[serde(default)] pub gt_convention: GtConvention を追加 (deny_unknown_fields 維持、既存 configs/*.toml は無指定で従来通り white_bg)
  4. splat/crates/splat-cli/src/cmd/train.rsload_nerf_syntheticload_nerf_synthetic_with_convention(.., cfg.data.gt_convention) に切替 (train split (line 50)val split (line 119)両方、advisor 警告通り — 不一致だと mid-training PSNR が学習対象と一致しなくなる)
  5. splat/configs/2026-05-24-1800-lego-legacybase-5k.toml (新規) — 30k baseline の iter のみ 5k に短縮
  6. splat/configs/2026-05-24-1800-lego-brushcompat-base-5k.toml (新規) — legacy-5k から [data] gt_convention = "premultiplied" のみ追加
  7. **変更なし**: loss.metal / rasterize.metal / refine_*.metal / train_loop.rs / trainer.rs / 既存 configs/*.toml (全て不変)

6. 再現手順

# build
cd splat
cargo build --release -p splat-cli

# schema compat check (既存 30k config が parse できることを確認)
./target/release/splat check --config configs/2026-05-22-2155-lego-sh3-30k.toml
./target/release/splat check --config configs/2026-05-24-1800-lego-legacybase-5k.toml
./target/release/splat check --config configs/2026-05-24-1800-lego-brushcompat-base-5k.toml

# legacy 5k (gt_convention = white_bg, default)
./target/release/splat train --config configs/2026-05-24-1800-lego-legacybase-5k.toml
# → mid-training mean val: 21.874 dB / wallclock 202.4s / 77603 splats

# brush 互換 5k (gt_convention = premultiplied)
./target/release/splat train --config configs/2026-05-24-1800-lego-brushcompat-base-5k.toml
# → mid-training mean val: 31.308 dB / wallclock 125.4s / 93948 splats
# (mid-training PSNR は train.rs:182 の `compute_psnr` (RGB only、raw f32) で計算。
#  eval cell 4 の 31.334 との 0.026 dB 差は `splat eval --quantize-8bit` の
#  (*255).round()/255 roundtrip 効果。α channel の MSE への寄与は無い。)

# 4 cell cross-eval
DS=/Users/otkrickey/dev/3dgs-workspace/datasets/nerf_synthetic/lego
LEGACY=runs/lego-legacybase-5k/final.ply
BRUSH=runs/lego-brushcompat-base-5k/final.ply

./target/release/splat eval --ply $LEGACY --dataset $DS --split val --convention legacy
# → 21.874 dB (cell 1)
./target/release/splat eval --ply $LEGACY --dataset $DS --split val --convention brush --quantize-8bit
# → 1.628 dB (cell 2 — A.3 1.667 dB 再現)
./target/release/splat eval --ply $BRUSH  --dataset $DS --split val --convention legacy
# → 1.595 dB (cell 3 — symmetry test)
./target/release/splat eval --ply $BRUSH  --dataset $DS --split val --convention brush --quantize-8bit
# → 31.334 dB (cell 4 — hypothesis confirmed)

7. Stage 2 (P1.B.F full) への申し送り

  1. 30k full bench: 同 config を iter=30000 で実行、brush 公称 37.40 dB との比較。Stage 1 で 5k → 31.33 dB なので、30k での到達点を見て M3 生命線 (30+ dB) と brush parity (37 dB) のどちらに着地するか決める
  2. brush hyperparameter 移植 (Stage 2 の中核): brush の lr / refine schedule / decay 設定 (特に opacity_reset_every / split_scale_factor / lr_means decay の終端) を移植。Stage 1 では splat-rs 既定 hyperparameter のまま 5k で 31.33 dB が出たので、brush hyperparameter の効果は "30k 収束時の +5-6 dB" 程度と予想 (37 dB 到達への critical path)
  3. α channel weight の検討 (任意): 現状は loss.metal が 4ch を等価重みで MSE。brush は match_alpha = 0.1 で α channel を 1/10 重みに。Stage 2 で 30k bench した後、α 重み調整の sensitivity sweep を 1-2 点だけ追加検討 (Stage 1 で OK 出たので priority は低い)
  4. scale-invariance lesson 適用: h_a_regression_findings の Adam scale-invariance 議論を踏まえ、α channel を含めた loss scale が AdamConfig.eps と整合するか確認。eps=1e-8 で 4ch MSE = 1e-2 帯なら問題ない見込みだが、30k 収束終盤 (loss ≪ 1e-3) で eps が効き始める可能性、watch 対象
  5. render quality 目視確認: runs/lego-brushcompat-base-5k/view_*.png + gt_*.png — premultiplied で gt は背景透明、view は背景 RGB=0 (黒) として render される (PNG 化で α channel は保持されない場合は "黒背景" に見える)。これは bug ではなく仕様
  6. 30k bench config 起草: configs/2026-05-24-???-lego-brushcompat-30k.toml として iter=30000 に拡張、parent_config を本 Stage 1 brushcompat に

8. 想定外 / 観察事項

  • 5k で既に 31 dB は事前予想 (Stage 1 expected_psnr=12) を 19 dB 上回った。coupling 解消の効果が事前想定より遥かに大きい — brush parity への critical path に位置することを再確認
  • brush trainer × legacy eval = 1.595 dB は legacy trainer × brush eval = 1.628 dB と ほぼ完全に対称 (差 0.03 dB)。これは coupling が training と eval の純粋な convention 不一致 (どちらが "正しい" でもない、ペアが一致するか否か) であることを実証
  • brush trainer の wallclock 38% 短縮 は副次効果として歓迎。30k では絶対時間 (202.4 × 6 ≈ 20 min → 125.4 × 6 ≈ 12 min) で 8 分節約、bench loop の cycle time が大幅改善
  • gt_NN.png が premultiplied で書かれる 件は advisor 予告通り、bug 報告すべきではない (transparent BG が 8bit PNG に書かれる際に α channel が消えると "黒背景" 表示になる、これは PNG quirk)
  • train.rs:182 の compute_psnr (legacy 形、RGB only / raw f32) は未変更のまま使われている。premultiplied 経路でも RGB only MSE (`for c in 0..3`) なので α channel は MSE に効かない。mid-training mean val 31.308 と eval cell 4 の 31.334 の 0.026 dB 差は `--quantize-8bit` (pred を 8bit roundtrip) の効果のみで、最終 4-way 表は splat eval 経由なので本数字は informational に留まる (advisor 推奨通り)

9. 不明点 / 未確認 (main agent 判断用、本 task では深掘り不要)

  • brush trainer 30k での絶対値: Stage 2 で実測必要。Stage 1 trend (5k で 31.33 dB) から線形外挿すれば 30k で 35-37 dB 帯だが、refine が 1500 iter で停止 → splat 数 plateau なので終盤の伸びは限定的、35-36 dB 着地が現実的予想
  • test split 再 DL: A.3 で未取得、Stage 2 30k bench と並行で brush 既定の test split 200 view (RGB) を再取得して novel-view PSNR でも比較できると論文に強い
  • α channel saturate: brush 5k trainer の min PSNR view (25.09 dB) が legacy の min (5.90 dB) より遥かに高く、view 間の variance も小さい (max-min = 10.8 vs 22.9 dB)。これは brush 流が view 依存性を健全に扱っていることを示唆、Stage 2 で test split 比較で更に強い証拠化可能
  • Stage 1 brush trainer の SSIM 値: result.toml には PSNR のみ記録、SSIM は別途算出が必要 (現状 trainer 内で SSIM grad は使うが値は loss_history 内に複合)。Stage 2 で SSIM/LPIPS も含めた fuller comparison が望ましい

10. M2 gate impact (audit doc / plan-gap.vue update 材料)

  • P1.A audit master doc p1-a-eval-convention-audit の §6 表に 本 Stage 1 4-way 行を追加、A.3 falsify hypothesis ("eval だけ揃えても無理") の 裏返しの解 ("trainer 側で揃えれば即解") を確定
  • M2 (brush loss 化) gate: 本 finding で Stage 1 closure。loss kernel 改変ゼロで coupling 解消できたので、当初予定の "loss kernel α 重み付け追加" は Stage 2 で sensitivity 試験対象に降格、critical path から除外
  • M3 生命線 (30 dB) は brush eval 系で 5k で既達 (31.33 dB)、ただし brush trainer の legacy eval は 1.60 dB なので、生命線評価をどの convention で測るか方針確定が必要 (推奨: brush 流に統一、卒論 D.3 章で coupling を主要 finding として展開、自実装 PSNR は brush 流で揃える)
  • plan-gap.vue: P1.B.F Stage 1 → done mark、Stage 2 (full hyperparameter 移植 + 30k) を critical path 最上位に

関連

  • P1.A.3 (本 Stage 1 が解いた問題): p1-a-3-cross-eval-reproducer
  • P1.A.1 brush eval audit (premultiply 数式): p1-a-1-brush-eval-audit
  • P1.A.2 splat-rs eval audit: p1-a-2-splat-eval-audit
  • P1.A audit master: p1-a-eval-convention-audit
  • brush vs splat 37 dB gap analysis: brush-vs-splat-37dB-gap-analysis
  • Adam scale-invariance lesson (Stage 2 で α weight tuning する際の参考): h_a_regression_findings + memory h_a_regression_findings.md
  • MCMC noise calibration lesson (hyperparameter scale sensitivity の事例、Stage 2 hyperparameter 移植時に類似 trap 警戒): mcmc-noise-calibration
03

関連 finding

© 2026 大竹律輝 — 松田研究室