Batch-effects-leaderboard / src /code_challenge.py
spell0's picture
Fix BERNN probability and batch mapping
173a895
Raw
History Blame Contribute Delete
76.6 kB
from __future__ import annotations
import ast
from datetime import datetime, timezone
from pathlib import Path
import builtins
from io import StringIO
import base64
import json
import threading
import time
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.ensemble import ExtraTreesClassifier, RandomForestClassifier
from sklearn.linear_model import LogisticRegression, RidgeClassifier, SGDClassifier
from sklearn.metrics import adjusted_rand_score, matthews_corrcoef, normalized_mutual_info_score, silhouette_score
from sklearn.model_selection import StratifiedGroupKFold, StratifiedKFold
from sklearn.naive_bayes import GaussianNB
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import MaxAbsScaler, MinMaxScaler, Normalizer, RobustScaler, StandardScaler
from sklearn.decomposition import PCA
from sklearn.decomposition import FastICA
from sklearn.svm import LinearSVC, SVC
from src.hf_utils import load_private_inference, load_private_labels
from src.leaderboard import evaluate_predictions, sorted_board
def fit(
X_train,
y_train,
X_test,
X_valid,
y_valid,
y_test,
batches_train,
batches_test,
batches_valid,
):
from bernn import TrainAEClassifierHoldout
from bernn.config.training_config import TrainingConfig
# Configure BERNN for leaderboard submission
config = TrainingConfig(
n_epochs=1000,
warmup=1000,
groupkfold=True,
optimize_hyperparams=False,
)
# BERNN 0.4.x auto-encodes non-numeric labels internally.
# Train only and return the trained model; inference is done via predict().
trainer = TrainAEClassifierHoldout(config=config, log_metrics=False, keep_models=False)
y_train_str = y_train.astype(str)
print(f"[data-profiler] Label Distribution (Train): {y_train_str.value_counts().to_dict()}")
fit_params = __import__("inspect").signature(trainer.fit).parameters
if "X_valid" not in fit_params or "y_valid" not in fit_params:
raise CodeValidationError(
"Installed BERNN does not expose fit(..., X_valid=..., y_valid=...). "
"Install the external-validation BERNN patch/release."
)
trainer.fit(
X_train, y_train_str,
X_valid=X_valid,
y_valid=y_valid.astype(str) if y_valid is not None else None,
X_test=X_test,
y_test=y_test.astype(str) if y_test is not None else None,
groups_train=batches_train,
groups_valid=batches_valid,
groups_test=batches_test,
)
try:
split_labels = getattr(trainer, "data", {}).get("labels", {})
split_names = ["train"] if getattr(trainer, "_no_internal_validation", False) else ["train", "valid", "test"]
for split_name in split_names:
split_vals = split_labels.get(split_name, None)
if split_vals is None:
continue
split_series = pd.Series(split_vals)
if split_series.empty:
continue
print(
f"[data-profiler] Label Distribution ({split_name.capitalize()} Split): "
f"{split_series.astype(str).value_counts().to_dict()}"
)
except Exception as split_exc:
print(f"[data-profiler] Failed label split profiling: {split_exc}")
return trainer
ROOT = Path(__file__).resolve().parent.parent
FORBIDDEN_CALLS = {
"open",
"eval",
"exec",
"compile",
"__import__",
"input",
"help",
"globals",
"locals",
"vars",
"dir",
"setattr",
"delattr",
}
ALLOWED_IMPORTS: set[str] = set()
FORBIDDEN_ATTRIBUTES = {
# File/model deserialization and pretrained-weight entry points.
"open", "load", "loads", "load_model", "load_state_dict", "load_library",
"from_pretrained", "from_file", "restore", "deserialize", "unpickle",
# Network/download helpers exposed by otherwise-allowed ML libraries.
"hub", "download", "download_url", "get_file", "urlopen", "request",
# Process and shell entry points.
"system", "popen", "spawn", "fork", "run", "call", "check_call",
"check_output",
# Common persistence/exfiltration entry points.
"save", "save_model", "save_pretrained", "dump", "dumps", "to_pickle",
"to_csv", "to_json", "to_parquet", "to_feather", "to_excel",
}
FORBIDDEN_ATTRIBUTE_PREFIXES = ("read_", "fetch_", "download_", "load_", "save_")
FORBIDDEN_PRETRAINED_KEYWORDS = {"pretrained", "weights", "model_file", "checkpoint"}
FORBIDDEN_ATTR_PREFIX = "__"
MAX_CODE_CHARS = 40000
HEARTBEAT_SECONDS = 15
class CodeValidationError(ValueError):
pass
def _safe_getattr(obj, name, default=None):
"""Restricted getattr that blocks dunder attribute access."""
if not isinstance(name, str):
raise ValueError("getattr attribute name must be a string")
if name.startswith(FORBIDDEN_ATTR_PREFIX):
raise ValueError("getattr cannot access dunder attributes")
return getattr(obj, name, default)
def _safe_import(name, globals=None, locals=None, fromlist=(), level=0):
base_module = name.split('.')[0]
if base_module not in ALLOWED_IMPORTS:
raise ImportError(f"Import of module '{name}' is forbidden.")
return builtins.__import__(name, globals, locals, fromlist, level)
class PlotCapture:
"""Capture matplotlib plots as base64-encoded images (private - not shared)."""
def __init__(self):
self.plots = []
def add_plot(self, title: str = ""):
"""Capture current matplotlib figure as base64 PNG."""
try:
import matplotlib.pyplot as plt
import io
buf = io.BytesIO()
plt.savefig(buf, format='png', dpi=100, bbox_inches='tight')
buf.seek(0)
img_base64 = base64.b64encode(buf.read()).decode('utf-8')
self.plots.append({
'title': title,
'type': 'matplotlib',
'data': img_base64
})
plt.close()
except Exception as e:
self.plots.append({
'title': title or 'Plot Error',
'type': 'error',
'data': str(e)
})
def to_html(self) -> str:
"""Convert plots to scrollable HTML (displayed on leaderboard)."""
if not self.plots:
return ""
html = '<div style="display: flex; gap: 10px; overflow-x: auto; max-height: 400px; padding: 10px; border: 1px solid #ddd; border-radius: 4px;">'
for plot in self.plots:
if plot['type'] == 'matplotlib':
html += f'''
<div style="flex-shrink: 0; text-align: center;">
<p style="margin: 0 0 5px 0; font-size: 12px; font-weight: bold;">{plot['title']}</p>
<img src="data:image/png;base64,{plot['data']}" style="max-width: 300px; max-height: 300px; border: 1px solid #ccc; border-radius: 2px;" />
</div>
'''
else:
html += f'<div style="color: red; padding: 10px;">{plot["data"]}</div>'
html += '</div>'
return html
def to_json(self) -> str:
"""Serialize plots to JSON for database storage."""
return json.dumps(self.plots)
@classmethod
def from_json(cls, json_str: str) -> "PlotCapture":
"""Restore plots from JSON."""
instance = cls()
try:
instance.plots = json.loads(json_str)
except Exception:
pass
return instance
class _BERNNFitPredictDescriptor:
"""Support both Trainer.fit_predict(...) and t.fit_predict(...)."""
def __init__(self, original=None):
self.original = original
def __get__(self, instance, owner):
if instance is None:
def _class_fit_predict(X_train, y_train, X_test=None, **kwargs):
trainer_kwargs = {}
for key in ("log_metrics", "keep_models", "log_inputs", "log_plots", "log_tb", "log_mlflow", "log_dvclive"):
if key in kwargs:
trainer_kwargs[key] = kwargs.pop(key)
config = kwargs.pop("config", None)
if config is None:
config = _training_config_from_submitter_config(
globals_dict=_caller_globals(),
locals_dict=_caller_locals(),
)
trainer = owner(config=config, **trainer_kwargs)
return _bernn_train_or_fit_predict(trainer, X_train, y_train, X_test=X_test, **kwargs)
return _class_fit_predict
def _instance_fit_predict(X_train, y_train, X_test=None, **kwargs):
return _bernn_train_or_fit_predict(instance, X_train, y_train, X_test=X_test, **kwargs)
return _instance_fit_predict
def _caller_globals():
try:
import inspect
frame = inspect.currentframe()
for _ in range(2):
frame = frame.f_back if frame is not None else None
return frame.f_globals if frame is not None else {}
except Exception:
return {}
def _caller_locals():
try:
import inspect
frame = inspect.currentframe()
for _ in range(2):
frame = frame.f_back if frame is not None else None
return frame.f_locals if frame is not None else {}
except Exception:
return {}
def _training_config_from_submitter_config(globals_dict=None, locals_dict=None):
"""Build a BERNN TrainingConfig from the submitter's CONFIG dict when present."""
cfg_dict = {}
if isinstance(globals_dict, dict) and isinstance(globals_dict.get("CONFIG"), dict):
cfg_dict = globals_dict["CONFIG"]
if isinstance(locals_dict, dict) and isinstance(locals_dict.get("CONFIG"), dict):
cfg_dict = locals_dict["CONFIG"]
TrainingConfigCls = None
if isinstance(globals_dict, dict):
TrainingConfigCls = globals_dict.get("TrainingConfig")
if TrainingConfigCls is None and isinstance(locals_dict, dict):
TrainingConfigCls = locals_dict.get("TrainingConfig")
if TrainingConfigCls is None:
from bernn.config.training_config import TrainingConfig as TrainingConfigCls
constructor_keys = {
"optimize_hyperparams", "dloss", "variational", "kan", "n_layers", "layer1",
"tied_weights", "use_mapping", "rec_loss", "scaler", "use_l1", "prune_network",
"update_grid", "n_epochs", "warmup", "n_repeats", "bs", "groupkfold", "device",
"class_triplet", "class_triplet_w", "triplet_dloss",
}
kwargs = {key: cfg_dict[key] for key in constructor_keys if key in cfg_dict}
kwargs.setdefault("optimize_hyperparams", False)
kwargs.setdefault("groupkfold", True)
if "kan" in cfg_dict:
kwargs.setdefault("update_grid", cfg_dict["kan"])
config = TrainingConfigCls(**kwargs)
if not hasattr(config, "dataset"):
config.dataset = "massbench"
for key in ("lr", "wd", "nu", "margin", "smoothing", "dropout", "thres", "gamma", "beta"):
if key in cfg_dict:
setattr(config, key, cfg_dict[key])
return config
def _accepts_kwarg(fn, name):
try:
import inspect
params = inspect.signature(fn).parameters
except Exception:
return True
return name in params or any(p.kind == p.VAR_KEYWORD for p in params.values())
def _has_named_kwarg(fn, name):
try:
import inspect
params = inspect.signature(fn).parameters
except Exception:
return False
return name in params
def _bernn_train_or_fit_predict(
trainer,
X_train,
y_train,
X_test=None,
X_valid=None,
batches_train=None,
batches_test=None,
batches_valid=None,
y_valid=None,
y_test=None,
groups_train=None,
groups_test=None,
groups_valid=None,
**kwargs,
):
"""Train BERNN with sklearn-style defaults and optional external monitors."""
forbidden = {"cross_validation", "cross_test"} & set(kwargs)
if forbidden:
raise CodeValidationError(
"BERNN submissions must not use cross_validation or cross_test. "
"The leaderboard owns all validation/test splitting."
)
train_groups = groups_train if groups_train is not None else batches_train
valid_groups = groups_valid if groups_valid is not None else batches_valid
test_groups = groups_test if groups_test is not None else batches_test
fit = getattr(trainer, "fit", None)
if fit is not None:
has_external_validation = _has_named_kwarg(fit, "X_valid") and _has_named_kwarg(fit, "y_valid")
if X_test is not None and _has_named_kwarg(fit, "X_test") and not has_external_validation:
raise CodeValidationError(
"Installed BERNN exposes a legacy fit(..., X_test=...) API but does not support "
"fit(..., X_valid=..., y_valid=...). Install the external-validation BERNN release."
)
fit_kwargs = dict(kwargs)
if _accepts_kwarg(fit, "groups_train"):
fit_kwargs["groups_train"] = train_groups
if has_external_validation:
if _has_named_kwarg(fit, "X_valid"):
fit_kwargs["X_valid"] = X_valid
if _has_named_kwarg(fit, "y_valid"):
fit_kwargs["y_valid"] = y_valid
if _has_named_kwarg(fit, "groups_valid"):
fit_kwargs["groups_valid"] = valid_groups
if _has_named_kwarg(fit, "X_test"):
fit_kwargs["X_test"] = X_test
if _has_named_kwarg(fit, "y_test"):
fit_kwargs["y_test"] = y_test
if _has_named_kwarg(fit, "groups_test"):
fit_kwargs["groups_test"] = test_groups
trainer.fit(X_train, y_train, **fit_kwargs)
return trainer
descriptor = getattr(type(trainer), "__dict__", {}).get("fit_predict")
original = getattr(descriptor, "original", None)
if original is None:
original = getattr(super(type(trainer), trainer), "fit_predict", None)
if original is None:
raise CodeValidationError("BERNN trainer does not provide fit() or fit_predict().")
fp_kwargs = dict(kwargs)
if _accepts_kwarg(original, "X_test"):
fp_kwargs.setdefault("X_test", X_test)
if _accepts_kwarg(original, "y_test"):
fp_kwargs.setdefault("y_test", y_test)
if _accepts_kwarg(original, "groups_train"):
fp_kwargs.setdefault("groups_train", train_groups)
if _accepts_kwarg(original, "groups_test"):
fp_kwargs.setdefault("groups_test", test_groups)
if _accepts_kwarg(original, "X_valid"):
fp_kwargs.setdefault("X_valid", X_valid)
if _accepts_kwarg(original, "y_valid"):
fp_kwargs.setdefault("y_valid", y_valid)
if _accepts_kwarg(original, "groups_valid"):
fp_kwargs.setdefault("groups_valid", valid_groups)
result = original(trainer, X_train, y_train, **fp_kwargs)
return trainer if result is None else result
def _adapt_bernn_trainer_class(trainer_cls):
"""Return a subclass whose fit_predict defaults to non-transductive training."""
original = getattr(trainer_cls, "fit_predict", None)
name = f"MassBench{getattr(trainer_cls, '__name__', 'BERNNTrainer')}"
return type(name, (trainer_cls,), {"fit_predict": _BERNNFitPredictDescriptor(original)})
def _safe_builtins() -> dict[str, object]:
allowed = {
"abs": builtins.abs,
"all": builtins.all,
"any": builtins.any,
"bool": builtins.bool,
"dict": builtins.dict,
"enumerate": builtins.enumerate,
"Exception": builtins.Exception,
"IndexError": builtins.IndexError,
"float": builtins.float,
"getattr": _safe_getattr,
"int": builtins.int,
"isinstance": builtins.isinstance,
"iter": builtins.iter,
"len": builtins.len,
"hasattr": builtins.hasattr,
"list": builtins.list,
"max": builtins.max,
"min": builtins.min,
"next": builtins.next,
"print": builtins.print,
"range": builtins.range,
"RuntimeError": builtins.RuntimeError,
"set": builtins.set,
"sorted": builtins.sorted,
"str": builtins.str,
"object": builtins.object,
"tuple": builtins.tuple,
"ValueError": builtins.ValueError,
"zip": builtins.zip,
"__build_class__": builtins.__build_class__,
"__import__": _safe_import,
}
return allowed
def _base_exec_env(
plot_capture: PlotCapture | None = None,
dataset_name: str | None = None,
) -> dict[str, object]:
from src.baselines import bernn_config as _default_bernn_config
env: dict[str, object] = {
"__builtins__": _safe_builtins(),
"__name__": "__submission__",
"np": np,
"pd": pd,
"Pipeline": Pipeline,
"StandardScaler": StandardScaler,
"RobustScaler": RobustScaler,
"MinMaxScaler": MinMaxScaler,
"MaxAbsScaler": MaxAbsScaler,
"Normalizer": Normalizer,
"PCA": PCA,
"FastICA": FastICA,
"LogisticRegression": LogisticRegression,
"RidgeClassifier": RidgeClassifier,
"SGDClassifier": SGDClassifier,
"LinearSVC": LinearSVC,
"SVC": SVC,
"RandomForestClassifier": RandomForestClassifier,
"ExtraTreesClassifier": ExtraTreesClassifier,
"KNeighborsClassifier": KNeighborsClassifier,
"GaussianNB": GaussianNB,
"CUDA_AVAILABLE": False,
# Optional user-facing BERNN CONFIG. Submitted code may override this
# locally, but short snippets can use it directly.
"CONFIG": _default_bernn_config("ae_inversetriplet"),
"plot_capture": plot_capture,
"CURRENT_DATASET": dataset_name or "",
}
print(f"[submission-runner] Base execution environment initialized. Dataset: {dataset_name}, Plot capture: {'enabled' if plot_capture else 'disabled'}")
"""
# Visualization
try:
import matplotlib.pyplot as plt
env["plt"] = plt
except Exception:
pass
print(f"[submission-runner] Matplotlib available: {'plt' in env}")
try:
import seaborn as sns
env["sns"] = sns
except Exception:
pass
print(f"[submission-runner] Seaborn available: {'sns' in env}")
try:
import plotly.graph_objects as go
import plotly.express as px
env["go"] = go
env["px"] = px
except Exception:
pass
# UMAP
try:
import umap
env["umap"] = umap
except Exception:
pass
"""
try:
import harmonypy
env["run_harmony"] = harmonypy.run_harmony
except Exception:
pass
print(f"[submission-runner] Harmony available: {'run_harmony' in env}")
print(f"[submission-runner] Execution environment ready. BERNN available: {env.get('BERNN_AVAILABLE', False)}, BERNN load error: {env.get('BERNN_LOAD_ERROR', '')}")
env["BERNN_AVAILABLE"] = False
env["BERNN_LOAD_ERROR"] = ""
# BERNN trainers exposed by the installed bernn package
# Suppress TF/CUDA noise before bernn import (bernn imports tensorflow at load time)
import os as _os
_os.environ.setdefault("TF_CPP_MIN_LOG_LEVEL", "3")
_os.environ.setdefault("CUDA_VISIBLE_DEVICES", "")
_os.environ.setdefault("TF_ENABLE_ONEDNN_OPTS", "0")
import logging as _logging
_logging.getLogger("tensorflow").setLevel(_logging.ERROR)
_logging.getLogger("absl").setLevel(_logging.ERROR)
bernn_error = None
print(f"[submission-runner] Attempting to import BERNN trainers from bernn package.")
# try:
print("A")
print("torch")
import torch
env["CUDA_AVAILABLE"] = bool(torch.cuda.is_available())
print(torch.__version__)
print("torchvision")
import torchvision
print(torchvision.__version__)
import bernn
print("B")
from bernn import TrainAEClassifierHoldout, TrainAEThenClassifierHoldout
print(f"[submission-runner] BERNN trainers imported successfully. TrainAEClassifierHoldout: {TrainAEClassifierHoldout}, TrainAEThenClassifierHoldout: {TrainAEThenClassifierHoldout}")
from bernn.config.training_config import TrainingConfig
print(f"[submission-runner] BERNN TrainingConfig imported successfully: {TrainingConfig}")
env["TrainAEClassifierHoldout"] = _adapt_bernn_trainer_class(TrainAEClassifierHoldout)
env["TrainAEThenClassifierHoldout"] = _adapt_bernn_trainer_class(TrainAEThenClassifierHoldout)
env["TrainingConfig"] = TrainingConfig
# Also expose the new head-sweep predictor (requires BERNN >= 0.6.1)
# try:
from bernn.dl.train.ae_head_predictor import AEHeadPredictor
import bernn.dl.train.ae_head_predictor as _ae_head_predictor_module
from bernn.dl.train.head_classifier import (
sweep_all_heads, cv_score_head, fit_and_score_head, HEAD_TYPES
)
# AEHeadPredictor normally prints only after a head completes all inner CV
# folds, which can look frozen on CPU Spaces. Wrap its module-level scorer
# once so every predictor reports the active head and elapsed time.
if not getattr(_ae_head_predictor_module.cv_score_head, "_massbench_progress", False):
_original_cv_score_head = _ae_head_predictor_module.cv_score_head
def _progress_cv_score_head(X, y, head_type, *args, **kwargs):
started = time.monotonic()
print(f"[head-sweep] Starting head: {head_type}", flush=True)
result = _original_cv_score_head(X, y, head_type, *args, **kwargs)
elapsed = time.monotonic() - started
print(
f"[head-sweep] Finished head: {head_type} "
f"in {elapsed:.1f}s",
flush=True,
)
return result
_progress_cv_score_head._massbench_progress = True
_ae_head_predictor_module.cv_score_head = _progress_cv_score_head
env["AEHeadPredictor"] = AEHeadPredictor
env["sweep_all_heads"] = sweep_all_heads
env["cv_score_head"] = cv_score_head
env["fit_and_score_head"] = fit_and_score_head
env["HEAD_TYPES"] = HEAD_TYPES
# except Exception as _head_exc:
# env["AEHeadPredictor"] = None # bernn package too old; code will error helpfully
env["BERNN_AVAILABLE"] = True
# except Exception as exc:
# print(f"[submission-runner] BERNN import failed: {type(exc).__name__}: {exc}")
# bernn_error = exc
if bernn_error is not None:
env["BERNN_LOAD_ERROR"] = f"{type(bernn_error).__name__}: {bernn_error}"
print(f"[submission-runner] Execution environment ready. BERNN available: {env.get('BERNN_AVAILABLE', False)}, BERNN load error: {env.get('BERNN_LOAD_ERROR', '')}")
return env
def _validate_code(code: str, label: str) -> None:
if len(code) > MAX_CODE_CHARS:
raise CodeValidationError(f"{label} code is too long (max {MAX_CODE_CHARS} chars).")
try:
tree = ast.parse(code)
except SyntaxError as exc:
raise CodeValidationError(f"{label} has a syntax error: {exc}") from exc
for node in ast.walk(tree):
if isinstance(node, ast.Import):
raise CodeValidationError(
f"{label} cannot import modules; use the approved preloaded ML symbols."
)
elif isinstance(node, ast.ImportFrom):
raise CodeValidationError(
f"{label} cannot import modules; use the approved preloaded ML symbols."
)
elif isinstance(node, (ast.Import, ast.ImportFrom)): # Fallback for weird nodes
raise CodeValidationError(f"{label} used an invalid import structure.")
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
if node.func.id == "getattr":
# Allow getattr only for non-dunder string literals to prevent bypassing attr guards.
if len(node.args) < 2:
raise CodeValidationError(f"{label} uses invalid getattr call (missing attribute name).")
attr_arg = node.args[1]
if not isinstance(attr_arg, ast.Constant) or not isinstance(attr_arg.value, str):
raise CodeValidationError(
f"{label} getattr attribute must be a string literal."
)
if attr_arg.value.startswith(FORBIDDEN_ATTR_PREFIX):
raise CodeValidationError(
f"{label} getattr cannot access dunder attributes."
)
if (
attr_arg.value in FORBIDDEN_ATTRIBUTES
or attr_arg.value.startswith(FORBIDDEN_ATTRIBUTE_PREFIXES)
):
raise CodeValidationError(
f"{label} getattr cannot access file/network/pretrained "
f"attribute: {attr_arg.value}"
)
if node.func.id in FORBIDDEN_CALLS:
raise CodeValidationError(f"{label} uses forbidden call: {node.func.id}")
if isinstance(node, ast.Attribute) and isinstance(node.attr, str):
if node.attr.startswith(FORBIDDEN_ATTR_PREFIX):
raise CodeValidationError(f"{label} uses forbidden dunder attribute access.")
if (
node.attr in FORBIDDEN_ATTRIBUTES
or node.attr.startswith(FORBIDDEN_ATTRIBUTE_PREFIXES)
):
raise CodeValidationError(
f"{label} uses forbidden file/network/pretrained attribute: {node.attr}"
)
if isinstance(node, ast.Call):
for keyword in node.keywords:
if keyword.arg in {"cross_validation", "cross_test"}:
raise CodeValidationError(
f"{label} cannot pass '{keyword.arg}'. "
"The leaderboard owns validation/test splitting."
)
if keyword.arg in FORBIDDEN_PRETRAINED_KEYWORDS:
value = keyword.value
is_disabled = (
isinstance(value, ast.Constant)
and value.value in (None, False)
)
if not is_disabled:
raise CodeValidationError(
f"{label} cannot request pretrained weights via '{keyword.arg}'."
)
if (
isinstance(node, ast.Constant)
and isinstance(node.value, str)
and node.value.lower().startswith(("http://", "https://", "hf://"))
):
raise CodeValidationError(f"{label} cannot contain remote resource URLs.")
def _exec_user_code(code: str, label: str, env: dict | None = None) -> dict[str, object]:
_validate_code(code, label)
if env is None:
env = _base_exec_env()
exec(compile(code, f"<{label}>", "exec"), env, env)
return env
def _clean_features(df: pd.DataFrame, feature_cols: list[str]) -> pd.DataFrame:
return (
df[feature_cols]
.apply(pd.to_numeric, errors="coerce")
.replace([np.inf, -np.inf], np.nan)
.fillna(0)
.reset_index(drop=True)
)
def _load_data_for_dataset(dataset: str) -> tuple[pd.DataFrame, pd.Series, pd.Series, pd.DataFrame, pd.Series, pd.DataFrame]:
train_path = ROOT / "data" / "datasets" / dataset / f"{dataset}_train.csv"
if not train_path.exists():
raise FileNotFoundError(f"Public train split not found: {train_path}")
train = pd.read_csv(train_path)
private_inference = load_private_inference(dataset)
required_train = {"name", "batch", "label"}
required_inf = {"name", "batch"}
if not required_train.issubset(set(train.columns)):
raise ValueError(f"Train file for {dataset} must contain name, batch, label")
if not required_inf.issubset(set(private_inference.columns)):
raise ValueError(f"Private inference for {dataset} must contain name and batch")
feature_cols = [c for c in train.columns if c not in {"name", "batch", "label"}]
missing = [c for c in feature_cols if c not in private_inference.columns]
if missing:
raise ValueError(f"Private inference for {dataset} is missing {len(missing)} feature columns")
y_train = train["label"].astype(str)
non_pool = y_train.str.lower() != "pool"
X_train = _clean_features(train.loc[non_pool], feature_cols)
y_train = y_train.loc[non_pool].reset_index(drop=True)
batches_train = train.loc[non_pool, "batch"].astype(str).reset_index(drop=True)
X_test = _clean_features(private_inference, feature_cols)
batches_test = private_inference["batch"].astype(str).reset_index(drop=True)
test_names = private_inference["name"].astype(str).reset_index(drop=True)
return X_train, y_train, batches_train, X_test, batches_test, test_names.to_frame(name="name")
def _apply_user_batch_correction(
correction_code: str,
X_train: pd.DataFrame,
batches_train: pd.Series,
X_test: pd.DataFrame,
batches_test: pd.Series,
plot_capture: PlotCapture | None = None,
) -> tuple[pd.DataFrame, pd.DataFrame]:
if not correction_code.strip():
return X_train.copy(), X_test.copy()
print(f"[submission-runner] Executing user batch correction code.")
env = _base_exec_env(plot_capture)
env = _exec_user_code(correction_code, "batch-correction", env)
batch_correct = env.get("batch_correct")
if not callable(batch_correct):
raise CodeValidationError(
"Batch-correction code must define a callable function: "
"batch_correct(X_train, train_batches, X_test, test_batches)."
)
print(f"[submission-runner] Batch correction function found.")
corrected = batch_correct(
X_train.copy(),
batches_train.copy(),
X_test.copy(),
batches_test.copy(),
)
print(f"[submission-runner] Batch correction function executed. Validating output.")
if not isinstance(corrected, (tuple, list)) or len(corrected) != 2:
raise CodeValidationError("batch_correct must return exactly two values: X_train_corr, X_test_corr")
X_train_corr = pd.DataFrame(corrected[0]).replace([np.inf, -np.inf], np.nan).fillna(0)
X_test_corr = pd.DataFrame(corrected[1]).replace([np.inf, -np.inf], np.nan).fillna(0)
if X_train_corr.shape[0] != X_train.shape[0]:
raise CodeValidationError("Corrected training data row count changed.")
if X_test_corr.shape[0] != X_test.shape[0]:
raise CodeValidationError("Corrected test data row count changed.")
return X_train_corr.reset_index(drop=True), X_test_corr.reset_index(drop=True)
def _compute_batch_effect_metrics(
X_train: pd.DataFrame,
X_test: pd.DataFrame,
batches_train: pd.Series,
batches_test: pd.Series,
) -> dict[str, float | int]:
metrics: dict[str, float | int] = {
"batch_silhouette": 1.0,
"batch_centroid_dispersion": 1.0,
"batch_nbe": 1.0,
"batch_nmi": 1.0,
"batch_nri": 1.0,
"batch_metric_samples": int(len(X_train) + len(X_test)),
}
try:
X_all = pd.concat([X_train.reset_index(drop=True), X_test.reset_index(drop=True)], ignore_index=True)
batches_all = pd.concat([batches_train.reset_index(drop=True), batches_test.reset_index(drop=True)], ignore_index=True).astype(str)
if len(X_all) < 3 or batches_all.nunique() < 2:
return metrics
values = X_all.to_numpy(dtype=float, copy=False)
values = np.nan_to_num(values, nan=0.0, posinf=0.0, neginf=0.0)
n_components = min(20, values.shape[1], max(1, values.shape[0] - 1))
if n_components >= 1 and values.shape[1] > n_components:
values = PCA(n_components=n_components).fit_transform(values)
if batches_all.value_counts().min() >= 2:
sil = float(silhouette_score(values, batches_all))
metrics["batch_silhouette"] = float(np.clip(sil, 0.0, 1.0))
centroids = []
for batch in sorted(batches_all.unique()):
mask = batches_all == batch
if int(mask.sum()) == 0:
continue
centroids.append(values[mask.to_numpy()].mean(axis=0))
if len(centroids) >= 2:
centroid_arr = np.vstack(centroids)
diffs = centroid_arr[:, None, :] - centroid_arr[None, :, :]
dists = np.sqrt(np.sum(diffs ** 2, axis=2))
tri = dists[np.triu_indices(len(centroids), k=1)]
if tri.size > 0:
centroid_disp = float(np.mean(tri))
global_center = np.mean(values, axis=0)
sample_disp = float(np.mean(np.linalg.norm(values - global_center, axis=1)))
denom = centroid_disp + sample_disp
if denom > 0:
metrics["batch_centroid_dispersion"] = float(np.clip(centroid_disp / denom, 0.0, 1.0))
else:
metrics["batch_centroid_dispersion"] = 0.0
n_clusters = min(max(2, batches_all.nunique()), len(values))
if n_clusters >= 2:
cluster_labels = KMeans(n_clusters=n_clusters, random_state=42, n_init=10).fit_predict(values)
batch_codes = pd.Categorical(batches_all).codes
metrics["batch_nmi"] = float(np.clip(normalized_mutual_info_score(batch_codes, cluster_labels), 0.0, 1.0))
# Keep only positive agreement with batch labels; lower is better.
ari = float(adjusted_rand_score(batch_codes, cluster_labels))
metrics["batch_nri"] = float(np.clip(max(0.0, ari), 0.0, 1.0))
n_batches = int(batches_all.nunique())
denom = np.log(max(n_batches, 2))
if denom > 0:
entropies: list[float] = []
weights: list[float] = []
cluster_series = pd.Series(cluster_labels)
for cluster_id in sorted(cluster_series.unique()):
mask = cluster_series == cluster_id
cluster_batches = batches_all[mask.to_numpy()]
if cluster_batches.empty:
continue
probs = cluster_batches.value_counts(normalize=True).to_numpy(dtype=float)
probs = probs[probs > 0]
entropy = -float(np.sum(probs * np.log(probs))) / float(denom)
entropies.append(entropy)
weights.append(float(len(cluster_batches)))
if entropies and np.sum(weights) > 0:
mean_entropy = float(np.average(entropies, weights=np.asarray(weights, dtype=float)))
metrics["batch_nbe"] = float(np.clip(1.0 - mean_entropy, 0.0, 1.0))
except Exception:
pass
return metrics
def _bernn_trainer_metrics(trainer: object) -> dict:
"""Pull internal CV metrics off a trained BERNN trainer.
BERNN exposes ``best_mcc`` (best internal monitor MCC, with the matching
model restored for prediction). Other splits are surfaced if present. Returns
{} for non-BERNN models. The server-owned outer CV remains authoritative
for leaderboard validation MCC.
"""
if not hasattr(trainer, "best_mcc"):
return {}
best_mcc = float(getattr(trainer, "best_mcc", -1.0))
# Prefer the caller-side CV mean (set by build_bernn_code's fold loop) when
# present; otherwise fall back to bernn's single-split best_mcc.
valid_mcc = float(getattr(trainer, "cv_mcc_mean",
getattr(trainer, "best_valid_mcc",
getattr(trainer, "best_mcc_val",
getattr(trainer, "best_mcc_valid", best_mcc)))))
metrics = {
"valid_mcc": valid_mcc,
"train_mcc": float(getattr(trainer, "best_mcc_train", -1.0)),
"test_mcc": float(getattr(trainer, "best_mcc_test", -1.0)),
}
# Surface the trained config so the app can match this to a BERNN family and
# update that family's registered default when the submission beats it.
a = getattr(trainer, "args", None)
if a is not None:
cfg = {"model_type": "joint"} # only the joint holdout trainer is used here
for k in ("dloss", "variational", "kan", "n_layers", "layer1", "scaler",
"warmup", "lr", "wd", "nu", "margin", "smoothing", "dropout", "thres", "gamma", "beta"):
v = getattr(a, k, None)
if v is not None:
cfg[k] = v
metrics["bernn_config"] = cfg
train_mcc = metrics["train_mcc"]
test_mcc = metrics["test_mcc"]
extra = []
if train_mcc >= 0:
extra.append(f"train MCC={train_mcc:.4f}")
if test_mcc >= 0:
extra.append(f"test MCC={test_mcc:.4f}")
suffix = f" ({', '.join(extra)})" if extra else ""
print(
f"[bernn] Internal BERNN monitor MCC (diagnostic only; "
f"outer CV is authoritative) = {valid_mcc:.4f}{suffix}"
)
return metrics
def _split_batch_summary(batch_series: pd.Series | None) -> str:
if batch_series is None:
return "None"
values = pd.Series(batch_series).dropna().astype(str)
counts = values.value_counts().sort_index()
formatted = [f"{batch}: {int(count)}" for batch, count in counts.items()]
return f"{len(counts)} unique: {formatted}"
def _log_external_split_batches(
label: str,
train_batches: pd.Series | None,
valid_batches: pd.Series | None,
test_batches: pd.Series | None,
) -> None:
print(f"[split-batches] {label} train batches: {_split_batch_summary(train_batches)}", flush=True)
print(f"[split-batches] {label} valid batches: {_split_batch_summary(valid_batches)}", flush=True)
print(f"[split-batches] {label} test batches: {_split_batch_summary(test_batches)}", flush=True)
def _run_user_model(
model_code: str,
X_train: pd.DataFrame,
y_train: pd.Series,
X_test: pd.DataFrame,
dataset_name: str,
batches_train: pd.Series | None = None,
batches_test: pd.Series | None = None,
X_valid: pd.DataFrame | None = None,
y_valid: pd.Series | None = None,
batches_valid: pd.Series | None = None,
y_test: pd.Series | None = None,
plot_capture: PlotCapture | None = None,
# groups: pd.Series = None,
) -> tuple[pd.Series, dict, object | None]:
if "AEHeadPredictor" in model_code:
model_kind = "BERNN AE + head sweep"
elif "TrainAEThenClassifierHoldout" in model_code:
model_kind = "BERNN two-stage AE classifier"
elif "TrainAEClassifierHoldout" in model_code:
model_kind = "BERNN joint AE classifier"
elif "GaussianNB" in model_code:
model_kind = "GaussianNB"
elif "LogisticRegression" in model_code:
model_kind = "LogisticRegression"
elif "RandomForestClassifier" in model_code:
model_kind = "RandomForestClassifier"
elif "SVC" in model_code:
model_kind = "SVC"
elif "KNeighborsClassifier" in model_code:
model_kind = "KNeighborsClassifier"
elif "RidgeClassifier" in model_code:
model_kind = "RidgeClassifier"
else:
model_kind = "custom model"
print(f"[model-runner] Executing model code: {model_kind}", flush=True)
if model_kind.startswith("BERNN"):
print(
"[model-runner] BERNN epoch-level Valid MCC is diagnostic; "
"leaderboard validation MCC is computed by the outer server CV "
"and printed as [submission-cv].",
flush=True,
)
def _decode_to_encoded(preds_raw: object, n_classes: int) -> np.ndarray:
arr = np.asarray(preds_raw)
if arr.ndim == 2:
if arr.shape[1] == 0:
raise CodeValidationError("predict returned an empty 2D output.")
return np.argmax(arr, axis=1).astype(int)
flat = np.asarray(arr).reshape(-1)
if flat.size == 0:
raise CodeValidationError("predict returned an empty output.")
if np.issubdtype(flat.dtype, np.floating):
if n_classes == 2 and np.nanmin(flat) >= 0.0 and np.nanmax(flat) <= 1.0:
return (flat >= 0.5).astype(int)
return np.rint(flat).astype(int)
return flat.astype(int)
def _decode_to_labels(preds_raw: object, le: object) -> pd.Series:
encoded = _decode_to_encoded(preds_raw, len(getattr(le, "classes_", [])) or 2)
return pd.Series(le.inverse_transform(encoded)).astype(str).reset_index(drop=True)
def _train_locked_bernn() -> tuple[object, dict]:
"""Train BERNN with fixed server-side settings and return trainer + label encoder."""
from bernn import TrainAEClassifierHoldout
from bernn.config.training_config import TrainingConfig
from src.baselines import set_bernn_seed
set_bernn_seed(0) # reproducible locked-baseline training
trainer_cls = TrainAEClassifierHoldout
bernn_config = TrainingConfig(
# dloss="inverseTriplet",
n_epochs=1000,
# device="cpu",
# bs=32,
# kan=False,
warmup=1000,
groupkfold=True,
optimize_hyperparams=False,
n_repeats=5,
# n_agg=1,
# n_trials=1,
)
trainer = trainer_cls(
config=bernn_config,
log_metrics=False,
keep_models=False,
log_inputs=False,
log_plots=False,
log_tb=False,
log_mlflow=False,
log_dvclive=False,
)
# groupkfold=True: use BERNN's sklearn-style fit path.
# Validation/test rows are external monitors; inference still uses predict().
y_train_str = y_train.astype(str)
y_test_str = y_test.astype(str) if y_test is not None else None
print(f"[data-profiler] Label Distribution (Train): {y_train_str.value_counts().to_dict()}")
if y_test_str is not None:
print(f"[data-profiler] Label Distribution (Test): {pd.Series(y_test_str).value_counts().to_dict()}")
fit_params = __import__("inspect").signature(trainer.fit).parameters
if "X_valid" not in fit_params or "y_valid" not in fit_params:
raise CodeValidationError(
"Installed BERNN does not expose fit(..., X_valid=..., y_valid=...). "
"Install the external-validation BERNN patch/release."
)
_log_external_split_batches(
"locked BERNN fit",
batches_train,
batches_valid,
batches_test,
)
trainer.fit(
X_train.copy(),
y_train_str,
X_valid=X_valid.copy() if X_valid is not None else None,
y_valid=y_valid.astype(str).copy() if y_valid is not None else None,
X_test=X_test.copy(),
y_test=y_test_str.copy() if y_test_str is not None else None,
groups_train=batches_train.copy() if batches_train is not None else None,
groups_valid=batches_valid.copy() if batches_valid is not None else None,
groups_test=batches_test.copy() if batches_test is not None else None,
)
try:
split_labels = getattr(trainer, "data", {}).get("labels", {})
split_names = ["train"] if getattr(trainer, "_no_internal_validation", False) else ["train", "valid", "test"]
for split_name in split_names:
split_vals = split_labels.get(split_name, None)
if split_vals is None:
continue
split_series = pd.Series(split_vals)
if split_series.empty:
continue
print(
f"[data-profiler] Label Distribution ({split_name.capitalize()} Split): "
f"{split_series.astype(str).value_counts().to_dict()}"
)
except Exception as split_exc:
print(f"[data-profiler] Failed label split profiling: {split_exc}")
# Extract BERNN validation monitor metrics (valid_mcc, etc.) from the trainer.
extra_metrics = _bernn_trainer_metrics(trainer)
return trainer, extra_metrics
if len(X_test) == 0:
raise CodeValidationError(
"No inference samples are available for this dataset (X_test is empty), so model evaluation cannot run."
)
env = _base_exec_env(plot_capture, dataset_name)
env = _exec_user_code(model_code, "model", env)
train_fn = env.get("fit")
train_fn_name = "fit"
if not callable(train_fn):
train_fn = env.get("fit_predict")
train_fn_name = "fit_predict"
if callable(train_fn):
import inspect
def _call_method_with_optional_kwargs(
fn: object,
frame: pd.DataFrame,
batch_series: pd.Series | None = None,
) -> object:
if not callable(fn):
raise CodeValidationError("Returned model method is not callable.")
method_sig = inspect.signature(fn)
method_kwargs: dict[str, object] = {}
method_params = method_sig.parameters
accepts_var_kwargs = any(p.kind == p.VAR_KEYWORD for p in method_params.values())
def _accepts_method_kwarg(name: str) -> bool:
return name in method_params or accepts_var_kwargs
if batch_series is not None:
# BERNN uses inference batches for per-batch scalers and batch mapping.
# Pass every supported alias so restored-checkpoint re-evaluation follows
# the same batch-aware preprocessing as BERNN's internal monitor path.
for key in ("batches_test", "groups_test", "groups"):
if _accepts_method_kwarg(key):
method_kwargs[key] = batch_series.copy()
# A few sklearn-style wrappers name their single inference-group input
# after the training split; support them without affecting BERNN.
for key in ("batches_train", "groups_train"):
if _accepts_method_kwarg(key) and key not in method_kwargs:
method_kwargs[key] = batch_series.copy()
if "dataset_name" in method_sig.parameters:
method_kwargs["dataset_name"] = dataset_name
return fn(frame.copy(), **method_kwargs)
sig = inspect.signature(train_fn)
def _invoke_train_fn(
train_x: pd.DataFrame,
train_y: pd.Series,
test_x: pd.DataFrame,
train_batches: pd.Series | None,
test_batches: pd.Series | None,
valid_x: pd.DataFrame | None = None,
valid_y: pd.Series | None = None,
valid_batches: pd.Series | None = None,
) -> object:
call_kwargs: dict[str, object] = {}
if "groups_train" in sig.parameters:
call_kwargs["groups_train"] = train_batches.copy() if train_batches is not None else None
if "batches_train" in sig.parameters:
call_kwargs["batches_train"] = train_batches.copy() if train_batches is not None else None
if "groups_test" in sig.parameters:
call_kwargs["groups_test"] = test_batches.copy() if test_batches is not None else None
if "batches_test" in sig.parameters:
call_kwargs["batches_test"] = test_batches.copy() if test_batches is not None else None
if "X_valid" in sig.parameters:
call_kwargs["X_valid"] = valid_x.copy() if valid_x is not None else None
if "groups_valid" in sig.parameters:
call_kwargs["groups_valid"] = valid_batches.copy() if valid_batches is not None else None
if "batches_valid" in sig.parameters:
call_kwargs["batches_valid"] = valid_batches.copy() if valid_batches is not None else None
if "y_valid" in sig.parameters:
call_kwargs["y_valid"] = valid_y.copy() if valid_y is not None else None
if "y_test" in sig.parameters:
call_kwargs["y_test"] = y_test.copy() if y_test is not None else None
# If 'groups' is required, pass it as a positional argument
if "groups" in sig.parameters:
if train_batches is None:
raise CodeValidationError(f"{train_fn_name} requires a 'groups' argument but none was provided.")
return train_fn(
train_x.copy(),
train_y.copy(),
test_x.copy(),
train_batches.copy(),
)
else:
return train_fn(
X_train=train_x.copy(),
y_train=train_y.copy(),
X_test=test_x.copy(),
**call_kwargs,
)
try:
_log_external_split_batches(
train_fn_name,
batches_train,
batches_valid,
batches_test,
)
result = _invoke_train_fn(
X_train,
y_train,
X_test,
batches_train,
batches_test,
X_valid,
y_valid,
batches_valid,
)
except ValueError as exc:
# BERNN can raise this when all test rows are dropped by its internal preprocessing.
err_msg = str(exc)
is_empty_dataset_err = "Dataset is empty: len(self.sets) == 0" in err_msg
if not is_empty_dataset_err:
raise
raise CodeValidationError(
"Model backend reported an empty internal test dataset. "
"For BERNN holdout models, train only on X_train/y_train and "
"let the leaderboard provide validation/test splits externally."
) from exc
pred_proba_raw: object | None = None
valid_pred_proba_raw: object | None = None
model = None
if isinstance(result, tuple) and len(result) == 2:
first, second = result
if hasattr(first, "predict") and callable(getattr(first, "predict", None)):
model = first
extra_metrics = second if isinstance(second, dict) else {}
preds = _call_method_with_optional_kwargs(getattr(model, "predict"), X_test, batches_test)
if hasattr(model, "predict_proba") and callable(getattr(model, "predict_proba", None)):
try:
pred_proba_raw = _call_method_with_optional_kwargs(getattr(model, "predict_proba"), X_test, batches_test)
except Exception:
pred_proba_raw = None
else:
preds = first
extra_metrics = second if isinstance(second, dict) else {}
elif hasattr(result, "predict") and callable(getattr(result, "predict", None)):
model = result
extra_metrics = _bernn_trainer_metrics(model)
preds = _call_method_with_optional_kwargs(getattr(model, "predict"), X_test, batches_test)
if hasattr(model, "predict_proba") and callable(getattr(model, "predict_proba", None)):
try:
pred_proba_raw = _call_method_with_optional_kwargs(getattr(model, "predict_proba"), X_test, batches_test)
except Exception:
pred_proba_raw = None
else:
if train_fn_name == "fit":
raise CodeValidationError("fit() must return a trained model with a callable predict() method.")
preds = result
extra_metrics = {}
preds_raw = pd.Series(preds).reset_index(drop=True)
extra_metrics["model_kind"] = model_kind
if model is not None and hasattr(model, "classes_"):
try:
extra_metrics["_model_classes"] = [str(c) for c in list(getattr(model, "classes_"))]
except Exception:
pass
out = preds_raw.astype(str).reset_index(drop=True)
if len(out) != len(X_test):
raise CodeValidationError(f"{train_fn_name} output length does not match test set rows.")
if model is not None and X_valid is not None and len(X_valid) > 0:
valid_preds = _call_method_with_optional_kwargs(getattr(model, "predict"), X_valid, batches_valid)
valid_out = pd.Series(valid_preds).astype(str).reset_index(drop=True)
if len(valid_out) != len(X_valid):
raise CodeValidationError(f"{train_fn_name} valid prediction length does not match validation rows.")
extra_metrics["_valid_predictions"] = valid_out
if hasattr(model, "predict_proba") and callable(getattr(model, "predict_proba", None)):
try:
valid_pred_proba_raw = _call_method_with_optional_kwargs(getattr(model, "predict_proba"), X_valid, batches_valid)
extra_metrics["_valid_pred_proba"] = valid_pred_proba_raw
except Exception:
pass
return out, extra_metrics, pred_proba_raw
predict_fn = env.get("predict")
if callable(predict_fn):
import inspect
sig = inspect.signature(predict_fn)
if len(sig.parameters) < 2:
raise CodeValidationError(
"predict() must be defined as predict(model, X_test, ...) for locked BERNN mode."
)
trainer, extra_metrics = _train_locked_bernn()
kwargs = {}
if "batches_test" in sig.parameters:
kwargs["batches_test"] = batches_test.copy() if batches_test is not None else None
if "dataset_name" in sig.parameters:
kwargs["dataset_name"] = dataset_name
preds_raw = predict_fn(trainer, X_test.copy(), **kwargs)
out_raw = pd.Series(preds_raw).reset_index(drop=True)
# BERNN predict already returns decoded labels.
out = out_raw.astype(str).reset_index(drop=True)
if len(out) != len(X_test):
raise CodeValidationError("predict output length does not match test set rows.")
extra_metrics["model_kind"] = model_kind
return out, extra_metrics, None
build_model = env.get("build_model")
if callable(build_model):
model = build_model()
if model is None:
raise CodeValidationError("build_model returned None.")
if not hasattr(model, "fit"):
raise CodeValidationError("Model returned by build_model must define fit().")
if not hasattr(model, "predict"):
raise CodeValidationError("Model returned by build_model must define predict().")
_log_external_split_batches(
"build_model.fit",
batches_train,
batches_valid,
batches_test,
)
model.fit(X_train.copy(), y_train.copy())
preds = model.predict(X_test.copy())
out = pd.Series(preds).astype(str).reset_index(drop=True)
if len(out) != len(X_test):
raise CodeValidationError("Model predict() output length does not match test set rows.")
return out, {"model_kind": model_kind}, None
raise CodeValidationError(
"Model code must define one of: "
"fit(X_train, y_train, ...), "
"fit_predict(X_train, y_train, X_test), "
"predict(model, X_test, ...) for locked BERNN mode, "
"or build_model()."
)
CV_N_SPLITS = 5
CV_RANDOM_STATE = 42
def _aligned_proba_frame(
raw: object,
classes: object,
labels: list[str],
n_rows: int,
) -> pd.DataFrame | None:
"""Return probability columns aligned to decoded labels, or None if unsafe."""
if raw is None:
return None
try:
arr = np.asarray(raw, dtype=float)
except Exception:
return None
if arr.ndim != 2 or arr.shape[0] != n_rows or arr.shape[1] < 2:
return None
if not np.isfinite(arr).all():
return None
labels = [str(label) for label in labels]
label_set = set(labels)
columns: list[str] | None = None
if classes is not None:
try:
class_labels = [str(c) for c in list(classes)]
except Exception:
class_labels = []
if len(class_labels) != arr.shape[1] or len(set(class_labels)) != len(class_labels):
return None
if set(class_labels).issubset(label_set):
columns = class_labels
else:
return None
elif arr.shape[1] == len(labels):
columns = labels
else:
return None
frame = pd.DataFrame(arr, columns=columns)
for label in labels:
if label not in frame.columns:
frame[label] = 0.0
return frame[labels].reset_index(drop=True)
def _submission_cv_splits(
X_train: pd.DataFrame,
y_train: pd.Series,
batches_train: pd.Series,
) -> tuple[str, list[tuple[np.ndarray, np.ndarray]]]:
"""Return the single server-owned CV protocol shared by every submission."""
labels = y_train.astype(str).reset_index(drop=True)
groups = batches_train.astype(str).reset_index(drop=True)
if groups.nunique() >= CV_N_SPLITS:
splitter = StratifiedGroupKFold(
n_splits=CV_N_SPLITS,
shuffle=True,
random_state=CV_RANDOM_STATE,
)
splits = list(splitter.split(X_train, labels, groups))
return "StratifiedGroupKFold(n_splits=5, shuffle=True, random_state=42)", splits
# Some datasets contain fewer than three batches. A three-fold group split is
# mathematically impossible there, so use one deterministic stratified protocol
# for every submission on that dataset.
if int(labels.value_counts().min()) < CV_N_SPLITS:
raise CodeValidationError(
"Five-fold validation requires at least five samples in every class."
)
splitter = StratifiedKFold(
n_splits=CV_N_SPLITS,
shuffle=True,
random_state=CV_RANDOM_STATE,
)
splits = list(splitter.split(X_train, labels))
return "StratifiedKFold(n_splits=5, shuffle=True, random_state=42)", splits
def _cross_validate_submission(
correction_code: str,
model_code: str,
dataset: str,
X_train: pd.DataFrame,
y_train: pd.Series,
batches_train: pd.Series,
X_test: pd.DataFrame,
batches_test: pd.Series,
) -> dict:
"""Evaluate the complete submitted pipeline on the shared outer CV folds."""
protocol, splits = _submission_cv_splits(X_train, y_train, batches_train)
fold_scores: list[float] = []
fold_details: list[dict[str, object]] = []
test_prediction_folds: list[pd.Series] = []
test_proba_folds: list[pd.DataFrame] = []
all_labels = sorted(y_train.astype(str).unique().tolist())
print(f"[submission-cv] Protocol: {protocol}")
from src.baselines import set_bernn_seed
for fold, (train_idx, valid_idx) in enumerate(splits, start=1):
set_bernn_seed(CV_RANDOM_STATE + fold)
fold_X_train = X_train.iloc[train_idx].reset_index(drop=True)
fold_y_train = y_train.iloc[train_idx].reset_index(drop=True)
fold_batches_train = batches_train.iloc[train_idx].reset_index(drop=True)
fold_X_valid = X_train.iloc[valid_idx].reset_index(drop=True)
fold_y_valid = y_train.iloc[valid_idx].astype(str).reset_index(drop=True)
fold_batches_valid = batches_train.iloc[valid_idx].reset_index(drop=True)
fold_X_eval = pd.concat([fold_X_valid, X_test.reset_index(drop=True)], ignore_index=True)
fold_batches_eval = pd.concat(
[fold_batches_valid, batches_test.reset_index(drop=True)],
ignore_index=True,
)
# Fit batch correction independently inside each fold. This prevents the
# validation rows from influencing preprocessing fitted on the fold train
# set while keeping the private test split fixed across folds.
corrected_train, corrected_eval = _apply_user_batch_correction(
correction_code,
fold_X_train,
fold_batches_train,
fold_X_eval,
fold_batches_eval,
plot_capture=None,
)
n_valid = len(fold_X_valid)
corrected_valid = corrected_eval.iloc[:n_valid].reset_index(drop=True)
corrected_test = corrected_eval.iloc[n_valid:].reset_index(drop=True)
fold_test_preds, fold_extra, fold_test_proba = _run_user_model(
model_code,
corrected_train,
fold_y_train,
corrected_test,
dataset,
fold_batches_train,
batches_test,
X_valid=corrected_valid,
y_valid=fold_y_valid,
batches_valid=fold_batches_valid,
y_test=None,
plot_capture=None,
)
fold_valid_preds = fold_extra.pop("_valid_predictions", None) if isinstance(fold_extra, dict) else None
if fold_valid_preds is None:
raise CodeValidationError(
"fit() must return a trained model with predict() so the runner can score validation folds."
)
fold_valid_preds = pd.Series(fold_valid_preds).astype(str).reset_index(drop=True)
if len(fold_valid_preds) != len(fold_y_valid):
raise CodeValidationError(
f"CV fold {fold} returned {len(fold_valid_preds)} validation predictions "
f"for {len(fold_y_valid)} validation rows."
)
if len(fold_test_preds) != len(X_test):
raise CodeValidationError(
f"CV fold {fold} returned {len(fold_test_preds)} test predictions "
f"for {len(X_test)} fixed test rows."
)
fold_mcc = float(matthews_corrcoef(fold_y_valid, fold_valid_preds))
fold_scores.append(fold_mcc)
test_prediction_folds.append(fold_test_preds.astype(str).reset_index(drop=True))
classes = fold_extra.get("_model_classes") if isinstance(fold_extra, dict) else None
proba_frame = _aligned_proba_frame(fold_test_proba, classes, all_labels, len(X_test))
if proba_frame is not None:
test_proba_folds.append(proba_frame)
elif fold_test_proba is not None:
print(
f"[submission-cv][fold {fold}/{CV_N_SPLITS}] "
"Ignoring predict_proba because its class labels do not align with decoded labels; "
"using vote consensus for test predictions if any fold is unsafe."
)
detail = {
"fold": fold,
"valid_mcc": fold_mcc,
"n_train": int(len(train_idx)),
"n_valid": int(len(valid_idx)),
"n_test": int(len(X_test)),
"train_batches": sorted(fold_batches_train.astype(str).unique().tolist()),
"valid_batches": sorted(fold_batches_valid.astype(str).unique().tolist()),
"test_batches": sorted(batches_test.astype(str).unique().tolist()),
}
fold_details.append(detail)
print(
f"[submission-cv][fold {fold}/{CV_N_SPLITS}] "
f"validation MCC={fold_mcc:.4f}, "
f"n_train={len(train_idx)}, n_valid={len(valid_idx)}, "
f"fixed_test={len(X_test)}"
)
mean_mcc = float(np.mean(fold_scores))
std_mcc = float(np.std(fold_scores))
if len(test_proba_folds) == len(test_prediction_folds) and test_proba_folds:
summed_proba = sum(test_proba_folds)
row_sums = summed_proba.sum(axis=1).replace(0.0, 1.0)
consensus_proba = summed_proba.div(row_sums, axis=0)
consensus_predictions = consensus_proba.idxmax(axis=1).astype(str).reset_index(drop=True)
consensus_proba_value: object | None = consensus_proba.to_numpy()
else:
votes = pd.concat(test_prediction_folds, axis=1)
consensus_predictions = votes.mode(axis=1).iloc[:, 0].astype(str).reset_index(drop=True)
consensus_proba_value = None
print(
f"[submission-cv] Mean validation MCC={mean_mcc:.4f} +/- "
f"{std_mcc:.4f}; folds={fold_scores}"
)
return {
"valid_mcc": mean_mcc,
"valid_mcc_std": std_mcc,
"valid_mcc_folds": fold_scores,
"valid_fold_details": fold_details,
"cv_protocol": protocol,
"_test_predictions": consensus_predictions,
"_test_pred_proba": consensus_proba_value,
}
def run_code_submission(
team: str,
model_name: str,
dataset: str,
correction_code: str,
model_code: str,
# groups: pd.Series = None,
) -> tuple[pd.DataFrame, dict[str, float | int], str, str]:
"""
Run code submission and capture visualizations.
Returns:
- predictions: DataFrame with predictions
- metrics: Evaluation metrics
- plot_html: HTML display of plots (shown publicly)
- plots_json: JSON serialized plots (stored privately in database, used when displaying)
"""
print(f"[submission-runner] Running code submission for team '{team}', model '{model_name}', dataset '{dataset}'")
plot_capture = PlotCapture()
print(f"[submission-runner] Capturing plots for submission display and storage.")
X_train, y_train, batches_train, X_test, batches_test, test_meta = _load_data_for_dataset(dataset)
print(f"[submission-runner] Loaded dataset '{dataset}': {len(X_train)} train rows, {len(X_test)} test rows.")
if len(X_test) == 0:
raise ValueError(
f"Private inference split for dataset '{dataset}' is empty (0 rows). "
"Evaluation cannot proceed until inference data is populated."
)
print(f"[submission-runner] Loaded dataset '{dataset}': {len(X_train)} train rows, {len(X_test)} test rows.")
# --- Data Profiling & UI Reporting ---
train_batch_counts = batches_train.value_counts().to_dict()
test_batch_counts = batches_test.value_counts().to_dict()
train_label_counts = y_train.astype(str).value_counts().to_dict()
print(f"[data-profiler] Train Batch Distribution: {train_batch_counts}")
print(f"[data-profiler] Test Batch Distribution: {test_batch_counts}")
print(f"[data-profiler] Train Label Distribution: {train_label_counts}")
# Create Batch Distribution Plot
try:
import matplotlib.pyplot as plt
import pandas as pd
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
# Train Plot
pd.Series(train_batch_counts).sort_index().plot(kind='bar', ax=ax1, color='skyblue')
ax1.set_title(f"Train Batch Distribution (n={len(X_train)})")
ax1.set_ylabel("Samples")
# Test Plot
pd.Series(test_batch_counts).sort_index().plot(kind='bar', ax=ax2, color='salmon')
ax2.set_title(f"Test Batch Distribution (n={len(X_test)})")
ax2.set_ylabel("Samples")
plt.tight_layout()
plot_capture.add_plot("Batch Distribution")
except Exception as e:
print(f"[data-profiler] Failed to create plot: {e}")
print(f"\n[data-profiler] Batch Distribution (Train): {train_batch_counts}")
print(f"[data-profiler] Batch Distribution (Test): {test_batch_counts}")
print(f"[data-profiler] Label Distribution (Train): {train_label_counts}")
# Run the same server-owned outer folds for every model and the complete
# submitted preprocessing pipeline before touching hidden labels.
cv_metrics = _cross_validate_submission(
correction_code,
model_code,
dataset,
X_train,
y_train,
batches_train,
X_test,
batches_test,
)
preds = cv_metrics.pop("_test_predictions")
pred_proba = cv_metrics.pop("_test_pred_proba", None)
extra_metrics: dict[str, object] = {}
print(f"[submission-runner] Running user batch correction code once on full train/test for batch-effect metrics.")
from src.baselines import set_bernn_seed
set_bernn_seed(CV_RANDOM_STATE)
X_train_corr, X_test_corr = _apply_user_batch_correction(
correction_code,
X_train,
batches_train,
X_test,
batches_test,
plot_capture,
)
print(f"[submission-runner] Applied user batch correction. Train shape: {X_train_corr.shape}, Test shape: {X_test_corr.shape}")
batch_effect_metrics = _compute_batch_effect_metrics(
X_train_corr,
X_test_corr,
batches_train,
batches_test,
)
print(f"[submission-runner] Computed batch effect metrics: {batch_effect_metrics}")
print(f"[submission-runner] Model predictions completed from {CV_N_SPLITS}-fold ensemble. Number of predictions: {len(preds)}")
# Hidden labels are loaded only after all submitted code has finished.
y_test_real = load_private_labels(dataset)["prediction"].astype(str).reset_index(drop=True)
if len(y_test_real) == 0:
raise ValueError(
f"Private label split for dataset '{dataset}' is empty (0 rows). "
"Evaluation cannot proceed until labels are populated."
)
if len(y_test_real) != len(test_meta):
raise ValueError(
f"Private inference rows ({len(test_meta)}) and private labels "
f"({len(y_test_real)}) do not match"
)
pred_df = pd.DataFrame({"name": test_meta["name"], "prediction": preds.astype(str)})
reference = pd.DataFrame({"name": test_meta["name"].astype(str), "prediction": y_test_real.astype(str)})
group_df = test_meta.copy()
group_df["group"] = batches_test.astype(str).reset_index(drop=True)
metrics = evaluate_predictions(pred_df, reference, predicted_proba=pred_proba, groups=group_df)
print(f"[submission-runner] Evaluated predictions. Metrics: {metrics}")
official_test_mcc_now = float(metrics.get("test_mcc", metrics.get("mcc", 0.0)))
print(
f"[submission-test] Official fixed-test ensemble MCC={official_test_mcc_now:.4f} "
f"(computed after all {CV_N_SPLITS} folds; hidden y_test is never passed to fit)"
)
metrics["prediction_unique_labels"] = int(pred_df["prediction"].astype(str).nunique(dropna=False))
metrics["reference_unique_labels"] = int(reference["prediction"].astype(str).nunique(dropna=False))
metrics["single_label_prediction"] = int(metrics["prediction_unique_labels"] <= 1)
metrics.update(batch_effect_metrics)
# Merge model-internal metrics (e.g. valid_mcc, train_mcc from BERNN or sklearn split).
# Do not overwrite already-computed official metrics.
if isinstance(extra_metrics, dict):
for k, v in extra_metrics.items():
if k not in metrics:
metrics[k] = v
# The server-owned outer CV is authoritative for every submission type.
metrics.update(cv_metrics)
# Persist debug artifacts to disk for submission-level diagnosis.
# This helps investigate cases where BERNN internal metrics look good but official score is low.
print(f"[submission-debug] Saving debug artifacts for team '{team}', model '{model_name}', dataset '{dataset}'")
try:
ts = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
def _slug(value: str) -> str:
text = str(value).strip().lower()
safe = []
for ch in text:
if ch.isalnum() or ch in {"-", "_"}:
safe.append(ch)
elif ch in {" ", "/", "\\", "."}:
safe.append("-")
compact = "".join(safe).strip("-")
return compact or "unknown"
debug_dir = ROOT / "logs" / "submission_debug" / dataset / f"{ts}_{_slug(team)}_{_slug(model_name)}"
debug_dir.mkdir(parents=True, exist_ok=True)
# Never persist hidden labels, row-level correctness, or mismatches. Those
# artifacts could be recovered by a later malicious submission.
pred_df.to_csv(debug_dir / "predictions_submitted.csv", index=False)
# Save quick summary in JSON for easy inspection.
summary = {
"timestamp_utc": datetime.now(timezone.utc).isoformat(),
"team": team,
"model_name": model_name,
"dataset": dataset,
"n_predictions": int(len(pred_df)),
"n_reference": int(len(reference)),
"official_metrics": {
"test_mcc": float(metrics.get("test_mcc", metrics.get("mcc", 0.0))),
"accuracy": float(metrics.get("accuracy", 0.0)),
"macro_f1": float(metrics.get("macro_f1", 0.0)),
"n_samples": int(metrics.get("n_samples", len(reference))),
},
"prediction_label_counts": pred_df["prediction"].astype(str).value_counts().to_dict(),
}
if isinstance(extra_metrics, dict):
summary["model_internal_metrics"] = {
k: float(v) for k, v in extra_metrics.items() if isinstance(v, (int, float, np.integer, np.floating))
}
with (debug_dir / "summary.json").open("w", encoding="utf-8") as fh:
json.dump(summary, fh, indent=2)
print(f"[submission-debug] Saved artifacts to: {debug_dir}")
except Exception as debug_exc:
print(f"[submission-debug] Failed to save debug artifacts: {type(debug_exc).__name__}: {debug_exc}")
# Keep official benchmark score authoritative; do not overwrite with model-internal metrics.
official_test_mcc = float(metrics.get("test_mcc", metrics.get("mcc", 0.0)))
if isinstance(extra_metrics, dict):
if "test_mcc" in extra_metrics:
metrics["model_test_mcc"] = float(extra_metrics["test_mcc"])
metrics["test_mcc"] = official_test_mcc
# Inject dataset metadata into metrics
metrics["train_batches"] = len(train_batch_counts)
metrics["test_batches"] = len(test_batch_counts)
metrics["train_samples"] = len(X_train)
metrics["test_samples"] = len(X_test)
# Log both validation and test scores for transparency
valid_mcc = float(metrics.get("valid_mcc", -1.0))
print(f"\n[submission-result] Dataset: {dataset} | Team: {team} | Model: {model_name}")
print(f"[submission-result] Valid MCC: {valid_mcc:.4f} (used for HP optimization)")
print(f"[submission-result] Test MCC: {official_test_mcc:.4f} (official leaderboard score)")
if "train_mcc" in metrics:
train_mcc = float(metrics["train_mcc"])
print(f"[submission-result] Train MCC: {train_mcc:.4f}")
plot_html = plot_capture.to_html()
plots_json = plot_capture.to_json()
return pred_df, metrics, plot_html, plots_json
def load_code_leaderboard(path: str | Path) -> pd.DataFrame:
p = Path(path)
p.parent.mkdir(parents=True, exist_ok=True)
if not p.exists():
_empty_code_board().to_csv(p, index=False)
return pd.read_csv(p)
def save_code_leaderboard(df: pd.DataFrame, path: str | Path) -> None:
p = Path(path)
p.parent.mkdir(parents=True, exist_ok=True)
df.to_csv(p, index=False)
def append_code_result(
board: pd.DataFrame,
team: str,
model_name: str,
dataset: str,
metrics: dict[str, float | int],
correction_code: str = "",
model_code: str = "",
public: bool = False,
) -> pd.DataFrame:
row = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"team": team,
"model": model_name,
"dataset": dataset,
"track": "real_code",
"accuracy": round(float(metrics["accuracy"]), 4),
"macro_f1": round(float(metrics["macro_f1"]), 4),
"n_samples": int(metrics["n_samples"]),
"correction_code": correction_code,
"model_code": model_code,
"public": int(public),
}
out = pd.concat([board, pd.DataFrame([row])], ignore_index=True)
return sorted_board(out)
def _empty_code_board() -> pd.DataFrame:
return pd.DataFrame(
columns=[
"timestamp",
"team",
"model",
"dataset",
"track",
"accuracy",
"macro_f1",
"n_samples",
"correction_code",
"model_code",
"public",
]
)