Compare commits
2 Commits
86d9d96ca4
...
cf15d5501e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf15d5501e | ||
|
|
ef0c36eed5 |
@@ -4,9 +4,11 @@ let
|
||||
torch-bin
|
||||
torchvision-bin
|
||||
aggdraw # for visualtorch
|
||||
nvidia-ml-py
|
||||
];
|
||||
tools = with pkgs; [
|
||||
ruff
|
||||
dmidecode
|
||||
];
|
||||
in
|
||||
{
|
||||
|
||||
501
Deep-SAD-PyTorch/hardware_survey/main.py
Normal file
501
Deep-SAD-PyTorch/hardware_survey/main.py
Normal file
@@ -0,0 +1,501 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Generate a LaTeX longtable with system + software info for a thesis (Linux + NVIDIA).
|
||||
|
||||
Requirements (preflight will check and error if missing):
|
||||
- Linux OS
|
||||
- lscpu (util-linux)
|
||||
- Python packages: nvidia-ml-py3 (pynvml), torch, numpy, scipy, scikit-learn
|
||||
- NVIDIA driver present and at least one GPU visible via NVML
|
||||
|
||||
What it reports (per user’s list):
|
||||
System:
|
||||
- OS name + version + distribution (Linux) + kernel version + system arch
|
||||
- CPU model name, number of cores and threads, base frequencies (best-effort via lscpu)
|
||||
- Total RAM capacity
|
||||
- GPU(s): model name (only the newer one; prefer a name matching “4090”, else highest compute capability),
|
||||
memory size, driver version, CUDA (driver) version, cuDNN version (if used via PyTorch)
|
||||
|
||||
Software environment:
|
||||
- Python version
|
||||
- PyTorch version + built CUDA/cuDNN version
|
||||
- scikit-learn version
|
||||
- NumPy / SciPy version (+ NumPy build config summary: MKL/OpenBLAS/etc.)
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
# -------------------- Helper --------------------
|
||||
|
||||
|
||||
def _import_nvml():
|
||||
"""
|
||||
Try to import NVML from the supported packages:
|
||||
- 'nvidia-ml-py' (preferred, maintained): provides module 'pynvml'
|
||||
- legacy 'pynvml' (deprecated but still widely installed)
|
||||
Returns the imported module object (with nvml... symbols).
|
||||
"""
|
||||
try:
|
||||
import pynvml as _nvml # provided by 'nvidia-ml-py' or old 'pynvml'
|
||||
|
||||
return _nvml
|
||||
except Exception as e:
|
||||
raise ImportError(
|
||||
"NVML not importable. Please install the maintained package:\n"
|
||||
" pip install nvidia-ml-py\n"
|
||||
"(and uninstall deprecated ones: pip uninstall nvidia-ml-py3 pynvml)"
|
||||
) from e
|
||||
|
||||
|
||||
def _to_text(x) -> str:
|
||||
"""Return a clean str whether NVML gives bytes or str."""
|
||||
if isinstance(x, bytes):
|
||||
try:
|
||||
return x.decode(errors="ignore")
|
||||
except Exception:
|
||||
return x.decode("utf-8", "ignore")
|
||||
return str(x)
|
||||
|
||||
|
||||
# -------------------- Utilities --------------------
|
||||
|
||||
|
||||
def which(cmd: str) -> str:
|
||||
return shutil.which(cmd) or ""
|
||||
|
||||
|
||||
def run(cmd: List[str], timeout: int = 6) -> str:
|
||||
try:
|
||||
out = subprocess.check_output(cmd, stderr=subprocess.STDOUT, timeout=timeout)
|
||||
return out.decode(errors="ignore").strip()
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def human_bytes(nbytes: int) -> str:
|
||||
try:
|
||||
n = float(nbytes)
|
||||
except Exception:
|
||||
return ""
|
||||
units = ["B", "KiB", "MiB", "GiB", "TiB"]
|
||||
i = 0
|
||||
while n >= 1024 and i < len(units) - 1:
|
||||
n /= 1024.0
|
||||
i += 1
|
||||
return f"{n:.2f} {units[i]}"
|
||||
|
||||
|
||||
LATEX_SPECIALS = {
|
||||
"\\": r"\textbackslash{}",
|
||||
"&": r"\&",
|
||||
"%": r"\%",
|
||||
"$": r"\$",
|
||||
"#": r"\#",
|
||||
"_": r"\_",
|
||||
"{": r"\{",
|
||||
"}": r"\}",
|
||||
"~": r"\textasciitilde{}",
|
||||
"^": r"\textasciicircum{}",
|
||||
}
|
||||
|
||||
|
||||
def tex_escape(s: str) -> str:
|
||||
if s is None:
|
||||
return ""
|
||||
return "".join(LATEX_SPECIALS.get(ch, ch) for ch in str(s))
|
||||
|
||||
|
||||
def latex_table(sections: List[Tuple[str, Dict[str, str]]], caption: str) -> str:
|
||||
lines = []
|
||||
lines.append(r"\begin{table}[p]") # float; use [p] or [tbp] as you prefer
|
||||
lines.append(r"\centering")
|
||||
lines.append(r"\caption{" + tex_escape(caption) + r"} \label{tab:system_setup}")
|
||||
lines.append(r"\begin{tabular}{p{0.34\linewidth} p{0.62\linewidth}}")
|
||||
lines.append(r"\toprule")
|
||||
lines.append(r"\textbf{Item} & \textbf{Details} \\")
|
||||
lines.append(r"\midrule")
|
||||
|
||||
for title, kv in sections:
|
||||
if not kv:
|
||||
continue
|
||||
lines.append(r"\multicolumn{2}{l}{\textbf{" + tex_escape(title) + r"}} \\")
|
||||
for k, v in kv.items():
|
||||
val = tex_escape(v)
|
||||
if "\n" in v or len(v) > 120:
|
||||
val = (
|
||||
r"\begin{minipage}[t]{\linewidth}\ttfamily\small "
|
||||
+ tex_escape(v)
|
||||
+ r"\end{minipage}"
|
||||
)
|
||||
else:
|
||||
val = r"\ttfamily " + val
|
||||
lines.append(tex_escape(k) + " & " + val + r" \\")
|
||||
lines.append(r"\addlinespace")
|
||||
|
||||
lines.append(r"\bottomrule")
|
||||
lines.append(r"\end{tabular}")
|
||||
lines.append(r"\end{table}")
|
||||
|
||||
preamble_hint = r"""
|
||||
% ---- Add to your LaTeX preamble ----
|
||||
% \usepackage{booktabs}
|
||||
% \usepackage{array}
|
||||
% ------------------------------------
|
||||
"""
|
||||
return preamble_hint + "\n".join(lines)
|
||||
|
||||
|
||||
def latex_longtable(sections: List[Tuple[str, Dict[str, str]]], caption: str) -> str:
|
||||
lines = []
|
||||
lines.append(r"\begin{longtable}{p{0.34\linewidth} p{0.62\linewidth}}")
|
||||
lines.append(r"\caption{" + tex_escape(caption) + r"} \label{tab:system_setup}\\")
|
||||
lines.append(r"\toprule")
|
||||
lines.append(r"\textbf{Item} & \textbf{Details} \\")
|
||||
lines.append(r"\midrule")
|
||||
lines.append(r"\endfirsthead")
|
||||
lines.append(r"\toprule \textbf{Item} & \textbf{Details} \\ \midrule")
|
||||
lines.append(r"\endhead")
|
||||
lines.append(r"\bottomrule")
|
||||
lines.append(r"\endfoot")
|
||||
lines.append(r"\bottomrule")
|
||||
lines.append(r"\endlastfoot")
|
||||
|
||||
for title, kv in sections:
|
||||
if not kv:
|
||||
continue
|
||||
lines.append(r"\multicolumn{2}{l}{\textbf{" + tex_escape(title) + r"}} \\")
|
||||
for k, v in kv.items():
|
||||
val = tex_escape(v)
|
||||
if "\n" in v or len(v) > 120:
|
||||
val = (
|
||||
r"\begin{minipage}[t]{\linewidth}\ttfamily\small "
|
||||
+ tex_escape(v)
|
||||
+ r"\end{minipage}"
|
||||
)
|
||||
else:
|
||||
val = r"\ttfamily " + val
|
||||
lines.append(tex_escape(k) + " & " + val + r" \\")
|
||||
lines.append(r"\addlinespace")
|
||||
lines.append(r"\end{longtable}")
|
||||
|
||||
preamble_hint = r"""
|
||||
% ---- Add to your LaTeX preamble ----
|
||||
% \usepackage{booktabs}
|
||||
% \usepackage{longtable}
|
||||
% \usepackage{array}
|
||||
% ------------------------------------
|
||||
"""
|
||||
return preamble_hint + "\n".join(lines)
|
||||
|
||||
|
||||
# -------------------- Preflight --------------------
|
||||
|
||||
REQUIRED_CMDS = ["lscpu"]
|
||||
REQUIRED_MODULES = [
|
||||
"torch",
|
||||
"numpy",
|
||||
"scipy",
|
||||
"sklearn",
|
||||
"pynvml",
|
||||
] # provided by nvidia-ml-py
|
||||
|
||||
|
||||
def preflight() -> List[str]:
|
||||
errors = []
|
||||
if platform.system().lower() != "linux":
|
||||
errors.append(
|
||||
f"This script supports Linux only (detected: {platform.system()})."
|
||||
)
|
||||
|
||||
for c in ["lscpu"]:
|
||||
if not which(c):
|
||||
errors.append(f"Missing required command: {c}")
|
||||
|
||||
for m in REQUIRED_MODULES:
|
||||
try:
|
||||
__import__(m)
|
||||
except Exception:
|
||||
errors.append(f"Missing required Python package: {m}")
|
||||
|
||||
# NVML driver availability
|
||||
if "pynvml" not in errors:
|
||||
try:
|
||||
pynvml = _import_nvml()
|
||||
pynvml.nvmlInit()
|
||||
count = pynvml.nvmlDeviceGetCount()
|
||||
if count < 1:
|
||||
errors.append("No NVIDIA GPUs detected by NVML.")
|
||||
pynvml.nvmlShutdown()
|
||||
except Exception as e:
|
||||
errors.append(f"NVIDIA NVML not available / driver not loaded: {e}")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
# -------------------- Collectors --------------------
|
||||
|
||||
|
||||
def collect_system() -> Dict[str, str]:
|
||||
info: Dict[str, str] = {}
|
||||
|
||||
# OS / distro / kernel / arch
|
||||
os_pretty = ""
|
||||
try:
|
||||
with open("/etc/os-release", "r") as f:
|
||||
txt = f.read()
|
||||
m = re.search(r'^PRETTY_NAME="?(.*?)"?$', txt, flags=re.M)
|
||||
if m:
|
||||
os_pretty = m.group(1)
|
||||
except Exception:
|
||||
pass
|
||||
info["Operating System"] = os_pretty or f"{platform.system()} {platform.release()}"
|
||||
info["Kernel"] = platform.release()
|
||||
info["Architecture"] = platform.machine()
|
||||
|
||||
# CPU (via lscpu)
|
||||
lscpu = run(["lscpu"])
|
||||
|
||||
def kvs(text: str) -> Dict[str, str]:
|
||||
out = {}
|
||||
for line in text.splitlines():
|
||||
if ":" in line:
|
||||
k, v = line.split(":", 1)
|
||||
out[k.strip()] = v.strip()
|
||||
return out
|
||||
|
||||
d = kvs(lscpu)
|
||||
info["CPU Model"] = d.get("Model name", d.get("Model Name", ""))
|
||||
|
||||
# cores / threads
|
||||
sockets = d.get("Socket(s)", "")
|
||||
cores_per_socket = d.get("Core(s) per socket", "")
|
||||
threads_total = d.get("CPU(s)", "")
|
||||
if sockets and cores_per_socket:
|
||||
info["CPU Cores (physical)"] = f"{cores_per_socket} × {sockets}"
|
||||
else:
|
||||
info["CPU Cores (physical)"] = cores_per_socket or ""
|
||||
info["CPU Threads (logical)"] = threads_total or str(os.cpu_count() or "")
|
||||
|
||||
# base / max freq
|
||||
# Prefer "CPU max MHz" and "CPU min MHz"; lscpu sometimes exposes "CPU MHz" (current)
|
||||
base = d.get("CPU min MHz", "")
|
||||
maxf = d.get("CPU max MHz", "")
|
||||
if base:
|
||||
info["CPU Base Frequency"] = f"{float(base):.0f} MHz"
|
||||
elif "@" in info["CPU Model"]:
|
||||
# fallback: parse from model string like "Intel(R) ... @ 2.30GHz"
|
||||
m = re.search(r"@\s*([\d.]+)\s*([GM]Hz)", info["CPU Model"])
|
||||
if m:
|
||||
info["CPU Base Frequency"] = f"{m.group(1)} {m.group(2)}"
|
||||
else:
|
||||
cur = d.get("CPU MHz", "")
|
||||
if cur:
|
||||
info["CPU (Current) Frequency"] = f"{float(cur):.0f} MHz"
|
||||
if maxf:
|
||||
info["CPU Max Frequency"] = f"{float(maxf):.0f} MHz"
|
||||
|
||||
# RAM total (/proc/meminfo)
|
||||
try:
|
||||
meminfo = open("/proc/meminfo").read()
|
||||
m = re.search(r"^MemTotal:\s+(\d+)\s+kB", meminfo, flags=re.M)
|
||||
if m:
|
||||
total_bytes = int(m.group(1)) * 1024
|
||||
info["Total RAM"] = human_bytes(total_bytes)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def collect_gpu() -> Dict[str, str]:
|
||||
"""
|
||||
Use NVML to enumerate GPUs and select the 'newer' one:
|
||||
1) Prefer a device whose name matches /4090/i
|
||||
2) Else highest CUDA compute capability (major, minor), tiebreaker by total memory
|
||||
Also reports driver version and CUDA driver version.
|
||||
"""
|
||||
pynvml = _import_nvml()
|
||||
pynvml.nvmlInit()
|
||||
try:
|
||||
count = pynvml.nvmlDeviceGetCount()
|
||||
if count < 1:
|
||||
return {"Error": "No NVIDIA GPUs detected by NVML."}
|
||||
|
||||
devices = []
|
||||
for i in range(count):
|
||||
h = pynvml.nvmlDeviceGetHandleByIndex(i)
|
||||
|
||||
# name can be bytes or str depending on wheel; normalize
|
||||
raw_name = pynvml.nvmlDeviceGetName(h)
|
||||
name = _to_text(raw_name)
|
||||
|
||||
mem_info = pynvml.nvmlDeviceGetMemoryInfo(h)
|
||||
total_mem = getattr(mem_info, "total", 0)
|
||||
|
||||
# compute capability may not exist on very old drivers
|
||||
try:
|
||||
maj, minr = pynvml.nvmlDeviceGetCudaComputeCapability(h)
|
||||
except Exception:
|
||||
maj, minr = (0, 0)
|
||||
|
||||
devices.append(
|
||||
{
|
||||
"index": i,
|
||||
"handle": h,
|
||||
"name": name,
|
||||
"mem": total_mem,
|
||||
"cc": (maj, minr),
|
||||
}
|
||||
)
|
||||
|
||||
# Prefer explicit "4090"
|
||||
pick = next(
|
||||
(d for d in devices if re.search(r"4090", d["name"], flags=re.I)), None
|
||||
)
|
||||
if pick is None:
|
||||
# Highest compute capability, then largest memory
|
||||
devices.sort(key=lambda x: (x["cc"][0], x["cc"][1], x["mem"]), reverse=True)
|
||||
pick = devices[0]
|
||||
|
||||
# Driver version and CUDA driver version can be bytes or str
|
||||
drv_raw = pynvml.nvmlSystemGetDriverVersion()
|
||||
drv = _to_text(drv_raw)
|
||||
|
||||
# CUDA driver version (integer like 12040 -> 12.4)
|
||||
cuda_drv_ver = ""
|
||||
try:
|
||||
v = pynvml.nvmlSystemGetCudaDriverVersion_v2()
|
||||
except Exception:
|
||||
v = pynvml.nvmlSystemGetCudaDriverVersion()
|
||||
try:
|
||||
major = v // 1000
|
||||
minor = (v % 1000) // 10
|
||||
patch = v % 10
|
||||
cuda_drv_ver = f"{major}.{minor}.{patch}" if patch else f"{major}.{minor}"
|
||||
except Exception:
|
||||
cuda_drv_ver = ""
|
||||
|
||||
gpu_info = {
|
||||
"Selected GPU Name": pick["name"],
|
||||
"Selected GPU Memory": human_bytes(pick["mem"]),
|
||||
"Selected GPU Compute Capability": f"{pick['cc'][0]}.{pick['cc'][1]}",
|
||||
"NVIDIA Driver Version": drv,
|
||||
"CUDA (Driver) Version": cuda_drv_ver,
|
||||
}
|
||||
return gpu_info
|
||||
finally:
|
||||
pynvml.nvmlShutdown()
|
||||
|
||||
|
||||
def summarize_numpy_build_config() -> str:
|
||||
"""
|
||||
Capture numpy.__config__.show() and try to extract the BLAS/LAPACK backend line(s).
|
||||
"""
|
||||
import numpy as np
|
||||
from io import StringIO
|
||||
import sys as _sys
|
||||
|
||||
buf = StringIO()
|
||||
_stdout = _sys.stdout
|
||||
try:
|
||||
_sys.stdout = buf
|
||||
np.__config__.show()
|
||||
finally:
|
||||
_sys.stdout = _stdout
|
||||
txt = buf.getvalue()
|
||||
|
||||
# Heuristic: capture lines mentioning MKL, OpenBLAS, BLIS, LAPACK
|
||||
lines = [
|
||||
l
|
||||
for l in txt.splitlines()
|
||||
if re.search(r"(MKL|OpenBLAS|BLAS|LAPACK|BLIS)", l, re.I)
|
||||
]
|
||||
if not lines:
|
||||
# fall back to first ~12 lines
|
||||
lines = txt.splitlines()[:12]
|
||||
# Keep it compact
|
||||
return "\n".join(lines[:20]).strip()
|
||||
|
||||
|
||||
def collect_software() -> Dict[str, str]:
|
||||
info: Dict[str, str] = {}
|
||||
import sys as _sys
|
||||
import torch
|
||||
import numpy as _np
|
||||
import scipy as _sp
|
||||
import sklearn as _sk
|
||||
|
||||
info["Python"] = _sys.version.split()[0]
|
||||
|
||||
# PyTorch + built CUDA/cuDNN + visible GPUs
|
||||
info["PyTorch"] = torch.__version__
|
||||
info["PyTorch Built CUDA"] = getattr(torch.version, "cuda", "") or ""
|
||||
try:
|
||||
cudnn_build = torch.backends.cudnn.version() # integer
|
||||
info["cuDNN (PyTorch build)"] = str(cudnn_build) if cudnn_build else ""
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# scikit-learn
|
||||
info["scikit-learn"] = _sk.__version__
|
||||
|
||||
# NumPy / SciPy + build config
|
||||
info["NumPy"] = _np.__version__
|
||||
info["SciPy"] = _sp.__version__
|
||||
info["NumPy Build Config"] = summarize_numpy_build_config()
|
||||
|
||||
return info
|
||||
|
||||
|
||||
# -------------------- Main --------------------
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(
|
||||
description="Generate LaTeX table of system/software environment for thesis (Linux + NVIDIA)."
|
||||
)
|
||||
ap.add_argument(
|
||||
"--output", "-o", type=str, help="Write LaTeX to this file instead of stdout."
|
||||
)
|
||||
ap.add_argument(
|
||||
"--caption", type=str, default="Computational Environment (Hardware & Software)"
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
errs = preflight()
|
||||
if errs:
|
||||
msg = (
|
||||
"Preflight check failed:\n- "
|
||||
+ "\n- ".join(errs)
|
||||
+ "\n"
|
||||
+ "Please install missing components and re-run."
|
||||
)
|
||||
print(msg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
sections: List[Tuple[str, Dict[str, str]]] = []
|
||||
sections.append(("System", collect_system()))
|
||||
sections.append(("GPU (Selected Newer Device)", collect_gpu()))
|
||||
sections.append(("Software Environment", collect_software()))
|
||||
|
||||
latex = latex_table(sections, caption=args.caption)
|
||||
|
||||
if args.output:
|
||||
with open(args.output, "w", encoding="utf-8") as f:
|
||||
f.write(latex)
|
||||
print(f"Wrote LaTeX to: {args.output}")
|
||||
else:
|
||||
print(latex)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -21,6 +21,7 @@ def load_dataset(
|
||||
k_fold_num: int = None,
|
||||
num_known_normal: int = 0,
|
||||
num_known_outlier: int = 0,
|
||||
split: float = 0.7,
|
||||
):
|
||||
"""Loads the dataset."""
|
||||
|
||||
@@ -49,6 +50,7 @@ def load_dataset(
|
||||
k_fold_num=k_fold_num,
|
||||
num_known_normal=num_known_normal,
|
||||
num_known_outlier=num_known_outlier,
|
||||
split=split,
|
||||
)
|
||||
|
||||
if dataset_name == "subtersplit":
|
||||
|
||||
@@ -152,6 +152,12 @@ from utils.visualization.plot_images_grid import plot_images_grid
|
||||
default=0.001,
|
||||
help="Initial learning rate for Deep SAD network training. Default=0.001",
|
||||
)
|
||||
@click.option(
|
||||
"--train_test_split",
|
||||
type=float,
|
||||
default=0.7,
|
||||
help="Ratio of training data in the train-test split (default: 0.7).",
|
||||
)
|
||||
@click.option("--n_epochs", type=int, default=50, help="Number of epochs to train.")
|
||||
@click.option(
|
||||
"--lr_milestone",
|
||||
@@ -307,6 +313,7 @@ def main(
|
||||
seed,
|
||||
optimizer_name,
|
||||
lr,
|
||||
train_test_split,
|
||||
n_epochs,
|
||||
lr_milestone,
|
||||
batch_size,
|
||||
@@ -416,6 +423,7 @@ def main(
|
||||
k_fold_num=k_fold_num,
|
||||
num_known_normal=num_known_normal,
|
||||
num_known_outlier=num_known_outlier,
|
||||
split=train_test_split,
|
||||
)
|
||||
# Log random sample of known anomaly classes if more than 1 class
|
||||
if n_known_outlier_classes > 1:
|
||||
@@ -694,6 +702,7 @@ def main(
|
||||
ratio_pollution,
|
||||
random_state=np.random.RandomState(cfg.settings["seed"]),
|
||||
k_fold_num=k_fold_num,
|
||||
split=train_test_split,
|
||||
)
|
||||
|
||||
# Set up k-fold passes
|
||||
@@ -804,12 +813,14 @@ def main(
|
||||
k_fold_num=cfg.settings["k_fold_num"],
|
||||
num_known_normal=cfg.settings["num_known_normal"],
|
||||
num_known_outlier=cfg.settings["num_known_outlier"],
|
||||
split=train_test_split,
|
||||
)
|
||||
|
||||
train_passes = (
|
||||
range(cfg.settings["k_fold_num"]) if cfg.settings["k_fold"] else [None]
|
||||
)
|
||||
|
||||
retest_autoencoder = False
|
||||
retest_isoforest = True
|
||||
retest_ocsvm = True
|
||||
retest_deepsad = True
|
||||
@@ -865,6 +876,25 @@ def main(
|
||||
k_fold_idx=fold_idx,
|
||||
)
|
||||
|
||||
if retest_autoencoder:
|
||||
# Initialize DeepSAD model and set neural network phi
|
||||
deepSAD = DeepSAD(cfg.settings["latent_space_dim"], cfg.settings["eta"])
|
||||
deepSAD.set_network(cfg.settings["net_name"])
|
||||
deepSAD.load_model(
|
||||
model_path=ae_model_path, load_ae=True, map_location=device
|
||||
)
|
||||
logger.info("Loading model from %s." % load_model)
|
||||
# Save pretraining results
|
||||
if fold_idx is None:
|
||||
deepSAD.save_ae_results(
|
||||
export_pkl=load_model / "results_ae_retest.pkl"
|
||||
)
|
||||
else:
|
||||
deepSAD.save_ae_results(
|
||||
export_pkl=load_model / f"results_ae_retest_{fold_idx}.pkl"
|
||||
)
|
||||
del deepSAD
|
||||
|
||||
# Initialize DeepSAD model and set neural network phi
|
||||
if retest_deepsad:
|
||||
deepSAD = DeepSAD(cfg.settings["latent_space_dim"], cfg.settings["eta"])
|
||||
|
||||
BIN
thesis/Main.pdf
BIN
thesis/Main.pdf
Binary file not shown.
113
thesis/Main.tex
113
thesis/Main.tex
@@ -71,6 +71,11 @@
|
||||
\usepackage[colorinlistoftodos]{todonotes}
|
||||
%\usepackage[disable]{todonotes}
|
||||
\usepackage{makecell}
|
||||
\usepackage{longtable}
|
||||
\usepackage{array}
|
||||
\usepackage{tabularx}
|
||||
|
||||
\newcolumntype{Y}{>{\centering\arraybackslash}X}
|
||||
|
||||
\DeclareRobustCommand{\threadtodo}[4]{%
|
||||
\todo[inline,
|
||||
@@ -1251,6 +1256,114 @@ Table~\ref{tab:exp_grid} summarizes the full experiment matrix.
|
||||
{table of hardware and of how long different trainings took}
|
||||
{experiment setup understood $\rightarrow$ what were the experiments' results}
|
||||
|
||||
|
||||
\begin{table}[p]
|
||||
\centering
|
||||
\caption{Computational Environment (Hardware \& Software)} \label{tab:system_setup}
|
||||
\begin{tabular}{p{0.34\linewidth} p{0.62\linewidth}}
|
||||
\toprule
|
||||
\textbf{Item} & \textbf{Details} \\
|
||||
\midrule
|
||||
\multicolumn{2}{l}{\textbf{System}} \\
|
||||
Operating System & \ttfamily NixOS 25.11 (Xantusia) \\
|
||||
Kernel & \ttfamily 6.12.45 \\
|
||||
Architecture & \ttfamily x86\_64 \\
|
||||
CPU Model & \ttfamily AMD Ryzen 5 3600 6-Core Processor \\
|
||||
CPU Cores (physical) & \ttfamily 6 × 1 \\
|
||||
CPU Threads (logical) & \ttfamily 12 \\
|
||||
CPU Base Frequency & \ttfamily 2200 MHz \\
|
||||
CPU Max Frequency & \ttfamily 4208 MHz \\
|
||||
Total RAM & \ttfamily 31.29 GiB \\
|
||||
\addlinespace
|
||||
\multicolumn{2}{l}{\textbf{GPU}} \\
|
||||
GPU Name & \ttfamily NVIDIA GeForce RTX 2070 SUPER \\
|
||||
GPU Memory & \ttfamily 8.00 GiB \\
|
||||
GPU Compute Capability & \ttfamily 7.5 \\
|
||||
NVIDIA Driver Version & \ttfamily 570.181 \\
|
||||
CUDA (Driver) Version & \ttfamily 12.8 \\
|
||||
\addlinespace
|
||||
\multicolumn{2}{l}{\textbf{Software Environment}} \\
|
||||
Python & \ttfamily 3.12.11 \\
|
||||
PyTorch & \ttfamily 2.7.1+cu128 \\
|
||||
PyTorch Built CUDA & \ttfamily 12.8 \\
|
||||
cuDNN (PyTorch build) & \ttfamily 91100 \\
|
||||
scikit-learn & \ttfamily 1.7.0 \\
|
||||
NumPy & \ttfamily 2.3.1 \\
|
||||
SciPy & \ttfamily 1.16.0 \\
|
||||
NumPy Build Config & \begin{minipage}[t]{\linewidth}\ttfamily\small blas:
|
||||
name: blas
|
||||
openblas configuration: unknown
|
||||
pc file directory: /nix/store/x19i4pf7zs1pp96mikj8azyn6v891i33-blas-3-dev/lib/pkgconfig
|
||||
lapack:
|
||||
name: lapack
|
||||
openblas configuration: unknown
|
||||
pc file directory: /nix/store/g819v6ri55f2gdczsi8s8bljkh0lkgwb-lapack-3-dev/lib/pkgconfig\end{minipage} \\
|
||||
\addlinespace
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{table}
|
||||
|
||||
\begin{table}
|
||||
\centering
|
||||
\caption{Autoencoder pretraining runtime (seconds): mean ± std across folds.}
|
||||
\label{tab:ae_pretrain_runtimes}
|
||||
\begin{tabularx}{\textwidth}{cYY}
|
||||
\toprule
|
||||
& Autoencoder Efficient & Autoencoder LeNet \\
|
||||
Latent Dim. & & \\
|
||||
\midrule
|
||||
32 & 1175.03 ± 35.87 s & 384.90 ± 34.59 s \\
|
||||
64 & 1212.53 ± 35.76 s & 398.22 ± 41.25 s \\
|
||||
128 & 1240.86 ± 11.51 s & 397.98 ± 33.43 s \\
|
||||
256 & 1169.72 ± 33.26 s & 399.40 ± 38.20 s \\
|
||||
512 & 1173.34 ± 34.99 s & 430.31 ± 38.02 s \\
|
||||
768 & 1204.45 ± 37.52 s & 436.49 ± 37.13 s \\
|
||||
1024 & 1216.79 ± 34.82 s & 411.69 ± 34.82 s \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
\end{table}
|
||||
|
||||
\begin{table}
|
||||
\centering
|
||||
\caption{Training runtime: total seconds (mean ± std). DeepSAD cells also show \textit{seconds per epoch} in parentheses.}
|
||||
\label{tab:train_runtimes_compact}
|
||||
\begin{tabularx}{\textwidth}{crrrr}
|
||||
\toprule
|
||||
& DeepSAD LeNet & DeepSAD Efficient & IsoForest & OCSVM \\
|
||||
Latent Dim. & & & & \\
|
||||
\midrule
|
||||
32 & 765.37 ± 91.74 s & 1026.18 ± 84.13 s & 0.55 ± 0.02 s & 1.07 ± 00.29 s \\
|
||||
64 & 815.88 ± 93.07 s & 1124.48 ± 60.84 s & 0.55 ± 0.02 s & 1.98 ± 01.57 s \\
|
||||
128 & 828.53 ± 63.00 s & 1164.94 ± 02.13 s & 0.55 ± 0.02 s & 3.17 ± 02.63 s \\
|
||||
256 & 794.54 ± 97.04 s & 986.88 ± 82.98 s & 0.55 ± 0.02 s & 12.81 ± 14.19 s \\
|
||||
512 & 806.63 ± 99.83 s & 998.23 ± 80.34 s & 0.55 ± 0.02 s & 22.76 ± 23.52 s \\
|
||||
768 & 818.56 ± 86.38 s & 1053.64 ± 78.72 s & 0.55 ± 0.02 s & 14.24 ± 01.21 s \\
|
||||
1024 & 770.05 ± 86.22 s & 1054.92 ± 87.49 s & 0.55 ± 0.02 s & 28.20 ± 24.04 s \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
\end{table}
|
||||
|
||||
|
||||
\begin{table}
|
||||
\centering
|
||||
\caption{Inference latency (ms/sample): mean ± std across folds; baselines collapsed across networks and semi-labeling.}
|
||||
\label{tab:inference_latency_compact}
|
||||
\begin{tabularx}{\textwidth}{cYcYY}
|
||||
\toprule
|
||||
& DeepSAD LeNet & DeepSAD Efficient & IsoForest & OCSVM \\
|
||||
Latent Dim. & & & & \\
|
||||
\midrule
|
||||
32 & 0.31 ± 0.04 ms & 0.36 ± 0.05 ms & 0.02 ± 0.00 ms & 0.07 ± 0.02 ms \\
|
||||
64 & 0.33 ± 0.06 ms & 0.43 ± 0.04 ms & 0.02 ± 0.00 ms & 0.10 ± 0.06 ms \\
|
||||
128 & 0.31 ± 0.04 ms & 0.45 ± 0.02 ms & 0.02 ± 0.00 ms & 0.16 ± 0.09 ms \\
|
||||
256 & 0.30 ± 0.04 ms & 0.33 ± 0.02 ms & 0.02 ± 0.00 ms & 0.30 ± 0.21 ms \\
|
||||
512 & 0.32 ± 0.04 ms & 0.33 ± 0.02 ms & 0.02 ± 0.00 ms & 0.63 ± 0.65 ms \\
|
||||
768 & 0.33 ± 0.03 ms & 0.41 ± 0.06 ms & 0.02 ± 0.00 ms & 0.39 ± 0.07 ms \\
|
||||
1024 & 0.27 ± 0.02 ms & 0.39 ± 0.05 ms & 0.02 ± 0.00 ms & 0.94 ± 0.98 ms \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
\end{table}
|
||||
|
||||
\newchapter{results_discussion}{Results and Discussion}
|
||||
\newsection{results}{Results}
|
||||
\todo[inline]{some results, ROC curves, for both global and local}
|
||||
|
||||
@@ -2,7 +2,10 @@ from pathlib import Path
|
||||
|
||||
import polars as pl
|
||||
|
||||
from load_results import load_pretraining_results_dataframe, load_results_dataframe
|
||||
from plot_scripts.load_results import (
|
||||
load_pretraining_results_dataframe,
|
||||
load_results_dataframe,
|
||||
)
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@@ -1,651 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import pickle
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
import polars as pl
|
||||
from polars.testing import assert_frame_equal
|
||||
|
||||
from diff_df import recursive_diff_frames
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Config you can tweak
|
||||
# ------------------------------------------------------------
|
||||
MODELS = ["deepsad", "isoforest", "ocsvm"]
|
||||
EVALS = ["exp_based", "manual_based"]
|
||||
|
||||
SCHEMA_STATIC = {
|
||||
# identifiers / dims
|
||||
"network": pl.Utf8, # e.g. "LeNet", "efficient"
|
||||
"latent_dim": pl.Int32,
|
||||
"semi_normals": pl.Int32,
|
||||
"semi_anomalous": pl.Int32,
|
||||
"model": pl.Utf8, # "deepsad" | "isoforest" | "ocsvm"
|
||||
"eval": pl.Utf8, # "exp_based" | "manual_based"
|
||||
"fold": pl.Int32,
|
||||
# metrics
|
||||
"auc": pl.Float64,
|
||||
"ap": pl.Float64,
|
||||
# per-sample scores: list of (idx, label, score)
|
||||
"scores": pl.List(
|
||||
pl.Struct(
|
||||
{
|
||||
"sample_idx": pl.Int32, # dataloader idx
|
||||
"orig_label": pl.Int8, # {-1,0,1}
|
||||
"score": pl.Float64, # anomaly score
|
||||
}
|
||||
)
|
||||
),
|
||||
# curves (normalized)
|
||||
"roc_curve": pl.Struct(
|
||||
{
|
||||
"fpr": pl.List(pl.Float64),
|
||||
"tpr": pl.List(pl.Float64),
|
||||
"thr": pl.List(pl.Float64),
|
||||
}
|
||||
),
|
||||
"prc_curve": pl.Struct(
|
||||
{
|
||||
"precision": pl.List(pl.Float64),
|
||||
"recall": pl.List(pl.Float64),
|
||||
"thr": pl.List(pl.Float64), # may be len(precision)-1
|
||||
}
|
||||
),
|
||||
# deepsad-only per-eval arrays (None for other models)
|
||||
"sample_indices": pl.List(pl.Int32),
|
||||
"sample_labels": pl.List(pl.Int8),
|
||||
"valid_mask": pl.List(pl.Boolean),
|
||||
# timings / housekeeping
|
||||
"train_time": pl.Float64,
|
||||
"test_time": pl.Float64,
|
||||
"folder": pl.Utf8,
|
||||
"k_fold_num": pl.Int32,
|
||||
"config_json": pl.Utf8, # full config.json as string (for reference)
|
||||
}
|
||||
|
||||
# Pretraining-only (AE) schema
|
||||
# Pretraining-only (AE) schema — lighter defaults
|
||||
PRETRAIN_SCHEMA = {
|
||||
# identifiers / dims
|
||||
"network": pl.Utf8, # e.g. "LeNet", "efficient"
|
||||
"latent_dim": pl.Int32,
|
||||
"semi_normals": pl.Int32,
|
||||
"semi_anomalous": pl.Int32,
|
||||
"model": pl.Utf8, # always "ae"
|
||||
"fold": pl.Int32,
|
||||
"split": pl.Utf8, # "train" | "test"
|
||||
# timings and optimization
|
||||
"time": pl.Float64,
|
||||
"loss": pl.Float64,
|
||||
# per-sample arrays (as lists)
|
||||
"indices": pl.List(pl.Int32),
|
||||
"labels_exp_based": pl.List(pl.Int32),
|
||||
"labels_manual_based": pl.List(pl.Int32),
|
||||
"semi_targets": pl.List(pl.Int32),
|
||||
"file_ids": pl.List(pl.Int32),
|
||||
"frame_ids": pl.List(pl.Int32),
|
||||
"scores": pl.List(pl.Float32), # <— use Float32 to match source and save space
|
||||
# file id -> name mapping from the result dict
|
||||
"file_names": pl.List(pl.Struct({"file_id": pl.Int32, "name": pl.Utf8})),
|
||||
# housekeeping
|
||||
"folder": pl.Utf8,
|
||||
"k_fold_num": pl.Int32,
|
||||
"config_json": pl.Utf8, # full config.json as string (for reference)
|
||||
}
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Helpers: curve/scores normalizers (tuples/ndarrays -> dict/list)
|
||||
# ------------------------------------------------------------
|
||||
def _tolist(x):
|
||||
if x is None:
|
||||
return None
|
||||
if isinstance(x, np.ndarray):
|
||||
return x.tolist()
|
||||
if isinstance(x, (list, tuple)):
|
||||
return list(x)
|
||||
# best-effort scalar wrap
|
||||
try:
|
||||
return [x]
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def normalize_float_list(a) -> Optional[List[float]]:
|
||||
if a is None:
|
||||
return None
|
||||
if isinstance(a, np.ndarray):
|
||||
a = a.tolist()
|
||||
return [None if x is None else float(x) for x in a]
|
||||
|
||||
|
||||
def normalize_file_names(d) -> Optional[List[dict]]:
|
||||
"""
|
||||
Convert the 'file_names' dict (keys like numpy.int64 -> str) to a
|
||||
list[ {file_id:int, name:str} ], sorted by file_id.
|
||||
"""
|
||||
if not isinstance(d, dict):
|
||||
return None
|
||||
out: List[dict] = []
|
||||
for k, v in d.items():
|
||||
try:
|
||||
file_id = int(k)
|
||||
except Exception:
|
||||
# keys are printed as np.int64 in the structure; best-effort cast
|
||||
continue
|
||||
out.append({"file_id": file_id, "name": str(v)})
|
||||
out.sort(key=lambda x: x["file_id"])
|
||||
return out
|
||||
|
||||
|
||||
def normalize_roc(obj: Any) -> Optional[dict]:
|
||||
if obj is None:
|
||||
return None
|
||||
fpr = tpr = thr = None
|
||||
if isinstance(obj, (tuple, list)):
|
||||
if len(obj) >= 2:
|
||||
fpr, tpr = _tolist(obj[0]), _tolist(obj[1])
|
||||
if len(obj) >= 3:
|
||||
thr = _tolist(obj[2])
|
||||
elif isinstance(obj, dict):
|
||||
fpr = _tolist(obj.get("fpr") or obj.get("x"))
|
||||
tpr = _tolist(obj.get("tpr") or obj.get("y"))
|
||||
thr = _tolist(obj.get("thr") or obj.get("thresholds"))
|
||||
else:
|
||||
return None
|
||||
if fpr is None or tpr is None:
|
||||
return None
|
||||
return {"fpr": fpr, "tpr": tpr, "thr": thr}
|
||||
|
||||
|
||||
def normalize_prc(obj: Any) -> Optional[dict]:
|
||||
if obj is None:
|
||||
return None
|
||||
precision = recall = thr = None
|
||||
if isinstance(obj, (tuple, list)):
|
||||
if len(obj) >= 2:
|
||||
precision, recall = _tolist(obj[0]), _tolist(obj[1])
|
||||
if len(obj) >= 3:
|
||||
thr = _tolist(obj[2])
|
||||
elif isinstance(obj, dict):
|
||||
precision = _tolist(obj.get("precision") or obj.get("y"))
|
||||
recall = _tolist(obj.get("recall") or obj.get("x"))
|
||||
thr = _tolist(obj.get("thr") or obj.get("thresholds"))
|
||||
else:
|
||||
return None
|
||||
if precision is None or recall is None:
|
||||
return None
|
||||
return {"precision": precision, "recall": recall, "thr": thr}
|
||||
|
||||
|
||||
def normalize_scores_to_struct(seq) -> Optional[List[dict]]:
|
||||
"""
|
||||
Input: list of (idx, label, score) tuples (as produced in your test()).
|
||||
Output: list of dicts with keys sample_idx, orig_label, score.
|
||||
"""
|
||||
if seq is None:
|
||||
return None
|
||||
if isinstance(seq, np.ndarray):
|
||||
seq = seq.tolist()
|
||||
if not isinstance(seq, (list, tuple)):
|
||||
return None
|
||||
out: List[dict] = []
|
||||
for item in seq:
|
||||
if isinstance(item, (list, tuple)) and len(item) >= 3:
|
||||
idx, lab, sc = item[0], item[1], item[2]
|
||||
out.append(
|
||||
{
|
||||
"sample_idx": None if idx is None else int(idx),
|
||||
"orig_label": None if lab is None else int(lab),
|
||||
"score": None if sc is None else float(sc),
|
||||
}
|
||||
)
|
||||
else:
|
||||
# fallback: single numeric -> score
|
||||
sc = (
|
||||
float(item)
|
||||
if isinstance(item, (int, float, np.integer, np.floating))
|
||||
else None
|
||||
)
|
||||
out.append({"sample_idx": None, "orig_label": None, "score": sc})
|
||||
return out
|
||||
|
||||
|
||||
def normalize_int_list(a) -> Optional[List[int]]:
|
||||
if a is None:
|
||||
return None
|
||||
if isinstance(a, np.ndarray):
|
||||
a = a.tolist()
|
||||
return list(a)
|
||||
|
||||
|
||||
def normalize_bool_list(a) -> Optional[List[bool]]:
|
||||
if a is None:
|
||||
return None
|
||||
if isinstance(a, np.ndarray):
|
||||
a = a.tolist()
|
||||
return [bool(x) for x in a]
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Low-level: read one experiment folder
|
||||
# ------------------------------------------------------------
|
||||
def read_config(exp_dir: Path) -> dict:
|
||||
cfg = exp_dir / "config.json"
|
||||
with cfg.open("r") as f:
|
||||
c = json.load(f)
|
||||
if not c.get("k_fold"):
|
||||
raise ValueError(f"{exp_dir.name}: not trained as k-fold")
|
||||
return c
|
||||
|
||||
|
||||
def read_pickle(p: Path) -> Any:
|
||||
with p.open("rb") as f:
|
||||
return pickle.load(f)
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Extractors for each model
|
||||
# ------------------------------------------------------------
|
||||
|
||||
counting = {
|
||||
(label_method, eval_method): []
|
||||
for label_method in ["exp_based", "manual_based"]
|
||||
for eval_method in ["roc", "prc"]
|
||||
}
|
||||
|
||||
|
||||
def rows_from_deepsad(data: dict, evals: List[str]) -> Dict[str, dict]:
|
||||
"""
|
||||
deepsad under data['test'][eval], with extra per-eval arrays and AP present.
|
||||
"""
|
||||
out: Dict[str, dict] = {}
|
||||
test = data.get("test", {})
|
||||
for ev in evals:
|
||||
evd = test.get(ev)
|
||||
if not isinstance(evd, dict):
|
||||
continue
|
||||
counting[(ev, "roc")].append(len(evd["roc"][0]))
|
||||
counting[(ev, "prc")].append(len(evd["prc"][0]))
|
||||
out[ev] = {
|
||||
"auc": float(evd["auc"])
|
||||
if "auc" in evd and evd["auc"] is not None
|
||||
else None,
|
||||
"roc": normalize_roc(evd.get("roc")),
|
||||
"prc": normalize_prc(evd.get("prc")),
|
||||
"ap": float(evd["ap"]) if "ap" in evd and evd["ap"] is not None else None,
|
||||
"scores": normalize_scores_to_struct(evd.get("scores")),
|
||||
"sample_indices": normalize_int_list(evd.get("indices")),
|
||||
"sample_labels": normalize_int_list(evd.get("labels")),
|
||||
"valid_mask": normalize_bool_list(evd.get("valid_mask")),
|
||||
"train_time": data.get("train", {}).get("time"),
|
||||
"test_time": test.get("time"),
|
||||
}
|
||||
return out
|
||||
|
||||
|
||||
def rows_from_isoforest(data: dict, evals: List[str]) -> Dict[str, dict]:
|
||||
"""
|
||||
Keys: test_auc_<eval>, test_roc_<eval>, test_prc_<eval>, test_ap_<eval>, test_scores_<eval>.
|
||||
"""
|
||||
out: Dict[str, dict] = {}
|
||||
for ev in evals:
|
||||
auc = data.get(f"test_auc_{ev}")
|
||||
if auc is None:
|
||||
continue
|
||||
out[ev] = {
|
||||
"auc": float(auc),
|
||||
"roc": normalize_roc(data.get(f"test_roc_{ev}")),
|
||||
"prc": normalize_prc(data.get(f"test_prc_{ev}")),
|
||||
"ap": float(data.get(f"test_ap_{ev}"))
|
||||
if data.get(f"test_ap_{ev}") is not None
|
||||
else None,
|
||||
"scores": normalize_scores_to_struct(data.get(f"test_scores_{ev}")),
|
||||
"sample_indices": None,
|
||||
"sample_labels": None,
|
||||
"valid_mask": None,
|
||||
"train_time": data.get("train_time"),
|
||||
"test_time": data.get("test_time"),
|
||||
}
|
||||
return out
|
||||
|
||||
|
||||
def rows_from_ocsvm_default(data: dict, evals: List[str]) -> Dict[str, dict]:
|
||||
"""
|
||||
Default OCSVM only (ignore linear variant entirely).
|
||||
"""
|
||||
out: Dict[str, dict] = {}
|
||||
for ev in evals:
|
||||
auc = data.get(f"test_auc_{ev}")
|
||||
if auc is None:
|
||||
continue
|
||||
out[ev] = {
|
||||
"auc": float(auc),
|
||||
"roc": normalize_roc(data.get(f"test_roc_{ev}")),
|
||||
"prc": normalize_prc(data.get(f"test_prc_{ev}")),
|
||||
"ap": float(data.get(f"test_ap_{ev}"))
|
||||
if data.get(f"test_ap_{ev}") is not None
|
||||
else None,
|
||||
"scores": normalize_scores_to_struct(data.get(f"test_scores_{ev}")),
|
||||
"sample_indices": None,
|
||||
"sample_labels": None,
|
||||
"valid_mask": None,
|
||||
"train_time": data.get("train_time"),
|
||||
"test_time": data.get("test_time"),
|
||||
}
|
||||
return out
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Build the Polars DataFrame
|
||||
# ------------------------------------------------------------
|
||||
def load_results_dataframe(root: Path, allow_cache: bool = True) -> pl.DataFrame:
|
||||
"""
|
||||
Walks experiment subdirs under `root`. For each (model, fold) it adds rows:
|
||||
Columns (SCHEMA_STATIC):
|
||||
network, latent_dim, semi_normals, semi_anomalous,
|
||||
model, eval, fold,
|
||||
auc, ap, scores{sample_idx,orig_label,score},
|
||||
roc_curve{fpr,tpr,thr}, prc_curve{precision,recall,thr},
|
||||
sample_indices, sample_labels, valid_mask,
|
||||
train_time, test_time,
|
||||
folder, k_fold_num
|
||||
"""
|
||||
if allow_cache:
|
||||
cache = root / "results_cache.parquet"
|
||||
if cache.exists():
|
||||
try:
|
||||
df = pl.read_parquet(cache)
|
||||
print(f"[info] loaded cached results frame from {cache}")
|
||||
return df
|
||||
except Exception as e:
|
||||
print(f"[warn] failed to load cache {cache}: {e}")
|
||||
|
||||
rows: List[dict] = []
|
||||
|
||||
exp_dirs = [p for p in root.iterdir() if p.is_dir()]
|
||||
for exp_dir in sorted(exp_dirs):
|
||||
try:
|
||||
cfg = read_config(exp_dir)
|
||||
cfg_json = json.dumps(cfg, sort_keys=True)
|
||||
except Exception as e:
|
||||
print(f"[warn] skipping {exp_dir.name}: {e}")
|
||||
continue
|
||||
|
||||
network = cfg.get("net_name")
|
||||
latent_dim = int(cfg.get("latent_space_dim"))
|
||||
semi_normals = int(cfg.get("num_known_normal"))
|
||||
semi_anomalous = int(cfg.get("num_known_outlier"))
|
||||
k = int(cfg.get("k_fold_num"))
|
||||
|
||||
for model in MODELS:
|
||||
for fold in range(k):
|
||||
pkl = exp_dir / f"results_{model}_{fold}.pkl"
|
||||
if not pkl.exists():
|
||||
continue
|
||||
|
||||
try:
|
||||
data = read_pickle(pkl)
|
||||
except Exception as e:
|
||||
print(f"[warn] failed to read {pkl.name}: {e}")
|
||||
continue
|
||||
|
||||
if model == "deepsad":
|
||||
per_eval = rows_from_deepsad(data, EVALS) # eval -> dict
|
||||
elif model == "isoforest":
|
||||
per_eval = rows_from_isoforest(data, EVALS) # eval -> dict
|
||||
elif model == "ocsvm":
|
||||
per_eval = rows_from_ocsvm_default(data, EVALS) # eval -> dict
|
||||
else:
|
||||
per_eval = {}
|
||||
|
||||
for ev, vals in per_eval.items():
|
||||
rows.append(
|
||||
{
|
||||
"network": network,
|
||||
"latent_dim": latent_dim,
|
||||
"semi_normals": semi_normals,
|
||||
"semi_anomalous": semi_anomalous,
|
||||
"model": model,
|
||||
"eval": ev,
|
||||
"fold": fold,
|
||||
"auc": vals["auc"],
|
||||
"ap": vals["ap"],
|
||||
"scores": vals["scores"],
|
||||
"roc_curve": vals["roc"],
|
||||
"prc_curve": vals["prc"],
|
||||
"sample_indices": vals.get("sample_indices"),
|
||||
"sample_labels": vals.get("sample_labels"),
|
||||
"valid_mask": vals.get("valid_mask"),
|
||||
"train_time": vals["train_time"],
|
||||
"test_time": vals["test_time"],
|
||||
"folder": str(exp_dir),
|
||||
"k_fold_num": k,
|
||||
"config_json": cfg_json,
|
||||
}
|
||||
)
|
||||
|
||||
# If empty, return a typed empty frame
|
||||
if not rows:
|
||||
return pl.DataFrame(schema=SCHEMA_STATIC)
|
||||
|
||||
df = pl.DataFrame(rows, schema=SCHEMA_STATIC)
|
||||
|
||||
# Cast to efficient dtypes (categoricals etc.) – no extra sanitation
|
||||
df = df.with_columns(
|
||||
pl.col("network", "model", "eval").cast(pl.Categorical),
|
||||
pl.col(
|
||||
"latent_dim", "semi_normals", "semi_anomalous", "fold", "k_fold_num"
|
||||
).cast(pl.Int32),
|
||||
pl.col("auc", "ap", "train_time", "test_time").cast(pl.Float64),
|
||||
# NOTE: no cast on 'scores' here; it's already List(Struct) per schema.
|
||||
)
|
||||
|
||||
if allow_cache:
|
||||
try:
|
||||
df.write_parquet(cache)
|
||||
print(f"[info] cached results frame to {cache}")
|
||||
except Exception as e:
|
||||
print(f"[warn] failed to write cache {cache}: {e}")
|
||||
|
||||
return df
|
||||
|
||||
|
||||
def load_pretraining_results_dataframe(
|
||||
root: Path,
|
||||
allow_cache: bool = True,
|
||||
include_train: bool = False, # <— default: store only TEST to keep cache tiny
|
||||
keep_file_names: bool = False, # <— drop file_names by default; they’re repeated
|
||||
parquet_compression: str = "zstd",
|
||||
parquet_compression_level: int = 7, # <— stronger compression than default
|
||||
) -> pl.DataFrame:
|
||||
"""
|
||||
Loads only AE pretraining results: files named `results_ae_<fold>.pkl`.
|
||||
Produces one row per (experiment, fold, split). By default we:
|
||||
- include only the TEST split (include_train=False)
|
||||
- store scores as Float32
|
||||
- drop the repeated file_names mapping to save space
|
||||
- write Parquet with zstd(level=7)
|
||||
"""
|
||||
if allow_cache:
|
||||
cache = root / "pretraining_results_cache.parquet"
|
||||
if cache.exists():
|
||||
try:
|
||||
df = pl.read_parquet(cache)
|
||||
print(f"[info] loaded cached pretraining frame from {cache}")
|
||||
return df
|
||||
except Exception as e:
|
||||
print(f"[warn] failed to load pretraining cache {cache}: {e}")
|
||||
|
||||
rows: List[dict] = []
|
||||
|
||||
exp_dirs = [p for p in root.iterdir() if p.is_dir()]
|
||||
for exp_dir in sorted(exp_dirs):
|
||||
try:
|
||||
cfg = read_config(exp_dir)
|
||||
cfg_json = json.dumps(cfg, sort_keys=True)
|
||||
except Exception as e:
|
||||
print(f"[warn] skipping {exp_dir.name} (pretraining): {e}")
|
||||
continue
|
||||
|
||||
network = cfg.get("net_name")
|
||||
latent_dim = int(cfg.get("latent_space_dim"))
|
||||
semi_normals = int(cfg.get("num_known_normal"))
|
||||
semi_anomalous = int(cfg.get("num_known_outlier"))
|
||||
k = int(cfg.get("k_fold_num"))
|
||||
|
||||
# Only test split by default (include_train=False)
|
||||
splits = ("train", "test") if include_train else ("test",)
|
||||
|
||||
for fold in range(k):
|
||||
pkl = exp_dir / f"results_ae_{fold}.pkl"
|
||||
if not pkl.exists():
|
||||
continue
|
||||
|
||||
try:
|
||||
data = read_pickle(pkl) # expected: {"train": {...}, "test": {...}}
|
||||
except Exception as e:
|
||||
print(f"[warn] failed to read {pkl.name}: {e}")
|
||||
continue
|
||||
|
||||
for split in splits:
|
||||
splitd = data.get(split)
|
||||
if not isinstance(splitd, dict):
|
||||
continue
|
||||
|
||||
rows.append(
|
||||
{
|
||||
"network": network,
|
||||
"latent_dim": latent_dim,
|
||||
"semi_normals": semi_normals,
|
||||
"semi_anomalous": semi_anomalous,
|
||||
"model": "ae",
|
||||
"fold": fold,
|
||||
"split": split,
|
||||
"time": float(splitd.get("time"))
|
||||
if splitd.get("time") is not None
|
||||
else None,
|
||||
"loss": float(splitd.get("loss"))
|
||||
if splitd.get("loss") is not None
|
||||
else None,
|
||||
# ints as Int32, scores as Float32 to save space
|
||||
"indices": normalize_int_list(splitd.get("indices")),
|
||||
"labels_exp_based": normalize_int_list(
|
||||
splitd.get("labels_exp_based")
|
||||
),
|
||||
"labels_manual_based": normalize_int_list(
|
||||
splitd.get("labels_manual_based")
|
||||
),
|
||||
"semi_targets": normalize_int_list(splitd.get("semi_targets")),
|
||||
"file_ids": normalize_int_list(splitd.get("file_ids")),
|
||||
"frame_ids": normalize_int_list(splitd.get("frame_ids")),
|
||||
"scores": (
|
||||
None
|
||||
if splitd.get("scores") is None
|
||||
else [
|
||||
float(x)
|
||||
for x in (
|
||||
splitd["scores"].tolist()
|
||||
if isinstance(splitd["scores"], np.ndarray)
|
||||
else splitd["scores"]
|
||||
)
|
||||
]
|
||||
),
|
||||
"file_names": normalize_file_names(splitd.get("file_names"))
|
||||
if keep_file_names
|
||||
else None,
|
||||
"folder": str(exp_dir),
|
||||
"k_fold_num": k,
|
||||
"config_json": cfg_json,
|
||||
}
|
||||
)
|
||||
|
||||
if not rows:
|
||||
return pl.DataFrame(schema=PRETRAIN_SCHEMA)
|
||||
|
||||
df = pl.DataFrame(rows, schema=PRETRAIN_SCHEMA)
|
||||
|
||||
# Cast/optimize a bit (categoricals, ints, floats)
|
||||
df = df.with_columns(
|
||||
pl.col("network", "model", "split").cast(pl.Categorical),
|
||||
pl.col(
|
||||
"latent_dim", "semi_normals", "semi_anomalous", "fold", "k_fold_num"
|
||||
).cast(pl.Int32),
|
||||
pl.col("time", "loss").cast(pl.Float64),
|
||||
pl.col("scores").cast(pl.List(pl.Float32)), # ensure downcast took
|
||||
)
|
||||
|
||||
if allow_cache:
|
||||
try:
|
||||
cache = root / "pretraining_results_cache.parquet"
|
||||
df.write_parquet(
|
||||
cache,
|
||||
compression=parquet_compression,
|
||||
compression_level=parquet_compression_level,
|
||||
statistics=True,
|
||||
)
|
||||
print(
|
||||
f"[info] cached pretraining frame to {cache} "
|
||||
f"({parquet_compression}, level={parquet_compression_level})"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[warn] failed to write pretraining cache {cache}: {e}")
|
||||
|
||||
return df
|
||||
|
||||
|
||||
def main():
|
||||
root = Path("/home/fedex/mt/results/copy")
|
||||
df1 = load_results_dataframe(root, allow_cache=True)
|
||||
exit(0)
|
||||
|
||||
retest_root = Path("/home/fedex/mt/results/copy/retest_nodrop")
|
||||
df2 = load_results_dataframe(retest_root, allow_cache=False).drop("folder")
|
||||
|
||||
# exact schema & shape first (optional but helpful messages)
|
||||
assert df1.shape == df2.shape, f"Shape differs: {df1.shape} vs {df2.shape}"
|
||||
assert set(df1.columns) == set(df2.columns), (
|
||||
f"Column sets differ: {df1.columns} vs {df2.columns}"
|
||||
)
|
||||
|
||||
# allow small float diffs, ignore column order differences if you want
|
||||
df1_sorted = df1.select(sorted(df1.columns))
|
||||
df2_sorted = df2.select(sorted(df2.columns))
|
||||
|
||||
# Optionally pre-align/sort both frames by a stable key before diffing.
|
||||
summary, leaves = recursive_diff_frames(
|
||||
df1,
|
||||
df2,
|
||||
ignore=["timestamp"], # columns to ignore
|
||||
float_atol=0.1, # absolute tolerance for floats
|
||||
float_rtol=0.0, # relative tolerance for floats
|
||||
max_rows_per_column=20, # limit expansion per column
|
||||
max_leafs_per_row=200, # cap leaves per row
|
||||
)
|
||||
|
||||
pl.Config.set_fmt_table_cell_list_len(100)
|
||||
pl.Config.set_tbl_rows(100)
|
||||
|
||||
print(summary) # which columns differ & how many rows
|
||||
print(leaves) # exact nested paths + scalar diffs
|
||||
|
||||
# check_exact=False lets us use atol/rtol for floats
|
||||
assert_frame_equal(
|
||||
df1_sorted,
|
||||
df2_sorted,
|
||||
check_exact=False,
|
||||
atol=0.1, # absolute tolerance for floats
|
||||
rtol=0.0, # relative tolerance (set if you want % based)
|
||||
check_dtypes=True, # set False if you only care about values
|
||||
)
|
||||
print("DataFrames match within tolerance ✅")
|
||||
|
||||
# df_pre = load_pretraining_results_dataframe(root, allow_cache=True)
|
||||
# print("pretraining:", df_pre.shape, df_pre.head())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -12,7 +12,7 @@ import numpy as np
|
||||
import polars as pl
|
||||
|
||||
# CHANGE THIS IMPORT IF YOUR LOADER MODULE IS NAMED DIFFERENTLY
|
||||
from load_results import load_pretraining_results_dataframe
|
||||
from plot_scripts.load_results import load_pretraining_results_dataframe
|
||||
|
||||
# ----------------------------
|
||||
# Config
|
||||
@@ -212,7 +212,7 @@ def plot_multi_loss_curve(arch_results, title, output_path, colors=None):
|
||||
|
||||
def main():
|
||||
# Load AE DF (uses your cache if enabled in the loader)
|
||||
df = load_pretraining_results_dataframe(ROOT, allow_cache=True, include_train=False)
|
||||
df = load_pretraining_results_dataframe(ROOT, allow_cache=True)
|
||||
|
||||
# Optional: filter to just LeNet vs Efficient; drop this set() to plot all nets
|
||||
wanted_nets = {"LeNet", "Efficient"}
|
||||
|
||||
@@ -3,10 +3,12 @@ from __future__ import annotations
|
||||
import json
|
||||
import pickle
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
import polars as pl
|
||||
from diff_df import recursive_diff_frames
|
||||
from polars.testing import assert_frame_equal
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Config you can tweak
|
||||
@@ -75,7 +77,8 @@ PRETRAIN_SCHEMA = {
|
||||
"fold": pl.Int32,
|
||||
"split": pl.Utf8, # "train" | "test"
|
||||
# timings and optimization
|
||||
"time": pl.Float64,
|
||||
"train_time": pl.Float64,
|
||||
"test_time": pl.Float64,
|
||||
"loss": pl.Float64,
|
||||
# per-sample arrays (as lists)
|
||||
"indices": pl.List(pl.Int32),
|
||||
@@ -247,6 +250,14 @@ def read_pickle(p: Path) -> Any:
|
||||
# ------------------------------------------------------------
|
||||
# Extractors for each model
|
||||
# ------------------------------------------------------------
|
||||
|
||||
counting = {
|
||||
(label_method, eval_method): []
|
||||
for label_method in ["exp_based", "manual_based"]
|
||||
for eval_method in ["roc", "prc"]
|
||||
}
|
||||
|
||||
|
||||
def rows_from_deepsad(data: dict, evals: List[str]) -> Dict[str, dict]:
|
||||
"""
|
||||
deepsad under data['test'][eval], with extra per-eval arrays and AP present.
|
||||
@@ -257,6 +268,8 @@ def rows_from_deepsad(data: dict, evals: List[str]) -> Dict[str, dict]:
|
||||
evd = test.get(ev)
|
||||
if not isinstance(evd, dict):
|
||||
continue
|
||||
counting[(ev, "roc")].append(len(evd["roc"][0]))
|
||||
counting[(ev, "prc")].append(len(evd["prc"][0]))
|
||||
out[ev] = {
|
||||
"auc": float(evd["auc"])
|
||||
if "auc" in evd and evd["auc"] is not None
|
||||
@@ -444,7 +457,6 @@ def load_results_dataframe(root: Path, allow_cache: bool = True) -> pl.DataFrame
|
||||
def load_pretraining_results_dataframe(
|
||||
root: Path,
|
||||
allow_cache: bool = True,
|
||||
include_train: bool = False, # <— default: store only TEST to keep cache tiny
|
||||
keep_file_names: bool = False, # <— drop file_names by default; they’re repeated
|
||||
parquet_compression: str = "zstd",
|
||||
parquet_compression_level: int = 7, # <— stronger compression than default
|
||||
@@ -484,9 +496,6 @@ def load_pretraining_results_dataframe(
|
||||
semi_anomalous = int(cfg.get("num_known_outlier"))
|
||||
k = int(cfg.get("k_fold_num"))
|
||||
|
||||
# Only test split by default (include_train=False)
|
||||
splits = ("train", "test") if include_train else ("test",)
|
||||
|
||||
for fold in range(k):
|
||||
pkl = exp_dir / f"results_ae_{fold}.pkl"
|
||||
if not pkl.exists():
|
||||
@@ -498,10 +507,8 @@ def load_pretraining_results_dataframe(
|
||||
print(f"[warn] failed to read {pkl.name}: {e}")
|
||||
continue
|
||||
|
||||
for split in splits:
|
||||
splitd = data.get(split)
|
||||
if not isinstance(splitd, dict):
|
||||
continue
|
||||
train_time = data.get("train", {}).get("time")
|
||||
data = data.get("test", {})
|
||||
|
||||
rows.append(
|
||||
{
|
||||
@@ -511,37 +518,35 @@ def load_pretraining_results_dataframe(
|
||||
"semi_anomalous": semi_anomalous,
|
||||
"model": "ae",
|
||||
"fold": fold,
|
||||
"split": split,
|
||||
"time": float(splitd.get("time"))
|
||||
if splitd.get("time") is not None
|
||||
else None,
|
||||
"loss": float(splitd.get("loss"))
|
||||
if splitd.get("loss") is not None
|
||||
"train_time": train_time,
|
||||
"test_time": data.get("time"),
|
||||
"loss": float(data.get("loss"))
|
||||
if data.get("loss") is not None
|
||||
else None,
|
||||
# ints as Int32, scores as Float32 to save space
|
||||
"indices": normalize_int_list(splitd.get("indices")),
|
||||
"indices": normalize_int_list(data.get("indices")),
|
||||
"labels_exp_based": normalize_int_list(
|
||||
splitd.get("labels_exp_based")
|
||||
data.get("labels_exp_based")
|
||||
),
|
||||
"labels_manual_based": normalize_int_list(
|
||||
splitd.get("labels_manual_based")
|
||||
data.get("labels_manual_based")
|
||||
),
|
||||
"semi_targets": normalize_int_list(splitd.get("semi_targets")),
|
||||
"file_ids": normalize_int_list(splitd.get("file_ids")),
|
||||
"frame_ids": normalize_int_list(splitd.get("frame_ids")),
|
||||
"semi_targets": normalize_int_list(data.get("semi_targets")),
|
||||
"file_ids": normalize_int_list(data.get("file_ids")),
|
||||
"frame_ids": normalize_int_list(data.get("frame_ids")),
|
||||
"scores": (
|
||||
None
|
||||
if splitd.get("scores") is None
|
||||
if data.get("scores") is None
|
||||
else [
|
||||
float(x)
|
||||
for x in (
|
||||
splitd["scores"].tolist()
|
||||
if isinstance(splitd["scores"], np.ndarray)
|
||||
else splitd["scores"]
|
||||
data["scores"].tolist()
|
||||
if isinstance(data["scores"], np.ndarray)
|
||||
else data["scores"]
|
||||
)
|
||||
]
|
||||
),
|
||||
"file_names": normalize_file_names(splitd.get("file_names"))
|
||||
"file_names": normalize_file_names(data.get("file_names"))
|
||||
if keep_file_names
|
||||
else None,
|
||||
"folder": str(exp_dir),
|
||||
@@ -561,7 +566,7 @@ def load_pretraining_results_dataframe(
|
||||
pl.col(
|
||||
"latent_dim", "semi_normals", "semi_anomalous", "fold", "k_fold_num"
|
||||
).cast(pl.Int32),
|
||||
pl.col("time", "loss").cast(pl.Float64),
|
||||
pl.col("test_time", "train_time", "loss").cast(pl.Float64),
|
||||
pl.col("scores").cast(pl.List(pl.Float32)), # ensure downcast took
|
||||
)
|
||||
|
||||
@@ -585,12 +590,53 @@ def load_pretraining_results_dataframe(
|
||||
|
||||
|
||||
def main():
|
||||
root = Path("/home/fedex/mt/results/done")
|
||||
df = load_results_dataframe(root, allow_cache=True)
|
||||
print(df.shape, df.head())
|
||||
root = Path("/home/fedex/mt/results/copy")
|
||||
df1 = load_results_dataframe(root, allow_cache=True)
|
||||
exit(0)
|
||||
|
||||
df_pre = load_pretraining_results_dataframe(root, allow_cache=True)
|
||||
print("pretraining:", df_pre.shape, df_pre.head())
|
||||
retest_root = Path("/home/fedex/mt/results/copy/retest_nodrop")
|
||||
df2 = load_results_dataframe(retest_root, allow_cache=False).drop("folder")
|
||||
|
||||
# exact schema & shape first (optional but helpful messages)
|
||||
assert df1.shape == df2.shape, f"Shape differs: {df1.shape} vs {df2.shape}"
|
||||
assert set(df1.columns) == set(df2.columns), (
|
||||
f"Column sets differ: {df1.columns} vs {df2.columns}"
|
||||
)
|
||||
|
||||
# allow small float diffs, ignore column order differences if you want
|
||||
df1_sorted = df1.select(sorted(df1.columns))
|
||||
df2_sorted = df2.select(sorted(df2.columns))
|
||||
|
||||
# Optionally pre-align/sort both frames by a stable key before diffing.
|
||||
summary, leaves = recursive_diff_frames(
|
||||
df1,
|
||||
df2,
|
||||
ignore=["timestamp"], # columns to ignore
|
||||
float_atol=0.1, # absolute tolerance for floats
|
||||
float_rtol=0.0, # relative tolerance for floats
|
||||
max_rows_per_column=20, # limit expansion per column
|
||||
max_leafs_per_row=200, # cap leaves per row
|
||||
)
|
||||
|
||||
pl.Config.set_fmt_table_cell_list_len(100)
|
||||
pl.Config.set_tbl_rows(100)
|
||||
|
||||
print(summary) # which columns differ & how many rows
|
||||
print(leaves) # exact nested paths + scalar diffs
|
||||
|
||||
# check_exact=False lets us use atol/rtol for floats
|
||||
assert_frame_equal(
|
||||
df1_sorted,
|
||||
df2_sorted,
|
||||
check_exact=False,
|
||||
atol=0.1, # absolute tolerance for floats
|
||||
rtol=0.0, # relative tolerance (set if you want % based)
|
||||
check_dtypes=True, # set False if you only care about values
|
||||
)
|
||||
print("DataFrames match within tolerance ✅")
|
||||
|
||||
# df_pre = load_pretraining_results_dataframe(root, allow_cache=True)
|
||||
# print("pretraining:", df_pre.shape, df_pre.head())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -10,7 +10,7 @@ import polars as pl
|
||||
from matplotlib.lines import Line2D
|
||||
|
||||
# CHANGE THIS IMPORT IF YOUR LOADER MODULE IS NAMED DIFFERENTLY
|
||||
from load_results import load_results_dataframe
|
||||
from plot_scripts.load_results import load_results_dataframe
|
||||
|
||||
# ----------------------------
|
||||
# Config
|
||||
|
||||
@@ -12,7 +12,7 @@ from matplotlib.lines import Line2D
|
||||
from scipy.stats import sem, t
|
||||
|
||||
# CHANGE THIS IMPORT IF YOUR LOADER MODULE NAME IS DIFFERENT
|
||||
from load_results import load_results_dataframe
|
||||
from plot_scripts.load_results import load_results_dataframe
|
||||
|
||||
# ---------------------------------
|
||||
# Config
|
||||
|
||||
704
tools/plot_scripts/setup_runtime_tables.py
Normal file
704
tools/plot_scripts/setup_runtime_tables.py
Normal file
@@ -0,0 +1,704 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import polars as pl
|
||||
from load_results import (
|
||||
load_pretraining_results_dataframe,
|
||||
load_results_dataframe,
|
||||
)
|
||||
|
||||
# ----------------------------
|
||||
# Config
|
||||
# ----------------------------
|
||||
RESULTS_ROOT = Path("/home/fedex/mt/results/done") # folder with experiment subdirs
|
||||
OUTPUT_DIR = Path("/home/fedex/mt/plots/setup_runtime_tables") # where .tex goes
|
||||
|
||||
# If you want to optionally prefer a specific network label for baselines in column names,
|
||||
# set to a substring to detect (e.g. "efficient"). If None, keep network as-is.
|
||||
BASELINE_NETWORK_HINT: Optional[str] = None # e.g., "efficient" or None
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# Helpers
|
||||
# ----------------------------
|
||||
def _net_label_for_display(net: str | None) -> str:
|
||||
s = (net or "").lower()
|
||||
if "effic" in s:
|
||||
return "Efficient"
|
||||
if "lenet" in s:
|
||||
return "LeNet"
|
||||
return net or ""
|
||||
|
||||
|
||||
def _fmt_mean_std_n(
|
||||
mean: float | None, std: float | None, n: int | None, unit: str = ""
|
||||
) -> str:
|
||||
if mean is None or (isinstance(mean, float) and (np.isnan(mean) or np.isinf(mean))):
|
||||
return "-"
|
||||
base = f"{mean:.2f}"
|
||||
if std is not None and not (
|
||||
isinstance(std, float) and (np.isnan(std) or np.isinf(std))
|
||||
):
|
||||
base = f"{base} ± {std:.2f}"
|
||||
if unit:
|
||||
base = f"{base} {unit}"
|
||||
if n is not None and n > 0:
|
||||
base = f"{base} (n={n})"
|
||||
return base
|
||||
|
||||
|
||||
def _fmt_pair(n: int, m: int) -> str:
|
||||
return f"{n}/{m}"
|
||||
|
||||
|
||||
def _fmt_mean_std(mean: float | None, std: float | None, n: int | None) -> str:
|
||||
if mean is None or (isinstance(mean, float) and (np.isnan(mean) or np.isinf(mean))):
|
||||
return "-"
|
||||
if std is None or (isinstance(std, float) and (np.isnan(std) or np.isinf(std))):
|
||||
return f"{mean:.2f}"
|
||||
if n is None or n < 1:
|
||||
return f"{mean:.2f} ± {std:.2f}"
|
||||
return f"{mean:.2f} ± {std:.2f} (n={n})"
|
||||
|
||||
|
||||
def _parse_cfg(cfg_json: Optional[str]) -> Dict[str, Any]:
|
||||
if not cfg_json:
|
||||
return {}
|
||||
try:
|
||||
return json.loads(cfg_json)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _key_params(model: str, cfg: Dict[str, Any]) -> str:
|
||||
"""Compact, model-specific parameter string for the table."""
|
||||
if model == "deepsad":
|
||||
bs = cfg.get("batch_size")
|
||||
ne = cfg.get("n_epochs")
|
||||
lr = cfg.get("lr")
|
||||
wd = cfg.get("weight_decay")
|
||||
return f"bs={bs}, epochs={ne}, lr={lr}, wd={wd}"
|
||||
if model == "isoforest":
|
||||
est = cfg.get("isoforest_n_estimators")
|
||||
ms = cfg.get("isoforest_max_samples")
|
||||
cont = cfg.get("isoforest_contamination")
|
||||
return f"n_estimators={est}, max_samples={ms}, cont={cont}"
|
||||
if model == "ocsvm":
|
||||
ker = cfg.get("ocsvm_kernel")
|
||||
nu = cfg.get("ocsvm_nu")
|
||||
return f"kernel={ker}, nu={nu}"
|
||||
return "-"
|
||||
|
||||
|
||||
def _method_col_name(model: str, network: str) -> str:
|
||||
"""
|
||||
Column heading for pivot tables:
|
||||
- deepsad carries the network (e.g., 'DeepSAD / LeNet')
|
||||
- baselines carry their own model name; optionally annotate network
|
||||
"""
|
||||
label = model.lower()
|
||||
if label == "deepsad":
|
||||
return f"DeepSAD / {network}"
|
||||
# baselines; optionally simplify/standardize network name
|
||||
if (
|
||||
BASELINE_NETWORK_HINT
|
||||
and BASELINE_NETWORK_HINT.lower() not in (network or "").lower()
|
||||
):
|
||||
# If you want to collapse baseline duplicates to a single name, you can force it here
|
||||
return model.capitalize()
|
||||
# Otherwise, keep network variant explicit
|
||||
return f"{model.capitalize()} / {network}"
|
||||
|
||||
|
||||
def _prepare_per_fold_metrics(df: pl.DataFrame) -> pl.DataFrame:
|
||||
"""
|
||||
Returns one row per (folder, model, fold) with:
|
||||
- train_time, test_time
|
||||
- n_test (len(scores))
|
||||
- n_epochs (from config_json; DeepSAD only)
|
||||
- latency_ms = 1000 * test_time / n_test
|
||||
- time_per_epoch = train_time / n_epochs (DeepSAD only)
|
||||
"""
|
||||
base = (
|
||||
df.select(
|
||||
"folder",
|
||||
"network",
|
||||
"model",
|
||||
"latent_dim",
|
||||
"semi_normals",
|
||||
"semi_anomalous",
|
||||
"fold",
|
||||
"train_time",
|
||||
"test_time",
|
||||
"scores",
|
||||
"config_json",
|
||||
)
|
||||
.with_columns(
|
||||
n_test=pl.col("scores").list.len(),
|
||||
n_epochs=pl.col("config_json")
|
||||
.str.json_path_match("$.n_epochs")
|
||||
.cast(pl.Int64),
|
||||
)
|
||||
.drop("scores")
|
||||
)
|
||||
|
||||
# de-dup across evals
|
||||
uniq = base.unique(subset=["folder", "model", "fold"])
|
||||
|
||||
# derived metrics
|
||||
uniq = uniq.with_columns(
|
||||
latency_ms=pl.when((pl.col("test_time") > 0) & (pl.col("n_test") > 0))
|
||||
.then(1000.0 * pl.col("test_time") / pl.col("n_test"))
|
||||
.otherwise(None)
|
||||
.cast(pl.Float64),
|
||||
time_per_epoch=pl.when(
|
||||
(pl.col("model") == "deepsad") & (pl.col("n_epochs") > 0)
|
||||
)
|
||||
.then(pl.col("train_time") / pl.col("n_epochs"))
|
||||
.otherwise(None)
|
||||
.cast(pl.Float64),
|
||||
network_disp=pl.col("network")
|
||||
.cast(pl.Utf8)
|
||||
.map_elements(_net_label_for_display, return_dtype=pl.Utf8),
|
||||
)
|
||||
return uniq
|
||||
|
||||
|
||||
def _prepare_aggregates(df: pl.DataFrame) -> pl.DataFrame:
|
||||
"""
|
||||
Deduplicate across evals, then aggregate times across folds for each
|
||||
(network, model, latent_dim, semi_normals, semi_anomalous).
|
||||
"""
|
||||
# Keep only columns we need
|
||||
base = df.select(
|
||||
"folder",
|
||||
"network",
|
||||
"model",
|
||||
"latent_dim",
|
||||
"semi_normals",
|
||||
"semi_anomalous",
|
||||
"fold",
|
||||
"train_time",
|
||||
"test_time",
|
||||
"config_json",
|
||||
)
|
||||
|
||||
# Drop duplicates across evals: same (folder, model, fold) should have identical timings
|
||||
uniq = base.unique(subset=["folder", "model", "fold"]).with_columns(
|
||||
# Normalize network to a simpler display label, if your config used long names
|
||||
pl.col("network").cast(pl.Utf8)
|
||||
)
|
||||
|
||||
# Group across folds
|
||||
agg = (
|
||||
uniq.group_by(
|
||||
["network", "model", "latent_dim", "semi_normals", "semi_anomalous"]
|
||||
)
|
||||
.agg(
|
||||
pl.len().alias("n_folds"),
|
||||
pl.col("train_time").mean().alias("train_mean"),
|
||||
pl.col("train_time").std(ddof=1).alias("train_std"),
|
||||
pl.col("test_time").mean().alias("test_mean"),
|
||||
pl.col("test_time").std(ddof=1).alias("test_std"),
|
||||
pl.col("config_json")
|
||||
.first()
|
||||
.alias("config_json"), # one exemplar cfg per group
|
||||
)
|
||||
.sort(["semi_normals", "semi_anomalous", "latent_dim", "network", "model"])
|
||||
)
|
||||
return agg
|
||||
|
||||
|
||||
def make_training_runtime_table(df: pl.DataFrame) -> str:
|
||||
"""
|
||||
Returns a LaTeX table (string) for TRAIN runtimes: mean ± std (seconds) across folds.
|
||||
Rows: Semi (N/O), Latent Dim
|
||||
Columns: methods split (DeepSAD/LeNet, DeepSAD/Efficient, IsoForest[/net], OCSVM[/net])
|
||||
"""
|
||||
agg = _prepare_aggregates(df)
|
||||
|
||||
# Prepare display strings and column keys
|
||||
tbl = agg.with_columns(
|
||||
pl.format("{}/{}", pl.col("semi_normals"), pl.col("semi_anomalous")).alias(
|
||||
"semi"
|
||||
),
|
||||
pl.col("model").cast(pl.Utf8),
|
||||
pl.col("network").cast(pl.Utf8),
|
||||
pl.col("latent_dim").cast(pl.Int64),
|
||||
# ADD return_dtype here
|
||||
pl.struct(["train_mean", "train_std", "n_folds"])
|
||||
.map_elements(
|
||||
lambda s: _fmt_mean_std(s["train_mean"], s["train_std"], s["n_folds"]),
|
||||
return_dtype=pl.Utf8,
|
||||
)
|
||||
.alias("train_fmt"),
|
||||
# And here
|
||||
pl.struct(["model", "network"])
|
||||
.map_elements(
|
||||
lambda s: _method_col_name(s["model"], s["network"]),
|
||||
return_dtype=pl.Utf8,
|
||||
)
|
||||
.alias("method"),
|
||||
).select("semi", "latent_dim", "method", "train_fmt")
|
||||
|
||||
# Pivot to wide form: one cell per (semi, latent_dim, method)
|
||||
wide = tbl.pivot(
|
||||
values="train_fmt",
|
||||
index=["semi", "latent_dim"],
|
||||
columns="method",
|
||||
aggregate_function="first",
|
||||
).sort(["semi", "latent_dim"])
|
||||
|
||||
# Fill missing with '-' and export
|
||||
pdf = wide.fill_null("-").to_pandas()
|
||||
pdf.index = pd.MultiIndex.from_frame(pdf[["semi", "latent_dim"]])
|
||||
pdf = pdf.drop(columns=["semi", "latent_dim"])
|
||||
latex = pdf.to_latex(
|
||||
index=True,
|
||||
escape=True,
|
||||
na_rep="-",
|
||||
multicolumn=True,
|
||||
multicolumn_format="c",
|
||||
bold_rows=False,
|
||||
caption="Training runtime (seconds): mean ± std across folds (n in parentheses).",
|
||||
label="tab:train_runtimes",
|
||||
)
|
||||
return latex
|
||||
|
||||
|
||||
def make_inference_runtime_table(df: pl.DataFrame) -> str:
|
||||
"""
|
||||
Returns a LaTeX table (string) for TEST/INFERENCE runtimes: mean ± std (seconds) across folds.
|
||||
Same layout as training table.
|
||||
"""
|
||||
agg = _prepare_aggregates(df)
|
||||
|
||||
tbl = agg.with_columns(
|
||||
pl.format("{}/{}", pl.col("semi_normals"), pl.col("semi_anomalous")).alias(
|
||||
"semi"
|
||||
),
|
||||
pl.col("model").cast(pl.Utf8),
|
||||
pl.col("network").cast(pl.Utf8),
|
||||
pl.col("latent_dim").cast(pl.Int64),
|
||||
pl.struct(["test_mean", "test_std", "n_folds"])
|
||||
.map_elements(
|
||||
lambda s: _fmt_mean_std(s["test_mean"], s["test_std"], s["n_folds"]),
|
||||
return_dtype=pl.Utf8,
|
||||
)
|
||||
.alias("test_fmt"),
|
||||
pl.struct(["model", "network"])
|
||||
.map_elements(
|
||||
lambda s: _method_col_name(s["model"], s["network"]),
|
||||
return_dtype=pl.Utf8,
|
||||
)
|
||||
.alias("method"),
|
||||
).select("semi", "latent_dim", "method", "test_fmt")
|
||||
|
||||
wide = tbl.pivot(
|
||||
values="test_fmt",
|
||||
index=["semi", "latent_dim"],
|
||||
columns="method",
|
||||
aggregate_function="first",
|
||||
).sort(["semi", "latent_dim"])
|
||||
|
||||
pdf = wide.fill_null("-").to_pandas()
|
||||
pdf.index = pd.MultiIndex.from_frame(pdf[["semi", "latent_dim"]])
|
||||
pdf = pdf.drop(columns=["semi", "latent_dim"])
|
||||
latex = pdf.to_latex(
|
||||
index=True,
|
||||
escape=True,
|
||||
na_rep="-",
|
||||
multicolumn=True,
|
||||
multicolumn_format="c",
|
||||
bold_rows=False,
|
||||
caption="Inference/Test runtime (seconds): mean ± std across folds (n in parentheses).",
|
||||
label="tab:test_runtimes",
|
||||
)
|
||||
return latex
|
||||
|
||||
|
||||
def make_longform_train_table_with_params(df: pl.DataFrame) -> str:
|
||||
"""
|
||||
(Optional) Long-form table that includes a 'Params' column extracted from config_json.
|
||||
Useful if you want to show per-model settings alongside the runtimes.
|
||||
"""
|
||||
agg = _prepare_aggregates(df)
|
||||
# Build params column from JSON for readability
|
||||
long = (
|
||||
agg.with_columns(
|
||||
pl.format("{}/{}", pl.col("semi_normals"), pl.col("semi_anomalous")).alias(
|
||||
"semi"
|
||||
),
|
||||
pl.col("latent_dim").cast(pl.Int64),
|
||||
pl.struct(["model", "config_json"])
|
||||
.map_elements(
|
||||
lambda s: _key_params(s["model"], _parse_cfg(s["config_json"])),
|
||||
return_dtype=pl.Utf8,
|
||||
)
|
||||
.alias("params"),
|
||||
pl.struct(["train_mean", "train_std", "n_folds"])
|
||||
.map_elements(
|
||||
lambda s: _fmt_mean_std(s["train_mean"], s["train_std"], s["n_folds"])
|
||||
)
|
||||
.alias("train_time_fmt"),
|
||||
)
|
||||
.select(
|
||||
"network",
|
||||
"model",
|
||||
"latent_dim",
|
||||
"semi",
|
||||
"params",
|
||||
"train_time_fmt",
|
||||
)
|
||||
.sort(["semi", "latent_dim", "network", "model"])
|
||||
)
|
||||
|
||||
pdf = long.to_pandas()
|
||||
pdf.rename(
|
||||
columns={
|
||||
"network": "Network",
|
||||
"model": "Method",
|
||||
"latent_dim": "Latent Dim",
|
||||
"semi": "Semi (N/O)",
|
||||
"params": "Params",
|
||||
"train_time_fmt": "Train time [s] (mean ± std)",
|
||||
},
|
||||
inplace=True,
|
||||
)
|
||||
latex = pdf.to_latex(
|
||||
index=False,
|
||||
escape=True,
|
||||
longtable=False,
|
||||
caption="Training runtime with key parameters.",
|
||||
label="tab:train_runtimes_params",
|
||||
)
|
||||
return latex
|
||||
|
||||
|
||||
def make_training_runtime_table_compact(df: pl.DataFrame) -> str:
|
||||
per_fold = _prepare_per_fold_metrics(df)
|
||||
|
||||
# DeepSAD: keep LeNet vs Efficient, collapse semis
|
||||
ds = (
|
||||
per_fold.filter(pl.col("model") == "deepsad")
|
||||
.group_by(["model", "network_disp", "latent_dim"])
|
||||
.agg(
|
||||
n=pl.len(),
|
||||
train_mean=pl.mean("train_time"),
|
||||
train_std=pl.std("train_time", ddof=1),
|
||||
tpe_mean=pl.mean("time_per_epoch"),
|
||||
tpe_std=pl.std("time_per_epoch", ddof=1),
|
||||
)
|
||||
.with_columns(
|
||||
method=pl.format("DeepSAD / {}", pl.col("network_disp")),
|
||||
)
|
||||
)
|
||||
|
||||
# Baselines: collapse networks & semis; only vary by latent_dim
|
||||
bl = (
|
||||
per_fold.filter(pl.col("model").is_in(["isoforest", "ocsvm"]))
|
||||
.group_by(["model", "latent_dim"])
|
||||
.agg(
|
||||
n=pl.len(),
|
||||
train_mean=pl.mean("train_time"),
|
||||
train_std=pl.std("train_time", ddof=1),
|
||||
)
|
||||
.with_columns(
|
||||
method=pl.when(pl.col("model") == "isoforest")
|
||||
.then(pl.lit("IsoForest"))
|
||||
.when(pl.col("model") == "ocsvm")
|
||||
.then(pl.lit("OCSVM"))
|
||||
.otherwise(pl.lit("Baseline"))
|
||||
)
|
||||
)
|
||||
|
||||
# --- Standardize schemas before concat ---
|
||||
ds_std = ds.select(
|
||||
pl.col("latent_dim").cast(pl.Int64),
|
||||
pl.col("method").cast(pl.Utf8),
|
||||
pl.col("train_mean").cast(pl.Float64),
|
||||
pl.col("train_std").cast(pl.Float64),
|
||||
pl.col("tpe_mean").cast(pl.Float64),
|
||||
pl.col("tpe_std").cast(pl.Float64),
|
||||
pl.col("n").cast(pl.Int64),
|
||||
)
|
||||
|
||||
bl_std = bl.select(
|
||||
pl.col("latent_dim").cast(pl.Int64),
|
||||
pl.col("method").cast(pl.Utf8),
|
||||
pl.col("train_mean").cast(pl.Float64),
|
||||
pl.col("train_std").cast(pl.Float64),
|
||||
pl.lit(None, dtype=pl.Float64).alias("tpe_mean"),
|
||||
pl.lit(None, dtype=pl.Float64).alias("tpe_std"),
|
||||
pl.col("n").cast(pl.Int64),
|
||||
)
|
||||
|
||||
agg = pl.concat([ds_std, bl_std], how="vertical")
|
||||
|
||||
# Format cell: total [s]; DeepSAD also appends (italic) per-epoch
|
||||
def _fmt_train_cell(s: dict) -> str:
|
||||
total = _fmt_mean_std_n(s["train_mean"], s["train_std"], s["n"], "s")
|
||||
if s.get("tpe_mean") is None or (
|
||||
isinstance(s.get("tpe_mean"), float) and np.isnan(s["tpe_mean"])
|
||||
):
|
||||
return total
|
||||
tpe = _fmt_mean_std_n(s["tpe_mean"], s["tpe_std"], None, "s/epoch")
|
||||
return f"{total} (\\textit{{{tpe}}})"
|
||||
|
||||
tbl = agg.with_columns(
|
||||
pl.struct(["train_mean", "train_std", "tpe_mean", "tpe_std", "n"])
|
||||
.map_elements(_fmt_train_cell, return_dtype=pl.Utf8)
|
||||
.alias("train_fmt"),
|
||||
).select("latent_dim", "method", "train_fmt")
|
||||
|
||||
# Pivot and order columns nicely
|
||||
wide = tbl.pivot(
|
||||
values="train_fmt",
|
||||
index=["latent_dim"],
|
||||
columns="method",
|
||||
aggregate_function="first",
|
||||
).sort("latent_dim")
|
||||
|
||||
pdf = wide.fill_null("-").to_pandas().set_index("latent_dim")
|
||||
desired_cols = [
|
||||
c
|
||||
for c in ["DeepSAD / LeNet", "DeepSAD / Efficient", "IsoForest", "OCSVM"]
|
||||
if c in pdf.columns
|
||||
]
|
||||
if desired_cols:
|
||||
pdf = pdf.reindex(columns=desired_cols)
|
||||
|
||||
latex = pdf.to_latex(
|
||||
index=True,
|
||||
escape=True,
|
||||
na_rep="-",
|
||||
multicolumn=True,
|
||||
multicolumn_format="c",
|
||||
bold_rows=False,
|
||||
caption="Training runtime: total seconds (mean ± std). DeepSAD cells also show \\textit{seconds per epoch} in parentheses.",
|
||||
label="tab:train_runtimes_compact",
|
||||
)
|
||||
return latex
|
||||
|
||||
|
||||
def make_inference_latency_table_compact(df: pl.DataFrame) -> str:
|
||||
per_fold = _prepare_per_fold_metrics(df)
|
||||
|
||||
# DeepSAD: keep networks; collapse semis
|
||||
ds = (
|
||||
per_fold.filter(pl.col("model") == "deepsad")
|
||||
.group_by(["model", "network_disp", "latent_dim"])
|
||||
.agg(
|
||||
n=pl.len(),
|
||||
lat_mean=pl.mean("latency_ms"),
|
||||
lat_std=pl.std("latency_ms", ddof=1),
|
||||
)
|
||||
.with_columns(
|
||||
method=pl.format("DeepSAD / {}", pl.col("network_disp")),
|
||||
)
|
||||
)
|
||||
|
||||
# Baselines: collapse networks & semis
|
||||
bl = (
|
||||
per_fold.filter(pl.col("model").is_in(["isoforest", "ocsvm"]))
|
||||
.group_by(["model", "latent_dim"])
|
||||
.agg(
|
||||
n=pl.len(),
|
||||
lat_mean=pl.mean("latency_ms"),
|
||||
lat_std=pl.std("latency_ms", ddof=1),
|
||||
)
|
||||
.with_columns(
|
||||
method=pl.when(pl.col("model") == "isoforest")
|
||||
.then(pl.lit("IsoForest"))
|
||||
.when(pl.col("model") == "ocsvm")
|
||||
.then(pl.lit("OCSVM"))
|
||||
.otherwise(pl.lit("Baseline"))
|
||||
)
|
||||
)
|
||||
|
||||
# --- Standardize schemas before concat ---
|
||||
ds_std = ds.select(
|
||||
pl.col("latent_dim").cast(pl.Int64),
|
||||
pl.col("method").cast(pl.Utf8),
|
||||
pl.col("lat_mean").cast(pl.Float64),
|
||||
pl.col("lat_std").cast(pl.Float64),
|
||||
pl.col("n").cast(pl.Int64),
|
||||
)
|
||||
|
||||
bl_std = bl.select(
|
||||
pl.col("latent_dim").cast(pl.Int64),
|
||||
pl.col("method").cast(pl.Utf8),
|
||||
pl.col("lat_mean").cast(pl.Float64),
|
||||
pl.col("lat_std").cast(pl.Float64),
|
||||
pl.col("n").cast(pl.Int64),
|
||||
)
|
||||
|
||||
agg = pl.concat([ds_std, bl_std], how="vertical")
|
||||
|
||||
def _fmt_lat_cell(s: dict) -> str:
|
||||
return _fmt_mean_std_n(s["lat_mean"], s["lat_std"], s["n"], "ms")
|
||||
|
||||
tbl = agg.with_columns(
|
||||
pl.struct(["lat_mean", "lat_std", "n"])
|
||||
.map_elements(_fmt_lat_cell, return_dtype=pl.Utf8)
|
||||
.alias("lat_fmt"),
|
||||
).select("latent_dim", "method", "lat_fmt")
|
||||
|
||||
wide = tbl.pivot(
|
||||
values="lat_fmt",
|
||||
index=["latent_dim"],
|
||||
columns="method",
|
||||
aggregate_function="first",
|
||||
).sort("latent_dim")
|
||||
|
||||
pdf = wide.fill_null("-").to_pandas().set_index("latent_dim")
|
||||
desired_cols = [
|
||||
c
|
||||
for c in ["DeepSAD / LeNet", "DeepSAD / Efficient", "IsoForest", "OCSVM"]
|
||||
if c in pdf.columns
|
||||
]
|
||||
if desired_cols:
|
||||
pdf = pdf.reindex(columns=desired_cols)
|
||||
|
||||
latex = pdf.to_latex(
|
||||
index=True,
|
||||
escape=True,
|
||||
na_rep="-",
|
||||
multicolumn=True,
|
||||
multicolumn_format="c",
|
||||
bold_rows=False,
|
||||
caption="Inference latency (ms/sample): mean ± std across folds; baselines collapsed across networks and semi-labeling.",
|
||||
label="tab:inference_latency_compact",
|
||||
)
|
||||
return latex
|
||||
|
||||
|
||||
def make_ae_pretraining_runtime_table(df_pre: pl.DataFrame) -> str:
|
||||
"""
|
||||
LaTeX table: Autoencoder (pretraining) runtime per latent dim.
|
||||
Rows: latent_dim
|
||||
Cols: AE / LeNet, AE / Efficient (mean ± std seconds across folds)
|
||||
"""
|
||||
# minimal columns we need
|
||||
base = df_pre.select(
|
||||
pl.col("network").cast(pl.Utf8),
|
||||
pl.col("latent_dim").cast(pl.Int64),
|
||||
pl.col("fold").cast(pl.Int64),
|
||||
pl.col("train_time").cast(pl.Float64),
|
||||
).drop_nulls(subset=["network", "latent_dim", "train_time"])
|
||||
|
||||
# Nice display label for network
|
||||
network_disp = (
|
||||
pl.when(pl.col("network").str.contains("efficient"))
|
||||
.then(pl.lit("Efficient"))
|
||||
.when(pl.col("network").str.contains("LeNet"))
|
||||
.then(pl.lit("LeNet"))
|
||||
.otherwise(pl.col("network"))
|
||||
.alias("network_disp")
|
||||
)
|
||||
|
||||
agg = (
|
||||
base.with_columns(network_disp)
|
||||
.group_by(["network_disp", "latent_dim"])
|
||||
.agg(
|
||||
n=pl.len(),
|
||||
train_mean=pl.mean("train_time"),
|
||||
train_std=pl.std("train_time", ddof=1),
|
||||
)
|
||||
.with_columns(
|
||||
pl.format("AE / {}", pl.col("network_disp")).alias("method"),
|
||||
pl.struct(["train_mean", "train_std", "n"])
|
||||
.map_elements(
|
||||
lambda s: _fmt_mean_std(s["train_mean"], s["train_std"], s["n"]),
|
||||
return_dtype=pl.Utf8,
|
||||
)
|
||||
.alias("train_fmt"),
|
||||
)
|
||||
.select("latent_dim", "method", "train_fmt")
|
||||
.sort(["latent_dim", "method"])
|
||||
)
|
||||
|
||||
wide = agg.pivot(
|
||||
values="train_fmt",
|
||||
index=["latent_dim"],
|
||||
columns="method",
|
||||
aggregate_function="first",
|
||||
).sort("latent_dim")
|
||||
|
||||
pdf = wide.fill_null("-").to_pandas().set_index("latent_dim")
|
||||
|
||||
# Order columns if both exist
|
||||
desired = [
|
||||
c for c in ["Autoencoder LeNet", "Autoencoder Efficient"] if c in pdf.columns
|
||||
]
|
||||
if desired:
|
||||
pdf = pdf.reindex(columns=desired)
|
||||
|
||||
latex = pdf.to_latex(
|
||||
index=True,
|
||||
escape=True,
|
||||
na_rep="-",
|
||||
multicolumn=True,
|
||||
multicolumn_format="c",
|
||||
bold_rows=False,
|
||||
caption="Autoencoder pretraining runtime (seconds): mean ± std across folds.",
|
||||
label="tab:ae_pretrain_runtimes",
|
||||
)
|
||||
return latex
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# Main
|
||||
# ----------------------------
|
||||
def main():
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Main results
|
||||
df = load_results_dataframe(RESULTS_ROOT, allow_cache=True)
|
||||
if "config_json" not in df.columns:
|
||||
df = df.with_columns(pl.lit(None).alias("config_json"))
|
||||
|
||||
# AE pretraining results
|
||||
df_pre = load_pretraining_results_dataframe(RESULTS_ROOT, allow_cache=True)
|
||||
|
||||
# Build LaTeX tables
|
||||
latex_train = make_training_runtime_table(df)
|
||||
latex_test = make_inference_runtime_table(df)
|
||||
latex_train_params = make_longform_train_table_with_params(df)
|
||||
latex_ae = make_ae_pretraining_runtime_table(df_pre)
|
||||
|
||||
# Timestamped output dirs
|
||||
ts = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||
ts_dir = OUTPUT_DIR / "archive" / ts
|
||||
ts_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Write files
|
||||
(ts_dir / "train_runtimes.tex").write_text(latex_train)
|
||||
(ts_dir / "test_runtimes.tex").write_text(latex_test)
|
||||
(ts_dir / "train_runtimes_with_params.tex").write_text(latex_train_params)
|
||||
(ts_dir / "ae_pretraining_runtimes.tex").write_text(latex_ae)
|
||||
|
||||
# Save script & mirror latest
|
||||
script_path = Path(__file__)
|
||||
shutil.copy2(script_path, ts_dir)
|
||||
|
||||
latest = OUTPUT_DIR / "latest"
|
||||
latest.mkdir(exist_ok=True, parents=True)
|
||||
for f in ts_dir.iterdir():
|
||||
if f.is_file():
|
||||
shutil.copy2(f, latest / f.name)
|
||||
|
||||
print(f"Saved LaTeX tables to: {ts_dir}")
|
||||
print(f"Also updated: {latest}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
20
tools/print_mat.py
Normal file
20
tools/print_mat.py
Normal file
@@ -0,0 +1,20 @@
|
||||
rows = 5
|
||||
cols = 4
|
||||
|
||||
mat = [range(0 + (cols * i), cols + (cols * i), 1) for i in range(rows)]
|
||||
|
||||
|
||||
def print_mat(mat):
|
||||
for s in mat:
|
||||
print(*s)
|
||||
|
||||
|
||||
def rotate_mat(mat):
|
||||
mat = [[mat[row][col] for row in range(rows - 1, -1, -1)] for col in range(cols)]
|
||||
return mat
|
||||
|
||||
|
||||
print_mat(mat)
|
||||
mat = rotate_mat(mat)
|
||||
print("rotated:")
|
||||
print_mat(mat)
|
||||
@@ -5,5 +5,7 @@ description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11.9"
|
||||
dependencies = [
|
||||
"pandas>=2.3.2",
|
||||
"polars>=1.33.0",
|
||||
"pyarrow>=21.0.0",
|
||||
]
|
||||
|
||||
209
tools/uv.lock
generated
209
tools/uv.lock
generated
@@ -1,6 +1,132 @@
|
||||
version = 1
|
||||
revision = 2
|
||||
requires-python = ">=3.11.9"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.12'",
|
||||
"python_full_version < '3.12'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.3.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", size = 20576648, upload-time = "2025-09-09T16:54:12.543Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/45/e80d203ef6b267aa29b22714fb558930b27960a0c5ce3c19c999232bb3eb/numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ffc4f5caba7dfcbe944ed674b7eef683c7e94874046454bb79ed7ee0236f59d", size = 21259253, upload-time = "2025-09-09T15:56:02.094Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/18/cf2c648fccf339e59302e00e5f2bc87725a3ce1992f30f3f78c9044d7c43/numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7e946c7170858a0295f79a60214424caac2ffdb0063d4d79cb681f9aa0aa569", size = 14450980, upload-time = "2025-09-09T15:56:05.926Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/fb/9af1082bec870188c42a1c239839915b74a5099c392389ff04215dcee812/numpy-2.3.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cd4260f64bc794c3390a63bf0728220dd1a68170c169088a1e0dfa2fde1be12f", size = 5379709, upload-time = "2025-09-09T15:56:07.95Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/0f/bfd7abca52bcbf9a4a65abc83fe18ef01ccdeb37bfb28bbd6ad613447c79/numpy-2.3.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f0ddb4b96a87b6728df9362135e764eac3cfa674499943ebc44ce96c478ab125", size = 6913923, upload-time = "2025-09-09T15:56:09.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/55/d69adad255e87ab7afda1caf93ca997859092afeb697703e2f010f7c2e55/numpy-2.3.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afd07d377f478344ec6ca2b8d4ca08ae8bd44706763d1efb56397de606393f48", size = 14589591, upload-time = "2025-09-09T15:56:11.234Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/a2/010b0e27ddeacab7839957d7a8f00e91206e0c2c47abbb5f35a2630e5387/numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc92a5dedcc53857249ca51ef29f5e5f2f8c513e22cfb90faeb20343b8c6f7a6", size = 16938714, upload-time = "2025-09-09T15:56:14.637Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/6b/12ce8ede632c7126eb2762b9e15e18e204b81725b81f35176eac14dc5b82/numpy-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7af05ed4dc19f308e1d9fc759f36f21921eb7bbfc82843eeec6b2a2863a0aefa", size = 16370592, upload-time = "2025-09-09T15:56:17.285Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/35/aba8568b2593067bb6a8fe4c52babb23b4c3b9c80e1b49dff03a09925e4a/numpy-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:433bf137e338677cebdd5beac0199ac84712ad9d630b74eceeb759eaa45ddf30", size = 18884474, upload-time = "2025-09-09T15:56:20.943Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/fa/7f43ba10c77575e8be7b0138d107e4f44ca4a1ef322cd16980ea3e8b8222/numpy-2.3.3-cp311-cp311-win32.whl", hash = "sha256:eb63d443d7b4ffd1e873f8155260d7f58e7e4b095961b01c91062935c2491e57", size = 6599794, upload-time = "2025-09-09T15:56:23.258Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/a2/a4f78cb2241fe5664a22a10332f2be886dcdea8784c9f6a01c272da9b426/numpy-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:ec9d249840f6a565f58d8f913bccac2444235025bbb13e9a4681783572ee3caa", size = 13088104, upload-time = "2025-09-09T15:56:25.476Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/64/e424e975adbd38282ebcd4891661965b78783de893b381cbc4832fb9beb2/numpy-2.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:74c2a948d02f88c11a3c075d9733f1ae67d97c6bdb97f2bb542f980458b257e7", size = 10460772, upload-time = "2025-09-09T15:56:27.679Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/5d/bb7fc075b762c96329147799e1bcc9176ab07ca6375ea976c475482ad5b3/numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf", size = 20957014, upload-time = "2025-09-09T15:56:29.966Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/0e/c6211bb92af26517acd52125a237a92afe9c3124c6a68d3b9f81b62a0568/numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25", size = 14185220, upload-time = "2025-09-09T15:56:32.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/f2/07bb754eb2ede9073f4054f7c0286b0d9d2e23982e090a80d478b26d35ca/numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe", size = 5113918, upload-time = "2025-09-09T15:56:34.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/0a/afa51697e9fb74642f231ea36aca80fa17c8fb89f7a82abd5174023c3960/numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b", size = 6647922, upload-time = "2025-09-09T15:56:36.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/f5/122d9cdb3f51c520d150fef6e87df9279e33d19a9611a87c0d2cf78a89f4/numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8", size = 14281991, upload-time = "2025-09-09T15:56:40.548Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/64/7de3c91e821a2debf77c92962ea3fe6ac2bc45d0778c1cbe15d4fce2fd94/numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20", size = 16641643, upload-time = "2025-09-09T15:56:43.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/e4/961a5fa681502cd0d68907818b69f67542695b74e3ceaa513918103b7e80/numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea", size = 16056787, upload-time = "2025-09-09T15:56:46.141Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/26/92c912b966e47fbbdf2ad556cb17e3a3088e2e1292b9833be1dfa5361a1a/numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7", size = 18579598, upload-time = "2025-09-09T15:56:49.844Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/b6/fc8f82cb3520768718834f310c37d96380d9dc61bfdaf05fe5c0b7653e01/numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf", size = 6320800, upload-time = "2025-09-09T15:56:52.499Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/ee/de999f2625b80d043d6d2d628c07d0d5555a677a3cf78fdf868d409b8766/numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb", size = 12786615, upload-time = "2025-09-09T15:56:54.422Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/6e/b479032f8a43559c383acb20816644f5f91c88f633d9271ee84f3b3a996c/numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5", size = 10195936, upload-time = "2025-09-09T15:56:56.541Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/b9/984c2b1ee61a8b803bf63582b4ac4242cf76e2dbd663efeafcb620cc0ccb/numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f5415fb78995644253370985342cd03572ef8620b934da27d77377a2285955bf", size = 20949588, upload-time = "2025-09-09T15:56:59.087Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/e4/07970e3bed0b1384d22af1e9912527ecbeb47d3b26e9b6a3bced068b3bea/numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d00de139a3324e26ed5b95870ce63be7ec7352171bc69a4cf1f157a48e3eb6b7", size = 14177802, upload-time = "2025-09-09T15:57:01.73Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/c7/477a83887f9de61f1203bad89cf208b7c19cc9fef0cebef65d5a1a0619f2/numpy-2.3.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9dc13c6a5829610cc07422bc74d3ac083bd8323f14e2827d992f9e52e22cd6a6", size = 5106537, upload-time = "2025-09-09T15:57:03.765Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/47/93b953bd5866a6f6986344d045a207d3f1cfbad99db29f534ea9cee5108c/numpy-2.3.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d79715d95f1894771eb4e60fb23f065663b2298f7d22945d66877aadf33d00c7", size = 6640743, upload-time = "2025-09-09T15:57:07.921Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/83/377f84aaeb800b64c0ef4de58b08769e782edcefa4fea712910b6f0afd3c/numpy-2.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:952cfd0748514ea7c3afc729a0fc639e61655ce4c55ab9acfab14bda4f402b4c", size = 14278881, upload-time = "2025-09-09T15:57:11.349Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b83648633d46f77039c29078751f80da65aa64d5622a3cd62aaef9d835b6c93", size = 16636301, upload-time = "2025-09-09T15:57:14.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/59/1287924242eb4fa3f9b3a2c30400f2e17eb2707020d1c5e3086fe7330717/numpy-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b001bae8cea1c7dfdb2ae2b017ed0a6f2102d7a70059df1e338e307a4c78a8ae", size = 16053645, upload-time = "2025-09-09T15:57:16.534Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/93/b3d47ed882027c35e94ac2320c37e452a549f582a5e801f2d34b56973c97/numpy-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e9aced64054739037d42fb84c54dd38b81ee238816c948c8f3ed134665dcd86", size = 18578179, upload-time = "2025-09-09T15:57:18.883Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/d9/487a2bccbf7cc9d4bfc5f0f197761a5ef27ba870f1e3bbb9afc4bbe3fcc2/numpy-2.3.3-cp313-cp313-win32.whl", hash = "sha256:9591e1221db3f37751e6442850429b3aabf7026d3b05542d102944ca7f00c8a8", size = 6312250, upload-time = "2025-09-09T15:57:21.296Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/b5/263ebbbbcede85028f30047eab3d58028d7ebe389d6493fc95ae66c636ab/numpy-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f0dadeb302887f07431910f67a14d57209ed91130be0adea2f9793f1a4f817cf", size = 12783269, upload-time = "2025-09-09T15:57:23.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/75/67b8ca554bbeaaeb3fac2e8bce46967a5a06544c9108ec0cf5cece559b6c/numpy-2.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:3c7cf302ac6e0b76a64c4aecf1a09e51abd9b01fc7feee80f6c43e3ab1b1dbc5", size = 10195314, upload-time = "2025-09-09T15:57:25.045Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/d0/0d1ddec56b162042ddfafeeb293bac672de9b0cfd688383590090963720a/numpy-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eda59e44957d272846bb407aad19f89dc6f58fecf3504bd144f4c5cf81a7eacc", size = 21048025, upload-time = "2025-09-09T15:57:27.257Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/9e/1996ca6b6d00415b6acbdd3c42f7f03ea256e2c3f158f80bd7436a8a19f3/numpy-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:823d04112bc85ef5c4fda73ba24e6096c8f869931405a80aa8b0e604510a26bc", size = 14301053, upload-time = "2025-09-09T15:57:30.077Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/24/43da09aa764c68694b76e84b3d3f0c44cb7c18cdc1ba80e48b0ac1d2cd39/numpy-2.3.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:40051003e03db4041aa325da2a0971ba41cf65714e65d296397cc0e32de6018b", size = 5229444, upload-time = "2025-09-09T15:57:32.733Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/14/50ffb0f22f7218ef8af28dd089f79f68289a7a05a208db9a2c5dcbe123c1/numpy-2.3.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ee9086235dd6ab7ae75aba5662f582a81ced49f0f1c6de4260a78d8f2d91a19", size = 6738039, upload-time = "2025-09-09T15:57:34.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/52/af46ac0795e09657d45a7f4db961917314377edecf66db0e39fa7ab5c3d3/numpy-2.3.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94fcaa68757c3e2e668ddadeaa86ab05499a70725811e582b6a9858dd472fb30", size = 14352314, upload-time = "2025-09-09T15:57:36.255Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/b1/dc226b4c90eb9f07a3fff95c2f0db3268e2e54e5cce97c4ac91518aee71b/numpy-2.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da1a74b90e7483d6ce5244053399a614b1d6b7bc30a60d2f570e5071f8959d3e", size = 16701722, upload-time = "2025-09-09T15:57:38.622Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/9d/9d8d358f2eb5eced14dba99f110d83b5cd9a4460895230f3b396ad19a323/numpy-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2990adf06d1ecee3b3dcbb4977dfab6e9f09807598d647f04d385d29e7a3c3d3", size = 16132755, upload-time = "2025-09-09T15:57:41.16Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/27/b3922660c45513f9377b3fb42240bec63f203c71416093476ec9aa0719dc/numpy-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ed635ff692483b8e3f0fcaa8e7eb8a75ee71aa6d975388224f70821421800cea", size = 18651560, upload-time = "2025-09-09T15:57:43.459Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/8e/3ab61a730bdbbc201bb245a71102aa609f0008b9ed15255500a99cd7f780/numpy-2.3.3-cp313-cp313t-win32.whl", hash = "sha256:a333b4ed33d8dc2b373cc955ca57babc00cd6f9009991d9edc5ddbc1bac36bcd", size = 6442776, upload-time = "2025-09-09T15:57:45.793Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/3a/e22b766b11f6030dc2decdeff5c2fb1610768055603f9f3be88b6d192fb2/numpy-2.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4384a169c4d8f97195980815d6fcad04933a7e1ab3b530921c3fef7a1c63426d", size = 12927281, upload-time = "2025-09-09T15:57:47.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/42/c2e2bc48c5e9b2a83423f99733950fbefd86f165b468a3d85d52b30bf782/numpy-2.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:75370986cc0bc66f4ce5110ad35aae6d182cc4ce6433c40ad151f53690130bf1", size = 10265275, upload-time = "2025-09-09T15:57:49.647Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/01/342ad585ad82419b99bcf7cebe99e61da6bedb89e213c5fd71acc467faee/numpy-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cd052f1fa6a78dee696b58a914b7229ecfa41f0a6d96dc663c1220a55e137593", size = 20951527, upload-time = "2025-09-09T15:57:52.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/d8/204e0d73fc1b7a9ee80ab1fe1983dd33a4d64a4e30a05364b0208e9a241a/numpy-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:414a97499480067d305fcac9716c29cf4d0d76db6ebf0bf3cbce666677f12652", size = 14186159, upload-time = "2025-09-09T15:57:54.407Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/af/f11c916d08f3a18fb8ba81ab72b5b74a6e42ead4c2846d270eb19845bf74/numpy-2.3.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:50a5fe69f135f88a2be9b6ca0481a68a136f6febe1916e4920e12f1a34e708a7", size = 5114624, upload-time = "2025-09-09T15:57:56.5Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/11/0ed919c8381ac9d2ffacd63fd1f0c34d27e99cab650f0eb6f110e6ae4858/numpy-2.3.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:b912f2ed2b67a129e6a601e9d93d4fa37bef67e54cac442a2f588a54afe5c67a", size = 6642627, upload-time = "2025-09-09T15:57:58.206Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/83/deb5f77cb0f7ba6cb52b91ed388b47f8f3c2e9930d4665c600408d9b90b9/numpy-2.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e318ee0596d76d4cb3d78535dc005fa60e5ea348cd131a51e99d0bdbe0b54fe", size = 14296926, upload-time = "2025-09-09T15:58:00.035Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/cc/70e59dcb84f2b005d4f306310ff0a892518cc0c8000a33d0e6faf7ca8d80/numpy-2.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce020080e4a52426202bdb6f7691c65bb55e49f261f31a8f506c9f6bc7450421", size = 16638958, upload-time = "2025-09-09T15:58:02.738Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/5a/b2ab6c18b4257e099587d5b7f903317bd7115333ad8d4ec4874278eafa61/numpy-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6687dc183aa55dae4a705b35f9c0f8cb178bcaa2f029b241ac5356221d5c021", size = 16071920, upload-time = "2025-09-09T15:58:05.029Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/f1/8b3fdc44324a259298520dd82147ff648979bed085feeacc1250ef1656c0/numpy-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d8f3b1080782469fdc1718c4ed1d22549b5fb12af0d57d35e992158a772a37cf", size = 18577076, upload-time = "2025-09-09T15:58:07.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/a1/b87a284fb15a42e9274e7fcea0dad259d12ddbf07c1595b26883151ca3b4/numpy-2.3.3-cp314-cp314-win32.whl", hash = "sha256:cb248499b0bc3be66ebd6578b83e5acacf1d6cb2a77f2248ce0e40fbec5a76d0", size = 6366952, upload-time = "2025-09-09T15:58:10.096Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/5f/1816f4d08f3b8f66576d8433a66f8fa35a5acfb3bbd0bf6c31183b003f3d/numpy-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:691808c2b26b0f002a032c73255d0bd89751425f379f7bcd22d140db593a96e8", size = 12919322, upload-time = "2025-09-09T15:58:12.138Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/de/072420342e46a8ea41c324a555fa90fcc11637583fb8df722936aed1736d/numpy-2.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:9ad12e976ca7b10f1774b03615a2a4bab8addce37ecc77394d8e986927dc0dfe", size = 10478630, upload-time = "2025-09-09T15:58:14.64Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/df/ee2f1c0a9de7347f14da5dd3cd3c3b034d1b8607ccb6883d7dd5c035d631/numpy-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9cc48e09feb11e1db00b320e9d30a4151f7369afb96bd0e48d942d09da3a0d00", size = 21047987, upload-time = "2025-09-09T15:58:16.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/92/9453bdc5a4e9e69cf4358463f25e8260e2ffc126d52e10038b9077815989/numpy-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:901bf6123879b7f251d3631967fd574690734236075082078e0571977c6a8e6a", size = 14301076, upload-time = "2025-09-09T15:58:20.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/77/1447b9eb500f028bb44253105bd67534af60499588a5149a94f18f2ca917/numpy-2.3.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:7f025652034199c301049296b59fa7d52c7e625017cae4c75d8662e377bf487d", size = 5229491, upload-time = "2025-09-09T15:58:22.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/f9/d72221b6ca205f9736cb4b2ce3b002f6e45cd67cd6a6d1c8af11a2f0b649/numpy-2.3.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:533ca5f6d325c80b6007d4d7fb1984c303553534191024ec6a524a4c92a5935a", size = 6737913, upload-time = "2025-09-09T15:58:24.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/5f/d12834711962ad9c46af72f79bb31e73e416ee49d17f4c797f72c96b6ca5/numpy-2.3.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0edd58682a399824633b66885d699d7de982800053acf20be1eaa46d92009c54", size = 14352811, upload-time = "2025-09-09T15:58:26.416Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/0d/fdbec6629d97fd1bebed56cd742884e4eead593611bbe1abc3eb40d304b2/numpy-2.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:367ad5d8fbec5d9296d18478804a530f1191e24ab4d75ab408346ae88045d25e", size = 16702689, upload-time = "2025-09-09T15:58:28.831Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/09/0a35196dc5575adde1eb97ddfbc3e1687a814f905377621d18ca9bc2b7dd/numpy-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8f6ac61a217437946a1fa48d24c47c91a0c4f725237871117dea264982128097", size = 16133855, upload-time = "2025-09-09T15:58:31.349Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/ca/c9de3ea397d576f1b6753eaa906d4cdef1bf97589a6d9825a349b4729cc2/numpy-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:179a42101b845a816d464b6fe9a845dfaf308fdfc7925387195570789bb2c970", size = 18652520, upload-time = "2025-09-09T15:58:33.762Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/c2/e5ed830e08cd0196351db55db82f65bc0ab05da6ef2b72a836dcf1936d2f/numpy-2.3.3-cp314-cp314t-win32.whl", hash = "sha256:1250c5d3d2562ec4174bce2e3a1523041595f9b651065e4a4473f5f48a6bc8a5", size = 6515371, upload-time = "2025-09-09T15:58:36.04Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/c7/b0f6b5b67f6788a0725f744496badbb604d226bf233ba716683ebb47b570/numpy-2.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b37a0b2e5935409daebe82c1e42274d30d9dd355852529eab91dab8dcca7419f", size = 13112576, upload-time = "2025-09-09T15:58:37.927Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/b9/33bba5ff6fb679aa0b1f8a07e853f002a6b04b9394db3069a1270a7784ca/numpy-2.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:78c9f6560dc7e6b3990e32df7ea1a50bbd0e2a111e05209963f5ddcab7073b0b", size = 10545953, upload-time = "2025-09-09T15:58:40.576Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/f2/7e0a37cfced2644c9563c529f29fa28acbd0960dde32ece683aafa6f4949/numpy-2.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1e02c7159791cd481e1e6d5ddd766b62a4d5acf8df4d4d1afe35ee9c5c33a41e", size = 21131019, upload-time = "2025-09-09T15:58:42.838Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/7e/3291f505297ed63831135a6cc0f474da0c868a1f31b0dd9a9f03a7a0d2ed/numpy-2.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:dca2d0fc80b3893ae72197b39f69d55a3cd8b17ea1b50aa4c62de82419936150", size = 14376288, upload-time = "2025-09-09T15:58:45.425Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/4b/ae02e985bdeee73d7b5abdefeb98aef1207e96d4c0621ee0cf228ddfac3c/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:99683cbe0658f8271b333a1b1b4bb3173750ad59c0c61f5bbdc5b318918fffe3", size = 5305425, upload-time = "2025-09-09T15:58:48.6Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/eb/9df215d6d7250db32007941500dc51c48190be25f2401d5b2b564e467247/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d9d537a39cc9de668e5cd0e25affb17aec17b577c6b3ae8a3d866b479fbe88d0", size = 6819053, upload-time = "2025-09-09T15:58:50.401Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/62/208293d7d6b2a8998a4a1f23ac758648c3c32182d4ce4346062018362e29/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8596ba2f8af5f93b01d97563832686d20206d303024777f6dfc2e7c7c3f1850e", size = 14420354, upload-time = "2025-09-09T15:58:52.704Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/0c/8e86e0ff7072e14a71b4c6af63175e40d1e7e933ce9b9e9f765a95b4e0c3/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1ec5615b05369925bd1125f27df33f3b6c8bc10d788d5999ecd8769a1fa04db", size = 16760413, upload-time = "2025-09-09T15:58:55.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/11/0cc63f9f321ccf63886ac203336777140011fb669e739da36d8db3c53b98/numpy-2.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2e267c7da5bf7309670523896df97f93f6e469fb931161f483cd6882b3b1a5dc", size = 12971844, upload-time = "2025-09-09T15:58:57.359Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pandas"
|
||||
version = "2.3.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "pytz" },
|
||||
{ name = "tzdata" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/79/8e/0e90233ac205ad182bd6b422532695d2b9414944a280488105d598c70023/pandas-2.3.2.tar.gz", hash = "sha256:ab7b58f8f82706890924ccdfb5f48002b83d2b5a3845976a9fb705d36c34dcdb", size = 4488684, upload-time = "2025-08-21T10:28:29.257Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/59/f3e010879f118c2d400902d2d871c2226cef29b08c09fb8dc41111730400/pandas-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1333e9c299adcbb68ee89a9bb568fc3f20f9cbb419f1dd5225071e6cddb2a743", size = 11563308, upload-time = "2025-08-21T10:26:56.656Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/18/48f10f1cc5c397af59571d638d211f494dba481f449c19adbd282aa8f4ca/pandas-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76972bcbd7de8e91ad5f0ca884a9f2c477a2125354af624e022c49e5bd0dfff4", size = 10820319, upload-time = "2025-08-21T10:26:59.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/3b/1e9b69632898b048e223834cd9702052bcf06b15e1ae716eda3196fb972e/pandas-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b98bdd7c456a05eef7cd21fd6b29e3ca243591fe531c62be94a2cc987efb5ac2", size = 11790097, upload-time = "2025-08-21T10:27:02.204Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/ef/0e2ffb30b1f7fbc9a588bd01e3c14a0d96854d09a887e15e30cc19961227/pandas-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d81573b3f7db40d020983f78721e9bfc425f411e616ef019a10ebf597aedb2e", size = 12397958, upload-time = "2025-08-21T10:27:05.409Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/82/e6b85f0d92e9afb0e7f705a51d1399b79c7380c19687bfbf3d2837743249/pandas-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e190b738675a73b581736cc8ec71ae113d6c3768d0bd18bffa5b9a0927b0b6ea", size = 13225600, upload-time = "2025-08-21T10:27:07.791Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/f1/f682015893d9ed51611948bd83683670842286a8edd4f68c2c1c3b231eef/pandas-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c253828cb08f47488d60f43c5fc95114c771bbfff085da54bfc79cb4f9e3a372", size = 13879433, upload-time = "2025-08-21T10:27:10.347Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/e7/ae86261695b6c8a36d6a4c8d5f9b9ede8248510d689a2f379a18354b37d7/pandas-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:9467697b8083f9667b212633ad6aa4ab32436dcbaf4cd57325debb0ddef2012f", size = 11336557, upload-time = "2025-08-21T10:27:12.983Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/db/614c20fb7a85a14828edd23f1c02db58a30abf3ce76f38806155d160313c/pandas-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fbb977f802156e7a3f829e9d1d5398f6192375a3e2d1a9ee0803e35fe70a2b9", size = 11587652, upload-time = "2025-08-21T10:27:15.888Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/b0/756e52f6582cade5e746f19bad0517ff27ba9c73404607c0306585c201b3/pandas-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b9b52693123dd234b7c985c68b709b0b009f4521000d0525f2b95c22f15944b", size = 10717686, upload-time = "2025-08-21T10:27:18.486Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/4c/dd5ccc1e357abfeee8353123282de17997f90ff67855f86154e5a13b81e5/pandas-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bd281310d4f412733f319a5bc552f86d62cddc5f51d2e392c8787335c994175", size = 11278722, upload-time = "2025-08-21T10:27:21.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/a4/f7edcfa47e0a88cda0be8b068a5bae710bf264f867edfdf7b71584ace362/pandas-2.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96d31a6b4354e3b9b8a2c848af75d31da390657e3ac6f30c05c82068b9ed79b9", size = 11987803, upload-time = "2025-08-21T10:27:23.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/61/1bce4129f93ab66f1c68b7ed1c12bac6a70b1b56c5dab359c6bbcd480b52/pandas-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:df4df0b9d02bb873a106971bb85d448378ef14b86ba96f035f50bbd3688456b4", size = 12766345, upload-time = "2025-08-21T10:27:26.6Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/46/80d53de70fee835531da3a1dae827a1e76e77a43ad22a8cd0f8142b61587/pandas-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:213a5adf93d020b74327cb2c1b842884dbdd37f895f42dcc2f09d451d949f811", size = 13439314, upload-time = "2025-08-21T10:27:29.213Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/30/8114832daff7489f179971dbc1d854109b7f4365a546e3ea75b6516cea95/pandas-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c13b81a9347eb8c7548f53fd9a4f08d4dfe996836543f805c987bafa03317ae", size = 10983326, upload-time = "2025-08-21T10:27:31.901Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/64/a2f7bf678af502e16b472527735d168b22b7824e45a4d7e96a4fbb634b59/pandas-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c6ecbac99a354a051ef21c5307601093cb9e0f4b1855984a084bfec9302699e", size = 11531061, upload-time = "2025-08-21T10:27:34.647Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/4c/c3d21b2b7769ef2f4c2b9299fcadd601efa6729f1357a8dbce8dd949ed70/pandas-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6f048aa0fd080d6a06cc7e7537c09b53be6642d330ac6f54a600c3ace857ee9", size = 10668666, upload-time = "2025-08-21T10:27:37.203Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/e2/f775ba76ecfb3424d7f5862620841cf0edb592e9abd2d2a5387d305fe7a8/pandas-2.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0064187b80a5be6f2f9c9d6bdde29372468751dfa89f4211a3c5871854cfbf7a", size = 11332835, upload-time = "2025-08-21T10:27:40.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/52/0634adaace9be2d8cac9ef78f05c47f3a675882e068438b9d7ec7ef0c13f/pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac8c320bded4718b298281339c1a50fb00a6ba78cb2a63521c39bec95b0209b", size = 12057211, upload-time = "2025-08-21T10:27:43.117Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/9d/2df913f14b2deb9c748975fdb2491da1a78773debb25abbc7cbc67c6b549/pandas-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:114c2fe4f4328cf98ce5716d1532f3ab79c5919f95a9cfee81d9140064a2e4d6", size = 12749277, upload-time = "2025-08-21T10:27:45.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/af/da1a2417026bd14d98c236dba88e39837182459d29dcfcea510b2ac9e8a1/pandas-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:48fa91c4dfb3b2b9bfdb5c24cd3567575f4e13f9636810462ffed8925352be5a", size = 13415256, upload-time = "2025-08-21T10:27:49.885Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/3c/f2af1ce8840ef648584a6156489636b5692c162771918aa95707c165ad2b/pandas-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:12d039facec710f7ba305786837d0225a3444af7bbd9c15c32ca2d40d157ed8b", size = 10982579, upload-time = "2025-08-21T10:28:08.435Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/98/8df69c4097a6719e357dc249bf437b8efbde808038268e584421696cbddf/pandas-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c624b615ce97864eb588779ed4046186f967374185c047070545253a52ab2d57", size = 12028163, upload-time = "2025-08-21T10:27:52.232Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/23/f95cbcbea319f349e10ff90db488b905c6883f03cbabd34f6b03cbc3c044/pandas-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0cee69d583b9b128823d9514171cabb6861e09409af805b54459bd0c821a35c2", size = 11391860, upload-time = "2025-08-21T10:27:54.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/1b/6a984e98c4abee22058aa75bfb8eb90dce58cf8d7296f8bc56c14bc330b0/pandas-2.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2319656ed81124982900b4c37f0e0c58c015af9a7bbc62342ba5ad07ace82ba9", size = 11309830, upload-time = "2025-08-21T10:27:56.957Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/d5/f0486090eb18dd8710bf60afeaf638ba6817047c0c8ae5c6a25598665609/pandas-2.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b37205ad6f00d52f16b6d09f406434ba928c1a1966e2771006a9033c736d30d2", size = 11883216, upload-time = "2025-08-21T10:27:59.302Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/86/692050c119696da19e20245bbd650d8dfca6ceb577da027c3a73c62a047e/pandas-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:837248b4fc3a9b83b9c6214699a13f069dc13510a6a6d7f9ba33145d2841a012", size = 12699743, upload-time = "2025-08-21T10:28:02.447Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/d7/612123674d7b17cf345aad0a10289b2a384bff404e0463a83c4a3a59d205/pandas-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d2c3554bd31b731cd6490d94a28f3abb8dd770634a9e06eb6d2911b9827db370", size = 13186141, upload-time = "2025-08-21T10:28:05.377Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polars"
|
||||
@@ -16,13 +142,94 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/58/13/824a81b43199202fc859c24515cd5b227930d6dce0dea488e4b415edbaba/polars-1.33.0-cp39-abi3-win_arm64.whl", hash = "sha256:c7d614644eda028907965f8203ac54b9a4f5b90303de2723bf1c1087433a0914", size = 35033820, upload-time = "2025-09-01T16:32:08.116Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyarrow"
|
||||
version = "21.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ef/c2/ea068b8f00905c06329a3dfcd40d0fcc2b7d0f2e355bdb25b65e0a0e4cd4/pyarrow-21.0.0.tar.gz", hash = "sha256:5051f2dccf0e283ff56335760cbc8622cf52264d67e359d5569541ac11b6d5bc", size = 1133487, upload-time = "2025-07-18T00:57:31.761Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/94/dc/80564a3071a57c20b7c32575e4a0120e8a330ef487c319b122942d665960/pyarrow-21.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c077f48aab61738c237802836fc3844f85409a46015635198761b0d6a688f87b", size = 31243234, upload-time = "2025-07-18T00:55:03.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/cc/3b51cb2db26fe535d14f74cab4c79b191ed9a8cd4cbba45e2379b5ca2746/pyarrow-21.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:689f448066781856237eca8d1975b98cace19b8dd2ab6145bf49475478bcaa10", size = 32714370, upload-time = "2025-07-18T00:55:07.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/11/a4431f36d5ad7d83b87146f515c063e4d07ef0b7240876ddb885e6b44f2e/pyarrow-21.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:479ee41399fcddc46159a551705b89c05f11e8b8cb8e968f7fec64f62d91985e", size = 41135424, upload-time = "2025-07-18T00:55:11.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/dc/035d54638fc5d2971cbf1e987ccd45f1091c83bcf747281cf6cc25e72c88/pyarrow-21.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:40ebfcb54a4f11bcde86bc586cbd0272bac0d516cfa539c799c2453768477569", size = 42823810, upload-time = "2025-07-18T00:55:16.301Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/3b/89fced102448a9e3e0d4dded1f37fa3ce4700f02cdb8665457fcc8015f5b/pyarrow-21.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8d58d8497814274d3d20214fbb24abcad2f7e351474357d552a8d53bce70c70e", size = 43391538, upload-time = "2025-07-18T00:55:23.82Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/bb/ea7f1bd08978d39debd3b23611c293f64a642557e8141c80635d501e6d53/pyarrow-21.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:585e7224f21124dd57836b1530ac8f2df2afc43c861d7bf3d58a4870c42ae36c", size = 45120056, upload-time = "2025-07-18T00:55:28.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/0b/77ea0600009842b30ceebc3337639a7380cd946061b620ac1a2f3cb541e2/pyarrow-21.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:555ca6935b2cbca2c0e932bedd853e9bc523098c39636de9ad4693b5b1df86d6", size = 26220568, upload-time = "2025-07-18T00:55:32.122Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/d4/d4f817b21aacc30195cf6a46ba041dd1be827efa4a623cc8bf39a1c2a0c0/pyarrow-21.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:3a302f0e0963db37e0a24a70c56cf91a4faa0bca51c23812279ca2e23481fccd", size = 31160305, upload-time = "2025-07-18T00:55:35.373Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/9c/dcd38ce6e4b4d9a19e1d36914cb8e2b1da4e6003dd075474c4cfcdfe0601/pyarrow-21.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:b6b27cf01e243871390474a211a7922bfbe3bda21e39bc9160daf0da3fe48876", size = 32684264, upload-time = "2025-07-18T00:55:39.303Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/74/2a2d9f8d7a59b639523454bec12dba35ae3d0a07d8ab529dc0809f74b23c/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e72a8ec6b868e258a2cd2672d91f2860ad532d590ce94cdf7d5e7ec674ccf03d", size = 41108099, upload-time = "2025-07-18T00:55:42.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/90/2660332eeb31303c13b653ea566a9918484b6e4d6b9d2d46879a33ab0622/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b7ae0bbdc8c6674259b25bef5d2a1d6af5d39d7200c819cf99e07f7dfef1c51e", size = 42829529, upload-time = "2025-07-18T00:55:47.069Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/27/1a93a25c92717f6aa0fca06eb4700860577d016cd3ae51aad0e0488ac899/pyarrow-21.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:58c30a1729f82d201627c173d91bd431db88ea74dcaa3885855bc6203e433b82", size = 43367883, upload-time = "2025-07-18T00:55:53.069Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/d9/4d09d919f35d599bc05c6950095e358c3e15148ead26292dfca1fb659b0c/pyarrow-21.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:072116f65604b822a7f22945a7a6e581cfa28e3454fdcc6939d4ff6090126623", size = 45133802, upload-time = "2025-07-18T00:55:57.714Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/30/f3795b6e192c3ab881325ffe172e526499eb3780e306a15103a2764916a2/pyarrow-21.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf56ec8b0a5c8c9d7021d6fd754e688104f9ebebf1bf4449613c9531f5346a18", size = 26203175, upload-time = "2025-07-18T00:56:01.364Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/ca/c7eaa8e62db8fb37ce942b1ea0c6d7abfe3786ca193957afa25e71b81b66/pyarrow-21.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e99310a4ebd4479bcd1964dff9e14af33746300cb014aa4a3781738ac63baf4a", size = 31154306, upload-time = "2025-07-18T00:56:04.42Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/e8/e87d9e3b2489302b3a1aea709aaca4b781c5252fcb812a17ab6275a9a484/pyarrow-21.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:d2fe8e7f3ce329a71b7ddd7498b3cfac0eeb200c2789bd840234f0dc271a8efe", size = 32680622, upload-time = "2025-07-18T00:56:07.505Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/52/79095d73a742aa0aba370c7942b1b655f598069489ab387fe47261a849e1/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f522e5709379d72fb3da7785aa489ff0bb87448a9dc5a75f45763a795a089ebd", size = 41104094, upload-time = "2025-07-18T00:56:10.994Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/4b/7782438b551dbb0468892a276b8c789b8bbdb25ea5c5eb27faadd753e037/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:69cbbdf0631396e9925e048cfa5bce4e8c3d3b41562bbd70c685a8eb53a91e61", size = 42825576, upload-time = "2025-07-18T00:56:15.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/62/0f29de6e0a1e33518dec92c65be0351d32d7ca351e51ec5f4f837a9aab91/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:731c7022587006b755d0bdb27626a1a3bb004bb56b11fb30d98b6c1b4718579d", size = 43368342, upload-time = "2025-07-18T00:56:19.531Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/c7/0fa1f3f29cf75f339768cc698c8ad4ddd2481c1742e9741459911c9ac477/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc56bc708f2d8ac71bd1dcb927e458c93cec10b98eb4120206a4091db7b67b99", size = 45131218, upload-time = "2025-07-18T00:56:23.347Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/63/581f2076465e67b23bc5a37d4a2abff8362d389d29d8105832e82c9c811c/pyarrow-21.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:186aa00bca62139f75b7de8420f745f2af12941595bbbfa7ed3870ff63e25636", size = 26087551, upload-time = "2025-07-18T00:56:26.758Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/ab/357d0d9648bb8241ee7348e564f2479d206ebe6e1c47ac5027c2e31ecd39/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:a7a102574faa3f421141a64c10216e078df467ab9576684d5cd696952546e2da", size = 31290064, upload-time = "2025-07-18T00:56:30.214Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/8a/5685d62a990e4cac2043fc76b4661bf38d06efed55cf45a334b455bd2759/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:1e005378c4a2c6db3ada3ad4c217b381f6c886f0a80d6a316fe586b90f77efd7", size = 32727837, upload-time = "2025-07-18T00:56:33.935Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/de/c0828ee09525c2bafefd3e736a248ebe764d07d0fd762d4f0929dbc516c9/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:65f8e85f79031449ec8706b74504a316805217b35b6099155dd7e227eef0d4b6", size = 41014158, upload-time = "2025-07-18T00:56:37.528Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/26/a2865c420c50b7a3748320b614f3484bfcde8347b2639b2b903b21ce6a72/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:3a81486adc665c7eb1a2bde0224cfca6ceaba344a82a971ef059678417880eb8", size = 42667885, upload-time = "2025-07-18T00:56:41.483Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/f9/4ee798dc902533159250fb4321267730bc0a107d8c6889e07c3add4fe3a5/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fc0d2f88b81dcf3ccf9a6ae17f89183762c8a94a5bdcfa09e05cfe413acf0503", size = 43276625, upload-time = "2025-07-18T00:56:48.002Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/da/e02544d6997037a4b0d22d8e5f66bc9315c3671371a8b18c79ade1cefe14/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6299449adf89df38537837487a4f8d3bd91ec94354fdd2a7d30bc11c48ef6e79", size = 44951890, upload-time = "2025-07-18T00:56:52.568Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/4e/519c1bc1876625fe6b71e9a28287c43ec2f20f73c658b9ae1d485c0c206e/pyarrow-21.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:222c39e2c70113543982c6b34f3077962b44fca38c0bd9e68bb6781534425c10", size = 26371006, upload-time = "2025-07-18T00:56:56.379Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2025.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tools"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "pandas" },
|
||||
{ name = "polars" },
|
||||
{ name = "pyarrow" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "polars", specifier = ">=1.33.0" }]
|
||||
requires-dist = [
|
||||
{ name = "pandas", specifier = ">=2.3.2" },
|
||||
{ name = "polars", specifier = ">=1.33.0" },
|
||||
{ name = "pyarrow", specifier = ">=21.0.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2025.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
|
||||
]
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import Sequence
|
||||
|
||||
import polars as pl
|
||||
|
||||
from load_results import load_results_dataframe
|
||||
from plot_scripts.load_results import load_results_dataframe
|
||||
|
||||
# --- configure your intended grid here (use the *canonical* strings used in df) ---
|
||||
NETWORKS_EXPECTED = ["subter_LeNet", "subter_efficient"]
|
||||
|
||||
Reference in New Issue
Block a user