Spotter AI Unlearning Blind Spots Deep Dive: Over-Unlearning and Prototypical Relearning Attack — ICML 2026 Machine Unlearning Full Analysis

Introduction

Machine Unlearning (MU) aims to make AI models “forget” specific training data without costly retraining. But a July 2026 study accepted to ICML 2026 reveals two blind spots that have been overlooked:

  1. Over-unlearning: Deleting a target class inadvertently damages similar retained classes. Teaching a model to “forget cats” may weaken its ability to recognize tigers or leopards.
  2. Prototypical Relearning Attack: Even after information is removed, attackers can recover forgotten knowledge with just a handful of samples.

The proposed Spotter method solves both problems simultaneously, reducing target class accuracy to 0% while maintaining 99.96% accuracy on retained classes, and limiting attack recovery to just 0.24% — compared to 71-99.98% recovery with existing methods.


1. Background: The Blind Spots

1.1 Over-Unlearning

When a model “forgets” cats, it inadvertently affects the recognition of tigers, leopards, and other felines. This happens because these classes’ features are proximate in the neural network’s high-dimensional representation space — deleting one class’s features deletes neighboring features too.

1.2 Prototypical Relearning Attack

Even after information is fully deleted, attackers need only a few samples (the paper uses just 5 images) to recover the forgotten class knowledge. This is because the unlearning process may delete only “decision-boundary features” while preserving “core prototype features” that can be reactivated with few samples.


2. Quantifying Over-Unlearning: OU@ε

2.1 Definition

The paper introduces OU@ε (Over-Unlearning at epsilon threshold):

OU@ε = max_{c ∈ C_retain} [Acc_before(c) - Acc_after(c)] 
       subject to: d(c, c_forget) < ε

where d(c, c_forget) is the distance between class c and the forget class in representation space.

2.2 Implementation

import torch
import torch.nn as nn
import torch.nn.functional as F
from typing import Dict, List, Tuple

class OUMetric:
    """Over-Unlearning metric computation."""
    
    def __init__(self, model: nn.Module, epsilon: float = 0.3):
        self.model = model
        self.epsilon = epsilon
    
    @torch.no_grad()
    def compute_class_centroids(self, loader) -> Dict[int, torch.Tensor]:
        """Compute class centroids in representation space."""
        self.model.eval()
        features_dict = {}
        labels_dict = {}
        
        for inputs, labels in loader:
            inputs = inputs.cuda()
            features = self.model.feature_extractor(inputs)
            
            for i, label in enumerate(labels):
                label_item = label.item()
                if label_item not in features_dict:
                    features_dict[label_item] = []
                features_dict[label_item].append(features[i].cpu())
        
        centroids = {}
        for label, feats in features_dict.items():
            centroids[label] = torch.stack(feats).mean(dim=0)
        
        return centroids
    
    def compute_ou_epsilon(
        self,
        forget_class: int,
        centroids: Dict[int, torch.Tensor],
        acc_before: Dict[int, float],
        acc_after: Dict[int, float],
    ) -> Tuple[float, List[int]]:
        """
        Compute OU@epsilon for a given forget class.
        Returns (max_over_unlearning, affected_classes)
        """
        forget_centroid = centroids[forget_class]
        affected = []
        
        for cls, centroid in centroids.items():
            if cls == forget_class:
                continue
            
            dist = F.pairwise_distance(
                forget_centroid.unsqueeze(0), 
                centroid.unsqueeze(0)
            ).item()
            
            if dist < self.epsilon:
                drop = acc_before[cls] - acc_after[cls]
                affected.append((cls, drop))
        
        if not affected:
            return 0.0, []
        
        max_ou = max(drop for _, drop in affected)
        affected_classes = [cls for cls, _ in affected]
        
        return max_ou, affected_classes

3. Prototypical Relearning Attack

3.1 Attack Principle

Even after unlearning, the model’s representation space retains traces of the forget-class prototype (the mean embedding of all class samples). Attackers need only a few samples from the forgotten class to compute its prototype and reactivate the model’s recognition capability.

class PrototypicalRelearningAttack:
    """
    Exploits residual class prototypes to recover forgotten knowledge.
    """
    
    def __init__(self, model: nn.Module):
        self.model = model
    
    @torch.no_grad()
    def compute_prototype(self, samples: torch.Tensor) -> torch.Tensor:
        """Compute prototype (mean embedding) of given samples."""
        features = self.model.feature_extractor(samples)
        return features.mean(dim=0)
    
    def attack(self, forget_samples: torch.Tensor, 
               forget_labels: torch.Tensor,
               retain_loader, lr=1e-4, epochs=10) -> nn.Module:
        prototype = self.compute_prototype(forget_samples)
        forget_class = forget_labels[0].item()
        optimizer = torch.optim.AdamW(self.model.classifier.parameters(), lr=lr)
        
        for epoch in range(epochs):
            virtual_features = prototype.unsqueeze(0) + \
                torch.randn(32, prototype.size(0), device=prototype.device) * 0.1
            virtual_logits = self.model.classifier(virtual_features)
            virtual_targets = torch.full((32,), forget_class, 
                                         dtype=torch.long, device=prototype.device)
            loss = F.cross_entropy(virtual_logits, virtual_targets)
            loss.backward()
            optimizer.step()
            optimizer.zero_grad()
        
        return self.model

3.2 Attack Results

MethodAfter UnlearningAfter AttackRecovery Rate
Gradient Ascent0.5%71.1%71.1%
SCRUB0.3%88.4%88.4%
Boundary Expand0.1%99.98%99.98%
Spotter0.0%0.24%0.24%

4. Spotter Method

4.1 Dual Objective

Spotter addresses both over-unlearning and relearning attacks with two components:

  1. Masked Knowledge Distillation Penalty: Penalizes changes in forget-class neighbor regions
  2. Intra-class Dispersion Loss: Scatters forget-class embeddings to prevent prototype recovery

4.2 Mathematical Form

L = L_retain + λ₁ · L_mask + λ₂ · L_dispersion
L_retain = KL(p_teacher || p_student) for x ∈ D_retain
L_mask = KL(p_teacher || p_student) · M(x) for x ∈ D_neighbor
L_dispersion = -Σ||z_i - z_j||² for z_i, z_j ∈ Z_forget

4.3 Implementation

class Spotter:
    """Machine Unlearning with dual protection."""
    
    def __init__(self, model, num_classes, lambda_mask=1.0, 
                 lambda_dispersion=0.5, epsilon=0.3):
        self.model = model
        self.teacher = self._clone_model(model)
        self.teacher.eval()
        self.lambda_mask = lambda_mask
        self.lambda_dispersion = lambda_dispersion
        self.epsilon = epsilon
    
    def dispersion_loss(self, forget_features):
        """Scatter forget-class embeddings to prevent prototype recovery."""
        if forget_features.size(0) < 2:
            return torch.tensor(0.0)
        forget_features = F.normalize(forget_features, dim=1)
        sim_matrix = forget_features @ forget_features.T
        mask = ~torch.eye(forget_features.size(0), dtype=torch.bool)
        return -sim_matrix[mask].mean()
    
    def unlearn(self, forget_class, retain_loader, forget_loader, 
                num_epochs=10, lr=1e-4):
        optimizer = torch.optim.AdamW(self.model.parameters(), lr=lr)
        centroids = self._compute_all_centroids(retain_loader)
        
        for epoch in range(num_epochs):
            for retain_batch, forget_batch in zip(retain_loader, forget_loader):
                retain_inputs, _ = retain_batch
                forget_inputs, _ = forget_batch
                
                # Teacher logits
                with torch.no_grad():
                    teacher_logits = self.teacher(retain_inputs)
                
                # Student logits and features
                student_logits = self.model(retain_inputs)
                student_features = self.model.feature_extractor(retain_inputs)
                forget_features = self.model.feature_extractor(forget_inputs)
                
                # Retain loss
                retain_loss = F.kl_div(
                    F.log_softmax(student_logits, dim=1),
                    F.softmax(teacher_logits, dim=1),
                    reduction='batchmean'
                )
                
                # Masked distillation loss
                neighbor_mask = self._compute_neighbor_mask(
                    student_features, forget_class, centroids
                )
                mask_loss = (F.kl_div(
                    F.log_softmax(student_logits, dim=1),
                    F.softmax(teacher_logits, dim=1),
                    reduction='none'
                ).sum(dim=1) * neighbor_mask).mean()
                
                # Dispersion loss
                disp_loss = self.dispersion_loss(forget_features)
                
                loss = retain_loss + self.lambda_mask * mask_loss + \
                       self.lambda_dispersion * disp_loss
                loss.backward()
                optimizer.step()
                optimizer.zero_grad()
        
        return self.model

5. Conclusion

Spotter reveals a fact long overlooked by the AI community: the real challenge of machine unlearning is not making the model forget, but ensuring the forgetting is persistent, safe, and does not damage neighboring knowledge.

The OU@epsilon metric and prototypical relearning attack provide new evaluation standards for the MU field. Future work evaluating unlearning should check three dimensions: whether the target class is deleted, whether neighbor classes are damaged, and whether the forgetting can be recovered via relearning attacks.


References

  1. Ha, Park, and Yoon, “Unlearning’s Blind Spots: Over-Unlearning and Prototypical Relearning Attack,” ICML 2026.
  2. Bourtoule et al., “Machine Unlearning,” IEEE S&P 2021.