Add g2q eval metrics and asymmetric MONA-24 configs

- _evaluate: compute R@K + AP for both directions (q2g and g2q) via inverted
  ground truth; g2q denominator counts only sat-tiles with at least one positive
  drone in the (sub)sampled query set. Surfaces in train.csv, val.csv,
  train_recall.csv, W&B summary, and final log.
- conf/gtauav_balanced_asym.gin: asymmetric WEB+SAT encoders, MONA in all 24
  ViT blocks (~17.6M trainable / ~733M total).
- conf/gtauav_baseline_asym.gin: same architecture, baseline_mode=True for
  Δ R@1 against balanced_asym.
- CLAUDE.md / README.md: document new configs, clarify that g2q is now
  computed (was claimed but missing).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pikaliov
2026-04-25 13:47:02 +03:00
parent 8f8cbb14dd
commit 814586ce3b
5 changed files with 123 additions and 12 deletions

View File

@@ -382,8 +382,42 @@ def _evaluate(
mrr_sum += 1.0 / (rank + 1)
break
metrics["ap_q2g"] = mrr_sum / max(n_scored, 1)
# --- g2q (satellite → drone): invert ground-truth ---
n_gallery = gallery.size(0)
valid_q_per_sat: list[set[int]] = [set() for _ in range(n_gallery)]
for q_idx, gset in enumerate(valid_idx_per_query):
for g_idx in gset:
valid_q_per_sat[g_idx].add(q_idx)
sorted_idx_g2q = sim.t().argsort(dim=1, descending=True) # [N_sat, n_query]
n_scored_g2q = sum(1 for s in valid_q_per_sat if s)
for k in k_values:
hits_g2q = 0
for i in range(n_gallery):
valid = valid_q_per_sat[i]
if not valid:
continue
top_k = set(sorted_idx_g2q[i, :k].tolist())
if valid & top_k:
hits_g2q += 1
metrics[f"r@{k}_g2q"] = hits_g2q / max(n_scored_g2q, 1)
mrr_sum_g2q = 0.0
for i in range(n_gallery):
valid = valid_q_per_sat[i]
if not valid:
continue
for rank, qidx in enumerate(sorted_idx_g2q[i].tolist()):
if qidx in valid:
mrr_sum_g2q += 1.0 / (rank + 1)
break
metrics["ap_g2q"] = mrr_sum_g2q / max(n_scored_g2q, 1)
metrics["n_query"] = float(n_query)
metrics["n_gallery"] = float(gallery.size(0))
metrics["n_gallery"] = float(n_gallery)
metrics["n_scored_g2q"] = float(n_scored_g2q)
metrics["gate_q"] = model.fusion_query.gate_value
metrics["gate_g"] = model.fusion_gallery.gate_value
@@ -953,17 +987,26 @@ def train(cfg: TrainConfigGTAUAV) -> None:
train_row["r@5_q2g"] = train_recall.get("r@5_q2g", 0.0)
train_row["r@10_q2g"] = train_recall.get("r@10_q2g", 0.0)
train_row["ap_q2g"] = train_recall.get("ap_q2g", 0.0)
train_row["r@1_g2q"] = train_recall.get("r@1_g2q", 0.0)
train_row["r@5_g2q"] = train_recall.get("r@5_g2q", 0.0)
train_row["r@10_g2q"] = train_recall.get("r@10_g2q", 0.0)
train_row["ap_g2q"] = train_recall.get("ap_g2q", 0.0)
csv_logger.log_train(epoch, train_row, optimizer.param_groups[0]["lr"], elapsed)
generate_plots(csv_logger.log_dir)
if train_recall:
LOGGER.info(
"train-recall epoch=%d R@1=%.4f R@5=%.4f R@10=%.4f AP=%.4f loss=%.4f",
"train-recall epoch=%d q2g R@1=%.4f R@5=%.4f R@10=%.4f AP=%.4f "
"g2q R@1=%.4f R@5=%.4f R@10=%.4f AP=%.4f loss=%.4f",
epoch,
train_recall.get("r@1_q2g", 0.0),
train_recall.get("r@5_q2g", 0.0),
train_recall.get("r@10_q2g", 0.0),
train_recall.get("ap_q2g", 0.0),
train_recall.get("r@1_g2q", 0.0),
train_recall.get("r@5_g2q", 0.0),
train_recall.get("r@10_g2q", 0.0),
train_recall.get("ap_g2q", 0.0),
train_recall.get("loss", 0.0),
)
@@ -985,12 +1028,17 @@ def train(cfg: TrainConfigGTAUAV) -> None:
tracker.log_scalar("val/best_r@1_q2g", best_r1, step=global_step)
LOGGER.info(
"val epoch=%d R@1=%.4f R@5=%.4f R@10=%.4f AP=%.4f loss=%.4f gate_q=%.4f",
"val epoch=%d q2g R@1=%.4f R@5=%.4f R@10=%.4f AP=%.4f "
"g2q R@1=%.4f R@5=%.4f R@10=%.4f AP=%.4f loss=%.4f gate_q=%.4f",
epoch,
val_metrics.get("r@1_q2g", 0.0),
val_metrics.get("r@5_q2g", 0.0),
val_metrics.get("r@10_q2g", 0.0),
val_metrics.get("ap_q2g", 0.0),
val_metrics.get("r@1_g2q", 0.0),
val_metrics.get("r@5_g2q", 0.0),
val_metrics.get("r@10_g2q", 0.0),
val_metrics.get("ap_g2q", 0.0),
val_metrics.get("loss", 0.0),
val_metrics.get("gate_q", 1.0),
)
@@ -1061,6 +1109,11 @@ def train(cfg: TrainConfigGTAUAV) -> None:
"final_r@1_q2g": final_metrics.get("r@1_q2g", 0.0),
"final_r@5_q2g": final_metrics.get("r@5_q2g", 0.0),
"final_r@10_q2g": final_metrics.get("r@10_q2g", 0.0),
"final_ap_q2g": final_metrics.get("ap_q2g", 0.0),
"final_r@1_g2q": final_metrics.get("r@1_g2q", 0.0),
"final_r@5_g2q": final_metrics.get("r@5_g2q", 0.0),
"final_r@10_g2q": final_metrics.get("r@10_g2q", 0.0),
"final_ap_g2q": final_metrics.get("ap_g2q", 0.0),
"final_gate_q": final_metrics.get("gate_q", 1.0),
"final_gate_g": final_metrics.get("gate_g", 1.0),
})
@@ -1073,10 +1126,16 @@ def train(cfg: TrainConfigGTAUAV) -> None:
LOGGER.info("Training complete. Report: %s", report_path)
LOGGER.info(
"Final — R@1=%.4f R@5=%.4f R@10=%.4f gate_q=%.4f gate_g=%.4f",
"Final — q2g R@1=%.4f R@5=%.4f R@10=%.4f AP=%.4f "
"g2q R@1=%.4f R@5=%.4f R@10=%.4f AP=%.4f gate_q=%.4f gate_g=%.4f",
final_metrics.get("r@1_q2g", 0.0),
final_metrics.get("r@5_q2g", 0.0),
final_metrics.get("r@10_q2g", 0.0),
final_metrics.get("ap_q2g", 0.0),
final_metrics.get("r@1_g2q", 0.0),
final_metrics.get("r@5_g2q", 0.0),
final_metrics.get("r@10_g2q", 0.0),
final_metrics.get("ap_g2q", 0.0),
final_metrics.get("gate_q", 1.0),
final_metrics.get("gate_g", 1.0),
)