LibContinual / core /model /ewcdr.py
boringKey's picture
Sync from GitHub
3cdd985 verified
Raw History Blame Contribute Delete
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
@staticmethod
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()}]