Files
caption-test/scripts/compare_runs.py
Pikaliov 2ce4017ebd Initial commit: caption quality test on UAV-VisLoc
Self-contained experimental track validating generated text captions
via retrieval R@1 lift on UAV-VisLoc.

Architecture: GeoRSCLIP ViT-B/32 dual encoder, 512-dim shared space.
Loss: 4-term InfoNCE (img-img + sat-cap + drone-cap + cap-cap)
      with cosine temperature decay, PALW-like curriculum.
Metric: delta R@1 (with text - without text) >= +3% => PASS.

Gin-configured (balanced / baseline_no_text / text_heavy variants).
Follows NADEZHDA code style.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 00:04:46 +03:00

139 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
"""Compare baseline vs full-caption runs and compute Delta R@1 report.
Reads eval reports produced by src.eval.evaluate.run_evaluation_from_checkpoint
and produces a markdown + JSON summary.
Usage:
python -m scripts.compare_runs \
--baseline_report out/caption_test/baseline_no_text/eval_report.json \
--full_report out/caption_test/balanced/eval_report.json \
--output out/caption_test/comparison.md
"""
import argparse
import json
from pathlib import Path
_DIRECTIONS = (
"drone_to_sat",
"sat_to_drone",
"text_to_sat",
"text_to_drone",
)
_KS = (1, 5, 10)
def _load_metrics(report_path: Path) -> dict[str, float]:
with report_path.open("r", encoding="utf-8") as f:
data = json.load(f)
return data.get("metrics", data)
def _format_row(name: str, baseline: dict[str, float], full: dict[str, float]) -> str:
"""Render one markdown row for a direction across R@1, R@5, R@10."""
cells = [name]
for k in _KS:
key = f"r@{k}_{name}"
b = baseline.get(key, float("nan"))
f_ = full.get(key, float("nan"))
delta = f_ - b if (b == b and f_ == f_) else float("nan") # NaN-safe
cells.append(f"{b:.4f}{f_:.4f}{delta:+.4f})")
return "| " + " | ".join(cells) + " |"
def _interpret_delta(delta: float) -> str:
"""Human-readable caption-quality verdict."""
if delta >= 0.03:
return "✅ PASS — captions informative (Δ R@1 ≥ +3%)"
if delta >= 0.01:
return "⚠️ MARGINAL — consider VLM refinement (+1% ≤ Δ < +3%)"
if delta >= 0:
return "❌ WEAK — captions add little signal (< +1%)"
return "❌❌ HARMFUL — captions confuse model (Δ < 0)"
def build_comparison_markdown(
baseline: dict[str, float],
full: dict[str, float],
) -> str:
"""Compose markdown comparison report."""
lines: list[str] = ["# Caption Quality Test: Comparison Report", ""]
# Headline Δ R@1 on primary direction.
primary = "drone_to_sat"
primary_key = f"r@1_{primary}"
primary_delta = full.get(primary_key, 0.0) - baseline.get(primary_key, 0.0)
verdict = _interpret_delta(primary_delta)
lines.append(f"## Primary metric: Δ R@1 ({primary}) = {primary_delta:+.4f}")
lines.append("")
lines.append(f"**Verdict:** {verdict}")
lines.append("")
# Full table.
lines.append("## All directions × K")
lines.append("")
header = "| Direction | R@1 base → full | R@5 base → full | R@10 base → full |"
sep = "|---|---|---|---|"
lines.extend([header, sep])
for direction in _DIRECTIONS:
row = _format_row(direction, baseline, full)
lines.append(row)
lines.append("")
# Decision rule recap.
lines.append("## Decision rule")
lines.append("")
lines.append("- Δ R@1 ≥ +3% → captions pass, proceed to World-UAV generation")
lines.append("- +1% ≤ Δ R@1 < +3% → add VLM refinement, re-run")
lines.append("- Δ R@1 < +1% → redesign caption pipeline")
lines.append("- Δ R@1 < 0 → critical bug, investigate caption/image alignment")
lines.append("")
return "\n".join(lines)
def main() -> None:
parser = argparse.ArgumentParser(
description="Compare baseline vs full-caption runs."
)
parser.add_argument("--baseline_report", type=Path, required=True)
parser.add_argument("--full_report", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
baseline = _load_metrics(args.baseline_report)
full = _load_metrics(args.full_report)
md = build_comparison_markdown(baseline=baseline, full=full)
args.output.parent.mkdir(parents=True, exist_ok=True)
with args.output.open("w", encoding="utf-8") as f:
f.write(md)
# Also write machine-readable summary.
summary = {
"baseline_metrics": baseline,
"full_metrics": full,
"deltas": {
f"delta_r@{k}_{d}": (
full.get(f"r@{k}_{d}", 0.0) - baseline.get(f"r@{k}_{d}", 0.0)
)
for d in _DIRECTIONS
for k in _KS
},
}
summary_path = args.output.with_suffix(".json")
with summary_path.open("w", encoding="utf-8") as f:
json.dump(summary, f, indent=2)
print(f"Comparison saved: {args.output}")
print(f"Summary saved: {summary_path}")
if __name__ == "__main__":
main()