ictonyx.runners
ExperimentRunner, VariabilityStudyResults, GridStudyResults.
Experiment runners for variability studies with memory management. Supports both standard and process-isolated execution modes.
- class ictonyx.runners.ExperimentRunner(model_builder, data_handler, model_config, tracker=None, use_process_isolation=False, gpu_memory_limit=None, seed=None, verbose=True)[source]
Bases:
objectCore engine for running variability studies with memory management.
Trains a model multiple times with different random seeds, collecting per-epoch metrics from each run. Supports standard in-process execution and subprocess isolation for GPU memory cleanup.
This class is the low-level interface. Most users should prefer
variability_study(), which handles data resolution and model wrapping automatically.- Parameters:
model_builder (Callable[[ModelConfig], BaseModelWrapper]) – A callable
f(ModelConfig) -> BaseModelWrapperthat constructs a fresh model for each run.data_handler (DataHandler) – A
DataHandlerthat provides train/val/test splits via itsload()method.model_config (ModelConfig) – A
ModelConfigcontaining training hyperparameters (epochs, batch_size, learning_rate, etc.).tracker (BaseLogger | None) – An optional
BaseLoggerfor experiment tracking. Defaults to a basic in-memory logger.use_process_isolation (bool) – If
True, each run executes in a child process, ensuring GPU memory is fully released between runs. Required for long Keras/TF studies that would otherwise OOM. DefaultFalse.gpu_memory_limit (int | None) – Optional GPU memory cap in MB, applied per run when using TensorFlow. Default
None(no limit).seed (int | None) – Base random seed for reproducibility. Each run receives an independent child seed derived via
np.random.SeedSequence.spawn(), which guarantees statistically uncorrelated RNG streams. IfNone, a random seed is generated and stored.verbose (bool) – If
True, log study progress to stdout. Iftqdmis installed, a progress bar replaces per-run log messages. DefaultTrue.
- __init__(model_builder, data_handler, model_config, tracker=None, use_process_isolation=False, gpu_memory_limit=None, seed=None, verbose=True)[source]
Initialize the experiment runner.
See class docstring for full parameter descriptions.
- Parameters:
model_builder (Callable[[ModelConfig], BaseModelWrapper])
data_handler (DataHandler)
model_config (ModelConfig)
tracker (BaseLogger | None)
use_process_isolation (bool)
gpu_memory_limit (int | None)
seed (int | None)
verbose (bool)
- run_study(num_runs=20, epochs_per_run=None, stop_on_failure_rate=0.8, checkpoint_dir=None, use_parallel=False, n_jobs=-1)[source]
Execute the complete variability study.
Trains the model
num_runstimes, collecting per-epoch training metrics from each run. Resets all internal accumulators before starting, so callingrun_study()twice on the same runner is safe.If
tqdmis installed andverbose=True, a progress bar is displayed with a live postfix showing the most informative validation metric from the latest completed run.- Parameters:
num_runs (int) – Number of independent training runs. Default 20.
epochs_per_run (int | None) – Epochs per run. If
None, uses theepochsvalue frommodel_config. DefaultNone.stop_on_failure_rate (float) – If the fraction of failed runs exceeds this threshold, the study halts early. Set to
1.0to never stop early. Default0.8.checkpoint_dir (str | None) – Optional path to a directory for saving progress after each completed run. If the directory contains a
checkpoint.pklfile from a previous interrupted run, execution resumes from where it left off. DefaultNone(no checkpointing).use_parallel (bool) – If
True, fan training runs across multiple processes usingjoblib.Parallel. Safe for sklearn models. Not recommended for Keras/TF. Mutually exclusive withuse_process_isolation. DefaultFalse.n_jobs (int) – Number of parallel workers.
-1uses all available CPUs. Ignored whenuse_parallel=False. Default-1.
- Returns:
VariabilityStudyResultswith per-run DataFrames, final metric distributions, and optional test-set metrics.- Return type:
- get_summary_stats()[source]
Get summary statistics for the completed study.
- Returns:
total_runs,successful_runs,failed_runs,failure_rate— run accounting.<metric>_mean,<metric>_std,<metric>_min,<metric>_max— descriptive statistics for each tracked metric (e.g.val_accuracy_mean).
- Return type:
Dict with keys
- class ictonyx.runners.VariabilityStudyResults(all_runs_metrics, final_metrics, final_test_metrics, seed=None, run_seeds=<factory>)[source]
Bases:
objectContainer for variability study results with analysis methods.
Returned by
variability_study()andExperimentRunner.run_study(). Holds per-run training history DataFrames, aggregated final-epoch metrics, and optional test-set evaluation results.- Parameters:
- all_runs_metrics
List of DataFrames, one per successful run. Each DataFrame has one row per epoch and columns for every tracked metric (e.g.
train_accuracy,val_loss).- Type:
List[pandas.DataFrame]
- final_metrics
Dict mapping metric names to lists of final-epoch values across runs. Example:
{'val_accuracy': [0.82, 0.85, 0.83]}.
- final_test_metrics
List of dicts, one per run, containing test-set evaluation results (empty if no test set was used).
- Type:
List[Dict[str, Any]]
- run_seeds
List of per-run child seeds generated from the base seed via
SeedSequence.spawn(). Empty when results are reconstructed from MLflow or loaded from JSON.- Type:
List[int]
- get_final_metrics(metric_name='val_accuracy')[source]
Extract final metric values for each run (labeled run_1, run_2, …).
Deprecated since version Emits:
UserWarning. Useget_metric_values()instead.get_final_metrics()will be removed in v0.5.0.
- to_dataframe()[source]
Convert results to a summary DataFrame with one row per run.
Test metrics are aligned by
run_id, not list position, so a failed test evaluation for one run does not corrupt the alignment of subsequent runs.- Return type:
- preferred_metric(base='accuracy', context='scalar')[source]
Return the preferred metric name for this study.
The preferred metric depends on the context in which it will be used:
scalar (default): Terminal metric reporting. Returns
f"test_{base}"when test data is present and the base metric was tracked,f"val_{base}"otherwise.epoch: Per-epoch plotting and analysis. Always returns
f"val_{base}"because test metrics are scalar (single final value) and per-epoch plotters cannot resolve them.
Before returning, verifies the resolved key exists in the study’s tracked metrics. If neither the primary nor alternate prefix resolves, raises a helpful
KeyErrornaming the attempted keys, what metrics are tracked, and how to passbaseexplicitly.Note
final_test_metricsstores metrics under bare keys (e.g."accuracy", not"test_accuracy"). This method compares against the bare key and returns the prefixed form.- Parameters:
- Returns:
The most appropriate metric key for this study’s results.
- Raises:
KeyError – If neither the primary candidate nor the alternate prefix resolves to a tracked metric on this study. The error message names the attempted keys, the available val/test metrics, and suggests how to fix the call.
- Return type:
- get_epoch_statistics(metric, confidence=0.95)[source]
Compute per-epoch mean, SD, SE, and confidence band across all runs.
Aligns all runs at each epoch position. Runs with fewer epochs than the maximum are excluded from epochs beyond their last.
- Parameters:
- Returns:
epoch,mean,sd,se,ci_lower,ci_upper,n_runs.- Return type:
DataFrame with columns
- Raises:
ValueError – If no runs are available.
KeyError – If metric is not found in any run’s DataFrame.
Example:
stats = results.get_epoch_statistics("val_accuracy") import matplotlib.pyplot as plt plt.fill_between( stats["epoch"], stats["ci_lower"], stats["ci_upper"], alpha=0.2, label="95% CI" ) plt.plot(stats["epoch"], stats["mean"], label="Mean") plt.legend()
- get_test_metric_values(metric)[source]
Get per-run final values for a test-set metric.
Accepts either bare key (
'accuracy') or prefixed form ('test_accuracy') — both are resolved against the bare key, which is howfinal_test_metricsactually stores values.
- test_against_null(null_value=0.5, metric=None, alpha=0.05, alternative='two-sided')[source]
Test whether a metric’s distribution differs from a null value.
Applies a one-sample Wilcoxon signed-rank test to the per-run final values of
metric.- Parameters:
null_value (float) – The null hypothesis value. Use 0.5 for chance-level binary classification accuracy.
metric (str | None) – Metric name. Must be a key in
final_metrics.alpha (float) – Significance threshold (default 0.05).
alternative (str) –
'two-sided'(default),'greater', or'less'. Use'greater'for one-sided tests of the form “is metric above the null” — or calltest_above_chance()which is a shortcut for that.
- Returns:
A StatisticalTestResult with
is_significant,p_value,statistic, andconclusion.- Return type:
Note
The Wilcoxon signed-rank test requires at least 6 non-zero differences for reliable results. Use
num_runs >= 20for reliable inference — consistent with the library-wide minimum for Mann-Whitney U tests.
- test_above_chance(metric=None, alpha=0.05, chance_level=0.5)[source]
Test whether a model performs significantly above chance.
One-sided variant of
test_against_null()that tests the specific hypothesis “this model’s median performance is greater than chance.” Null hypothesis: median = chance_level. Alternative: median > chance_level.Uses a one-sided Wilcoxon signed-rank test (equivalent to calling
test_against_null(null_value=chance_level, alternative='greater')).- Parameters:
metric (str | None) – Metric name. Must be a key in
final_metricsor the bare key in afinal_test_metricsentry. Defaults to the study’s preferred accuracy metric.alpha (float) – Significance threshold. Default 0.05.
chance_level (float) – The chance-level baseline to test against. Default 0.5 (binary classification with balanced classes). For multi-class tasks with k balanced classes, pass
chance_level = 1.0 / k.
- Returns:
A StatisticalTestResult with
is_significant,p_value,statistic, andconclusion. The test is one-sided, sop_valuereflects P(median <= chance_level | observed data).- Return type:
Note
Recommended sample size:
num_runs >= 20for reliable inference, consistent with the library-wide minimum for rank-based tests.Example
>>> results = ix.variability_study(model=..., runs=20) >>> outcome = results.test_above_chance() >>> if outcome.is_significant(): ... print(f"Model above chance (p={outcome.p_value:.4f})")
- compare_models_statistically(*args, **kwargs)[source]
Removed in v0.3.10.
This method applied Kruskal-Wallis to groups of one observation each (one per run), which is statistically incoherent — within-group variance is undefined with a single observation.
To compare a single model’s run distribution against a null value, use:
results.test_against_null(null_value=0.5, metric="val_accuracy")
To compare multiple distinct models against each other, use:
ix.compare_models(models=[model_a, model_b], data=..., runs=20)
- save(path)[source]
Persist results to disk as a plain dict via pickle.
Preserves all_runs_metrics, final_metrics, final_test_metrics, seed, and run_seeds. Restore with
load().- Parameters:
path (str) – File path. Recommended extension:
.pkl.- Return type:
None
- classmethod load(path)[source]
Restore results previously saved with
save().- Parameters:
- Returns:
Reconstructed VariabilityStudyResults.
- Return type:
- to_json()[source]
Serialise final metrics to a compact JSON string.
Does not include
all_runs_metrics(per-epoch DataFrames). Usesave()/load()for full round-trip fidelity.- Returns:
JSON string containing
final_metrics,final_test_metrics,seed, andn_runs.- Return type:
- classmethod from_json(json_str)[source]
Reconstruct a VariabilityStudyResults from a JSON string.
Symmetric with
to_json(). Note thatto_jsondoes not serializeall_runs_metrics(per-epoch DataFrames), so the reconstructed object has an emptyall_runs_metricsand emptyrun_seeds. Usesave()/load()for full round-trip fidelity including per-epoch history.- Parameters:
- Returns:
VariabilityStudyResults with
final_metrics,final_test_metrics, andseedpopulated from the JSON.all_runs_metricsis empty;run_seedsis empty.- Raises:
ValueError – If the JSON is malformed or missing required keys.
- Return type:
Example
>>> serialized = results.to_json() >>> restored = VariabilityStudyResults.from_json(serialized) >>> assert restored.final_metrics == results.final_metrics
- classmethod from_mlflow_experiment(experiment_name, metric, run_filter=None, tracking_uri=None)[source]
Reconstruct results from a completed MLflow experiment.
Allows running ictonyx statistical analysis on existing MLflow experiments without re-running training.
- Parameters:
experiment_name (str) – Name of the MLflow experiment.
metric (str) – Metric to extract per run (e.g. ‘val_accuracy’).
run_filter (str | None) – Optional MLflow filter string (e.g. “params.seed = ‘42’”).
tracking_uri (str | None) – MLflow tracking URI. Uses the environment’s MLFLOW_TRACKING_URI if not set.
- Returns:
VariabilityStudyResults with final_metrics populated from the experiment’s runs. all_runs_metrics is empty — per-epoch history is not reconstructed from MLflow.
- Raises:
ImportError – If mlflow is not installed.
ValueError – If no matching runs are found, or if the requested metric is not present in the runs.
- Return type:
- class ictonyx.runners.GridStudyResults(results, param_grid, base_config, metric='val_accuracy')[source]
Bases:
objectContainer for grid study results across multiple parameter configurations.
Returned by
run_grid_study(). Holds oneVariabilityStudyResultsper parameter configuration, the param grid that generated them, and the base config used as the template for all runs.- Parameters:
results (Dict[Tuple, VariabilityStudyResults])
base_config (ModelConfig)
metric (str)
- results
Dict mapping frozen parameter tuples to VariabilityStudyResults. Keys are sorted tuples of (param_name, value) pairs for hashability and deterministic ordering.
- Type:
Dict[Tuple, ictonyx.runners.VariabilityStudyResults]
- base_config
The ModelConfig used as the template for all runs.
- results: Dict[Tuple, VariabilityStudyResults]
- base_config: ModelConfig
- get_results_for_config(param_combo)[source]
Retrieve VariabilityStudyResults for a specific parameter combination.
- Parameters:
param_combo (Dict[str, Any]) – Dict of parameter names to values, e.g. {‘learning_rate’: 0.001, ‘batch_size’: 8}
- Returns:
VariabilityStudyResultsfor that configuration.- Raises:
KeyError – If the configuration was not found in results.
- Return type:
- to_dataframe(metric=None)[source]
Summary DataFrame with one row per configuration.
Columns: all parameter names, mean, sd, se, min, max, n.
- summarize(metric=None)[source]
Generate text summary of grid study results.
For executed studies, returns a summary of configurations and their metric statistics. For dry-run results (empty
self.results), returns a preview showing the parameter grid and the planned configurations that would be executed.
- ictonyx.runners.run_variability_study(model_builder, data_handler, model_config, num_runs=20, epochs_per_run=None, tracker=None, use_process_isolation=False, gpu_memory_limit=None, seed=None, verbose=True, use_parallel=False, n_jobs=-1)[source]
Run a complete variability study (convenience function).
Creates an
ExperimentRunnerand callsrun_study(). This is the lower-level entry point; most users should prefervariability_study().- Parameters:
model_builder (Callable[[ModelConfig], BaseModelWrapper]) – Callable
f(ModelConfig) -> BaseModelWrapper.data_handler (DataHandler) – A
DataHandlerinstance.model_config (ModelConfig) – A
ModelConfiginstance.num_runs (int) – Number of training runs. Default 20.
epochs_per_run (int | None) – Epochs per run. Default
None(uses config).tracker (BaseLogger | None) – Optional experiment logger. Default
None.use_process_isolation (bool) – Run each iteration in a subprocess. Default
False.gpu_memory_limit (int | None) – Optional GPU memory cap in MB.
seed (int | None) – Base random seed. If
None, one is generated.verbose (bool) – Print progress to stdout. Default
True.use_parallel (bool)
n_jobs (int)
- Returns:
- Return type:
- ictonyx.runners.run_grid_study(model_builder, data_handler, base_config, param_grid, num_runs=20, metric='val_accuracy', use_process_isolation=True, dry_run=False, verbose=True, seed=None)[source]
Run a variability study across a grid of parameter configurations.
For each combination in the Cartesian product of
param_grid, creates a modified copy ofbase_config, runs a full variability study, and collects results. All runs use process isolation by default to prevent CUDA memory accumulation across configurations.- Parameters:
model_builder (Callable) – Callable accepting a ModelConfig, returning a compiled model.
data_handler (DataHandler) – DataHandler instance providing train/val data.
base_config (ModelConfig) – ModelConfig used as template. Values are overridden per configuration.
param_grid (Dict[str, List[Any]]) –
Dict mapping parameter names to lists of values to sweep. Example:
{ 'learning_rate': [0.001, 0.0001], 'batch_size': [8, 16] }
Generates the full Cartesian product: 4 configurations × num_runs each.
num_runs (int) – Number of training runs per configuration. Default 20.
metric (str) – Primary metric for summary statistics.
use_process_isolation (bool) – If True, each run executes in a subprocess. Strongly recommended to prevent GPU memory accumulation across configurations.
dry_run (bool) – If True, print execution plan and return without training. Use to verify configuration before committing to a long run.
verbose (bool) – If
True, log study progress. DefaultTrue.seed (int | None) – Base random seed for reproducibility. Each configuration receives an independent child seed derived via
SeedSequence.spawn(). IfNone, each configuration uses a random seed.
- Returns:
- Raises:
ValueError – If param_grid is empty or contains no values.
- Return type:
Example:
results = run_grid_study( model_builder=build_cnn, data_handler=data_handler, base_config=config, param_grid={'learning_rate': [0.001, 0.0001, 0.00001]}, num_runs=20, use_process_isolation=True ) print(results.summarize()) df = results.to_dataframe()