Online Continual Learning and Event Handlers#

The OCL model is experimenting with an event-based system to facilitate communication between different objects. This allows objects to hook into stages of training, evaluation, and inference. A common use is to implement custom metrics or loggers.

This block shows how to use the event system in isolation:

from dataclasses import dataclass

from capymoa.ocl.events import Dispatcher, Event


@dataclass
class MyEvent(Event):
    value: int


dispatcher = Dispatcher()


def print_event_type(event):
    print(type(event))


dispatcher.subscribe(MyEvent, print_event_type)
dispatcher.notify(MyEvent(42))
<class '__main__.MyEvent'>

This block shows how to use the event system within the OCL training loop. You can subscribe to the None event to get notified about every event emitted.

from capymoa.classifier import NoChange
from capymoa.ocl.datasets import TinySplitMNIST
from capymoa.ocl.evaluation import ocl_train_eval_loop

dispatcher = Dispatcher()
# Subscribe to all events by using None as the event type
dispatcher.subscribe(None, print_event_type)

scenario = TinySplitMNIST()
_ = ocl_train_eval_loop(
    NoChange(scenario.schema),
    train_streams=scenario.train_loaders(64)[:1],
    test_streams=scenario.test_loaders(64)[:1],
    dispatcher=dispatcher,
)
<class 'capymoa.ocl.evaluation.events.TrainBegin'>
<class 'capymoa.ocl.evaluation.events.TrainTaskBegin'>
<class 'capymoa.ocl.evaluation.events.TrainBatchPredict'>
<class 'capymoa.ocl.evaluation.events.TrainBatchPredict'>
<class 'capymoa.ocl.evaluation.events.TrainBatchPredict'>
<class 'capymoa.ocl.evaluation.events.TrainBatchPredict'>
<class 'capymoa.ocl.evaluation.events.TestBegin'>
<class 'capymoa.ocl.evaluation.events.TestTaskBegin'>
<class 'capymoa.ocl.evaluation.events.EvalBatchPredict'>
<class 'capymoa.ocl.evaluation.events.TestTaskEnd'>
<class 'capymoa.ocl.evaluation.events.TestEnd'>
<class 'capymoa.ocl.evaluation.events.TrainTaskEnd'>
<class 'capymoa.ocl.evaluation.events.TrainEnd'>

We can use the TestTaskBegin and TrainTaskBegin events to implement a task-incremental learner. To standardize subscription to a dispatcher, our method implements attach_with from the Handler class.

from torch import Tensor

from capymoa.base import BatchClassifier
from capymoa.classifier import Finetune
from capymoa.core.torch.ann import Perceptron
from capymoa.ocl.evaluation.events import TestTaskBegin, TrainTaskBegin
from capymoa.ocl.events import Dispatcher, Handler


class PerceptronTI(BatchClassifier, Handler):
    def __init__(self, schema, n_tasks: int) -> None:
        super().__init__(schema)
        self._classifiers = [Finetune(schema, Perceptron) for _ in range(n_tasks)]
        self._train_task = 0
        self._test_task = 0

    def batch_train(self, x: Tensor, y: Tensor) -> None:
        self._classifiers[self._train_task].batch_train(x, y)

    def batch_predict_proba(self, x: Tensor) -> Tensor:
        return self._classifiers[self._test_task].batch_predict_proba(x)

    def _on_train_task_begin(self, event: TrainTaskBegin) -> None:
        print(f"Train task {event.train_task} has begun.")
        self._train_task = event.train_task

    def _on_test_task_begin(self, event: TestTaskBegin) -> None:
        self._test_task = event.test_task

    def attach_with(self, dispatcher: Dispatcher) -> Handler:
        dispatcher.subscribe(TrainTaskBegin, self._on_train_task_begin)
        dispatcher.subscribe(TestTaskBegin, self._on_test_task_begin)
        return super().attach_with(dispatcher)


learner = PerceptronTI(scenario.schema, n_tasks=5)
results = ocl_train_eval_loop(
    learner,  # If the learner is a Handler, it will be automatically subscribed.
    train_streams=scenario.train_loaders(32),
    test_streams=scenario.test_loaders(32),
)
print(f"Accuracy {results.accuracy_seen_avg * 100:.2f}")
Train task 0 has begun.
Train task 1 has begun.
Train task 2 has begun.
Train task 3 has begun.
Train task 4 has begun.
Accuracy 63.57

If you use a custom train-test loop with a Classifier that is also a Handler you will need to manually notify the classifier as required.