Continual Learning Evaluation › Code Implementation
Language AI HandbookAll chapters Continual Learning Evaluation › Code ImplementationOne section · Sections in this chapter11 / 14 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
This section builds a framework to evaluate a continual learning scenario from scratch, computing the accuracy matrix and all derived metrics.
We will simulate a simple continual learning run on Split MNIST: 5 sequential binary classification tasks, each distinguishing a different pair of digit classes. The "model" is represented by a simulated accuracy matrix that reproduces the forgetting pattern of a naive gradient descent baseline and a replay-based method. Using simulation rather than a full MNIST training loop keeps the focus on the evaluation machinery rather than the training mechanics, while still producing realistic-looking metric values.
Setup and Task Construction
import random
import numpy as np
# Fix random seeds for reproducibility
np.random.seed(42)
random.seed(42)
# Number of tasks
N = 5
# Simulate the accuracy matrix for a naive fine-tuning baseline
# After training on task i, accuracy on task j follows a forgetting pattern:
# - diagonal: high accuracy right after training (~92-96%)
# - off-diagonal: decays over subsequent tasks (~random baseline = 50% for binary)
def simulate_accuracy_matrix(
n_tasks, diag_acc=0.93, forget_rate=0.70, noise_std=0.03
):
"""Simulate a lower-triangular accuracy matrix for a continual learning model.
After training on task i, performance on earlier tasks j < i decays
according to a forgetting rate. We add Gaussian noise to reflect
real run-to-run variation.
"""
R = np.zeros((n_tasks, n_tasks))
for i in range(n_tasks):
for j in range(i + 1):
if i == j:
# Diagonal: accuracy right after training on task j
R[i, j] = diag_acc + np.random.normal(0, noise_std)
else:
# Off-diagonal: forgetting after (i - j) subsequent tasks
# Performance decays toward chance (0.50 for binary)
steps_later = i - j
floor_acc = 0.50 # binary chance
retained_above_chance = (diag_acc - floor_acc) * (
1 - forget_rate
) ** steps_later
R[i, j] = (
floor_acc
+ retained_above_chance
+ np.random.normal(0, noise_std)
)
# Binary-task accuracy should not fall below the chance baseline.
R = np.clip(R, 0.50, 1.0)
return R
# Simulate naive baseline accuracy matrix
R_naive = simulate_accuracy_matrix(N, diag_acc=0.93, forget_rate=0.70)
# Simulate a replay-based method with much slower forgetting
R_replay = simulate_accuracy_matrix(N, diag_acc=0.92, forget_rate=0.04)
# Isolated training baselines (each task trained from scratch)
b = np.full(N, 0.95)Naive fine-tuning accuracy matrix R (rows=after training task i, cols=task j):
T1 T2 T3 T4 T5
T1: 0.945 --- --- --- ---
T2: 0.625 0.949 --- --- ---
T3: 0.584 0.622 0.923 --- ---
T4: 0.559 0.562 0.615 0.946 ---
T5: 0.500 0.500 0.546 0.572 0.878
Isolated training baselines b: [0.95 0.95 0.95 0.95 0.95]The matrix makes forgetting concrete. Right after training on each task, the model achieves around 93% accuracy (the diagonal). But once subsequent tasks overwrite the weights, performance on earlier tasks collapses toward the 50% binary chance level. The bottom-left corner of the matrix contains the most damning evidence: by the time the model has learned all five tasks, its accuracy on the first task has fallen to near chance.
Visualizing the accuracy matrix as a heatmap makes the forgetting pattern immediately apparent. The diagonal glows bright while the off-diagonal entries fade toward chance.


Computing Average Accuracy
def average_accuracy(R):
"""Average accuracy across all tasks after training on all N tasks."""
N = R.shape[0]
return np.mean([R[N - 1, j] for j in range(N)])
avg_naive = average_accuracy(R_naive)
avg_replay = average_accuracy(R_replay)Average accuracy (naive fine-tuning): 0.599 Average accuracy (replay method): 0.878
The replay method achieves higher average accuracy. But average accuracy alone does not tell us whether the gap comes from better retention of old tasks, faster learning of new tasks, or both. That is why we need BWT and FWT. The numerical difference between methods on average accuracy often understates the true behavioral difference, because some of the naive model's forgetting is partially offset by the fact that its most recently trained task performs well.
Computing Backward Transfer
def backward_transfer(R):
"""Backward transfer: how much final accuracy differs from peak accuracy on each task."""
N = R.shape[0]
bwt = np.mean([R[N - 1, j] - R[j, j] for j in range(N - 1)])
return bwt
bwt_naive = backward_transfer(R_naive)
bwt_replay = backward_transfer(R_replay)BWT (naive fine-tuning): -0.412 BWT (replay method): -0.055
The naive method shows strongly negative BWT, confirming catastrophic forgetting. The replay method's BWT is much closer to zero, showing that replaying past examples substantially preserves old task performance. The magnitude of the BWT difference between the two methods is much larger than the average accuracy difference, which illustrates exactly why BWT is a more diagnostic metric than average accuracy for understanding forgetting behavior.
Computing Forward Transfer
def forward_transfer(R, b):
"""Forward transfer: how much prior tasks help learning new tasks, vs. isolated training."""
N = R.shape[0]
fwt = np.mean([R[j, j] - b[j] for j in range(1, N)])
return fwt
fwt_naive = forward_transfer(R_naive, b)
fwt_replay = forward_transfer(R_replay, b)FWT (naive fine-tuning): -0.026 FWT (replay method): -0.023
Both methods show slightly negative FWT in this simulation. This is typical for Split MNIST: the tasks are designed to be distinct enough that prior training does not transfer positively. The replay method's FWT is closer to zero because replaying diverse examples helps maintain useful general features that modestly benefit new task learning, even when the tasks themselves are not semantically related.
Computing Forgetting and Intransigence
def forgetting_measure(R):
"""Average worst-case forgetting: max accuracy ever achieved minus final accuracy, per task."""
N = R.shape[0]
forgetting_per_task = []
for j in range(N - 1):
# Maximum accuracy achieved on task j at any point during training
max_acc = max(R[k, j] for k in range(j, N))
final_acc = R[N - 1, j]
forgetting_per_task.append(max_acc - final_acc)
return np.mean(forgetting_per_task)
def intransigence(R, b):
"""Intransigence: how much accumulated training hurts learning new tasks."""
N = R.shape[0]
intr = np.mean([b[j] - R[j, j] for j in range(N)])
return intr
forget_naive = forgetting_measure(R_naive)
forget_replay = forgetting_measure(R_replay)
intr_naive = intransigence(R_naive, b)
intr_replay = intransigence(R_replay, b)Forgetting (naive fine-tuning): 0.412 Forgetting (replay method): 0.055 Intransigence (naive): 0.022 Intransigence (replay): 0.028
Forgetting confirms the large retention gap between the two methods. Intransigence is near zero for both, which is expected: naive fine-tuning suffers from forgetting but not from rigidity, and replay can still learn new tasks freely because it does not impose weight change penalties. This is a clean example of the asymmetry between forgetting and intransigence: a method can have severe forgetting (overwriting old knowledge) without having any intransigence (blocking new learning), and vice versa.
Summary of All Metrics
metrics = {
"Method": ["Naive Fine-tuning", "Replay"],
"Avg Acc": [avg_naive, avg_replay],
"BWT": [bwt_naive, bwt_replay],
"FWT": [fwt_naive, fwt_replay],
"Forgetting": [forget_naive, forget_replay],
"Intransigence": [intr_naive, intr_replay],
}Method Avg Acc BWT FWT Forgetting Intransigence ---------------------------------------------------------------------------------------------------- Naive Fine-tuning 0.599 -0.412 -0.026 0.412 0.022 Replay 0.878 -0.055 -0.023 0.055 0.028
The metric table gives a complete picture of each method's behavior. Naive fine-tuning achieves high accuracy right after training each task but loses most of it by the end. The replay method sacrifices almost nothing to retention while achieving similar immediate learning. Both methods have near-zero intransigence on these short 5-task sequences. Average accuracy shows replay's practical advantage, while BWT and forgetting identify retention as the reason for that advantage.
Plotting all five metrics side by side makes the contrast between methods clear at a glance.



The trajectory plots tell a story that the summary metrics cannot fully convey. For naive fine-tuning, each task's accuracy is highest at the moment of its own training, then falls precipitously as subsequent tasks overwrite the shared weights. The drop is not gradual. Task 1's accuracy falls most steeply because it is followed by four additional tasks, each contributing further degradation. Task 4's accuracy falls less because only one task follows. The shape of the trajectories reflects the geometric decay built into the simulation, but real networks show similar patterns.
For the replay method, the trajectories are almost horizontal. The periodic rehearsal of stored examples continuously refreshes the model's memory of earlier tasks, preventing the cascade of forgetting visible in the naive baseline. The residual downward slope visible in some task trajectories for the replay method reflects the imperfect coverage of past distributions by a finite replay buffer.
Key Parameters
The key parameters governing this evaluation framework are:
- N: Number of tasks. Longer sequences stress-test forgetting more severely because each task has more subsequent training to degrade its performance. A method that performs well on 5-task sequences may fail on 20-task sequences if its forgetting mitigation does not scale.
- Evaluation protocol: Task-IL versus class-IL versus domain-IL determines what task information the model can use during inference, fundamentally changing the difficulty of the problem and the ranking of methods.
- Baseline : How the isolated training baseline is computed affects FWT and intransigence interpretation. The baseline should use the same architecture and training budget as the continual learner, with no other tasks present, to ensure a fair comparison.
- Evaluation frequency: End-of-task evaluation (standard) versus per-update tracking (for dynamics analysis). Fine-grained tracking reveals when during training forgetting occurs and how rapidly it progresses.