clarena.cul_algorithms

Continual Unlearning Algorithms

This submodule provides the continual unlearning algorithms in CLArena.

Here are the base classes for CUL algorithms:

Please note that this is an API documantation. Please refer to the main documentation pages for more information about how to configure and implement CUL algorithms:

 1r"""
 2
 3# Continual Unlearning Algorithms
 4
 5This submodule provides the **continual unlearning algorithms** in CLArena.
 6
 7Here are the base classes for CUL algorithms:
 8
 9- `CULAlgorithm`: the base class for all continual unlearning algorithms.
10    - `AmnesiacCULAlgorithm`: the base class for Amnesiac continual unlearning algorithms.
11
12Please note that this is an API documantation. Please refer to the main documentation pages for more information about how to configure and implement CUL algorithms:
13
14- [**Configure CUL Algorithm**](https://pengxiang-wang.com/projects/continual-learning-arena/docs/components/cul-algorithm)
15- [**Implement Custom CUL Algorithm**](https://pengxiang-wang.com/projects/continual-learning-arena/docs/custom-implementation/cul-algorithm)
16
17
18
19"""
20
21from .base import CULAlgorithm, AmnesiacCULAlgorithm
22from .independent_unlearn import IndependentUnlearn
23
24from .finetuning_unlearn import AmnesiacFinetuningUnlearn
25from .lwf_unlearn import AmnesiacLwFUnlearn
26from .ewc_unlearn import AmnesiacEWCUnlearn
27from .der_unlearn import AmnesiacDERUnlearn, AmnesiacDERppUnlearn
28
29from .clpu_derpp_unlearn import CLPUDERppUnlearn
30from .amnesiac_hat_unlearn import AmnesiacHATUnlearn
31
32__all__ = [
33    "CULAlgorithm",
34    "AmnesiacCULAlgorithm",
35    "independent_unlearn",
36    "finetuning_unlearn",
37    "lwf_unlearn",
38    "ewc_unlearn",
39    "der_unlearn",
40    "clpu_derpp_unlearn",
41    "amnesiac_hat_unlearn",
42]
class CULAlgorithm:
18class CULAlgorithm:
19    r"""The base class of continual unlearning algorithms."""
20
21    def __init__(self, model: UnlearnableCLAlgorithm) -> None:
22        r"""
23        **Args:**
24        - **model** (`UnlearnableCLAlgorithm`): the continual learning model.
25        """
26
27        # components
28        self.model: UnlearnableCLAlgorithm = model
29        r"""The continual learning model."""
30
31        # task ID control
32        self.task_id: int
33        r"""Task ID counter indicating which task is being processed. Self updated during the task loop. Valid from 1 to `cl_dataset.num_tasks`."""
34        self.processed_task_ids: list[int] = []
35        r"""Task IDs that have been processed."""
36        self.unlearning_task_ids: list[int] = []
37        r"""The list of task IDs that are requested to be unlearned after training `self.task_id`."""
38        self.unlearned_task_ids: set[int] = set()
39        r"""The list of task IDs that have been unlearned in the experiment. """
40        self.unlearnable_task_ids: list[int] = []
41        r"""The list of task IDs that are unlearnable at the current `self.task_id`."""
42        self.task_ids_just_no_longer_unlearnable: list[int] = []
43        r"""The list of task IDs that are just no longer unlearnable at the current `self.task_id`."""
44
45    def setup_task_id(
46        self,
47        task_id: int,
48        unlearning_requests: dict[int, list[int]],
49        unlearnable_task_ids: list[int],
50        task_ids_just_no_longer_unlearnable: list[int],
51    ) -> None:
52        r"""Set up which task the CUL experiment is on. This must be done before `unlearn()` method is called.
53
54        **Args:**
55        - **task_id** (`int`): the target task ID to be set up.
56        - **unlearning_requests** (`dict[int, list[int]]`): the entire unlearning requests. Keys are IDs of the tasks that request unlearning after their learning, and values are the list of the previous tasks to be unlearned.
57        - **unlearnable_task_ids** (`list[int]`): the list of unlearnable task IDs at the current `self.task_id`.
58        - **task_ids_just_no_longer_unlearnable** (`list[int]`): the list of task IDs that are just no longer unlearnable at the current `self.task_id`.
59        """
60        self.task_id = task_id
61        self.processed_task_ids.append(task_id)
62
63        unlearning_task_ids = (
64            unlearning_requests[task_id] if task_id in unlearning_requests else []
65        )
66        self.unlearning_task_ids = unlearning_task_ids
67        self.model.unlearning_task_ids = unlearning_task_ids
68
69        self.unlearnable_task_ids = unlearnable_task_ids
70        self.model.unlearnable_task_ids = unlearnable_task_ids
71
72        self.task_ids_just_no_longer_unlearnable = task_ids_just_no_longer_unlearnable
73        self.model.task_ids_just_no_longer_unlearnable = (
74            task_ids_just_no_longer_unlearnable
75        )
76
77    def setup_test_task_id(self) -> None:
78        r"""Set up before testing `self.task_id`. This must be done after `unlearn()` method is called."""
79
80        self.unlearned_task_ids.update(
81            self.unlearning_task_ids
82        )  # update the maintained set of unlearned task IDs
83        self.model.unlearned_task_ids = (
84            self.unlearned_task_ids
85        )  # let model know the unlearned task IDs
86
87    @abstractmethod
88    def unlearn(self) -> None:
89        r"""Unlearn the requested unlearning tasks (`self.unlearning_task_ids`) after training `self.task_id`. **It must be implemented in subclasses.**"""

The base class of continual unlearning algorithms.

CULAlgorithm(model: clarena.cl_algorithms.UnlearnableCLAlgorithm)
21    def __init__(self, model: UnlearnableCLAlgorithm) -> None:
22        r"""
23        **Args:**
24        - **model** (`UnlearnableCLAlgorithm`): the continual learning model.
25        """
26
27        # components
28        self.model: UnlearnableCLAlgorithm = model
29        r"""The continual learning model."""
30
31        # task ID control
32        self.task_id: int
33        r"""Task ID counter indicating which task is being processed. Self updated during the task loop. Valid from 1 to `cl_dataset.num_tasks`."""
34        self.processed_task_ids: list[int] = []
35        r"""Task IDs that have been processed."""
36        self.unlearning_task_ids: list[int] = []
37        r"""The list of task IDs that are requested to be unlearned after training `self.task_id`."""
38        self.unlearned_task_ids: set[int] = set()
39        r"""The list of task IDs that have been unlearned in the experiment. """
40        self.unlearnable_task_ids: list[int] = []
41        r"""The list of task IDs that are unlearnable at the current `self.task_id`."""
42        self.task_ids_just_no_longer_unlearnable: list[int] = []
43        r"""The list of task IDs that are just no longer unlearnable at the current `self.task_id`."""

Args:

  • model (UnlearnableCLAlgorithm): the continual learning model.

The continual learning model.

task_id: int

Task ID counter indicating which task is being processed. Self updated during the task loop. Valid from 1 to cl_dataset.num_tasks.

processed_task_ids: list[int]

Task IDs that have been processed.

unlearning_task_ids: list[int]

The list of task IDs that are requested to be unlearned after training self.task_id.

unlearned_task_ids: set[int]

The list of task IDs that have been unlearned in the experiment.

unlearnable_task_ids: list[int]

The list of task IDs that are unlearnable at the current self.task_id.

task_ids_just_no_longer_unlearnable: list[int]

The list of task IDs that are just no longer unlearnable at the current self.task_id.

def setup_task_id( self, task_id: int, unlearning_requests: dict[int, list[int]], unlearnable_task_ids: list[int], task_ids_just_no_longer_unlearnable: list[int]) -> None:
45    def setup_task_id(
46        self,
47        task_id: int,
48        unlearning_requests: dict[int, list[int]],
49        unlearnable_task_ids: list[int],
50        task_ids_just_no_longer_unlearnable: list[int],
51    ) -> None:
52        r"""Set up which task the CUL experiment is on. This must be done before `unlearn()` method is called.
53
54        **Args:**
55        - **task_id** (`int`): the target task ID to be set up.
56        - **unlearning_requests** (`dict[int, list[int]]`): the entire unlearning requests. Keys are IDs of the tasks that request unlearning after their learning, and values are the list of the previous tasks to be unlearned.
57        - **unlearnable_task_ids** (`list[int]`): the list of unlearnable task IDs at the current `self.task_id`.
58        - **task_ids_just_no_longer_unlearnable** (`list[int]`): the list of task IDs that are just no longer unlearnable at the current `self.task_id`.
59        """
60        self.task_id = task_id
61        self.processed_task_ids.append(task_id)
62
63        unlearning_task_ids = (
64            unlearning_requests[task_id] if task_id in unlearning_requests else []
65        )
66        self.unlearning_task_ids = unlearning_task_ids
67        self.model.unlearning_task_ids = unlearning_task_ids
68
69        self.unlearnable_task_ids = unlearnable_task_ids
70        self.model.unlearnable_task_ids = unlearnable_task_ids
71
72        self.task_ids_just_no_longer_unlearnable = task_ids_just_no_longer_unlearnable
73        self.model.task_ids_just_no_longer_unlearnable = (
74            task_ids_just_no_longer_unlearnable
75        )

Set up which task the CUL experiment is on. This must be done before unlearn() method is called.

Args:

  • task_id (int): the target task ID to be set up.
  • unlearning_requests (dict[int, list[int]]): the entire unlearning requests. Keys are IDs of the tasks that request unlearning after their learning, and values are the list of the previous tasks to be unlearned.
  • unlearnable_task_ids (list[int]): the list of unlearnable task IDs at the current self.task_id.
  • task_ids_just_no_longer_unlearnable (list[int]): the list of task IDs that are just no longer unlearnable at the current self.task_id.
def setup_test_task_id(self) -> None:
77    def setup_test_task_id(self) -> None:
78        r"""Set up before testing `self.task_id`. This must be done after `unlearn()` method is called."""
79
80        self.unlearned_task_ids.update(
81            self.unlearning_task_ids
82        )  # update the maintained set of unlearned task IDs
83        self.model.unlearned_task_ids = (
84            self.unlearned_task_ids
85        )  # let model know the unlearned task IDs

Set up before testing self.task_id. This must be done after unlearn() method is called.

@abstractmethod
def unlearn(self) -> None:
87    @abstractmethod
88    def unlearn(self) -> None:
89        r"""Unlearn the requested unlearning tasks (`self.unlearning_task_ids`) after training `self.task_id`. **It must be implemented in subclasses.**"""

Unlearn the requested unlearning tasks (self.unlearning_task_ids) after training self.task_id. It must be implemented in subclasses.

class AmnesiacCULAlgorithm(clarena.cul_algorithms.CULAlgorithm):
 92class AmnesiacCULAlgorithm(CULAlgorithm):
 93    r"""The base class of Amnesiac continual unlearning algorithm.
 94
 95    The Amnesiac continual unlearning algorithm refers to update deletion operation that directly delete the parameter updates during a task's training. This is inspired by [AmnesiacML](https://arxiv.org/abs/2010.10981) in machine unlearning. In detail, the task-wise parameter updates are stored:
 96
 97    $$\theta_{l,ij}^{(t)} = \theta_{l,ij}^{(0)} + \sum_{\tau=1}^{t} \Delta \theta_{l,ij}^{(\tau)}$$
 98
 99    To unlearn $u(t)$, delete these updates:
100
101    $$\theta_{l,ij}^{(t-u(t))} = \theta_{l,ij}^{(t)} - \sum_{\tau\in u(t)}\Delta \theta_{l,ij}^{(\tau)}$$
102
103    It is mainly used in AmnesaicHAT, but can also be used in constructing other vanilla baseline continual unlearning algorithms based on different continual learning algorithms.
104    """
105
106    def __init__(
107        self,
108        model: AmnesiacCLAlgorithm,
109    ) -> None:
110        r"""Initialize the unlearning algorithm with the continual learning model.
111
112        **Args:**
113        - **model** (`AmnesiacHAT`): the continual learning model. It must be `AmnesicCLAlgorithm`.
114        """
115        super().__init__(model=model)
116
117    def delete_update(self):
118        r"""Delete the updates for unlearning tasks from the current parameters."""
119
120        # substract the corresponding parameter update from backbone
121        updated_state_dict = deepcopy(self.model.backbone.state_dict())
122        for task_id in self.unlearning_task_ids:
123            param_update = self.model.parameters_task_update.get(task_id)
124            if param_update is None:
125                pylogger.warning(
126                    "Attempted to delete backbone update for task %d, but it was not found.",
127                    task_id,
128                )
129                continue
130            for layer_name, param_tensor in param_update.items():
131                if layer_name in updated_state_dict:
132                    target_tensor = updated_state_dict[layer_name]
133                    if (
134                        param_tensor.device != target_tensor.device
135                        or param_tensor.dtype != target_tensor.dtype
136                    ):
137                        param_tensor = param_tensor.to(
138                            device=target_tensor.device, dtype=target_tensor.dtype
139                        )
140                    updated_state_dict[layer_name] -= param_tensor
141
142            del self.model.parameters_task_update[task_id]  # delete the record
143
144        self.model.backbone.load_state_dict(updated_state_dict, strict=False)
145
146        # substract the corresponding parameter update from heads
147        updated_heads_state_dict = deepcopy(self.model.heads.state_dict())
148        for task_id in self.unlearning_task_ids:
149            param_update = self.model.parameters_task_update_heads.get(task_id)
150            if param_update is None:
151                pylogger.warning(
152                    "Attempted to delete head update for task %d, but it was not found.",
153                    task_id,
154                )
155                continue
156            for param_name, param_tensor in param_update.items():
157                if param_name in updated_heads_state_dict:
158                    target_tensor = updated_heads_state_dict[param_name]
159                    if (
160                        param_tensor.device != target_tensor.device
161                        or param_tensor.dtype != target_tensor.dtype
162                    ):
163                        param_tensor = param_tensor.to(
164                            device=target_tensor.device, dtype=target_tensor.dtype
165                        )
166                    updated_heads_state_dict[param_name] -= param_tensor
167
168            del self.model.parameters_task_update_heads[task_id]  # delete the record
169
170        self.model.heads.load_state_dict(updated_heads_state_dict, strict=False)
171
172    def unlearn(self) -> None:
173        r"""Unlearn the requested unlearning tasks in the current task `self.task_id`.
174
175        This is the default implementation of `unlearn()` method for Amnesiac continual unlearning algorithms. Please override it in subclasses if necessary.
176        """
177
178        # delete the corresponding parameter update records
179        self.delete_update()

The base class of Amnesiac continual unlearning algorithm.

The Amnesiac continual unlearning algorithm refers to update deletion operation that directly delete the parameter updates during a task's training. This is inspired by AmnesiacML in machine unlearning. In detail, the task-wise parameter updates are stored:

$$\theta_{l,ij}^{(t)} = \theta_{l,ij}^{(0)} + \sum_{\tau=1}^{t} \Delta \theta_{l,ij}^{(\tau)}$$

To unlearn $u(t)$, delete these updates:

$$\theta_{l,ij}^{(t-u(t))} = \theta_{l,ij}^{(t)} - \sum_{\tau\in u(t)}\Delta \theta_{l,ij}^{(\tau)}$$

It is mainly used in AmnesaicHAT, but can also be used in constructing other vanilla baseline continual unlearning algorithms based on different continual learning algorithms.

AmnesiacCULAlgorithm(model: clarena.cl_algorithms.AmnesiacCLAlgorithm)
106    def __init__(
107        self,
108        model: AmnesiacCLAlgorithm,
109    ) -> None:
110        r"""Initialize the unlearning algorithm with the continual learning model.
111
112        **Args:**
113        - **model** (`AmnesiacHAT`): the continual learning model. It must be `AmnesicCLAlgorithm`.
114        """
115        super().__init__(model=model)

Initialize the unlearning algorithm with the continual learning model.

Args:

  • model (AmnesiacHAT): the continual learning model. It must be AmnesicCLAlgorithm.
def delete_update(self):
117    def delete_update(self):
118        r"""Delete the updates for unlearning tasks from the current parameters."""
119
120        # substract the corresponding parameter update from backbone
121        updated_state_dict = deepcopy(self.model.backbone.state_dict())
122        for task_id in self.unlearning_task_ids:
123            param_update = self.model.parameters_task_update.get(task_id)
124            if param_update is None:
125                pylogger.warning(
126                    "Attempted to delete backbone update for task %d, but it was not found.",
127                    task_id,
128                )
129                continue
130            for layer_name, param_tensor in param_update.items():
131                if layer_name in updated_state_dict:
132                    target_tensor = updated_state_dict[layer_name]
133                    if (
134                        param_tensor.device != target_tensor.device
135                        or param_tensor.dtype != target_tensor.dtype
136                    ):
137                        param_tensor = param_tensor.to(
138                            device=target_tensor.device, dtype=target_tensor.dtype
139                        )
140                    updated_state_dict[layer_name] -= param_tensor
141
142            del self.model.parameters_task_update[task_id]  # delete the record
143
144        self.model.backbone.load_state_dict(updated_state_dict, strict=False)
145
146        # substract the corresponding parameter update from heads
147        updated_heads_state_dict = deepcopy(self.model.heads.state_dict())
148        for task_id in self.unlearning_task_ids:
149            param_update = self.model.parameters_task_update_heads.get(task_id)
150            if param_update is None:
151                pylogger.warning(
152                    "Attempted to delete head update for task %d, but it was not found.",
153                    task_id,
154                )
155                continue
156            for param_name, param_tensor in param_update.items():
157                if param_name in updated_heads_state_dict:
158                    target_tensor = updated_heads_state_dict[param_name]
159                    if (
160                        param_tensor.device != target_tensor.device
161                        or param_tensor.dtype != target_tensor.dtype
162                    ):
163                        param_tensor = param_tensor.to(
164                            device=target_tensor.device, dtype=target_tensor.dtype
165                        )
166                    updated_heads_state_dict[param_name] -= param_tensor
167
168            del self.model.parameters_task_update_heads[task_id]  # delete the record
169
170        self.model.heads.load_state_dict(updated_heads_state_dict, strict=False)

Delete the updates for unlearning tasks from the current parameters.

def unlearn(self) -> None:
172    def unlearn(self) -> None:
173        r"""Unlearn the requested unlearning tasks in the current task `self.task_id`.
174
175        This is the default implementation of `unlearn()` method for Amnesiac continual unlearning algorithms. Please override it in subclasses if necessary.
176        """
177
178        # delete the corresponding parameter update records
179        self.delete_update()

Unlearn the requested unlearning tasks in the current task self.task_id.

This is the default implementation of unlearn() method for Amnesiac continual unlearning algorithms. Please override it in subclasses if necessary.