Skip to main content

DeepAL6 — Deep Active Learning Library for Tabular and Image Domains

Project description

DeepAL6 — Deep Active Learning Library

Python PyTorch License Version Strategies

Bob Philip Aila · AIMS Rwanda
Pool-based deep active learning: 6 query strategies benchmarked against a random baseline,
for tabular and image domains.


Table of Contents


Overview

deepal6 is a flexible, research-grade active learning framework built for reproducible experimentation. It lets you:

  • Run 6 query strategies — Random, Entropy, Margin, BALD, CoreSet, BADGE — under identical budget protocols
  • Work with tabular data (CreditNet) or image data (ResNet-18 + Dropout head)
  • Control every parameter: initial size, batch size, rounds, seeds, augmentations, and more
  • Get publication-quality plots — learning curves, strategy-vs-random gap, calibration (ECE)
  • Extend with custom strategies via a one-line registration API
  • Compare strategies fairly with stratified initial draws and class-weighted loss

Installation

Standard install (recommended)

pip install deepal6

With image support (ResNet-18 / NIH Chest X-ray / MedMNIST)

pip install "deepal6[image]"

Development install (editable, from source)

git clone https://github.com/bob-aila/deepal6.git
cd deepal6
pip install -e .

# With image support
pip install -e ".[image]"

Verify the install

from deepal6 import list_strategies
print(list_strategies())
# ['Random', 'Entropy', 'Margin', 'BALD', 'CoreSet', 'BADGE']

Requirements

Package Minimum version
Python 3.8+
PyTorch 1.12+
scikit-learn 1.0+
numpy 1.21+
matplotlib 3.4+
torchvision (image only) 0.13+
Pillow (image only) 9.0+

Quick Start

Tabular (German Credit / any binary classification)

import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from deepal6 import ActiveLearner, TabularDataModule, ALConfig

# 1. Prepare data — scale your features, binary labels 0/1
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42)
scaler  = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test  = scaler.transform(X_test)

# 2. Wrap in DataModule
data = TabularDataModule(X_train, y_train, X_test, y_test, pos_label=0)

# 3. Configure experiment
config = ALConfig(
    strategy     = ['Random', 'Entropy', 'BALD', 'CoreSet', 'BADGE'],
    initial_size = 50,    # stratified initial labeled set
    budget       = 450,   # total labelling budget (n_rounds computed automatically)
    batch_size   = 20,    # samples queried per round
    n_seeds      = 5,     # independent runs for mean ± std
    train_epochs = 50,
)

# 4. Run
learner = ActiveLearner(data, config)
results = learner.run()

# 5. Visualise
learner.plot(results, metric='auc')
learner.summary_table(results)

Image (NIH Chest X-ray / any binary image dataset)

A) Images on disk — DataFrame with filepath + label columns

import pandas as pd
from deepal6 import ActiveLearner, ImageDataModule, ALConfig
from torchvision import transforms

train_df = pd.DataFrame({
    'filepath': ['/data/train/img001.png', ...],
    'label':    [0, 1, ...]
})
test_df = pd.DataFrame({
    'filepath': ['/data/test/img100.png', ...],
    'label':    [1, ...]
})

my_aug = transforms.Compose([
    transforms.Resize((256, 256)),
    transforms.RandomHorizontalFlip(),
    transforms.RandomRotation(10),
    transforms.ColorJitter(brightness=0.2, contrast=0.2),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])

data = ImageDataModule(
    train_df, test_df,
    train_transform = my_aug,
    img_size        = 256,
    pos_label       = 1,
)

B) In-memory arrays — MedMNIST / numpy arrays

When your images are already loaded into memory (e.g. MedMNIST, numpy arrays), wrap them in a NumpyImageDataset and apply the transform inside the Dataset. Also set num_workers=0 in notebooks and on macOS.

import numpy as np
from torch.utils.data import Dataset
from PIL import Image
from torchvision import transforms
from sklearn.model_selection import train_test_split
from deepal6 import ActiveLearner, ImageDataModule, ALConfig

# -- Define transform (must convert PIL → tensor) ----------------------------
my_aug = transforms.Compose([
    transforms.Grayscale(num_output_channels=3),   # handles grayscale images
    transforms.Resize((64, 64)),
    transforms.RandomHorizontalFlip(),
    transforms.RandomRotation(10),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])

test_tf = transforms.Compose([
    transforms.Grayscale(num_output_channels=3),
    transforms.Resize((64, 64)),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])

# Wrap numpy arrays as a deepal6-compatible Dataset
class NumpyImageDataset(Dataset):
    def __init__(self, images, labels, transform=None):
        # images: float32 in [0,1] or uint8 — both handled
        if images.dtype != np.uint8:
            images = (images * 255).clip(0, 255).astype(np.uint8)
        self.images    = images
        self._labels   = labels.astype(int)
        self.transform = transform

    def __len__(self):
        return len(self.images)

    def __getitem__(self, idx):
        img = Image.fromarray(self.images[idx])   # PIL Image
        if self.transform:
            img = self.transform(img)             # → tensor
        return img, int(self._labels[idx])

    def get_labels(self):                         # required by deepal6
        return self._labels

# Example: MedMNIST
import medmnist
from medmnist import INFO

info   = INFO['bloodmnist']
DClass = getattr(medmnist, info['python_class'])
parts  = [DClass(split=s, download=True) for s in ('train', 'val', 'test')]
imgs   = np.concatenate([p.imgs for p in parts]).astype(np.float32) / 255.0
labels = np.concatenate([p.labels.squeeze() for p in parts])
labels = (labels >= 4).astype(int)   # binarise

X_train, X_test, y_train, y_test = train_test_split(
    imgs, labels, test_size=0.2, stratify=labels, random_state=42)

data = ImageDataModule(
    NumpyImageDataset(X_train, y_train, transform=my_aug),
    NumpyImageDataset(X_test,  y_test,  transform=test_tf),
    pos_label   = 1,
    num_workers = 0,    # always 0 in notebooks and on macOS
)

# Configure and run
config = ALConfig(
    strategy     = ['Random', 'BALD', 'CoreSet', 'BADGE'],
    initial_size = 50,
    budget       = 450,   # n_rounds computed as floor((450-50)/20) = 20
    batch_size   = 20,
    n_seeds      = 3,
    train_epochs = 10,
    lr           = 1e-4,
    dropout_rate = 0.4,
)

learner = ActiveLearner(data, config)
results = learner.run()
learner.plot(results, metric='auc', show_std=True)
learner.summary_table(results, metric='auc')

Strategies

Strategy Type Selection Criterion When it shines
Random Baseline Uniform draw — no model used Always run as lower bound
Entropy Uncertainty Highest Shannon entropy H[y|x] Well-calibrated models
Margin Uncertainty Smallest gap |p − 0.5| Fast, low compute
BALD Bayesian Mutual information I[y;θ|x,D] via MC Dropout Best epistemic uncertainty estimate
CoreSet Diversity Greedy k-center in embedding space Rich embeddings (image > tabular)
BADGE Hybrid Gradient embeddings + k-means++ Combines uncertainty + diversity

Note: BALD requires dropout_rate > 0 (default 0.3). All strategies use the same budget, training procedure, and evaluation protocol so results are directly comparable.


ALConfig Parameters

from deepal6 import ALConfig

config = ALConfig(
    strategy         = ['Random', 'BALD'],  # or a single string: 'BALD'
    initial_size     = 50,
    budget           = 450,   #n_rounds computed automatically
    batch_size       = 20,
    n_seeds          = 5,
    train_epochs     = 50,
    lr               = 1e-3,
    weight_decay     = 1e-4,
    dropout_rate     = 0.3,
    mc_passes        = 20,
    train_batch_size = 32,
    device           = None,    # auto-detect GPU/CPU
    seed             = 42,
    verbose          = True,
    save_checkpoints = False,
    checkpoint_dir   = './checkpoints',
)
print(config.summary())        # prints a formatted parameter table
print(config.total_budget)     # effective labels used (may differ from budget if remainder)
Parameter Default Description
strategy all 6 Strategy name(s) to run
initial_size 50 Stratified initial labeled set size
batch_size 20 Samples queried per AL round
budget None Total labelling budget — sets n_rounds automatically via floor((budget - initial_size) / batch_size)
n_rounds 20 AL rounds — set directly if not using budget (legacy API, still supported)
n_seeds 5 Independent runs per strategy (for mean ± std)
train_epochs 50 Training epochs per round
lr 1e-3 Adam learning rate
weight_decay 1e-4 L2 regularisation
dropout_rate 0.3 Dropout probability (also controls BALD MC stochasticity)
mc_passes 20 MC Dropout forward passes for BALD
train_batch_size 32 Mini-batch size during model training
device auto 'cpu', 'cuda', or None (auto-detect)
seed 42 Base seed; each of n_seeds runs uses seed + i
verbose True Print per-round metrics during experiment
save_checkpoints False Save best model checkpoint per strategy per seed
checkpoint_dir './checkpoints' Directory for saved checkpoints
extra_strategy_kwargs {} Extra kwargs forwarded to strategy functions

Available Plots

# Learning curves — metric vs labelling budget
learner.plot(results, metric='auc')          # AUC-ROC
learner.plot(results, metric='bal_acc')      # Balanced accuracy
learner.plot(results, metric='recall')       # Recall (minority class)
learner.plot(results, metric='ece')          # Calibration error

# Strategy vs random gap — shown automatically when 'Random' is in results
# Positive gap = strategy beats random at that budget point

# ECE calibration detail
learner.plot_calibration(results)

# Printed summary table
learner.summary_table(results, metric='auc')

# Save any plot to file
learner.plot(results, metric='auc', save_path='results/auc_curves.png')

All plots support show_std=True (default) to shade ± 1 std band across seeds.


Custom Strategies

Register your own query strategy with one line and use it alongside the built-ins:

import numpy as np
from deepal6.strategies import register_strategy
from deepal6 import ActiveLearner, ALConfig

def my_strategy(model, data, pool_idx, n_query, **kwargs):
    """
    Custom query strategy.

    Parameters
    ----------
    model    : trained PyTorch model
    data     : TabularDataModule or ImageDataModule
    pool_idx : list of global indices into the unlabeled pool
    n_query  : number of samples to select
    **kwargs : extra kwargs from ALConfig.extra_strategy_kwargs

    Returns
    -------
    np.ndarray of LOCAL indices into pool_idx (not global indices)
    """
    probs  = data.predict_proba(model, pool_idx)
    scores = np.abs(probs - 0.5)
    return np.argsort(scores)[:n_query]

register_strategy('MyStrategy', my_strategy)

# Use exactly like any built-in strategy
config = ALConfig(strategy=['Random', 'BALD', 'MyStrategy'])
results = ActiveLearner(data, config).run()

Batch Size Ablation

Study how query batch size affects learning efficiency:

from deepal6 import ActiveLearner, ALConfig
from deepal6.plotting import plot_batch_size_ablation

ablation = {}
for b in [10, 20, 50]:
    cfg = ALConfig(strategy='BALD', budget=450, batch_size=b, n_seeds=3)
    r   = ActiveLearner(data, cfg).run()
    ablation[b] = r['BALD']

plot_batch_size_ablation(ablation, metric='auc')

Loading Any Data Type

Tabular

TabularDataModule requires numpy arrays:

import pandas as pd
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.model_selection import train_test_split
from deepal6 import TabularDataModule

df = pd.read_csv('your_data.csv')
df['label'] = df['label'].map({'Good': 1, 'Bad': 0})
for col in df.select_dtypes(include='object').columns:
    if col != 'label':
        df[col] = LabelEncoder().fit_transform(df[col])

X = df.drop('label', axis=1).values
y = df['label'].values

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y)
scaler  = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test  = scaler.transform(X_test)

data = TabularDataModule(X_train, y_train, X_test, y_test)
Source How to convert
pandas DataFrame .values on X and y columns
PyTorch tensor .numpy() first
sklearn dataset bunch.data, bunch.target directly
.npy / .npz file np.load('file.npz')['X_train']

Image

Format When to use
DataFrame with filepath + label Images stored as files on disk
NumpyImageDataset wrapper Images already in memory (MedMNIST, numpy arrays)

Important for notebooks and macOS: always set num_workers=0 in ImageDataModule. Using num_workers > 0 in notebooks causes a multiprocessing error because worker processes cannot access classes defined interactively in notebook cells.

Important for in-memory datasets: the transform (PIL → tensor conversion) must live inside your Dataset's __getitem__, not in ImageDataModule(train_transform=...). The train_transform parameter is only for the DataFrame/filepath approach.


Design Principles

Decision Reason
Stratified initial draw Prevents random baseline winning by lucky class balance in L₀
Class-weighted BCE loss Prevents majority-class collapse on small labeled sets
Fresh model each round No representation drift — tabular: random init, image: ImageNet weights
Unified strategy interface fn(model, data, pool_idx, n_query, **kwargs) → np.ndarray
DataModule pattern Decouples data I/O from strategy logic — easy to extend
Fail-fast validation All config/data errors surface before the experiment starts

Project Structure

deepal6/
├── __init__.py          ← Public API — import everything from here
├── config.py            ← ALConfig — all experiment parameters + validation
├── learner.py           ← ActiveLearner — main experiment loop
├── metrics.py           ← ECE, AULC, aggregate_seeds, summary table
├── plotting.py          ← Learning curves, gap plot, calibration plots
├── exceptions.py        ← DeepAL6Error, ConfigurationError, DataError, ...
├── data/
│   ├── base.py          ← BaseDataModule (abstract interface)
│   ├── tabular.py       ← TabularDataModule + CreditNet architecture
│   └── image.py         ← ImageDataModule + ResNet-18 with Dropout head
├── strategies/
│   ├── __init__.py      ← STRATEGIES registry + register_strategy()
│   ├── registry.py      ← Re-export for config
│   └── query.py         ← All 6 strategy implementations
└── models/
    └── __init__.py      ← CreditNet, build_resnet18 re-exports

Common Errors

Error Cause Fix
ConfigurationError: Unknown strategy Typo in strategy name Names are case-sensitive: 'BALD' not 'bald'
DataError: NaN values Missing values in features Impute or drop NaNs before passing to TabularDataModule
DataError: length mismatch X and y have different lengths Check your train/test split code
DataError: feature counts X_train and X_test have different columns Apply the same scaler/encoder to both
ModelError: build_model failed CUDA out of memory Set device='cpu' or reduce train_batch_size
StrategyError: no Dropout layers BALD with dropout_rate=0 Set dropout_rate > 0 (default: 0.3)
ImportError: torchvision Image support not installed pip install "deepal6[image]"
DataError: missing get_labels() Custom Dataset missing method Add def get_labels(self): return self._labels
batch must contain tensors ... PIL.Image Transform not applied in __getitem__ Move transform inside your Dataset class (see Image section above)
Can't get attribute on __main__ num_workers > 0 in notebook/macOS Set num_workers=0 in ImageDataModule

Citation

If you use DeepAL6 in your research, please cite:

@misc{aila2025deepal6,
  author       = {Aila, Bob Philip and Gaba, Yae},
  title        = {DeepAL6: A Deep Active Learning Library for Tabular and Image Domains},
  year         = {2025},
  publisher    = {GitHub},
  howpublished = {\url{https://github.com/bob-aila/deepal6}},
  note         = {AIMS Rwanda Master's Thesis}
}

The full citation will be updated once the thesis is published. Check back here or contact the author.


License

This project is licensed under the MIT License — see the LICENSE file for details.


Let's save the budgets by labelling data informatively.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

deepal6-1.0.4.tar.gz (37.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

deepal6-1.0.4-py3-none-any.whl (36.5 kB view details)

Uploaded Python 3

File details

Details for the file deepal6-1.0.4.tar.gz.

File metadata

  • Download URL: deepal6-1.0.4.tar.gz
  • Upload date:
  • Size: 37.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.13

File hashes

Hashes for deepal6-1.0.4.tar.gz
Algorithm Hash digest
SHA256 4405efbf68da6f08edb9bf4b4954b29c780b4b7ffdf57437b7a69ba71eb9e8bf
MD5 b47b403e563cb4e88f9eea1d36d59f36
BLAKE2b-256 b0e3941b613e8f373973c8058f68409dcd907d05ba42132313b799b2ef8dcbeb

See more details on using hashes here.

File details

Details for the file deepal6-1.0.4-py3-none-any.whl.

File metadata

  • Download URL: deepal6-1.0.4-py3-none-any.whl
  • Upload date:
  • Size: 36.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.13

File hashes

Hashes for deepal6-1.0.4-py3-none-any.whl
Algorithm Hash digest
SHA256 48fe9fbf11a70a8ced066b7cc3133cbf837797efcf9af3cbe97d674cf5de9cd3
MD5 659affd5c746df4ebbb6274682efe6cf
BLAKE2b-256 bd18446a27ae9916145e5dad1ed22c66052060cdfd09eff4d17fbf196b5b85d1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page