deepsafe's picture
Add stripped inference-only model code mirror
9e14838 verified
Raw History Blame Contribute Delete
8.68 kB
import os
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from numpy import ndarray
from sklearn import metrics as M
from src.utils.decorators import TryExcept
@TryExcept("plot_curve")
def plot_curve(
xs: list[ndarray],
ys: list[ndarray],
auc_threshold: float = 0.01,
class_names: None | dict[int, str] = None,
):
# Create figure with larger size and better aspect ratio
plt.figure(figsize=(10, 8), tight_layout=True)
# Create two subplots - one for the plot, one for the legend
gs = plt.GridSpec(1, 2, width_ratios=[4, 1])
ax_plot = plt.subplot(gs[0])
ax_legend = plt.subplot(gs[1])
palette = sns.husl_palette(len(xs))
linestyles = ["-", "--", "-.", ":"]
# Plot curves on the main axis
active_classes = []
for c, (x, y) in enumerate(zip(xs, ys)):
auc = M.auc(x, y)
if auc >= auc_threshold: # Only plot and include in legend if AUC > threshold
class_name = f"{c}: {class_names[c]}" if class_names else c
label = f"{class_name} (AUC: {auc:.2f})"
linestyle = linestyles[c % len(linestyles)]
line = ax_plot.plot(x, y, label=label, linewidth=1.5, color=palette[c], linestyle=linestyle)
active_classes.append((line[0], label))
# Set square aspect ratio
ax_plot.set_aspect("equal")
# Set limits explicitly to ensure square plot
ax_plot.set_xlim(-0.02, 1.02) # Slight padding for better visibility
ax_plot.set_ylim(-0.02, 1.02)
# Customize the main plot
ax_plot.grid(True, linestyle="--", alpha=0.3)
# Create legend in the second subplot
ax_legend.axis("off") # Hide the axis
if active_classes:
lines, labels = zip(*active_classes)
ax_legend.legend(lines, labels, loc="center left", fontsize=10, borderaxespad=0)
return ax_plot
@TryExcept("plot_roc_curve")
def plot_roc_curve(
fprs: list[ndarray],
tprs: list[ndarray],
ths: list[ndarray],
title: str = "ROC",
path: str = "roc_curve.png",
auc_threshold: float = 0.01,
class_names: None | dict[int, str] = None,
):
"""
Plot ROC curve for multiple classes.
"""
ax_plot = plot_curve(fprs, tprs, auc_threshold, class_names)
# Add the diagonal line
ax_plot.plot([0, 1], [0, 1], color="black", linestyle="--", alpha=0.5)
ax_plot.set_xlabel("False Positive Rate (FPR)", fontsize=12)
ax_plot.set_ylabel("True Positive Rate (TPR)", fontsize=12)
ax_plot.set_title(title, fontsize=14, pad=20)
# Save with high quality
os.makedirs(os.path.dirname(path), exist_ok=True)
plt.savefig(path, dpi=300, bbox_inches="tight")
plt.close()
@TryExcept("plot_prc_curve")
def plot_prc_curve(
prcs: list[ndarray],
recs: list[ndarray],
ths: list[ndarray],
title: str = "PRC",
path: str = "pr_curve.png",
auc_threshold: float = 0.01,
class_names: None | dict[int, str] = None,
show_f1_lines: bool = True,
):
"""
Plot Precision-Recall curve for multiple classes.
"""
ax_plot = plot_curve(recs, prcs, auc_threshold, class_names)
if show_f1_lines:
f_scores = np.linspace(0.1, 0.9, num=9) # F1 scores to plot
for f_score in f_scores:
r = np.linspace(0.001, 1, 100) # Recall
p = f_score * r / (2 * r - f_score) # Precision for given F1 score
mask = p > 0
ax_plot.plot(r[mask], p[mask], color="gray", alpha=0.2, linestyle="--")
ax_plot.annotate("F1={0:0.1f}".format(f_score), xy=(0.95, p[-1] - 0.02), alpha=0.2)
# Customize the main plot
ax_plot.set_xlabel("Recall", fontsize=12)
ax_plot.set_ylabel("Precision", fontsize=12)
ax_plot.set_title(title, fontsize=14, pad=20)
# Save with high quality
os.makedirs(os.path.dirname(path), exist_ok=True)
plt.savefig(path, dpi=300, bbox_inches="tight")
plt.close()
@TryExcept("plot_f1_curve")
def plot_f1_curve(
prcs: list[ndarray],
recs: list[ndarray],
ths: list[ndarray],
title: str = "F1",
path: str = "f1_curve.png",
auc_threshold: float = 0.01,
class_names: None | dict[int, str] = None,
):
"""
Plot F1 curve for multiple classes
"""
f1s = []
for prc, rec in zip(prcs, recs):
with np.errstate(divide="ignore", invalid="ignore"):
f1 = np.where((prc + rec) == 0, 0, 2 * prc * rec / (prc + rec))
f1 = f1[:-1]
f1s.append(f1)
ax_plot = plot_curve(ths, f1s, auc_threshold, class_names)
# Customize the main plot
ax_plot.set_xlabel("Threshold", fontsize=12)
ax_plot.set_ylabel("F1 Score", fontsize=12)
# Save with high quality
os.makedirs(os.path.dirname(path), exist_ok=True)
plt.savefig(path, dpi=300, bbox_inches="tight")
plt.close()
@TryExcept("plot_confusion_matrix")
def plot_confusion_matrix(
confusion_matrix: ndarray,
class_names: None | dict[int, str] = None,
title: str = "Confusion Matrix",
path: str = "confusion_matrix.png",
normalize: bool = False,
):
"""
Plot confusion matrix
"""
N = len(confusion_matrix)
size = max(10, N / 2)
plt.figure(figsize=(size, size), tight_layout=True)
fmt = "d"
if normalize:
confusion_matrix = confusion_matrix / confusion_matrix.sum(axis=1, keepdims=True) * 100
confusion_matrix[np.isnan(confusion_matrix)] = 0
fmt = ".2f"
labels = [f"{k}: {v}" for k, v in class_names.items()] if class_names else None
sns.heatmap(
confusion_matrix,
annot=True,
fmt=fmt,
cmap="Blues",
xticklabels=labels,
yticklabels=labels,
annot_kws={"fontsize": 8},
)
plt.xlabel("Predicted", fontsize=12)
plt.ylabel("Actual", fontsize=12)
plt.title(title, fontsize=14, pad=20)
dirname = os.path.dirname(path)
if dirname:
os.makedirs(dirname, exist_ok=True)
plt.savefig(path, dpi=100, bbox_inches="tight")
plt.close()
@TryExcept("plot_tsne")
def plot_tsne(
tsne_features: np.ndarray, # (N, 2)
set_ids: np.ndarray, # (N,)
id2label: dict[int, str], # dict {id: label}
output_path: str,
):
assert isinstance(tsne_features, np.ndarray)
assert isinstance(set_ids, np.ndarray)
assert isinstance(id2label, dict)
plt.figure(figsize=(25, 25))
palette = sns.husl_palette(len(id2label))
id2color = {id: palette[i] for i, id in enumerate(id2label)}
for id, label in id2label.items():
mask = set_ids == id
if not np.any(mask):
continue
xs = tsne_features[mask, 0]
ys = tsne_features[mask, 1]
if "real" in label:
marker = "."
else:
marker = "x"
plt.scatter(xs, ys, c=[id2color[id]] * len(xs), marker=marker, label=label)
for x, y, label in zip(xs, ys, set_ids[mask]):
plt.text(x, y, label, c=id2color[id], fontsize=9)
plt.legend(loc="best", title="Models")
plt.tight_layout()
os.makedirs(os.path.dirname(output_path), exist_ok=True)
plt.savefig(output_path)
plt.savefig(output_path.replace(".png", ".svg"))
@TryExcept("plot_probs_distribution")
def plot_probs_distribution(
probs: np.ndarray, # (N, C)
labels: np.ndarray, # (N,)
class_names: dict[int, str], # dict {id: label}
output_path: str,
):
n_classes = len(class_names)
fig, axes = plt.subplots(n_classes, 1, figsize=(10, 4 * n_classes))
palette = sns.husl_palette(n_classes)
# Find global min and max for x-axis limits
x_min = probs.min()
x_max = probs.max()
for idx, (class_idx, class_name) in enumerate(class_names.items()):
ax = axes[idx]
# Get probabilities for current class
class_mask = labels == class_idx
class_probs = probs[class_mask]
# Plot probability distribution for each possible class prediction
for pred_idx, pred_name in class_names.items():
pred_probs = class_probs[:, pred_idx]
sns.histplot(
data=pred_probs,
label=pred_name,
color=palette[pred_idx],
alpha=0.2,
# bins=30,
stat="probability",
kde=True,
element="step",
ax=ax,
)
ax.set_xlabel("Scores")
ax.set_ylabel("Probability")
ax.set_title(f"True Class: {class_name}", color=palette[class_idx])
ax.set_xlim(x_min, x_max)
ax.legend()
plt.tight_layout()
os.makedirs(os.path.dirname(output_path), exist_ok=True)
plt.savefig(output_path, dpi=300, bbox_inches="tight")
plt.close()