Add train recall evaluation (R@K on train subset each epoch)
- Evaluate R@K on train set (subset matching test size) alongside val - New train_recall.csv with per-epoch train R@1/R@5/R@10 - Plot train vs val recall on same chart (solid=val, dashed=train) - Helps detect overfitting: train R@1 up + val R@1 flat = overfit - Train eval uses clean transforms (no augmentation) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -69,34 +69,38 @@ def plot_train_metrics(train_df: pd.DataFrame, out_dir: Path) -> None:
|
||||
LOGGER.info("📊 Saved %s", path)
|
||||
|
||||
|
||||
def plot_val_metrics(val_df: pd.DataFrame, out_dir: Path) -> None:
|
||||
"""Plot validation metrics: R@K, gates."""
|
||||
def plot_val_metrics(val_df: pd.DataFrame, out_dir: Path, train_recall_df: pd.DataFrame | None = None) -> None:
|
||||
"""Plot recall metrics: train vs val R@K."""
|
||||
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
|
||||
fig.suptitle("Validation Metrics", fontsize=16, fontweight="bold")
|
||||
fig.suptitle("Recall Metrics (train vs val)", fontsize=16, fontweight="bold")
|
||||
|
||||
# 1. Recall@K (q→g).
|
||||
# 1. Recall@K (q→g): train + val.
|
||||
ax = axes[0]
|
||||
for k in [1, 5, 10]:
|
||||
col = f"r@{k}_q2g"
|
||||
if col in val_df.columns:
|
||||
sns.lineplot(data=val_df, x="epoch", y=col, ax=ax, marker="o", linewidth=2, label=f"R@{k}")
|
||||
sns.lineplot(data=val_df, x="epoch", y=col, ax=ax, marker="o", linewidth=2, label=f"val R@{k}")
|
||||
if train_recall_df is not None and col in train_recall_df.columns:
|
||||
sns.lineplot(data=train_recall_df, x="epoch", y=col, ax=ax, marker="x", linewidth=1.5, linestyle="--", label=f"train R@{k}")
|
||||
ax.set_title("Recall@K (drone → satellite)")
|
||||
ax.set_xlabel("Epoch")
|
||||
ax.set_ylabel("Recall")
|
||||
ax.set_ylim(0, 1)
|
||||
ax.legend()
|
||||
ax.legend(fontsize=8)
|
||||
|
||||
# 2. Recall@K (g→q).
|
||||
# 2. Recall@K (g→q): train + val.
|
||||
ax = axes[1]
|
||||
for k in [1, 5, 10]:
|
||||
col = f"r@{k}_g2q"
|
||||
if col in val_df.columns:
|
||||
sns.lineplot(data=val_df, x="epoch", y=col, ax=ax, marker="s", linewidth=2, label=f"R@{k}")
|
||||
sns.lineplot(data=val_df, x="epoch", y=col, ax=ax, marker="s", linewidth=2, label=f"val R@{k}")
|
||||
if train_recall_df is not None and col in train_recall_df.columns:
|
||||
sns.lineplot(data=train_recall_df, x="epoch", y=col, ax=ax, marker="x", linewidth=1.5, linestyle="--", label=f"train R@{k}")
|
||||
ax.set_title("Recall@K (satellite → drone)")
|
||||
ax.set_xlabel("Epoch")
|
||||
ax.set_ylabel("Recall")
|
||||
ax.set_ylim(0, 1)
|
||||
ax.legend()
|
||||
ax.legend(fontsize=8)
|
||||
|
||||
plt.tight_layout()
|
||||
path = out_dir / "val_metrics.png"
|
||||
@@ -166,9 +170,14 @@ def generate_plots(log_dir: str | Path) -> None:
|
||||
LOGGER.warning("No train.csv found in %s", log_dir)
|
||||
train_df = None
|
||||
|
||||
train_recall_csv = log_dir / "train_recall.csv"
|
||||
train_recall_df = None
|
||||
if train_recall_csv.exists():
|
||||
train_recall_df = pd.read_csv(train_recall_csv)
|
||||
|
||||
if val_csv.exists():
|
||||
val_df = pd.read_csv(val_csv)
|
||||
plot_val_metrics(val_df, log_dir)
|
||||
plot_val_metrics(val_df, log_dir, train_recall_df=train_recall_df)
|
||||
else:
|
||||
LOGGER.warning("No val.csv found in %s", log_dir)
|
||||
val_df = None
|
||||
|
||||
Reference in New Issue
Block a user