Checklist OAI 2025 Final (Stage III) · Task 2
Machine (Un)learning
Polish title: (Od)uczenie maszynowe
Make a trained LeNet forget one (unknown in advance) Fashion-MNIST class by changing only its feature extractor, while keeping accuracy on the other classes and staying close to the original weights.
The task
Machine unlearning removes selected information from a trained model (for example for privacy under GDPR, removal of unsafe concepts, or bias reduction) without retraining from scratch. In the classic setting the model should behave as if the forgotten data had never been seen; for this task the goal is simplified to predicting the chosen class with as low accuracy as possible.
A LeNet model trained on all ten Fashion-MNIST classes is provided. The contestant must make it unlearn a given class; the final classification layer must remain untouched, so all changes must be made in the earlier layers (the feature extractor). The class to be forgotten at test time, and the subset of data used for unlearning, are not known in advance, so the unlearn() procedure must work for any class.
Four aspects are assessed: accuracy on the forgotten class Ψ (to be minimised), accuracy on the remaining classes Φ (to be maximised), the L2 distance between the original and modified parameters, and the KL divergence between the distribution of predictions for the forgotten class and the uniform distribution (diversity of predictions).
Abridged and translated by SOTA from the official Polish materials. The official statement has the exact rules, and it wins wherever this summary differs.
In English
This task was published in Polish. SOTA translated it into English on 16 September 2026. Only the words changed in the notebooks: markdown, code comments, messages and printed output. The code, file names and paths are the original's, so a translated notebook runs with the original data.
Read the task notebook in English
Machine (Un)learning

Image generated using the DALL-E model.
Introduction
Motivation
Machine unlearning is a fascinating and increasingly popular topic related to learning algorithms, above all to deep learning networks. As the number of parameters and the sophistication of models grow, their ability to remove outdated, erroneous or sensitive information (e.g. information related to user privacy) becomes crucial. Unlearning makes it possible to remove selected information from the model's memory in a way that minimises the resulting harm with respect to the other learned data. This allows us to obtain more precise and safer models, without the constant need to train models from scratch.
Benefits
Introducing unlearning into the world of deep learning networks can bring many benefits. Among other things, it makes it possible to remove a model's ability to generate harmful content. For example, if a generative model has been trained on a huge set of images, but some of them contain inappropriate content, such as nudity, unlearning can be applied to remove the concept of nudity from the model's memory, which will reduce the likelihood of similar images being generated in the future.
Secondly, under personal data protection legislation (such as the GDPR, i.e. the General Data Protection Regulation in the European Union), users have the right to withdraw their consent to the processing of their data by a given company. However, if a model has been trained using that data, it is not trivial to get rid of this knowledge without having to retrain the model.
Finally, unlearning can help to eliminate biases and errors that may appear in models, which leads to fairer and more accurate results. Machine learning models can sometimes absorb biases and inequalities contained in the training data (for instance, related to sex or skin colour), which results in biased predictions. Unlearning makes it possible to remove these biases, which improves the quality and fairness of the results.
Note: In the classic machine unlearning problem, we do not so much want the model to be unable to predict a given class, but rather want it to behave as if that data had not been in the training set (which means that the model is resistant to a membership inference attack). Hence, the desired property is often not to minimise performance on a certain part of the data directly, but to aim for the model to behave randomly towards that data, or in the same way as towards data that really was not in the training set. For the purposes of our task, we simplify this goal: we want the model simply to predict the given class with the lowest possible accuracy.
Problem description
For our adventure with unlearning, we will analyse a classification problem on a standard computer vision dataset, Fashion MNIST. To solve this problem, we will use the classic LeNet convolutional architecture (visualisation and description below). We are provided with a baseline model that has been trained on the whole dataset, i.e. on all ten classes of the Fashion MNIST dataset (T-shirt/top, trouser, pullover, dress, coat, sandal, shirt, sneaker, bag, ankle boot). The code for loading this model is given below.
Your task
Your task is to make the baseline model unlearn a given, selected class of the Fashion MNIST dataset. We require the last layer (the classification layer) to remain untouched; in other words, your intervention must take place at the level of the feature extractor, and not through a mechanical modification of the signal responsible for generating the logits for the individual classes. The intervention should be confined exclusively to the earlier layers of the model, excluding any direct change to the model's classification function itself.
Evaluation
To check how well you have coped with this task, we have prepared a set of metrics that will allow us to assess the quality of your solution.
We will assess you on four aspects:
- Unlearning the classification of the selected class - after all, that is what unlearning is about! However, you do not know which class you will ultimately be assessed on unlearning, within the test set. For validation, in this notebook we have chosen one of the ten classes of the Fashion MNIST dataset, but remember that in the end you will be assessed on a different class!
- Maintaining high performance on the remaining classes - the model must stop working only on the selected class, but it must not lose performance on the remaining classes - here the accuracy must be as high as possible!
- Intervention in the baseline model - try to keep the modification of the baseline model's weights as small as possible!
- Diversity of predictions for the forgotten class - the point is for the model to be resistant to the previously mentioned membership inference attack, i.e. it must not be possible to tell that the model has ever seen the data it is supposed to unlearn!
Detailed description:
Unlearning the i-th class, .
The process of unlearning the knowledge of the i-th class (one of the ten possible classes) consists in removing the model's ability to recognise and classify data belonging to that particular class. This is a key aspect of unlearning, which makes it possible to eliminate undesirable or inappropriate data from the model. Specifically, we can measure the accuracy on the data belonging to the specific class number i as :
where denotes the number of examples correctly classified into class (true positive rate), and denotes the number of examples from class that were incorrectly classified as belonging to another class (false negative rate). In our problem we want to minimise the value of .
Maintaining high performance on the remaining classes
An important aspect of unlearning is ensuring that removing the i-th class does not adversely affect the model's performance on the remaining classes. The model must still be able to achieve high classification accuracy on all the remaining classes. This is a challenge, because the unlearning process can disturb the balance of the model and adversely affect its ability to extract features, including for the remaining classes. The set of the data that should still be classified well by the model is simply called the remain dataset. We can assess the model's classification accuracy on this data by measuring it for all classes except class number , and define this as a function that depends on .
Specifically, we can use the accuracy on the remain set as :
where: is the number of correctly classified examples belonging to class , excluding class (true positive rate), and is the number of examples incorrectly not classified as class , excluding class (false negative rate). In other words, the numerator contains the number of correctly classified examples from all classes except class number , and the denominator the number of examples belonging to all classes, excluding the -th class.
Intervention in the baseline model
The unlearning process involves an intervention in the baseline model, which may have various consequences. It is important that this intervention is minimal and does not lead to destabilisation of the model. In practice, this means that the changes made to the model should be limited to the modifications necessary to remove class i, without affecting the structure and functionality of the model, and therefore with minimal modification of the model's parameters. We require that the last layer of the LeNet model remains untouched, but we will also measure the distance between the original model and the unlearned model that you propose. This is also a way to better understand how the model works. If the distance from the baseline model is not too large, and we trust the baseline model, then the model you obtain is close enough to earn our trust.
In our problem we will use the traditional distance, which can be expressed by the formula:
where
- denotes the vector of the model's parameters after the unlearning process,
- denotes the vector of the baseline model's parameters,
- and are the corresponding parameter values for the j-th layer of the models.
The distance sums the squared differences between the successive layers of the baseline model and the corresponding layers of the modified model, and finally takes the square root of this sum. The smaller the value of , the smaller the intervention in the baseline model, which is desirable in the context of minimising changes to the structure of the model.
Diversity of predictions for the forgotten class
The last aspect of the assessment is the diversity of predictions for the forgotten class. After the unlearning process, the model should generate diverse predictions for data that belong to class , which the model was supposed to forget. This means that the model should simply assign them to other classes in a varied way, so that it cannot easily be determined whether this data was originally in the training set of the baseline model.
The model should also not assign all predictions to one particular class, because this could be interpreted as merging the selected classes, which we do not want.
To measure the diversity of predictions, we will use the Kullback-Leibler (KL) divergence. It is a standard measure of the discrepancy between two probability distributions. In our case, we examine the distance of the distribution of the model's predictions from the uniform distribution, which means that none of the remaining classes should be favoured.
We define the KL divergence as:
where:
- is the probability, assigned by the model, that the sample belongs to class ,
- is the probability according to the uniform distribution, i.e. ,
- is the total number of classes.
In the case of ideal diversity of predictions for the forgotten class, the distribution should be as close as possible to the uniform distribution , which means a minimal value of .
If the KL divergence still seems rather complicated, do not worry - in the Supplementary Information section we have provided some helpful hints. In addition, take a look at the code of the function definition - we have left a few comments in it.
We will evaluate all four of the above objectives on the Fashion MNIST data, but you do not know which class we will ultimately want to unlearn. Moreover, you do not know which subset of the data from the individual classes will be used for unlearning. To prepare your solution you may choose any class, but the solution must be ready to unlearn any class of this dataset. Although the data distribution in the test set will be similar to the distribution in this notebook, try to avoid overfitting to the selected data.
So check whether your solution is universal. Make sure that it works for different labels designated to be forgotten and for different datasets (the architecture is fixed, adapted to processing visual data of size ).
Final score
Overall, your score can be written mathematically as a weighted sum:
Note: We would like to maximise the metric and to minimise the others, i.e. , and . Hence, pay attention to the order of subtraction when computing the individual components for the final evaluation!
– the score for the effectiveness of unlearning on the selected class (for simplicity, in this task we assume that the lower the accuracy on the target class, the better; scaled to the range [0, 100] according to the thresholds ):
– the score for maintaining accuracy on the remaining classes (the higher the accuracy, the higher the score for this metric; scaled to the range [0, 100] according to the thresholds ):
– the score for the degree of intervention in the model (the smaller the distance, the higher the score for this metric; scaled to the range [0, 100] according to the thresholds ):
– the score for the diversity of predictions (the lower the KL divergence, the higher the score for this metric; scaled to the range [0, 100] according to the thresholds ):
However, the task is scored outright as 0 points in all categories if your solution:
- does not comply with the guidelines, for example:
- a change is made to the network architecture;
- the last layer of the network is modified;
- any attempt at cheating is made, e.g. by modifying the evaluation function;
- is unsatisfactory:
- the classification accuracy on the set of the class to be forgotten stays above 0.5;
- the classification accuracy on the set of the remaining classes falls below 0.75;
- the distance is greater than 8.0;
- the value of the Kullback-Leibler divergence exceeds 1.75.
Furthermore, the model unlearning you propose, i.e. the execution of the unlearn() function, may take no longer than 5 minutes using a GPU.
You can receive a maximum of 100 points for this task.
Remember that during checking, the FINAL_EVALUATION_MODE flag will be set to True.
Good luck!
Starter code
######################### DO NOT CHANGE THIS CELL ##########################
# If needed, import additional libraries below, in your own code.
import os
from copy import deepcopy
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import tarfile
import tempfile
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {DEVICE}")
######################### DO NOT CHANGE THIS CELL ##########################
FINAL_EVALUATION_MODE = False # During checking, we will set this flag to True.
######################### DO NOT CHANGE THIS CELL ##########################
seed = 101
os.environ["PYTHONHASHSEED"] = str(seed)
torch.manual_seed(seed)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
np.random.seed(seed)
Data
######################### DO NOT CHANGE THIS CELL ##########################
class FilteredFashionMNIST(datasets.FashionMNIST):
def __init__(self, *args, classes=None, **kwargs):
super().__init__(*args, **kwargs)
self.classes = classes
if self.classes is not None:
self.data, self.targets = self._filter_classes(
self.data, self.targets, self.classes)
def _filter_classes(self, data, targets, classes):
mask = torch.zeros_like(targets, dtype=torch.bool)
for c in classes:
mask = mask | (targets == c)
return data[mask], targets[mask]
######################### DO NOT CHANGE THIS CELL ##########################
BATCH_SIZE = 64
tempdir = tempfile.TemporaryDirectory()
TMP_DIR = tempdir.name
DATA_PATH = os.path.join(TMP_DIR, "data")
if not FINAL_EVALUATION_MODE:
import gdown
GDRIVE_DATA = [
("1SeXrzvs64MBG57ayK6965cya3WRfCMx_", "data/FashionMNIST.tar.gz"),
("1mqzxg-_0PJjvErvfwOGad6siNcQKqwk5", "data/FilteredFashionMNIST.tar.gz"),
("1YSn8EFjbYDcDCVdlByA_kDKCg4VZLwH8", "models/lenet_base_final.pt"),
]
for file_id, output in GDRIVE_DATA:
url = f'https://drive.google.com/uc?id={file_id}'
os.makedirs(os.path.dirname(output), exist_ok=True)
gdown.download(url, output, quiet=False)
print(f"Downloaded: {output}")
######################### DO NOT CHANGE THIS CELL ##########################
def unpack_tar_gz(filename: str, path: str = DATA_PATH) -> None:
"""Unpacks a tar.gz archive into the specified directory."""
with tarfile.open(filename, "r:gz") as tar:
tar.extractall(path=path)
unpack_tar_gz("data/FashionMNIST.tar.gz", os.path.join(DATA_PATH, "FashionMNIST"))
unpack_tar_gz("data/FilteredFashionMNIST.tar.gz", os.path.join(DATA_PATH, "FilteredFashionMNIST"))
transform_fashion = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.2860,), (0.3530,))
])
def get_data_dict():
def create_filtered_loader(classes):
dataset = FilteredFashionMNIST(
root=DATA_PATH,
download=True,
transform=transform_fashion,
classes=classes)
loader = DataLoader(
dataset=dataset,
batch_size=BATCH_SIZE,
shuffle=True)
return loader
loader_fashion = DataLoader(
dataset=datasets.FashionMNIST(
root=DATA_PATH,
download=True,
transform=transform_fashion),
batch_size=BATCH_SIZE,
shuffle=True
)
class_groups = {}
num_classes = 10
for i in range(num_classes):
class_groups[f"{i}"] = [i]
class_groups[f"~{i}"] = [j for j in range(num_classes) if j != i]
data_dict = {
"fashion": {
"loader": loader_fashion,
}
}
for group_name, classes in class_groups.items():
loader = create_filtered_loader(classes)
data_dict["fashion"][f"loader_{group_name}"] = loader
return data_dict
data_dict = get_data_dict()
Model
######################### DO NOT CHANGE THIS CELL ##########################
class LeNet(nn.Module):
def __init__(self, num_classes=10):
super(LeNet, self).__init__()
self.block1 = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5, stride=1, padding=0),
nn.BatchNorm2d(6),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.block2 = nn.Sequential(
nn.Conv2d(6, 16, kernel_size=5, stride=1, padding=0),
nn.BatchNorm2d(16),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
latent_dim = 256
self.fc = nn.Linear(latent_dim, 120)
self.relu = nn.ReLU()
self.fc1 = nn.Linear(120, 84)
self.relu1 = nn.ReLU()
self.fc2 = nn.Linear(84, num_classes)
def forward(self, x):
out = self.block1(x)
out = self.block2(out)
out = out.reshape(out.size(0), -1)
out = self.fc(out)
out = self.relu(out)
out = self.fc1(out)
out = self.relu1(out)
out = self.fc2(out)
return out

Source: GeeksForGeeks
Loading the model
pretrained_model = LeNet(num_classes=10)
initial_state_dict = torch.load("./models/lenet_base_final.pt")
pretrained_model.load_state_dict(initial_state_dict)
pretrained_model = pretrained_model.to(DEVICE)
Metrics
######################### DO NOT CHANGE THIS CELL ##########################
def evaluate_target(model, data_loader, criterion, device="cpu"):
"""
Function that evaluates the model's performance on the target data.
:param model: The PyTorch model we want to evaluate.
:param data_loader: DataLoader containing the data for evaluating the classification accuracy.
:param criterion: The loss function used to compute the loss.
:param device: The device on which the computations are performed (default "cpu").
:return: A tuple containing the mean loss and the accuracy on the target data.
"""
model.eval()
total_loss = 0
correct = 0
with torch.no_grad():
for data, target in data_loader:
data, target = data.to(device), target.to(device)
output = model(data)
loss = criterion(output, target)
total_loss += loss.item()
pred = output.argmax(dim=1, keepdim=True)
correct += pred.eq(target.view_as(pred)).sum().item()
return total_loss / len(data_loader.dataset), correct / len(data_loader.dataset)
def measure_target_uniformity(model, loader_target, device="cpu", num_classes=10):
"""
Measures how close the model's predictions for the target class are to the uniform distribution.
We use the KL divergence: KL(U || p(x)) or KL(p(x) || U) as the measure of uniformity.
We compute the mean KL(p || U) = sum_{y} p(y) log [p(y) / (1/num_classes)].
A lower KL value means greater uniformity.
A detailed explanation is given below in the supplementary information.
"""
model.eval()
kl_sum = 0.0
total_samples = 0
with torch.no_grad():
for images, _ in loader_target:
images = images.to(device)
logits = model(images)
log_probs = F.log_softmax(logits, dim=1)
probs = torch.exp(log_probs)
entropy_term = (probs * log_probs).sum(dim=1)
kl_batch = entropy_term + torch.log(
torch.tensor(num_classes, device=device)
)
kl_sum += kl_batch.sum().item()
total_samples += images.size(0)
return kl_sum / total_samples
def measure_l2_distance(model, initial_state_dict, device="cpu"):
"""
Measures the L_2 distance between the current state of the model and its initial state.
"""
l2_distance = 0.0
for name, param in model.named_parameters():
if param.requires_grad:
initial_param = initial_state_dict[name]
l2_distance += torch.sum((param.to(device) - initial_param.to(device)) ** 2).item()
return l2_distance ** 0.5
######################### DO NOT CHANGE THIS CELL ##########################
WEIGHTS = [0.25, 0.25, 0.25, 0.25]
acc_forget_thresholds = [0.09, 0.3]
acc_other_thresholds = [0.87, 0.90]
dist_thresholds = [1.3, 3.0]
d_kl_thresholds = [0.2, 0.5]
acc_forget_absolute_threshold = 0.5
acc_target_absolute_threshold = 0.75
distance_absolute_threshold = 8.0
dkl_absolute_threshold = 1.75
def compute_final_score(
kl_after, acc_rest_after, l2_dist, acc_target_after
):
"""
Function that computes the final score based on various metrics.
:param kl_after: The KL value after training.
:param acc_rest_after: Accuracy on the data from the set that the model is to remember.
:param l2_dist: The L2 distance after training.
:param acc_target_after: Accuracy on the data from the set that the model is to forget.
:return: The final score."""
def compute_metric_score(value, thresholds, maximize=True):
if maximize:
if value >= thresholds[1]:
return 100
elif value <= thresholds[0]:
return 0
else:
return 100 * (value - thresholds[0]) / (thresholds[1] - thresholds[0])
else:
if value <= thresholds[0]:
return 100
elif value >= thresholds[1]:
return 0
else:
return 100 * (thresholds[1] - value) / (thresholds[1] - thresholds[0])
print(f"KL divergence: {kl_after:.2f}")
print(f"Classification accuracy on the remaining set: {acc_rest_after:.2f}")
print(f"L2 distance: {l2_dist:.2f}")
print(f"Accuracy on the set to be forgotten: {acc_target_after:.2f}")
if acc_rest_after < acc_target_absolute_threshold or acc_target_after > acc_forget_absolute_threshold \
or l2_dist > distance_absolute_threshold or kl_after > dkl_absolute_threshold:
return 0
kl_score = compute_metric_score(kl_after, d_kl_thresholds, maximize=False)
acc_rest_score = compute_metric_score(acc_rest_after, acc_other_thresholds, maximize=True)
l2_dist_score = compute_metric_score(l2_dist, dist_thresholds, maximize=False)
acc_target_score = compute_metric_score(acc_target_after, acc_forget_thresholds, maximize=False)
total_score = (
WEIGHTS[0] * kl_score +
WEIGHTS[1] * acc_rest_score +
WEIGHTS[2] * l2_dist_score +
WEIGHTS[3] * acc_target_score
)
return total_score
def evaluate_model(model, data_loader_target, data_loader_rest, initial_state_dict, device):
"""
Function that evaluates the model on the target data and the remaining data, and checks the L2 distance
and the similarity to the uniform distribution.
:param model: the model to evaluate.
:param data_loader_target: DataLoader for the target data.
:param data_loader_rest: DataLoader for the remaining data.
:param initial_state_dict: the initial state of the model.
:param criterion: the loss function.
:param device: the device for the computations.
:return: the model's score.
"""
model.eval()
criterion = nn.CrossEntropyLoss()
_, acc_rest = evaluate_target(model, data_loader_rest, criterion, device)
_, acc_target = evaluate_target(model, data_loader_target, criterion, device)
l2_dist = measure_l2_distance(model, initial_state_dict, device)
kl = measure_target_uniformity(model, data_loader_target, device, num_classes=10)
return compute_final_score(kl, acc_rest, l2_dist, acc_target)
Compliance
######################### DO NOT CHANGE THIS CELL ##########################
def has_same_last_layer(model1: LeNet, model2: LeNet) -> bool:
return all(torch.equal(p1, p2) for p1, p2 in zip(model1.fc2.parameters(), model2.fc2.parameters()))
def has_same_architecture(model: LeNet) -> bool:
return set([k for (k, v) in list(model.named_parameters())]) == \
{'block1.0.weight', 'block1.0.bias', 'block1.1.weight', 'block1.1.bias', \
'block2.0.weight', 'block2.0.bias', 'block2.1.weight', 'block2.1.bias', \
'fc.weight', 'fc.bias', 'fc1.weight', 'fc1.bias', 'fc2.weight', 'fc2.bias'}
Trivial solution
def dumb_solution(model, data, target_class):
"""
Function that adds noise to the model's weights in order to change its behaviour on the target data.
:param model: The PyTorch model we want to modify.
:param data: The data on which we want to modify the model.
:param target_class: The target class on which we want to modify the model.
"""
new_model = deepcopy(model) # We create a copy of the model so as not to modify the original
with torch.no_grad():
for name, param in new_model.named_parameters():
if "fc2" not in name: # The last layer remains unchanged
param.add_(torch.randn_like(param) * 0.1) # Adding noise to the weights
return new_model
Trivial solution - evaluation
if not FINAL_EVALUATION_MODE:
perturbed_model = dumb_solution(pretrained_model, None, None)
assert has_same_last_layer(pretrained_model, perturbed_model), "The last layer of the model is not the same as in the original model."
assert has_same_architecture(perturbed_model), "The architecture of the model is not the same as in the original model."
target_class = 9
print(f"Target class: {target_class}")
target_loader = data_dict["fashion"][f"loader_{target_class}"]
other_loader = data_dict["fashion"][f"loader_~{target_class}"]
score = evaluate_model(perturbed_model, target_loader, other_loader, initial_state_dict, DEVICE)
print(f"Score: {score:.2f}")
Supplementary information
Explanation of the Kullback-Leibler divergence
The Kullback-Leibler divergence (Kullback-Leibler divergence, KL) is a measure of the discrepancy between two probability distributions. In the context of machine learning and statistics, the KL divergence makes it possible to assess how much one distribution differs from another. Formally, for two discrete distributions and , it is defined as:
where:
- is the true distribution (e.g. real data),
- is the approximating distribution (e.g. the model's prediction),
- is the event space.
The KL divergence is not a symmetric measure, which means that . The value of the divergence is 0 when the two distributions are identical.
Examples of comparisons of discrete distributions:
-
Example 1: Identical distributions
-
Example 2: Slightly different distributions
-
Example 3: Very different distributions
- .
Visualisation of the KL divergence
Below is Python code that visualises the KL divergence for two discrete distributions.
import numpy as np
import matplotlib.pyplot as plt
def kl_divergence(p, q):
"""Computes the KL divergence between two distributions."""
p = np.array(p)
q = np.array(q)
return np.sum(p * np.log(p / q))
# Example distributions
P = [0.4, 0.6]
Q_list = [
[0.4, 0.6], # Identical distribution
[0.5, 0.5], # Slightly different
[0.9, 0.1] # Very different
]
# Computing the KL divergence
kl_values = [kl_divergence(P, Q) for Q in Q_list]
# Visualisation
labels = ['Q1 (identyczny)', 'Q2 (nieznacznie różny)', 'Q3 (bardzo różny)'] # "identical", "slightly different", "very different"
x = np.arange(len(Q_list))
plt.bar(x, kl_values, color='skyblue')
plt.xticks(x, labels, rotation=15)
plt.ylabel('KL Dywergencja') # "KL divergence"
plt.title('KL Dywergencja dla różnych rozkładów Q względem P') # "KL divergence for different distributions Q relative to P"
plt.show()
Interpretation
- When and are identical, the KL divergence is 0.
- When differs from , the KL divergence increases.
- The KL divergence is not symmetric, so swapping the order of and changes the result. This is worth bearing in mind when interpreting the results.
- You may notice a difference between the formula in this task and the formula in the measure_target_uniformity() function. This is because we compare our probability distribution with the uniform distribution, in which case the formula transforms into that form.
Submission files
All we need from you is this notebook - together with the key definition of the unlearn method, which will find the final weights of the model on which the evaluation will be carried out for a class of the Fashion MNIST dataset chosen by us.
Constraints
We have proposed guidelines that enforce low performance of the model on the target class. Remember that, in the general case, the goal of our unlearning algorithm should be to reach a state in which the model behaves as if the data from the target class had never been part of the training set.
In addition, we prohibit modifying the hyperparameters of the architecture, as well as any parameters of the classification layer.
We expect your solution to be based on, and to use, only standard libraries used in machine learning, such as torch, numpy, matplotlib/seaborn and scikit-learn.
Your solution
def unlearn(model, data, target_class):
"""
Function that performs the "unlearn" operation on the model.
:param model: The model we want to modify.
:param data: The data on which we want to modify the model, containing
the full dataset.
:param target_class: The index of the target class that we want the model to unlearn.
return: The modified model.
"""
# propose your solution here
# ...
return model
######################### DO NOT CHANGE THIS CELL ##########################
if not FINAL_EVALUATION_MODE:
def prepare_data(data_dict, target_class):
"""
Prepares the data for training the model.
:param data_dict: Dictionary with the data.
:param target_class: The target class.
:return: A tuple with the data to be forgotten and the remaining data.
"""
target_data = data_dict["fashion"][f"loader_{target_class}"]
other_data = data_dict["fashion"][f"loader_~{target_class}"]
return target_data, other_data
target_class = 9
unlearned_model = unlearn(pretrained_model, data_dict, target_class)
target_loader, other_loader = prepare_data(data_dict, target_class)
score = evaluate_model(unlearned_model, target_loader, other_loader, initial_state_dict, DEVICE)
print(f"Model score after unlearning: {score}")
######################### DO NOT CHANGE THIS CELL ##########################
if FINAL_EVALUATION_MODE:
import cloudpickle
OUTPUT_PATH = "file_output"
FUNCTION_FILENAME = "your_model.pkl"
FUNCTION_OUTPUT_PATH = os.path.join(OUTPUT_PATH, FUNCTION_FILENAME)
if not os.path.exists(OUTPUT_PATH):
os.makedirs(OUTPUT_PATH)
with open(FUNCTION_OUTPUT_PATH, "wb") as f:
cloudpickle.dump(unlearn, f)
Translated by SOTA. The Polish original is the official version and wins wherever the two differ. The Fashion MNIST classes are given under their standard English names. In the example plotting code under Supplementary information, the chart labels stay in Polish, with English glosses in comments. If you organise this olympiad and would like the translation removed, email [email protected] and we will take it down.
At a glance
- You get
- FashionMNIST.tar.gz, FilteredFashionMNIST.tar.gz and the base model
lenet_base_final.pt, downloaded from Google Drive. - You submit
- This notebook with the unlearning procedure.
- Scoring
- Σ = ¼Σ_target + ¼Σ_remain + ¼Σ_dist + ¼Σ_kl, each in [0, 100]: Σ_target = 100 if Ψ ≤ 0.09, 0 if Ψ ≥ 0.3, linear in between; Σ_remain = 100 if Φ ≥ 0.90, 0 if Φ ≤ 0.87, linear; Σ_dist = 100 if L2 ≤ 1.3, 0 if L2 ≥ 3.0, linear; Σ_kl = 100 if D_KL ≤ 0.2, 0 if D_KL ≥ 0.5, linear. The whole task scores 0 if the architecture or last layer is changed, if cheating is attempted, or if Ψ > 0.5, Φ < 0.75, L2 > 8.0 or D_KL > 1.75.
- Rules
- The architecture and the last (classification) layer must not be modified.
- unlearn() may take at most 5 minutes with a GPU.
- The solution must work for any of the ten classes and for different unlearning subsets.
- Format
- Final (Stage III), 30 May – 2 June 2025, Faculty of Mathematics and Computer Science, University of Wrocław; two contest days with two tasks and a 5-hour session each (400 points in total). Evaluated automatically on the Competition Platform (Platforma Konkursowa) on a hidden test set; points are rounded to an integer, and a notebook that fails the requirements or does not run scores 0.