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:
@@ -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),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user