Download core/model/ewcdr.py from RL-MIND/LibContinual: direct link, hf CLI and curl.
- Browser
- Download file 5.6 kB
-
https://huggingface.co/RL-MIND/LibContinual/resolve/main/core/model/ewcdr.py
- Command line
-
hf download hf://RL-MIND/LibContinual/core/model/ewcdr.py
-
curl -L -o ewcdr.py https://huggingface.co/RL-MIND/LibContinual/resolve/main/core/model/ewcdr.py
5.6 kB
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from .finetune import Finetune | |
| class IncrementalLinearNet(nn.Module): | |
| def __init__(self, backbone, feat_dim, num_class): | |
| super().__init__() | |
| self.backbone = backbone | |
| self.classifier = nn.Linear(feat_dim, num_class) | |
| def _features(self, x): | |
| output = self.backbone(x) | |
| if isinstance(output, dict): | |
| return output["features"] | |
| return output | |
| def forward(self, x): | |
| return self.classifier(self._features(x)) | |
| def update_classifier(self, num_class): | |
| old_classifier = self.classifier | |
| if old_classifier.out_features == num_class: | |
| return | |
| new_classifier = nn.Linear(old_classifier.in_features, num_class) | |
| new_classifier = new_classifier.to(old_classifier.weight.device) | |
| with torch.no_grad(): | |
| old_out = old_classifier.out_features | |
| new_classifier.weight[:old_out].copy_(old_classifier.weight) | |
| new_classifier.bias[:old_out].copy_(old_classifier.bias) | |
| self.classifier = new_classifier | |
| class EWCDR(Finetune): | |
| """EWC Done Right with logits reversal during importance estimation.""" | |
| def __init__(self, backbone, feat_dim, num_class, **kwargs): | |
| super().__init__(backbone, feat_dim, num_class, **kwargs) | |
| self.init_cls_num = kwargs["init_cls_num"] | |
| self.inc_cls_num = kwargs["inc_cls_num"] | |
| self.lamda = kwargs["lamda"] | |
| self.omega_max = kwargs.get("omega_max", 1e-4) | |
| self.task_idx = 0 | |
| self.known_cls_num = 0 | |
| self.total_cls_num = self.init_cls_num | |
| self.network = IncrementalLinearNet(backbone, feat_dim, self.init_cls_num) | |
| self.ref_param = {} | |
| self.omega = None | |
| def before_task(self, task_idx, buffer, train_loader, test_loaders): | |
| self.task_idx = task_idx | |
| self.known_cls_num = ( | |
| 0 if task_idx == 0 else self.init_cls_num + (task_idx - 1) * self.inc_cls_num | |
| ) | |
| self.total_cls_num = self.init_cls_num + task_idx * self.inc_cls_num | |
| self.network.update_classifier(self.total_cls_num) | |
| self.network.to(self.device) | |
| def observe(self, data): | |
| x = data["image"].to(self.device) | |
| y = data["label"].to(self.device) | |
| logits = self.network(x) | |
| if self.task_idx == 0: | |
| loss = F.cross_entropy(logits, y) | |
| else: | |
| loss = F.cross_entropy( | |
| logits[:, self.known_cls_num:], y - self.known_cls_num | |
| ) | |
| loss = loss + self.lamda * self.compute_ewc() | |
| pred = torch.argmax(logits, dim=1) | |
| acc = torch.sum(pred == y).item() | |
| return pred, acc / x.size(0), loss | |
| def inference(self, data): | |
| x = data["image"].to(self.device) | |
| y = data["label"].to(self.device) | |
| logits = self.network(x) | |
| pred = torch.argmax(logits, dim=1) | |
| acc = torch.sum(pred == y).item() | |
| return pred, acc / x.size(0) | |
| def after_task(self, task_idx, buffer, train_loader, test_loaders): | |
| new_omega = self.get_importance(train_loader) | |
| if self.omega is not None: | |
| alpha = self.known_cls_num / self.total_cls_num | |
| for name, old_omega in self.omega.items(): | |
| if name not in new_omega: | |
| continue | |
| old_slice = self._prefix_slice(old_omega) | |
| new_omega[name][old_slice] = ( | |
| alpha * old_omega + (1 - alpha) * new_omega[name][old_slice] | |
| ) | |
| self.omega = new_omega | |
| self.ref_param = { | |
| name: param.clone().detach() | |
| for name, param in self.network.named_parameters() | |
| if param.requires_grad | |
| } | |
| def get_importance(self, train_loader): | |
| omega = { | |
| name: torch.zeros_like(param, device=self.device) | |
| for name, param in self.network.named_parameters() | |
| if param.requires_grad | |
| } | |
| was_training = self.network.training | |
| self.network.train() | |
| for data in train_loader: | |
| x = data["image"].to(self.device) | |
| y = data["label"].to(self.device) | |
| logits = -self.network(x) | |
| loss = F.cross_entropy(logits, y) | |
| self.network.zero_grad(set_to_none=True) | |
| loss.backward() | |
| for name, param in self.network.named_parameters(): | |
| if param.grad is not None and name in omega: | |
| omega[name] += param.grad.pow(2).detach() | |
| for name, value in omega.items(): | |
| value = value / len(train_loader) | |
| omega[name] = torch.clamp(value, max=self.omega_max) | |
| self.network.zero_grad(set_to_none=True) | |
| self.network.train(was_training) | |
| return omega | |
| def compute_ewc(self): | |
| if self.omega is None: | |
| return torch.zeros((), device=self.device) | |
| loss = torch.zeros((), device=self.device) | |
| for name, param in self.network.named_parameters(): | |
| if name not in self.omega: | |
| continue | |
| ref = self.ref_param[name] | |
| old_slice = self._prefix_slice(ref) | |
| loss += torch.sum(self.omega[name] * (param[old_slice] - ref).pow(2)) / 2 | |
| return loss | |
| def _prefix_slice(tensor): | |
| return (slice(0, tensor.shape[0]),) + (slice(None),) * (tensor.dim() - 1) | |
| def forward(self, x): | |
| return self.network(x) | |
| def get_parameters(self, config): | |
| return [{"params": self.network.parameters()}] | |