| """RSC -- Representation Self-Challenging (Huang et al., ECCV 2020) -- DG ablation.
|
|
|
| Each step, mute the features with the largest loss-gradient -- the dominant,
|
| most-relied-upon directions, i.e. exactly the studio shortcut -- and force the
|
| prediction from the remainder, so the model cannot lean on a single acquisition
|
| cue. This module is the core masking primitive; the trainer computes the feature
|
| gradient, builds the mask on a random subset of the batch, and re-forwards through
|
| the head. Identity at eval (the trainer skips it).
|
| """
|
| import torch
|
| import torch.nn.functional as F
|
|
|
|
|
| def rsc_feature_mask(grad: torch.Tensor, drop_frac: float = 1.0 / 3.0) -> torch.Tensor:
|
| """Return a 0/1 mask (shape of ``grad``) that zeros, per sample, the
|
| ``drop_frac`` fraction of features with the largest absolute gradient."""
|
| _, c = grad.shape
|
| k = int(round(drop_frac * c))
|
| mask = torch.ones_like(grad)
|
| if k <= 0:
|
| return mask
|
| _, idx = grad.abs().topk(k, dim=1)
|
| return mask.scatter(1, idx, 0.0)
|
|
|
|
|
| def rsc_step(feature_fn, head, imgs, labels,
|
| drop_frac: float = 1.0 / 3.0, apply_frac: float = 0.5):
|
| """One Representation Self-Challenging step (Huang et al., ECCV 2020).
|
|
|
| ``feature_fn(imgs)`` gives pooled features ``[B, C]`` and ``head(features)`` the
|
| logits ``[B, K]``. For a random ``apply_frac`` of the batch we mute, per sample,
|
| the ``drop_frac`` fraction of channels carrying the largest loss-gradient, the
|
| most-relied-upon directions, then predict from the remainder so the network
|
| cannot lean on a single dominant (acquisition) cue. Returns
|
| ``(loss, logits_challenged)``. With ``drop_frac <= 0`` or ``apply_frac <= 0`` it
|
| is plain cross-entropy, so the method reduces exactly to ERM. Use only in
|
| training; at eval the caller does an ordinary forward.
|
| """
|
| features = feature_fn(imgs)
|
| if drop_frac <= 0 or apply_frac <= 0:
|
| logits = head(features)
|
| return F.cross_entropy(logits, labels), logits
|
| loss_pre = F.cross_entropy(head(features), labels)
|
| grad = torch.autograd.grad(loss_pre, features, retain_graph=True)[0]
|
| mask = rsc_feature_mask(grad, drop_frac)
|
| if apply_frac < 1.0:
|
| b = features.size(0)
|
| keep = torch.rand(b, device=features.device) >= apply_frac
|
| mask = torch.where(keep.view(b, 1), torch.ones_like(mask), mask)
|
| logits = head(features * mask)
|
| return F.cross_entropy(logits, labels), logits
|
|
|