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:
pikaliov
2026-04-22 08:01:09 +03:00
parent 66c83469ef
commit 93ad66810d
2 changed files with 72 additions and 13 deletions

View File

@@ -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