File size: 4,626 Bytes
1ea7ba6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | import json
import random
import time
from pathlib import Path
import numpy as np
import torch
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import (
classification_report, confusion_matrix,
accuracy_score, f1_score,
)
import config
def set_seed(seed: int = config.RANDOM_SEED):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
def topk_accuracy(output: torch.Tensor, target: torch.Tensor, k: int = 5) -> float:
with torch.no_grad():
batch = target.size(0)
_, pred = output.topk(k, dim=1, largest=True, sorted=True)
correct = pred.eq(target.view(-1, 1).expand_as(pred))
return correct.any(dim=1).float().sum().item() / batch
@torch.no_grad()
def measure_inference_time(model, loader, device, n_batches: int = 20) -> float:
"""Returns mean inference time in ms per image. Works with both SpiceFusionNet
(uses forward_image) and plain timm models (uses __call__)."""
model.eval()
forward_fn = getattr(model, "forward_image", model)
times = []
for i, batch in enumerate(loader):
if i >= n_batches:
break
imgs = batch[0].to(device)
if device.type == "cuda":
torch.cuda.synchronize()
t0 = time.perf_counter()
_ = forward_fn(imgs)
if device.type == "cuda":
torch.cuda.synchronize()
elapsed = (time.perf_counter() - t0) * 1000 # ms
times.append(elapsed / imgs.size(0))
return float(np.mean(times))
def plot_training_curves(history: dict, output_dir: Path, prefix: str = ""):
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
epochs = range(1, len(history["train_loss"]) + 1)
axes[0].plot(epochs, history["train_loss"], label="train")
axes[0].plot(epochs, history["val_loss"], label="val")
axes[0].set_title("Loss"); axes[0].set_xlabel("Epoch"); axes[0].legend()
axes[1].plot(epochs, history["train_acc"], label="train")
axes[1].plot(epochs, history["val_acc"], label="val")
axes[1].set_title("Accuracy"); axes[1].set_xlabel("Epoch"); axes[1].legend()
axes[2].plot(epochs, history["lr"])
axes[2].set_title("Learning Rate"); axes[2].set_xlabel("Epoch"); axes[2].set_yscale("log")
plt.tight_layout()
name = f"{prefix}_training_curves.png" if prefix else "training_curves.png"
plt.savefig(output_dir / name, dpi=150)
plt.close()
def plot_confusion_matrix(y_true, y_pred, classes, output_dir: Path, prefix: str = ""):
cm = confusion_matrix(y_true, y_pred)
cm_norm = cm.astype(float) / cm.sum(axis=1, keepdims=True)
fig, axes = plt.subplots(1, 2, figsize=(22, 9))
for ax, data, title, fmt in [
(axes[0], cm, "Counts", "d"),
(axes[1], cm_norm, "Normalized", ".2f"),
]:
sns.heatmap(data, annot=True, fmt=fmt, cmap="Blues",
xticklabels=classes, yticklabels=classes, ax=ax, linewidths=0.5)
ax.set_title(title, fontsize=13)
ax.set_xlabel("Predicted"); ax.set_ylabel("True")
ax.tick_params(axis="x", rotation=45)
plt.tight_layout()
name = f"{prefix}_confusion_matrix.png" if prefix else "confusion_matrix.png"
plt.savefig(output_dir / name, dpi=150)
plt.close()
def compute_and_print_metrics(y_true, y_pred, classes, top5_acc=None, infer_ms=None) -> dict:
acc = accuracy_score(y_true, y_pred)
f1_w = f1_score(y_true, y_pred, average="weighted")
f1_m = f1_score(y_true, y_pred, average="macro")
report = classification_report(y_true, y_pred, target_names=classes, digits=4)
print(f"\n{'='*50}")
print(f" Top-1 Accuracy : {acc:.4f}")
if top5_acc is not None:
print(f" Top-5 Accuracy : {top5_acc:.4f}")
print(f" Weighted F1 : {f1_w:.4f}")
print(f" Macro F1 : {f1_m:.4f}")
if infer_ms is not None:
print(f" Inference time : {infer_ms:.2f} ms/image")
print(f"{'='*50}")
print(f"\n{report}")
metrics = {"top1_accuracy": acc, "f1_weighted": f1_w, "f1_macro": f1_m}
if top5_acc is not None: metrics["top5_accuracy"] = top5_acc
if infer_ms is not None: metrics["inference_ms"] = infer_ms
return metrics
def save_metrics(metrics: dict, path: Path):
with open(path, "w") as f:
json.dump(metrics, f, indent=2)
print(f"Metrics saved -> {path}")
|