Using PyTorch with CapyMOA#

  • This notebook demonstrates how use PyTorch with CapyMOA.

  • It contains examples showing:

    • How to define a PyTorch Network to be used with CapyMOA.

    • How a simple PyTorch model can be used in a CapyMOA Instance loop.

    • How to define a PyTorch CapyMOA Classifier based on CapyMOA Classifier framework and how to use it with prequential_evaluation().

    • How to use a PyTorch dataset with a CapyMOA classifier.

  • Exploring Advanced Features (notebooks/common/advanced_API.py) includes an example using TensorBoard and a PyTorchClassifier.


More information about CapyMOA can be found at https://www.capymoa.org.

last update on 28/11/2025

Setup#

  • Sets random seed for reproducibility.

  • Sets PyTorch network .

Set random seeds#

import random

random_seed = 1
random.seed(random_seed)

Define network structure#

  • Here, the network uses the CPU device.

import torch
from torch import nn

torch.manual_seed(random_seed)
torch.use_deterministic_algorithms(True)

# Get cpu device for training.
device = "cpu"
print(f"Using {device} device")


# Define model
class NeuralNetwork(nn.Module):
    def __init__(self, input_size=0, number_of_classes=0):
        super().__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(input_size, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, number_of_classes),
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits
Using cpu device

Using an instance loop#

  • Model is initialised after receiving the first instance.

from capymoa.datasets import ElectricityTiny
from capymoa.evaluation import ClassificationEvaluator

elec_stream = ElectricityTiny()

# Creating the evaluator
evaluator = ClassificationEvaluator(schema=elec_stream.get_schema())

model = None
optimiser = None
loss_fn = nn.CrossEntropyLoss()

i = 0
while elec_stream.has_more_instances():
    i += 1
    instance = elec_stream.next_instance()
    if model is None:
        moa_instance = instance.java_instance.getData()
        # initialise the model and send it to the device
        model = NeuralNetwork(
            input_size=elec_stream.get_schema().get_num_attributes(),
            number_of_classes=elec_stream.get_schema().get_num_classes(),
        ).to(device)
        # set the optimiser
        optimiser = torch.optim.SGD(model.parameters(), lr=1e-3)
        print(model)

    X = torch.tensor(instance.x, dtype=torch.float32)
    y = torch.tensor(instance.y_index, dtype=torch.long)
    # set the device and add a dimension to the tensor
    X, y = torch.unsqueeze(X.to(device), 0), torch.unsqueeze(y.to(device), 0)

    # turn off gradient collection for test
    with torch.no_grad():
        pred = model(X)
        prediction = torch.argmax(pred)

    # update evaluator with predicted class
    evaluator.update(instance.y_index, prediction.item())

    # Compute prediction error
    pred = model(X)
    loss = loss_fn(pred, y)

    # Backpropagation
    loss.backward()
    optimiser.step()
    optimiser.zero_grad()

    if i % 500 == 0:
        print(f"Accuracy at {i} : {evaluator.accuracy()}")

print(f"Accuracy at {i} : {evaluator.accuracy()}")
NeuralNetwork(
  (flatten): Flatten(start_dim=1, end_dim=-1)
  (linear_relu_stack): Sequential(
    (0): Linear(in_features=6, out_features=512, bias=True)
    (1): ReLU()
    (2): Linear(in_features=512, out_features=512, bias=True)
    (3): ReLU()
    (4): Linear(in_features=512, out_features=2, bias=True)
  )
)
Accuracy at 500 : 50.4
Accuracy at 1000 : 55.2
Accuracy at 1500 : 61.199999999999996
Accuracy at 2000 : 61.1
Accuracy at 2000 : 61.1

PyTorchClassifier#

  • Defining a PyTorchClassifier using the CapyMOA API makes it compatible with CapyMOA functions like prequential_evaluation() without losing the flexibility of specifying the architecture and the training method.

  • The model is initialised after receiving the first instance.

  • PyTorchClassifier is based on the capymoa.base Classifier abstract class.

  • Important: We can access information about the stream through any of its instances. See set_model(self, instance) for an example:

...
moa_instance = instance.java_instance.getData()
self.model = NeuralNetwork(input_size=moa_instance.get_num_attributes(), 
                           number_of_classes=moa_instance.get_num_classes()).to(self.device)
...
import numpy as np

from capymoa.base import Classifier


class PyTorchClassifier(Classifier):
    def __init__(
        self,
        schema=None,
        random_seed=1,
        nn_model: nn.Module = None,
        optimiser=None,
        loss_fn=None,
        device=("cpu"),
        lr=1e-3,
    ):
        super().__init__(schema, random_seed)
        if loss_fn is None:
            loss_fn = nn.CrossEntropyLoss()
        self.model = None
        self.optimiser = None
        self.loss_fn = loss_fn
        self.lr = lr
        self.device = device

        torch.manual_seed(random_seed)

        if nn_model is None:
            self.set_model(None)
        else:
            self.model = nn_model.to(device)
        if optimiser is None:
            if self.model is not None:
                self.optimiser = torch.optim.SGD(self.model.parameters(), lr=lr)
        else:
            self.optimiser = optimiser

    def __str__(self):
        return str(self.model)

    def cli_help(self):
        return 'schema=None, random_seed=1, nn_model: nn.Module=None, optimiser=None, loss_fn=nn.CrossEntropyLoss(), device=("cpu"), lr=1e-3'

    def set_model(self, instance):
        if self.schema is None:
            moa_instance = instance.java_instance.getData()
            self.model = NeuralNetwork(
                input_size=moa_instance.get_num_attributes(),
                number_of_classes=moa_instance.get_num_classes(),
            ).to(self.device)
        elif instance is not None:
            self.model = NeuralNetwork(
                input_size=self.schema.get_num_attributes(),
                number_of_classes=self.schema.get_num_classes(),
            ).to(self.device)

    def train(self, instance):
        if self.model is None:
            self.set_model(instance)

        X = torch.tensor(instance.x, dtype=torch.float32)
        y = torch.tensor(instance.y_index, dtype=torch.long)
        # set the device and add a dimension to the tensor
        X, y = (
            torch.unsqueeze(X.to(self.device), 0),
            torch.unsqueeze(y.to(self.device), 0),
        )

        # Compute prediction error
        pred = self.model(X)
        loss = self.loss_fn(pred, y)

        # Backpropagation
        loss.backward()
        self.optimiser.step()
        self.optimiser.zero_grad()

    def predict(self, instance):
        return np.argmax(self.predict_proba(instance))

    def predict_proba(self, instance):
        if self.model is None:
            self.set_model(instance)
        X = torch.unsqueeze(
            torch.tensor(instance.x, dtype=torch.float32).to(self.device), 0
        )
        # turn off gradient collection
        with torch.no_grad():
            pred = np.asarray(self.model(X).numpy(), dtype=np.double)
        return pred

Using PyTorchClassifier + prequential_evaluation#

  • We can access information about the stream through the schema directly, from the example below:

...
nn_model=NeuralNetwork(input_size=elec_stream.get_schema().get_num_attributes(),
                       number_of_classes=elec_stream.get_schema().get_num_classes()).to(device)
...
from capymoa.evaluation import prequential_evaluation

## Opening a file as a stream
elec_stream = ElectricityTiny()

# Creating a learner
simple_pyTorch_classifier = PyTorchClassifier(
    schema=elec_stream.get_schema(),
    nn_model=NeuralNetwork(
        input_size=elec_stream.get_schema().get_num_attributes(),
        number_of_classes=elec_stream.get_schema().get_num_classes(),
    ).to(device),
)

evaluator = prequential_evaluation(
    stream=elec_stream,
    learner=simple_pyTorch_classifier,
    window_size=4500,
    optimise=False,
)

print(f"Accuracy: {evaluator.cumulative.accuracy()}")
Accuracy: 62.849999999999994

How to use a PyTorch dataset with a CapyMOA classifier#

  • One may want to use various PyTorch datasets with different CapyMOA classifiers.

  • In this example we use PyTorch Dataset + prequential evaluation + CapyMOA Classifier.

Observation: Using a learner like Online Bagging without any feature extraction is not going to yield meaningful performance

from torchvision import datasets
from torchvision.transforms import ToTensor

from capymoa.classifier import OnlineBagging
from capymoa.evaluation import prequential_evaluation
from capymoa.evaluation.visualization import plot_windowed_results
from capymoa.stream import TorchStream

pytorch_dataset = datasets.FashionMNIST(
    root="data", train=True, download=True, transform=ToTensor()
)
pytorch_stream = TorchStream.from_classification(
    dataset=pytorch_dataset, num_classes=10
)

# Creating a learner
ob_learner = OnlineBagging(schema=pytorch_stream.get_schema(), ensemble_size=5)

results_ob_learner = prequential_evaluation(
    stream=pytorch_stream, learner=ob_learner, window_size=100, max_instances=1000
)

print(f"Accuracy: {results_ob_learner.cumulative.accuracy()}")
display(results_ob_learner.windowed.metrics_per_window())
plot_windowed_results(results_ob_learner, metric="accuracy")
  0%|          | 0.00/26.4M [00:00<?, ?B/s]
  0%|          | 32.8k/26.4M [00:00<01:25, 310kB/s]
  0%|          | 65.5k/26.4M [00:00<01:25, 308kB/s]
  0%|          | 131k/26.4M [00:00<00:58, 448kB/s] 
  1%|          | 229k/26.4M [00:00<00:41, 634kB/s]
  1%|▏         | 360k/26.4M [00:00<00:30, 847kB/s]
  2%|▏         | 492k/26.4M [00:00<00:26, 975kB/s]
  2%|▏         | 623k/26.4M [00:00<00:24, 1.06MB/s]
  3%|▎         | 754k/26.4M [00:00<00:23, 1.11MB/s]
  4%|▎         | 950k/26.4M [00:00<00:19, 1.34MB/s]
  5%|▌         | 1.34M/26.4M [00:01<00:12, 2.05MB/s]
  7%|▋         | 1.74M/26.4M [00:01<00:09, 2.54MB/s]
  8%|▊         | 2.06M/26.4M [00:01<00:08, 2.71MB/s]
  9%|▉         | 2.36M/26.4M [00:01<00:08, 2.72MB/s]
 11%|█▏        | 3.01M/26.4M [00:01<00:06, 3.75MB/s]
 14%|█▍        | 3.67M/26.4M [00:01<00:05, 4.47MB/s]
 19%|█▉        | 5.05M/26.4M [00:01<00:03, 6.99MB/s]
 24%|██▍       | 6.32M/26.4M [00:01<00:02, 8.48MB/s]
 35%|███▍      | 9.18M/26.4M [00:01<00:01, 13.9MB/s]
 46%|████▌     | 12.1M/26.4M [00:02<00:00, 17.8MB/s]
 61%|██████▏   | 16.2M/26.4M [00:02<00:00, 23.7MB/s]
 77%|███████▋  | 20.3M/26.4M [00:02<00:00, 27.9MB/s]
 93%|█████████▎| 24.4M/26.4M [00:02<00:00, 30.8MB/s]
100%|██████████| 26.4M/26.4M [00:02<00:00, 11.2MB/s]

  0%|          | 0.00/29.5k [00:00<?, ?B/s]
100%|██████████| 29.5k/29.5k [00:00<00:00, 278kB/s]
100%|██████████| 29.5k/29.5k [00:00<00:00, 276kB/s]

  0%|          | 0.00/4.42M [00:00<?, ?B/s]
  1%|          | 32.8k/4.42M [00:00<00:13, 314kB/s]
  1%|▏         | 65.5k/4.42M [00:00<00:13, 313kB/s]
  2%|▏         | 98.3k/4.42M [00:00<00:13, 312kB/s]
  4%|▍         | 197k/4.42M [00:00<00:07, 558kB/s] 
  7%|▋         | 295k/4.42M [00:00<00:05, 691kB/s]
 10%|▉         | 426k/4.42M [00:00<00:04, 879kB/s]
 13%|█▎        | 590k/4.42M [00:00<00:03, 1.10MB/s]
 19%|█▉        | 852k/4.42M [00:00<00:02, 1.54MB/s]
 25%|██▌       | 1.11M/4.42M [00:00<00:01, 1.84MB/s]
 36%|███▋      | 1.61M/4.42M [00:01<00:01, 2.70MB/s]
 47%|████▋     | 2.10M/4.42M [00:01<00:00, 3.31MB/s]
 68%|██████▊   | 3.01M/4.42M [00:01<00:00, 4.95MB/s]
 90%|█████████ | 4.00M/4.42M [00:01<00:00, 6.28MB/s]
100%|██████████| 4.42M/4.42M [00:01<00:00, 3.22MB/s]

  0%|          | 0.00/5.15k [00:00<?, ?B/s]
100%|██████████| 5.15k/5.15k [00:00<00:00, 17.8MB/s]

Accuracy: 42.9
instances accuracy kappa kappa_t kappa_m f1_score F1 Score macro (percent) f1_score_0 f1_score_1 f1_score_2 ... recall_1 recall_2 recall_3 recall_4 recall_5 recall_6 recall_7 recall_8 recall_9 roc_auc
0 100.0 17.0 6.267645 0.000000 0.000000 17.0 14.458980 23.188406 30.769231 46.153846 ... 18.181818 33.333333 6.666667 0.000000 0.000000 0.000000 12.500000 0.000000 18.181818 NaN
1 200.0 41.0 33.288105 32.954545 30.588235 41.0 35.453087 39.130435 57.142857 36.363636 ... 40.000000 22.222222 0.000000 22.222222 22.222222 63.636364 23.076923 75.000000 12.500000 NaN
2 300.0 30.0 22.127044 23.076923 23.076923 30.0 32.728376 24.390244 72.727273 51.851852 ... 57.142857 53.846154 0.000000 9.090909 18.181818 33.333333 22.222222 18.181818 50.000000 NaN
3 400.0 48.0 41.775837 43.478261 42.222222 48.0 46.874041 36.363636 70.588235 22.222222 ... 54.545455 25.000000 8.333333 25.000000 66.666667 50.000000 63.636364 66.666667 33.333333 NaN
4 500.0 51.0 45.591828 45.555556 42.352941 51.0 50.423644 39.024390 50.000000 23.529412 ... 40.000000 16.666667 12.500000 50.000000 75.000000 25.000000 62.500000 78.571429 54.545455 NaN
5 600.0 48.0 42.164387 39.534884 36.585366 48.0 46.150940 38.888889 50.000000 47.058824 ... 33.333333 40.000000 0.000000 50.000000 71.428571 46.153846 50.000000 62.500000 61.538462 NaN
6 700.0 45.0 38.347719 35.294118 28.571429 45.0 43.987433 32.653061 80.000000 50.000000 ... 66.666667 57.142857 0.000000 61.538462 25.000000 33.333333 16.666667 68.750000 42.857143 NaN
7 800.0 54.0 48.221522 48.314607 45.882353 54.0 48.401026 44.444444 96.551724 60.000000 ... 93.333333 50.000000 0.000000 66.666667 53.846154 27.272727 10.000000 63.636364 37.500000 NaN
8 900.0 48.0 40.895658 43.478261 40.229885 48.0 44.715797 51.851852 81.818182 15.384615 ... 69.230769 16.666667 45.454545 16.666667 50.000000 33.333333 20.000000 44.444444 42.857143 NaN
9 1000.0 47.0 41.410568 44.791667 39.772727 47.0 45.810789 34.782609 85.714286 55.555556 ... 85.714286 50.000000 12.500000 75.000000 60.000000 27.272727 6.250000 87.500000 12.500000 NaN

10 rows × 42 columns

../../_images/e174c75d32fd1ea62484a4754dcc06e432ae34b91f2ff4442eda841d9f2db796.png