Skip to content

Commit 4a5333f

Browse files
feat(batch): x batch is now of shape (batch_size, *schema.shape) rather than a flat vector
1 parent 1946868 commit 4a5333f

18 files changed

Lines changed: 310 additions & 241 deletions

File tree

notebooks/10_ocl.ipynb

Lines changed: 162 additions & 160 deletions
Large diffs are not rendered by default.

src/capymoa/base/_batch.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ class Batch(ABC):
1616
def batch_train(self, x: torch.Tensor, y: torch.Tensor) -> None:
1717
"""Train the model with a batch of instances.
1818
19-
:param x: A batch of feature vectors of shape ``(batch_size, num_features)``.
19+
:param x: A batch of feature vectors of shape ``(batch_size, *schema.shape)``.
2020
:param y: A batch of target values, typically a vector of shape ``(batch_size,)``.
2121
"""
2222

2323
@abstractmethod
2424
def batch_predict(self, x: torch.Tensor) -> torch.Tensor:
2525
"""Predict the target values for a batch of instances.
2626
27-
:param x: A batch of feature vectors of shape ``(batch_size, num_features)``.
27+
:param x: A batch of feature vectors of shape ``(batch_size, *schema.shape)``.
2828
:return: Predicted target values, typically a vector of shape ``(batch_size,)``.
2929
"""

src/capymoa/base/_classifier.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ def batch_predict(self, x: torch.Tensor) -> torch.Tensor:
179179

180180
def train(self, instance: LabeledInstance) -> None:
181181
"""Calls :func:`batch_train` with a batch of size 1."""
182-
x = torch.from_numpy(instance.x).view(1, -1)
182+
x = torch.from_numpy(instance.x).view(1, *self.schema.shape)
183183
x = x.to(self.device, self.x_dtype)
184184
y = torch.scalar_tensor(
185185
instance.y_index, dtype=self.y_dtype, device=self.device
@@ -188,7 +188,7 @@ def train(self, instance: LabeledInstance) -> None:
188188

189189
def predict_proba(self, instance: Instance) -> Optional[LabelProbabilities]:
190190
"""Calls :func:`batch_predict_proba` with a batch of size 1."""
191-
x = torch.from_numpy(instance.x.reshape(1, -1))
191+
x = torch.from_numpy(instance.x.reshape(1, *self.schema.shape))
192192
x = x.to(self.device, self.x_dtype)
193193
return self.batch_predict_proba(x).flatten().numpy().astype(np.float64)
194194

src/capymoa/classifier/_finetune.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def __init__(
6565
function that takes the model parameters and returns an optimizer.
6666
:param criterion: Loss function to use for training.
6767
:param device: Hardware for training.
68-
:param random_seed: Seeds torch :py:func:`torch.manual_seed`.
68+
:param random_seed: Seed for PyTorch.
6969
"""
7070
super().__init__(schema, random_seed)
7171
# seed for reproducibility

src/capymoa/evaluation/evaluation.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,8 +1040,10 @@ def prequential_evaluation(
10401040
if isinstance(learner, Batch):
10411041
# Collect a batch of instances and predict them all at once
10421042
np_x = np.stack([instance.x for instance in batch])
1043-
torch_x = torch.from_numpy(np_x).to(
1044-
device=learner.device, dtype=learner.x_dtype
1043+
torch_x = (
1044+
torch.from_numpy(np_x)
1045+
.to(device=learner.device, dtype=learner.x_dtype)
1046+
.view(len(batch), *stream.get_schema().shape)
10451047
)
10461048
torch_y = torch.tensor(
10471049
yb_true, dtype=learner.y_dtype, device=learner.device

src/capymoa/ocl/evaluation.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -328,11 +328,11 @@ def _abstain_prediction_uniform(rng: np.random.Generator, n_classes: int) -> Lab
328328
def _batch_test(rng: np.random.Generator, learner: Classifier, x: Tensor) -> np.ndarray:
329329
"""Test a batch of instances using the learner."""
330330
batch_size = x.shape[0]
331-
x = x.view(batch_size, -1)
332331
if isinstance(learner, BatchClassifier):
333332
x = x.to(dtype=learner.x_dtype, device=learner.device)
334333
return learner.batch_predict(x).cpu().numpy()
335334
else:
335+
x = x.view(batch_size, -1)
336336
yb_pred = np.zeros(batch_size, dtype=int)
337337
for i in range(batch_size):
338338
instance = Instance.from_array(learner.schema, x[i].numpy())
@@ -345,16 +345,16 @@ def _batch_test(rng: np.random.Generator, learner: Classifier, x: Tensor) -> np.
345345
return yb_pred
346346

347347

348-
def _batch_train(learner: Classifier, x: Tensor, y: Tensor):
348+
def _batch_train(learner: Classifier, x: Tensor, y: Tensor, x_shape: Sequence[int]):
349349
"""Train a batch of instances using the learner."""
350-
batch_size = x.shape[0]
351-
x = x.view(batch_size, -1)
350+
bs = x.shape[0]
352351
if isinstance(learner, BatchClassifier):
353-
x = x.to(dtype=learner.x_dtype, device=learner.device)
352+
x = x.to(dtype=learner.x_dtype, device=learner.device).view(bs, *x_shape)
354353
y = y.to(dtype=learner.y_dtype, device=learner.device)
355354
learner.batch_train(x, y)
356355
else:
357-
for i in range(batch_size):
356+
x = x.view(bs, -1)
357+
for i in range(bs):
358358
instance = LabeledInstance.from_array(
359359
learner.schema, x[i].numpy(), int(y[i].item())
360360
)
@@ -441,7 +441,7 @@ def ocl_train_eval_loop(
441441
yb: Tensor
442442
pbar.update(1)
443443
yb_pred = _batch_test(rng, learner, xb)
444-
_batch_train(learner, xb, yb)
444+
_batch_train(learner, xb, yb, learner.schema.shape)
445445

446446
for y, y_pred in zip(yb, yb_pred, strict=True):
447447
online_eval.update(y.item(), y_pred)

src/capymoa/ocl/strategy/_der.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def __init__(
6868
self._buffer = ReservoirSampler(
6969
capacity=buffer_size,
7070
buffers=dict(
71-
x=((schema.get_num_attributes(),), torch.float32),
71+
x=(schema.shape, torch.float32),
7272
z=((schema.get_num_classes(),), torch.float32),
7373
y=((), torch.long),
7474
),
@@ -82,14 +82,14 @@ def _train_step(self, x: Tensor, y: Tensor, update_buffer: bool) -> None:
8282
n = x.shape[0]
8383

8484
# Update Buffer
85-
x_t = self._augment(x.view(n, *self.schema.shape)).flatten(1) # $x_t$
85+
x_t = self._augment(x) # $x_t$
8686
z = self._model(x_t) # $z$
8787
if update_buffer:
8888
self._buffer.update(x=x, z=z, y=y)
8989

9090
# Sample buffer and augment
9191
xp, zp, _ = self._buffer.sample(n).values() # $x'$, $z'$, $y'$
92-
x_tp = self._augment(xp.view(n, *self.schema.shape)).flatten(1) # $x_t'$
92+
x_tp = self._augment(xp) # $x_t'$
9393

9494
reg = self._alpha * self._logit_loss(zp, self._model(x_tp)) # Line 7
9595
loss = self._criterion(z, y) + reg # Line 8

src/capymoa/ocl/strategy/_ewc.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,7 @@ def __init__(
179179
self._optimiser = optimiser
180180
self._model = model
181181
self._criterion = torch.nn.CrossEntropyLoss()
182-
self._buffer = SlidingWindow.new_xybuffer(
183-
fim_buffer, schema.get_num_attributes()
184-
)
182+
self._buffer = SlidingWindow.new_xybuffer(fim_buffer, schema.shape)
185183

186184
# Buffers for anchoring the model
187185
self._anchor_params = BufferList(

src/capymoa/ocl/strategy/_experience_replay.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import torch
2-
from collections import OrderedDict
32
from torch import Tensor
43

54
from capymoa.base import BatchClassifier
@@ -57,21 +56,16 @@ def __init__(
5756
super().__init__(learner.schema, learner.random_seed)
5857
#: The wrapped learner to be trained with experience replay.
5958
self.learner = learner
60-
self._buffer = ReservoirSampler(
61-
capacity=buffer_size,
62-
buffers=OrderedDict(
63-
{
64-
"x": ((self.schema.get_num_attributes(),), self.learner.x_dtype),
65-
"y": ((), self.learner.y_dtype),
66-
}
67-
),
68-
rng=torch.Generator().manual_seed(learner.random_seed),
59+
self._buffer = ReservoirSampler.new_xybuffer(
60+
buffer_size,
61+
learner.schema.shape,
62+
torch.Generator().manual_seed(self.random_seed),
6963
)
7064
self.repeat = repeat
7165

7266
def batch_train(self, x: Tensor, y: Tensor) -> None:
7367
# update the buffer with the new data
74-
self._buffer.update(x=x.detach(), y=y.detach())
68+
self._buffer.update(x=x, y=y)
7569

7670
for _ in range(self.repeat):
7771
# sample from the buffer and construct training batch

src/capymoa/ocl/strategy/_gdumb.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def __init__(
4343

4444
self.original_state_dict = model.state_dict()
4545
self.buffer = GreedySampler.new_xybuffer(
46-
capacity, schema.get_num_attributes(), torch.Generator().manual_seed(seed)
46+
capacity, schema.shape, torch.Generator().manual_seed(seed)
4747
)
4848
self.loss_func = nn.CrossEntropyLoss()
4949

0 commit comments

Comments
 (0)