Training
Continual Learning Algorithms Prototyping Made Easy
Welcome to the "Training" tutorial of the "From Zero to Hero" series. In this part we will present the functionalities offered by the training module.
1
!pip install git+https://github.com/ContinualAI/avalanche.git
Copied!

๐Ÿ’ช The Training Module

The training module in Avalanche is designed with modularity in mind. Its main goals are to:
  1. 1.
    provide a set of popular continual learning baselines that can be easily used to run experimental comparisons;
  2. 2.
    provide simple abstractions to create and run your own strategy as efficiently and easily as possible starting from a couple of basic building blocks we already prepared for you.
At the moment, the training module includes two main components:
  • Strategies: these are popular baselines already implemented for you which you can use for comparisons or as base classes to define a custom strategy.
  • Plugins: these are classes that allow to add some specific behaviour to your own strategy. The plugin system allows to define reusable components which can be easily combined together (e.g. a replay strategy, a regularization strategy). They are also used to automatically manage logging and evaluation.
Keep in mind that many Avalanche components are independent from Avalanche strategies. If you already have your own strategy which does not use Avalanche, you can use Avalanche's benchmarks, models, data loaders, and metrics without ever looking at Avalanche's strategies.

๐Ÿ“ˆ How to Use Strategies & Plugins

If you want to compare your strategy with other classic continual learning algorithm or baselines, in Avalanche you can instantiate a strategy with a couple lines of code.

Strategy Instantiation

Most strategies require only 3 mandatory arguments:
  • model: this must be a torch.nn.Module.
  • optimizer: torch.optim.Optimizer already initialized on your model.
  • loss: a loss function such as those in torch.nn.functional.
Additional arguments are optional and allow you to customize training (batch size, epochs, ...) or strategy-specific parameters (buffer size, regularization strength, ...).
1
from torch.optim import SGD
2
from torch.nn import CrossEntropyLoss
3
from avalanche.models import SimpleMLP
4
from avalanche.training.strategies import Naive, CWRStar, Replay, GDumb, Cumulative, LwF, GEM, AGEM, EWC
5
โ€‹
6
model = SimpleMLP(num_classes=10)
7
optimizer = SGD(model.parameters(), lr=0.001, momentum=0.9)
8
criterion = CrossEntropyLoss()
9
cl_strategy = Naive(
10
model, optimizer, criterion,
11
train_mb_size=100, train_epochs=4, eval_mb_size=100
12
)
Copied!

Training & Evaluation

Each strategy object offers two main methods: train and eval. Both of them, accept either a single experience(Experience) or a list of them, for maximum flexibility.
We can train the model continually by iterating over the train_stream provided by the scenario.
1
from avalanche.benchmarks.classic import SplitMNIST
2
โ€‹
3
# scenario
4
benchmark = SplitMNIST(n_experiences=5, seed=1)
5
โ€‹
6
# TRAINING LOOP
7
print('Starting experiment...')
8
results = []
9
for experience in benchmark.train_stream:
10
print("Start of experience: ", experience.current_experience)
11
print("Current Classes: ", experience.classes_in_this_experience)
12
โ€‹
13
cl_strategy.train(experience)
14
print('Training completed')
15
โ€‹
16
print('Computing accuracy on the whole test set')
17
results.append(cl_strategy.eval(benchmark.test_stream))
Copied!

Adding Plugins

Most continual learning strategies follow roughly the same training/evaluation loops, i.e. a simple naive strategy (a.k.a. finetuning) augmented with additional behavior to counteract catastrophic forgetting. The plugin systems in Avalanche is designed to easily augment continual learning strategies with custom behavior, without having to rewrite the training loop from scratch. Avalanche strategies accept an optional list of plugins that will be executed during the training/evaluation loops.
For example, early stopping is implemented as a plugin:
1
from avalanche.training.plugins import EarlyStoppingPlugin
2
โ€‹
3
strategy = Naive(
4
model, optimizer, criterion,
5
plugins=[EarlyStoppingPlugin(patience=10, val_stream_name='train')])
Copied!
In Avalanche, most continual learning strategies are implemented using plugins, which makes it easy to combine them together. For example, it is extremely easy to create a hybrid strategy that combines replay and EWC together by passing the appropriate plugins list to the BaseStrategy:
1
from avalanche.training.strategies import BaseStrategy
2
from avalanche.training.plugins import ReplayPlugin, EWCPlugin
3
โ€‹
4
replay = ReplayPlugin(mem_size=100)
5
ewc = EWCPlugin(ewc_lambda=0.001)
6
strategy = BaseStrategy(
7
model, optimizer, criterion,
8
plugins=[replay, ewc])
Copied!
Beware that most strategy plugins modify the internal state. As a result, not all the strategy plugins can be combined together. For example, it does not make sense to use multiple replay plugins since they will try to modify the same strategy variables (mini-batches, dataloaders), and therefore they will be in conflict.

๐Ÿ“ A Look Inside Avalanche Strategies

If you arrived at this point you already know how to use Avalanche strategies and are ready to use it. However, before making your own strategies you need to understand a little bit the internal implementation of the training and evaluation loops.
In Avalanche you can customize a strategy in 2 ways:
  1. 1.
    Plugins: Most strategies can be implemented as additional code that runs on top of the basic training and evaluation loops (Naive strategy, or BaseStrategy). Therefore, the easiest way to define a custom strategy such as a regularization or replay strategy, is to define it as a custom plugin. The advantage of plugins is that they can be combined together, as long as they are compatible, i.e. they do not modify the same part of the state. The disadvantage is that in order to do so you need to understand the BaseStrategy loop, which can be a bit complex at first.
  2. 2.
    Subclassing: In Avalanche, continual learning strategies inherit from the BaseStrategy, which provides generic training and evaluation loops. Most BaseStrategy methods can be safely overridden (with some caveats that we will see later).
Keep in mind that if you already have a working continual learning strategy that does not use Avalanche, you can use most Avalanche components such as benchmarks, evaluation, and models without using Avalanche's strategies!

Training and Evaluation Loops

As we already mentioned, Avalanche strategies inherit from BaseStrategy. This strategy provides:
  1. 1.
    Basic Training and Evaluation loops which define a naive (finetuning) strategy.
  2. 2.
    Callback points, which are used to call the plugins at a specific moments during the loop's execution.
  3. 3.
    A set of variables representing the state of the loops (current model, data, mini-batch, predictions, ...) which allows plugins and child classes to easily manipulate the state of the training loop.
The training loop has the following structure:
1
train
2
before_training
3
โ€‹
4
before_train_dataset_adaptation
5
train_dataset_adaptation
6
after_train_dataset_adaptation
7
make_train_dataloader
8
model_adaptation
9
make_optimizer
10
before_training_exp # for each exp
11
before_training_epoch # for each epoch
12
before_training_iteration # for each iteration
13
before_forward
14
after_forward
15
before_backward
16
after_backward
17
after_training_iteration
18
before_update
19
after_update
20
after_training_epoch
21
after_training_exp
22
after_training
Copied!
The evaluation loop is similar:
1
eval
2
before_eval
3
before_eval_dataset_adaptation
4
eval_dataset_adaptation
5
after_eval_dataset_adaptation
6
make_eval_dataloader
7
model_adaptation
8
before_eval_exp # for each exp
9
eval_epoch # we have a single epoch in evaluation mode
10
before_eval_iteration # for each iteration
11
before_eval_forward
12
after_eval_forward
13
after_eval_iteration
14
after_eval_exp
15
after_eval
Copied!
Methods starting with before/after are the methods responsible for calling the plugins. Notice that before the start of each experience during training we have several phases:
  • dataset adaptation: This is the phase where the training data can be modified by the strategy, for example by adding other samples from a separate buffer.
  • dataloader initialization: Initialize the data loader. Many strategies (e.g. replay) use custom dataloaders to balance the data.
  • model adaptation: Here, the dynamic models (see the models tutorial) are updated by calling their adaptation method.
  • optimizer initialization: After the model has been updated, the optimizer should also be updated to ensure that the new parameters are optimized.

Strategy State

The strategy state is accessible via several attributes. Most of these can be modified by plugins and subclasses:
  • self.clock: keeps track of several event counters.
  • self.experience: the current experience.
  • self.adapted_dataset: the data modified by the dataset adaptation phase.
  • self.dataloader: the current dataloader.
  • self.mbatch: the current mini-batch. For classification problems, mini-batches have the form <x, y, t>, where x is the input, y is the label, and t is the target.
  • self.mb_output: the current model's output.
  • self.loss: the current loss.
  • self.is_training: True if the strategy is in training mode.

How to Write a Plugin

Plugins provide a simple solution to define a new strategy by augmenting the behavior of another strategy (typically a naive strategy). This approach reduces the overhead and code duplication, improving code readability and prototyping speed.
Creating a plugin is straightforward. You create a class which inherits from StrategyPlugin and implements the callbacks that you need. The exact callback to use depend on your strategy. You can use the loop shown above to understand what callbacks you need to use. For example, we show below a simple replay plugin that uses after_training_exp to update the buffer after each training experience, and the before_training_exp to customize the dataloader. Notice that before_training_exp is executed after make_train_dataloader, which means that the BaseStrategy already updated the dataloader. If we used another callback, such as before_train_dataset_adaptation, our dataloader would have been overwritten by the BaseStrategy. Plugin methods always receive the strategy as an argument, so they can access and modify the strategy's state.
1
from avalanche.benchmarks.utils.data_loader import ReplayDataLoader
2
from avalanche.training.plugins import StrategyPlugin
3
from avalanche.training.storage_policy import ReservoirSamplingBuffer
4
โ€‹
5
โ€‹
6
class ReplayP(StrategyPlugin):
7
โ€‹
8
def __init__(self, mem_size):
9
""" A simple replay plugin with reservoir sampling. """
10
super().__init__()
11
self.buffer = ReservoirSamplingBuffer(max_size=mem_size)
12
โ€‹
13
def before_training_exp(self, strategy: "BaseStrategy",
14
num_workers: int = 0, shuffle: bool = True,
15
**kwargs):
16
""" Use a custom dataloader to combine samples from the current data and memory buffer. """
17
if len(self.buffer.buffer) == 0:
18
# first experience. We don't use the buffer, no need to change
19
# the dataloader.
20
return
21
strategy.dataloader = ReplayDataLoader(
22
strategy.adapted_dataset,
23
self.buffer.buffer,
24
oversample_small_tasks=True,
25
num_workers=num_workers,
26
batch_size=strategy.train_mb_size,
27
shuffle=shuffle)
28
โ€‹
29
def after_training_exp(self, strategy: "BaseStrategy", **kwargs):
30
""" Update the buffer. """
31
self.buffer.update(strategy, **kwargs)
32
โ€‹
33
โ€‹
34
benchmark = SplitMNIST(n_experiences=5, seed=1)
35
model = SimpleMLP(num_classes=10)
36
optimizer = SGD(model.parameters(), lr=0.01, momentum=0.9)
37
criterion = CrossEntropyLoss()
38
strategy = Naive(model=model, optimizer=optimizer, criterion=criterion, train_mb_size=128,
39
plugins=[ReplayP(mem_size=2000)])
40
strategy.train(benchmark.train_stream)
41
strategy.eval(benchmark.test_stream)
Copied!
Check StrategyPlugin's documentation for a complete list of the available callbacks.

How to Write a Custom Strategy

You can always define a custom strategy by overriding BaseStrategy methods. However, There is an important caveat to keep in mind. If you override a method, you must remember to call all the callback's handlers (the methods starting with before/after) at the appropriate points. For example, train calls before_training and after_training before and after the training loops, respectively. The easiest way to avoid mistakes is to start from the BaseStrategy method that you want to override and modify it to your own needs without removing the callbacks handling.
Notice that even though you don't use plugins, BaseStrategy implements some internal components as plugins. Also, the EvaluationPlugin (see evaluation tutorial) uses the strategy callbacks.
BaseStrategy provides the global state of the loop in the strategy's attributes, which you can safely use when you override a method. As an example, the Cumulative strategy trains a model continually on the union of all the experiences encountered so far. To achieve this, the cumulative strategy overrides adapt_train_dataset and updates `self.adapted_dataset' by concatenating all the previous experiences with the current one.
1
from avalanche.benchmarks.utils import AvalancheConcatDataset
2
from avalanche.training import BaseStrategy
3
โ€‹
4
โ€‹
5
class Cumulative(BaseStrategy):
6
def __init__(self, *args, **kwargs):
7
super().__init__(*args, **kwargs)
8
self.dataset = None # cumulative dataset
9
โ€‹
10
def train_dataset_adaptation(self, **kwargs):
11
super().train_dataset_adaptation(**kwargs)
12
curr_data = self.experience.dataset
13
if self.dataset is None:
14
self.dataset = curr_data
15
else:
16
self.dataset = AvalancheConcatDataset([self.dataset, curr_data])
17
self.adapted_dataset = self.dataset.train()
18
โ€‹
19
strategy = Cumulative(model=model, optimizer=optimizer, criterion=criterion, train_mb_size=128)
20
strategy.train(benchmark.train_stream)
Copied!
Easy, isn't it? :-)
In general, we recommend to implement a Strategy via plugins, if possible. This approach is the easiest to use and requires a minimal knowledge of the BaseStrategy. It also allows other people to use your plugin and facilitates interoperability among different strategies.
For example, replay strategies can be implemented as a custom strategy of the BaseStrategy or as plugins. However, creating a plugin is allows to use the replay strategy in conjunction with other strategies.
This completes the "Training" chapter for the "From Zero to Hero" series. We hope you enjoyed it!

๐Ÿค Run it on Google Colab

You can run this chapter and play with it on Google Colaboratory: โ€‹
โ€‹
โ€‹
Last modified 10d ago