松田研究室
FindingID: p1-a-3-cross-eval-reproducer
Status: stable / task P1.A.3 + P1.A.4
← Findings Index
P1 Phase A · A.3 reproducer · M1 gate closure

P1.A.3 cross-eval reproducer — brush convention で 24.879 → 1.67 dB に崩壊、主仮説 falsify

P1.A.4 (eval を brush 準拠 convention で再計算する CLI flag) を実装し、splat-rs lego sh3 30k final.ply (training baseline 24.879 dB) を 4 通り (legacy/brush × quantize on/off) で eval。結果は audit §6 の主仮説 (brush 流で +3〜+5 dB) を完全に falsify、brush convention に切り替えると PSNR は逆に -23 dB 崩壊して 1.67 dB。原因は splat-rs trainer が white-bg target 前提で学習されており、背景 pixel を白い不透明 splat で埋めるよう収束しているため、bg=ZERO premultiplied 比較では背景全体で MSE≈1 が乗る。これは bug ではなく training/eval convention の coupling を実証した結果で、apparent gap 仮説 (eval pipeline だけ揃えれば +3〜+5 dB) が成立しないことを示す。M2 以降の brush parity loss は「training-loss convention も brush 化」を含む必要がある。

negativerank: highhypothesis-falsified-stronger-finding第 1 軸第 2 軸第 3 軸p1phase-amilestone-m1brush-parityevalconventionpsnrpremultipliedreproducerfalsified-hypothesis
Δ PSNR
-23.21 dB (brush convention 化で 24.879 → 1.667)
Δ Wallclock
N/A (eval only)
Impact
splat-rs `final.ply` (24.879 dB legacy/val baseline) を brush 準拠 convention (premultiplied GT + bg=ZERO 比較 + 8-bit roundtrip) で再評価すると **1.67 dB に崩壊**。audit §6 が予測した +3〜+5 dB 底上げと逆方向に -23 dB。原因は trainer が white-bg target で学習されており、背景領域を opaque-white splat で埋めるよう収束した結果、brush 流の bg=ZERO 比較では背景 pixel 全体で MSE≈1 が systematic に乗る。`view_00.png` 目視確認 (背景は白い不透明領域) で機構を確定。**training と eval の convention は coupling しており、eval pipeline だけ揃える apparent-gap 仮説は不成立**。卒研 P1.M2/M3 に向けては「training loss も brush 化 (RGBA 4ch L1 を α=0 領域で背景に penalty を吹かさない構造)」が必須要件。8-bit quantize 単体の impact はほぼ無視可能 (legacy 24.879 → 24.879、brush 1.605 → 1.667、+0.06 dB)。
01

本文

P1.A.4 (eval を brush 準拠 convention で再計算する CLI flag)splat eval subcommand として実装し、P1.A.3 reproducer として splat-rs lego-sh3-30k/final.ply4 通り (legacy/brush × quantize on/off) で再評価。audit §6 の "brush 流で +3〜+5 dB" 主仮説は完全に falsify、代わりに training-eval convention が coupling している という、より重要な構造的発見が得られた。M1 gate (eval convention 統一確定 + 数式 diff 定量化) は 実測値で完全 closure

結論 (M1 closure)

apparent gap 仮説 (eval だけ揃えれば +3〜+5 dB) は falsified。brush convention に切り替えると 24.879 → 1.667 dB に -23 dB 崩壊。原因は trainer が white-bg target で学習されており、背景領域を opaque-white splat で埋めるよう収束している (`view_00.png` で目視確認、背景は白い不透明領域)。brush 流の bg=ZERO premultiplied 比較では、この opaque-white 背景 vs premultiplied GT=(0,0,0) で 背景 pixel 全体で MSE≈1 が systematic に乗るbrush parity は training loss も brush 化 (α=0 領域で背景に penalty を吹かさない構造) を必要とする。

1. 4 通り eval 実測 table

#conventionsplitquantize8PSNR (dB)ΔPSNR vs baselineΔPSNR vs brush 37.40備考
1legacyvaloff**24.879**0.000 (baseline)-12.521sanity check pass、訓練時 result.toml と完全一致
2legacyvalon24.879+0.000-12.5218-bit roundtrip 単体は impact 無視可能
3brushvaloff**1.605**-23.274-35.795premultiplied GT vs opaque-white 背景で MSE≈1 崩壊
4brushvalon**1.667**-23.212-35.733**完全 brush 準拠 (val)**、3 と +0.06 dB のみ差
5legacytestN/Atest split PNG が 36/200 しか disk に無い (要再 DL、後続 task)
6brushtestN/A同上 (dataset 不完全)

2. audit §6 主仮説の falsification table

audit §6 予測A.3 実測差分判定
D2 (bg composite)+3〜+5 dB (splat 底上げ)-23 dB (崩壊)-28 dB**仮説と逆方向、falsified**
D3 (α-mask premultiply)+1〜+2 dB (splat 底上げ)(D2 と一括)D2 と coupling、独立評価不能
D4 (8-bit roundtrip)-0.5〜-1 dB (splat 微減)+0.06 dB (微増)+~1 dB**impact ほぼ無視可能、仮説と方向逆**
D1 (split file)-1〜-2 dB (test 難)(dataset 不完全)後続 task で実測予定
**合計 (val 限定)****+2.5〜+4 dB 底上げ****-23 dB 崩壊****-27 dB****主仮説 falsified**

3. 真因: training/eval convention の coupling

メカニズム

splat-rs trainer は white-bg pre-composite した GT (`load_rgba_white_bg`) を target に L1/SSIM loss で fit する。background pixel (元 PNG α=0 領域) は target=(1,1,1) なので、loss が 背景にも "白く塗れ" と要求する。これに対し rasterize.metal は bg-agnostic な premultiplied 出力 (`pix_out = Σ rgb·α·T`, `α_out = 1-T`) を返すため、trainer はこの要求を opaque-white splat を背景に大量配置する ことで満たした。

結果として final.ply は "白い不透明背景を持つ rendered" を返す model。これを brush 流 (背景 = premultiplied GT = (0,0,0) と直接比較) で eval すると、背景 pixel 全体で rendered ≈ (1,1,1) vs target = (0,0,0)、MSE ≈ 1。NeRF Synthetic lego の背景比率は 50% 以上なので、image 全体 MSE ≈ 0.5 → PSNR = -10·log10(0.5) ≈ 3 dB 帯に押し下げ。実測 1.67 dB はこの帯 (frontier 50% で完全一致しても背景で 100% miss するので)。

4. 機構の目視確認

  • splat/runs/lego-sh3-30k/view_00.png (forward_rgba output 直書き、白背景に lego モデル) → trainer 出力は 背景が白い不透明領域、premultiplied 出力なら背景は透明 RGB=0 のはずだが、white-bg loss で fit された結果 opaque-white splat が背景を埋めている
  • gt_00.png (load_rgba_white_bg で white-bg composite 済み target) → 同じ白背景、これが loss target、両者は legacy convention 下で 30.36 dB と密接にマッチ
  • brush convention の per-view PSNR を見ると 全 view で 1.6 dB 帯に systematic に集中 (max 2.84, min -0.62, median 1.65) → noise でなく structural mismatch、原因が "前景 fit" でなく "背景 mismatch" であることを示唆
  • rendered の clamp 範囲 (`min < -0.6` view あり) → premultiplied 期待では rendered ∈ [0, α] のはずが overshoot あり、bg-as-opaque-white の training pressure で SH coefficient が saturate していることを示唆

5. brush convention で test split が測れなかった件

/Users/otkrickey/dev/3dgs-workspace/datasets/nerf_synthetic/lego/test/ には r_*.png が 36 frame しか無く、transforms_test.json は 200 frame を参照 (差分は normal/depth aux PNG)。**dataset の RGB test split が不完全**。後続 task (P1.A.3 followup or P1.B) で nerf_synthetic.zip の test 部分を再 DL するか、別 dataset (m4-brush-bench に存在する場合) を使う必要あり。今回 val (100 view) で M1 gate を締めるのに impact 無し (主仮説 falsify には val 1 set で十分)。

6. P1.A.4 実装内容 (本 task で実装した部分)

  1. splat/crates/splat-io/src/dataset.rsGtConvention { WhiteBg, Premultiplied } enum + load_nerf_synthetic_with_convention(dir, split, convention) wrapper + load_rgba_premultiplied 関数 (byte 空間 premultiply、brush scene.rs:212-232 AlphaMode::Transparent と同等)
  2. splat/crates/splat-cli/src/cmd/eval.rs (新規) — splat eval --ply --dataset --split --convention --quantize-8bit subcommand、既存 trained PLY を再評価。training は触らない (config 拡張も避け、CLI flag 経路で完結)
  3. splat/crates/splat-cli/src/main.rs, cmd/mod.rs — subcommand wire
  4. 既存 compute_psnr (train.rs:182) は 未変更、新 compute_psnr_legacy + compute_psnr_brush を eval.rs に複製 (training 内部の loss path への影響ゼロを保証)
  5. FullConfig schema 変更なし、既存 configs/*.toml は無変更で動作 (deny_unknown_fields も触らない)

7. apparent gap 仮説 falsify の意味 (audit doc §6 update 材料)

  • audit §6 table の "推定 dB shift" 列は 実測 column を追加して update すべき (D2: -23 dB / D4: +0.06 dB / 合計: -23 dB)
  • audit §6 footer の "主仮説: -3〜-6 dB apparent gap が消える" は 取り消し、新仮説: "brush parity には training loss convention も brush 化が必要、eval だけでは不可能"
  • audit §11 (Phase A の残作業) の P1.A.4 は 本 task で完了、ただし "brush 準拠 eval が default で意味を持つ" には trainer の brush 化が前提なので、しばらく config flag (CLI flag) として保持し default=legacy 維持が妥当
  • audit §12 (卒論への含意) は補強される: "eval convention の選択は trainer 設計と coupling する" を D.3 negative-findings-chapter の中核トピック として書ける材料が揃った

8. 次 phase への申し送り (P1.B 互換 config 起草へ)

  1. P1.B trainer 改修: train loop の loss を brush 流 (RGBA 4ch、target は premultiplied GT、α channel に match_alpha=0.1 weight) に切り替える設計を起草。load_rgba_white_bgload_rgba_premultiplied を train 経路でも選択可能化、loss kernel (`splat/shaders/loss/loss.metal`) が premultiplied 4ch を受け取れることを確認 (既存 4ch path を活用)
  2. P1.A.3 followup (任意): NeRF Synthetic test split (200 view) を再 DL し、legacy/val baseline と test/val 差を独立に測る。これで D1 (split) の独立 impact が確定し、audit §6 row が完全更新できる
  3. P1.M1 gate update: 本 finding で gate closure。audit §6 table を実測値で update、status を "audit-complete" → "audit-complete-hypothesis-revised" に変更
  4. plan-gap.vue: P1.A.3 / P1.A.4 → done mark、主仮説 falsify を明記、M2 (brush loss 化) を新 critical path として上位に

9. 再現手順 (artifacts なし、source / dataset から完全再現可能)

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

# 4 通り sweep (test 2 通りは dataset 不完全のため skip)
PLY=runs/lego-sh3-30k/final.ply
DS=/Users/otkrickey/dev/3dgs-workspace/datasets/nerf_synthetic/lego

# 1: legacy/val/no-quant (sanity check vs result.toml の 24.879)
./target/release/splat eval --ply $PLY --dataset $DS --split val --convention legacy
# → 24.8787

# 2: legacy/val/quant (8-bit impact 単体)
./target/release/splat eval --ply $PLY --dataset $DS --split val --convention legacy --quantize-8bit
# → 24.8787

# 3: brush/val/no-quant (premultiplied 単体 impact)
./target/release/splat eval --ply $PLY --dataset $DS --split val --convention brush
# → 1.6049

# 4: brush/val/quant (完全 brush 準拠)
./target/release/splat eval --ply $PLY --dataset $DS --split val --convention brush --quantize-8bit
# → 1.6665

10. 不明点 / 未確認事項 (main agent 判断用)

  • brush trainer 37.40 dB は brush convention で測られているはず だが、brush 側で legacy convention に揃えたとき何 dB 出るか未確認 (brush trainer の training-eval は両方 premultiplied なので、convention 切り替え可能か仕組み確認要)
  • test split RGB PNG 完全版の再 DL (NeRF Synthetic 公式) — 1.x GB 程度、後続 task or P1.B 着手時に必要
  • rendered overshoot (min PSNR view で -0.6 dB) は SH の saturate を示唆、debug すれば trainer の "opaque-white 背景埋め" 戦略の構造詳細が見える (本 task は scope 外)
  • brush mean PSNR 1.667 dB の理論値: 背景 50% 完全 mismatch + 前景 30 dB なら image MSE = 0.5·1.0 + 0.5·0.001 ≈ 0.5005 → PSNR ≈ 3.01 dB のはずだが実測 1.67 dB と差 1.3 dB。前景の SH saturate (overshoot で MSE > 0.001) と背景比率が 50% でなく ~60-70% である可能性。impact 小なので scope 外

関連

  • P1.A audit master finding: p1-a-eval-convention-audit (本 doc が §6 main hypothesis を falsify)
  • P1.A.1 brush eval audit: p1-a-1-brush-eval-audit
  • P1.A.2 splat-rs eval audit: p1-a-2-splat-eval-audit
  • brush vs splat 37 dB gap analysis: brush-vs-splat-37dB-gap-analysis
  • P1.M3 生命線 (Lego 30k で PSNR > 30 dB): legacy convention 下で 24.879 dB → +5.1 dB 不足、brush convention では遥かに遠い
03

関連 finding

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