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:
- 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.
- 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
| Method | After Unlearning | After Attack | Recovery Rate |
|---|---|---|---|
| Gradient Ascent | 0.5% | 71.1% | 71.1% |
| SCRUB | 0.3% | 88.4% | 88.4% |
| Boundary Expand | 0.1% | 99.98% | 99.98% |
| Spotter | 0.0% | 0.24% | 0.24% |
4. Spotter Method
4.1 Dual Objective
Spotter addresses both over-unlearning and relearning attacks with two components:
- Masked Knowledge Distillation Penalty: Penalizes changes in forget-class neighbor regions
- 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
- Ha, Park, and Yoon, “Unlearning’s Blind Spots: Over-Unlearning and Prototypical Relearning Attack,” ICML 2026.
- Bourtoule et al., “Machine Unlearning,” IEEE S&P 2021.