pre-refactor 041426

This commit is contained in:
rpotter6298
2026-04-14 19:42:16 +02:00
parent eb9eafe715
commit 13290575d5
75 changed files with 8900 additions and 77 deletions
@@ -0,0 +1,171 @@
#!/usr/bin/env python
"""
Bar plot comparing CNN standalone vs HyperTower image_only AUC per backbone,
with PAPILA paper reference lines.
Usage:
python -m v3.scripts.output_analysis.plot_cnn_backbone_comparison \
--results-dir v3/results/phase1 \
--output v3/results/phase1/cnn_backbone_comparison.png
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn.metrics import roc_auc_score
sys.path.insert(0, str(Path(__file__).resolve().parents[3]))
BACKBONES = ["densenet121", "vgg16", "mobilenet_v2", "inception_v3", "resnet50"]
BACKBONE_LABELS = {
"densenet121": "DenseNet121",
"vgg16": "VGG16",
"mobilenet_v2": "MobileNetV2",
"inception_v3": "Inception V3",
"resnet50": "ResNet50",
}
# Per-backbone paper AUCs (binary, Test #2, PAPILA 2022)
PAPER_AUC = {
"densenet121": 0.80,
"vgg16": 0.84,
"mobilenet_v2": 0.75,
"inception_v3": 0.78,
"resnet50": 0.78,
}
PAPER_STD = {
"densenet121": 0.05,
"vgg16": 0.02,
"mobilenet_v2": 0.06,
"inception_v3": 0.08,
"resnet50": 0.07,
}
COLOURS = {
"cnn": "#4878CF",
"ht": "#D65F5F",
"paper_ref": "black",
}
def load_cnn_fold_aucs(results_dir: Path, backbone: str) -> list[float]:
fpath = results_dir / f"cnn_{backbone}" / "fold_metrics.csv"
if not fpath.exists():
print(f" WARNING: missing {fpath}")
return []
df = pd.read_csv(fpath)
return df["auc"].tolist()
def load_ht_fold_aucs(results_dir: Path, backbone: str, n_folds: int = 5) -> list[float]:
aucs = []
for fold in range(n_folds):
fold_dir = results_dir / "imageonly_ht" / backbone / "binary" / "single" / f"fold{fold}"
y_path = fold_dir / "test_y_true.npy"
p_path = fold_dir / "test_probs_fused.npy"
if not (y_path.exists() and p_path.exists()):
print(f" WARNING: missing predictions for {backbone} fold{fold}")
continue
y = np.load(y_path)
pr = np.load(p_path)
if len(np.unique(y)) < 2:
print(f" WARNING: single-class test set for {backbone} fold{fold}, skipping")
continue
aucs.append(float(roc_auc_score(y, pr[:, 1])))
return aucs
def plot(cnn_data: dict, ht_data: dict, output: Path):
n = len(BACKBONES)
x = np.arange(n)
group_width = 0.7
bar_w = group_width / 2 * 0.88
offsets = [-group_width / 4, group_width / 4]
fig, ax = plt.subplots(figsize=(10, 5.5))
for bi, backbone in enumerate(BACKBONES):
for si, (tag, data, colour) in enumerate([
("CNN standalone", cnn_data, COLOURS["cnn"]),
("HyperTower (image only)", ht_data, COLOURS["ht"]),
]):
aucs = data.get(backbone, [])
if not aucs:
continue
xpos = bi + offsets[si]
mean, std = np.mean(aucs), np.std(aucs)
ax.bar(
xpos, mean, width=bar_w,
color=colour, alpha=0.80,
label=tag if bi == 0 else "_nolegend_",
)
ax.errorbar(
xpos, mean, yerr=std,
fmt="none", color="black", capsize=4, linewidth=1.2,
)
# Paper reference line spanning this backbone's group
paper_val = PAPER_AUC.get(backbone)
if paper_val is not None:
lw = group_width / 2 + bar_w / 2
label = "PAPILA paper" if bi == 0 else "_nolegend_"
ax.hlines(
paper_val,
bi - group_width / 2, bi + group_width / 2,
colors=COLOURS["paper_ref"], linestyles=":", linewidths=1.8,
label=label,
)
ax.set_xticks(x)
ax.set_xticklabels([BACKBONE_LABELS[b] for b in BACKBONES], fontsize=11)
ax.set_ylabel("AUC (ROC)", fontsize=11)
ax.set_title("Phase 1: CNN backbone AUC — standalone vs HyperTower (image only)", fontsize=12)
ax.set_ylim(0.45, 1.02)
ax.axhline(0.5, color="grey", linestyle="--", linewidth=0.8, alpha=0.4)
ax.grid(axis="y", alpha=0.3, linestyle="--")
ax.legend(loc="lower right", fontsize=10, framealpha=0.9)
fig.tight_layout()
output.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(output, dpi=180)
plt.close(fig)
print(f"Saved: {output}")
def main():
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--results-dir", default="v3/results/phase1")
ap.add_argument("--output", default=None)
args = ap.parse_args()
results_dir = Path(args.results_dir)
output = Path(args.output) if args.output else results_dir / "cnn_backbone_comparison.png"
cnn_data = {b: load_cnn_fold_aucs(results_dir, b) for b in BACKBONES}
ht_data = {b: load_ht_fold_aucs(results_dir, b) for b in BACKBONES}
plot(cnn_data, ht_data, output)
# Summary table
print(f"\n{'Backbone':<16} {'CNN standalone':>18} {'HT image_only':>18} {'Paper':>12}")
print("-" * 72)
for b in BACKBONES:
cnn_aucs = cnn_data[b]
ht_aucs = ht_data[b]
cnn_str = f"{np.mean(cnn_aucs):.3f} ± {np.std(cnn_aucs):.3f}" if cnn_aucs else ""
ht_str = f"{np.mean(ht_aucs):.3f} ± {np.std(ht_aucs):.3f}" if ht_aucs else ""
p_str = f"{PAPER_AUC[b]:.2f} ± {PAPER_STD[b]:.2f}"
print(f"{BACKBONE_LABELS[b]:<16} {cnn_str:>18} {ht_str:>18} {p_str:>12}")
if __name__ == "__main__":
main()