Ictonyx: Iteration Comparison Testing Over N-runs: Yield eXamination
Project description
Ictonyx
Iteration Comparison Testing Over N-runs: Yield eXamination
A Python framework for studying machine learning model variability and performing rigorous statistical comparisons.
The problem
Training a neural network involves stochastic factors: random weight initialisation, data shuffling, dropout. Train the same architecture on the same dataset twice and you will get different weights, different predictions, and different evaluation metrics.
This means a model's performance is a random variable, not a constant — and treating it as a constant, as most practitioners do, leads to conclusions that cannot be replicated or trusted. Reporting a single accuracy from a single training run is not a measurement; it is a sample of size one.
Ictonyx trains a model N times under independent random seeds, collects the full distribution of outcomes, and provides the statistical machinery to reason about that distribution rigorously.
Installation
# scikit-learn (no deep learning)
pip install ictonyx[sklearn]
# TensorFlow / Keras
pip install ictonyx[tensorflow]
# PyTorch
pip install ictonyx[torch]
# Everything
pip install ictonyx[all]
# Process isolation for long GPU runs (recommended with tensorflow or torch)
pip install "ictonyx[tensorflow,isolation]"
Requires Python 3.10+. Current release: 0.3.15 — changelog · PyPI
Quick start
Train a small CNN on CIFAR-10 twenty times and observe the distribution of outcomes. The task is genuinely difficult — 10 classes, colour images, a non-convex loss landscape — so initialisation matters and the spread across runs is real.
import numpy as np
import tensorflow as tf
import ictonyx as ix
from ictonyx import ModelConfig, KerasModelWrapper, ArraysDataHandler, run_variability_study
# Load CIFAR-10 — downloads once to ~/.keras/datasets/, cached permanently after
(X_train, y_train), _ = tf.keras.datasets.cifar10.load_data()
X = (X_train[:20000].astype('float32') / 255.0)
y = y_train[:20000].flatten()
def build_cnn(config: ModelConfig) -> KerasModelWrapper:
model = tf.keras.Sequential([
tf.keras.Input(shape=(32, 32, 3)),
tf.keras.layers.Conv2D(32, (3, 3), activation='relu', padding='same'),
tf.keras.layers.MaxPooling2D((2, 2)),
tf.keras.layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
tf.keras.layers.MaxPooling2D((2, 2)),
tf.keras.layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dropout(0.4),
tf.keras.layers.Dense(10, activation='softmax'),
])
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'],
)
return KerasModelWrapper(model)
config = ModelConfig({'epochs': 15, 'batch_size': 64, 'verbose': 0})
data_handler = ArraysDataHandler(X, y)
results = run_variability_study(
model_builder=build_cnn,
data_handler=data_handler,
model_config=config,
num_runs=20,
seed=42,
)
print(results.summarize())
Variability Study Results
==============================
Successful runs: 20
Seed: 42
train_accuracy:
Mean: 0.8198
SD (sample, N-1): 0.0182
Min: 0.7869
Max: 0.8491
train_loss:
Mean: 0.4975
SD (sample, N-1): 0.0489
Min: 0.4177
Max: 0.5945
val_accuracy:
Mean: 0.6448
SD (sample, N-1): 0.0101
Min: 0.6260
Max: 0.6615
val_loss:
Mean: 1.1925
SD (sample, N-1): 0.0523
Min: 1.1163
Max: 1.2751
Here we have a 3.7 percent range for validation accuracy across twenty runs of the same architecture on the same data. This represents a source of variability that shouldn't be neglected.
ix.plot_variability_summary(
all_runs_metrics_list=results.all_runs_metrics,
final_metrics_series=results.final_metrics['val_accuracy'],
metric='accuracy',
)
Comparing two architectures
A common question in deep learning: does adding depth or regularisation actually help, or does it just happen to get a better seed? Ictonyx answers this by running a full variability study for each architecture and applying a statistical test to the resulting distributions.
def build_dense(config: ModelConfig) -> KerasModelWrapper:
"""A simpler dense baseline — no convolutions."""
model = tf.keras.Sequential([
tf.keras.Input(shape=(32, 32, 3)),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(256, activation='relu'),
tf.keras.layers.Dropout(0.4),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(10, activation='softmax'),
])
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'],
)
return KerasModelWrapper(model)
comparison = ix.compare_models(
models=[build_cnn, build_dense],
data=data_handler,
runs=20,
metric='val_accuracy',
seed=42,
)
print(comparison.get_summary())
Model Comparison Results (val_accuracy)
========================================
Models compared: 2
Omnibus test: Kruskal-Wallis H-Test: 29.279, p=0.0000 ***, epsilon-squared=0.744
Pairwise comparisons (holm correction):
build_cnn_vs_build_dense: Mann-Whitney U Test: 400.000, p_corr=0.0000 ***, rank-biserial correlation=1.000 *
Significant pairs: build_cnn_vs_build_dense
A rank-biserial correlation of 1.0 means the CNN outperformed the dense network in every single one of the ten paired runs. The p-value establishes that this is not attributable to chance; the effect size establishes that it is absolute.
Process isolation for GPU runs
Keras models accumulate GPU memory across training runs in a loop. For studies with many runs or large models, run each training session in a subprocess:
results = run_variability_study(
model_builder=build_cnn,
data_handler=data_handler,
model_config=config,
num_runs=20,
use_process_isolation=True,
gpu_memory_limit=4096,
seed=42,
)
Each run executes in a child process and exits cleanly, releasing all GPU memory before the next run begins.
scikit-learn
Ictonyx works equally well with sklearn estimators. Pass a class and Ictonyx constructs a fresh instance for each run; pass a configured instance and Ictonyx clones it per run via sklearn.base.clone().
import ictonyx as ix
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.datasets import load_breast_cancer
import pandas as pd
data = load_breast_cancer()
df = pd.DataFrame(data.data, columns=data.feature_names)
df['target'] = data.target
comparison = ix.compare_models(
models=[RandomForestClassifier, GradientBoostingClassifier],
data=df,
target_column='target',
runs=20,
metric='val_accuracy',
seed=42,
)
print(comparison.get_summary())
Model Comparison Results (val_accuracy)
========================================
Models compared: 2
Omnibus test: Kruskal-Wallis H-Test: 2.053, p=0.1519 ns, epsilon-squared=0.028
No pairwise comparisons performed (omnibus test not significant).
PyTorch
import torch
import torch.nn as nn
import ictonyx as ix
from ictonyx import ModelConfig, PyTorchModelWrapper, ArraysDataHandler, run_variability_study
def build_net(config: ModelConfig) -> PyTorchModelWrapper:
model = nn.Sequential(
nn.Linear(30, 64), nn.ReLU(),
nn.Linear(64, 32), nn.ReLU(),
nn.Linear(32, 2),
)
return PyTorchModelWrapper(
model,
criterion=nn.CrossEntropyLoss(),
optimizer_class=torch.optim.Adam,
optimizer_params={'lr': config.get('learning_rate', 0.001)},
task='classification',
)
# Pass arrays directly — Ictonyx handles splitting
import numpy as np
from sklearn.datasets import load_breast_cancer
data = load_breast_cancer()
X = data.data.astype(np.float32)
y = data.target.astype(np.int64)
results = run_variability_study(
model_builder=build_net,
data_handler=ArraysDataHandler(X, y, val_split=0.2, test_split=0.1),
model_config=ModelConfig({'epochs': 30, 'batch_size': 32, 'learning_rate': 0.001}),
num_runs=20,
seed=42,
)
ix.plot_variability_summary(
all_runs_metrics_list=results.all_runs_metrics,
final_metrics_series=results.final_metrics['val_accuracy'],
metric='accuracy',
)
Working with results
# Full distribution of any metric across runs
results.get_metric_values('val_accuracy') # List[float]
# Per-epoch statistics across all runs
results.get_epoch_statistics('val_accuracy') # DataFrame: epoch, mean, sd, se, ci_lower, ci_upper
# All per-run, per-epoch DataFrames
results.all_runs_metrics # List[pd.DataFrame]
# Seed for exact reproducibility
results.seed
Examples
The examples/ directory contains Jupyter notebooks:
quickstart.ipynb— CIFAR-10 variability study and two-architecture comparison01_cifar10_variability_study.ipynb— deep dive into CNN variability with full visualisation02_cifar10_model_comparison.ipynb— comparing architectures statistically03_learning_rate_variability.ipynb— grid study across learning rates and batch sizes04_pytorch_classification.ipynb— PyTorch classification workflow05_pytorch_regression.ipynb— PyTorch regression workflow06_sklearn_models.ipynb— comparing multiple sklearn classifiers
License
MIT. See LICENSE.
Citation
If you use Ictonyx in published work, please cite it using the metadata in
CITATION.cff, or use the Cite this repository button
on the GitHub repository page.
@software{kizlik_ictonyx,
author = {Kizlik, Stephen},
title = {Ictonyx: A Framework for Variability Analysis in Machine Learning Training},
version = {0.3.15},
url = {https://github.com/skizlik/ictonyx},
license = {MIT},
}
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ictonyx-0.3.15.tar.gz.
File metadata
- Download URL: ictonyx-0.3.15.tar.gz
- Upload date:
- Size: 111.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.2 CPython/3.12.3 Linux/6.11.0-29-generic
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f49978f9407da5e350d34eaf8ea4a33dfe43d2f471597d3957243a6aa339486c
|
|
| MD5 |
6fa1281b9b21e5d926c231b48f9bce53
|
|
| BLAKE2b-256 |
ab9d8fc0ac2e0facbfd5c56a55510291301c4730abc0e01a36b20150a3f5245f
|
File details
Details for the file ictonyx-0.3.15-py3-none-any.whl.
File metadata
- Download URL: ictonyx-0.3.15-py3-none-any.whl
- Upload date:
- Size: 115.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.2 CPython/3.12.3 Linux/6.11.0-29-generic
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e6cef02fb38f34216c54688fb52a4a68e7e79e2b76b9f58194427645a46e369d
|
|
| MD5 |
b55263e657c4011c26776d51d387f71b
|
|
| BLAKE2b-256 |
02763ca15b6273a0950f02b0d706c5826999740391c393d94c9648366fb2a965
|