Checklist OAI 2026 Stage I · Task 2
Multi-Label Classification
Polish title: Klasyfikacja wieloetykietowa
Train a network that detects which of ten clothing types appear in a 168×168 composite image.
The task
Photographs often contain many objects, so a single label per image discards information; multi-label classification assigns an image several categories at once. In this simplified setting the images show only items of clothing, and the model must decide for each clothing type whether it is present.
Each sample is a 168×168 single-channel image with a 10-element 0/1 vector indicating the presence of the classes T-shirt/top, Trouser, Pullover, Dress, Coat, Sandal, Shirt, Sneaker, Bag and Ankle boot (dictionary LABEL_NAMES). The training set has 6,318 samples and the validation set 702; the hidden test set has 780 samples created in the same way as the validation set.
The contestant defines the model, a training function and a prediction function (class Solution, train_solution, predict_solution).
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
Multi-Label Classification

Image generated with the ChatGPT image generation tool.
Introduction
Modern photographs often contain many objects and complex scenes. An example is the photograph above: in it we see a man in a jacket, shirt and tie, a woman in a dress with a veil, and a group of people in summer clothes, with the whole scene taking place on a beach, against a background of water and a sunset.
If we stuck to the classic "one image — one label" approach, we would have to choose just one category: does this photo show a wedding, a man, a woman, a jacket, a dress, a beach…? In practice, this means artificially restricting the information.
However, we do not have to limit ourselves to a single label. This is precisely why multi-label classification is used: it allows many categories, describing different objects, to be assigned to a single image. So, instead of choosing one label, we can say: "This photo contains instances of the classes: person, jacket, shirt, tie, dress, beach, etc."
Task
Your task is to define and train a neural network that performs multi-label classification. In this task, the composition of the images will be simplified compared with the example above. Only clothes will be visible in the images, and your task is to create a model that can determine whether a given type of clothing appears in the image.
Data
In this task you have at your disposal
- a training set ( samples),
- a validation set ( samples).
The test set, on which your solution will finally be evaluated, has 780 samples and is not public. It was created in the same way as the validation set, so it has analogous characteristics.
Each sample is an image of size pixels. Each image is associated with a 10-element vector of values and , which indicates the presence of each class in the image. Which item of clothing corresponds to each index in the label vector is given in the LABEL_NAMES dictionary, defined in one of the code cells.
Scoring Criterion
The final scoring of the task will be based on the mean value of the measure computed under the macro scheme.
For this task you can earn between 0 and 100 points. Your final score for the solution will be calculated using the function below (the higher the value, the better), with additional rounding to integer values:
Constraints
- Your solution will be tested on the Contest Platform in an environment with a GPU. There is no Internet access on the Platform; however, you can use the pretrained ResNet models (ResNet18, ResNet34, ResNet50) from the torchvision package, which are stored locally as files. To use them, use the same command in your code as you would when Internet access is available.
- The evaluation of your final solution on the test data on the Contest Platform must not take longer than 2.5 minutes with a GPU.
Submission files
This notebook, completed with your solution (the model definition, the function that trains the model and the function that returns the model's predictions).
Evaluation
Remember that during grading the FINAL_EVALUATION_MODE flag will be set to True.
For this task you can earn between 0 and 100 points. The number of points you earn will be calculated on the (secret) test set on the Contest Platform using the formula given above, rounded to an integer. If your solution does not meet the criteria above or does not run correctly, you will receive 0 points for the task.
Starter Code
In this section we initialise the environment by importing the required libraries and functions. The code provided will make it easier for you to work with the data efficiently and to build your solution.
######################### DO NOT CHANGE THIS CELL WHEN SUBMITTING #############################
FINAL_EVALUATION_MODE: bool = False # We will set this flag to True during grading.
######################### DO NOT CHANGE THIS CELL WHEN SUBMITTING #############################
import os
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as fun
import torchvision
from torch.utils.data import DataLoader, TensorDataset
from torchvision import transforms
from tqdm import tqdm
from sklearn.metrics import f1_score
######################### DO NOT CHANGE THIS CELL WHEN SUBMITTING #############################
def seed_everything(seed: int):
"""Sets the seed for reproducibility of results in Python, NumPy and PyTorch."""
os.environ["PYTHONHASHSEED"] = str(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
######################### DO NOT CHANGE THIS CELL WHEN SUBMITTING #############################
# number of classes to classify
N_CLASSES: int = 10
# mapping of the class index to its name
LABEL_NAMES: dict[int, str] = {
0: "T-shirt/top",
1: "Trouser",
2: "Pullover",
3: "Dress",
4: "Coat",
5: "Sandal",
6: "Shirt",
7: "Sneaker",
8: "Bag",
9: "Ankle boot"
}
######################### DO NOT CHANGE THIS CELL WHEN SUBMITTING #############################
if not FINAL_EVALUATION_MODE:
FILES: list[str] = [
"runway-mnist/train-x.npz",
"runway-mnist/train-y.npz",
"runway-mnist/val-x.npz",
"runway-mnist/val-y.npz"
]
# download the data again if anything is missing
if not all(os.path.exists(file) for file in FILES):
import gzip
import tarfile
import shutil
if not os.path.exists("runway-mnist"):
os.mkdir("runway-mnist")
COMPRESSED_ARCHIVE = "runway-mnist.tar.gz"
TAR_ARCHIVE = COMPRESSED_ARCHIVE.rstrip(".gz")
DOWNLOAD_URL = "https://drive.google.com/uc?id=1oNAFYdJyCVe3Po90KLUPxAG9HGuL7NSw"
try:
import gdown
except ImportError as err:
raise RuntimeError("To download the dataset, you need a local installation of the gdown package: `pip install gdown`") from err
gdown.download(DOWNLOAD_URL, str(COMPRESSED_ARCHIVE), quiet=False)
with gzip.open(COMPRESSED_ARCHIVE, "rb") as compressed:
with open(TAR_ARCHIVE, "wb") as archive:
shutil.copyfileobj(compressed, archive)
os.remove(COMPRESSED_ARCHIVE)
print(f"Decompressed: {TAR_ARCHIVE}")
with tarfile.open(TAR_ARCHIVE, "r") as tar:
tar.extractall("runway-mnist")
os.remove(TAR_ARCHIVE)
print(f"Extracted: {TAR_ARCHIVE}")
Loading the data
######################### DO NOT CHANGE THIS CELL WHEN SUBMITTING #############################
SEED: int = 42
seed_everything(SEED)
def load_x(usage) -> torch.Tensor:
path = f"runway-mnist/{usage}-x.npz"
return torch.tensor(np.load(path)["images"], dtype=torch.float32).unsqueeze(1)
def load_y(usage) -> torch.Tensor:
path = f"runway-mnist/{usage}-y.npz"
return torch.tensor(np.load(path)["labels"], dtype=torch.long)
train_dataset = TensorDataset(load_x("train"), load_y("train"))
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
val_dataset = TensorDataset(load_x("val"), load_y("val"))
val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False)
######################### DO NOT CHANGE THIS CELL WHEN SUBMITTING #############################
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
Scoring function
######################### DO NOT CHANGE THIS CELL WHEN SUBMITTING #############################
def compute_score(f1: float) -> int:
"""Computes the score in points from the value of the F1 metric."""
lower_bound = 0.57
upper_bound = 0.87
if f1 <= lower_bound:
return 0
elif lower_bound < f1 < upper_bound:
return int(round(100 * (f1 - lower_bound) / (upper_bound - lower_bound)))
else:
return 100
def evaluate_algorithm(model, predict, loader) -> float:
"""Computes the metrics and scores your solution on their basis. Returns the computed value of the F1 metric."""
preds = []
labels = []
model.eval()
with torch.no_grad():
for x, y in loader:
prediction = predict(model, x.to(device)).cpu()
preds.append(prediction)
labels.append(y)
predictions = torch.cat(preds)
labels = torch.cat(labels)
f1 = f1_score(labels.numpy(), predictions.numpy(), average="macro")
points = compute_score(f1)
print(f"Your F1 score: {f1:.3f}, which gives {points} points.")
return f1
Example solution
Below we present a simplified solution that demonstrates the basic functionality of the notebook. It can serve as a starting point for developing your solution.
######################### DO NOT CHANGE THIS CELL WHEN SUBMITTING #############################
class NaiveSolution(nn.Module):
"""Naive solution."""
def __init__(self):
super().__init__()
def forward(self, input: torch.Tensor) -> torch.Tensor:
"""This naive model predicts that all classes are present in the image."""
BATCH_SIZE = input.size(0)
return torch.Tensor([1] * BATCH_SIZE * N_CLASSES).reshape(BATCH_SIZE, -1)
def train_naive(_: NaiveSolution):
"""The model does not require training."""
pass
def predict_naive(model: NaiveSolution, input: torch.Tensor) -> torch.Tensor:
"""Our model returns the model's predictions directly; we do not process them further."""
return model(input).to(torch.long)
######################### DO NOT CHANGE THIS CELL WHEN SUBMITTING #############################
if not FINAL_EVALUATION_MODE:
naive_solution = NaiveSolution().to(device)
naive_solution.train()
train_naive(naive_solution)
evaluate_algorithm(naive_solution, predict_naive, val_loader)
Your solution
Place your solution in the cell below. Make changes only here!
class Solution(nn.Module):
"""Your solution."""
def __init__(self):
super().__init__()
def forward(self, input: torch.Tensor) -> torch.Tensor:
"""Model inference"""
BATCH_SIZE = input.size(0)
return torch.rand(BATCH_SIZE * N_CLASSES).reshape(BATCH_SIZE, -1)
def train_solution(_: Solution):
"""Training loop for your model."""
pass
def predict_solution(model: Solution, input: torch.Tensor) -> torch.Tensor:
"""Classification using the model.
This function is kept separate to make it easy to post-process the model's outputs."""
predictions = model(input).round().to(torch.long)
return predictions
######################### DO NOT CHANGE THIS CELL WHEN SUBMITTING ############################
solution = Solution().to(device)
solution.train()
train_solution(solution)
Evaluation
The code below will be used to evaluate the solution. After you send us your solution, the function evaluate_algorithm(solution, predict_solution) will be executed, i.e. code almost identical to the code below will be run on the test set, which is available only to the graders.
Before submitting, make sure that the whole notebook runs from start to finish without errors and without user intervention after executing the Run All command.
######################### DO NOT CHANGE THIS CELL WHEN SUBMITTING ############################
if not FINAL_EVALUATION_MODE:
evaluate_algorithm(solution, predict_solution, val_loader)
Translated by SOTA. The Polish original is the official version and wins wherever the two differ. 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
- runway-mnist.tar.gz (train-x, train-y, val-x, val-y .npz files), downloaded from Google Drive.
- You submit
- This notebook with the model definition, training function and prediction function.
- Scoring
- Macro-averaged F1. Score = 0 if F1 ≤ 0.57, 100 × (F1 − 0.57)/(0.87 − 0.57) if 0.57 < F1 < 0.87, 100 if F1 ≥ 0.87; rounded.
- Rules
- Tested with a GPU and without Internet access; pre-trained torchvision ResNet18, ResNet34 and ResNet50 weights are available offline and are loaded with the usual command.
- Evaluation on the test data must take at most 2.5 minutes with a GPU.
- Format
- Stage I (online), 1 December 2025 – 25 January 2026; up to 100 points per task (500 in total; the qualification threshold for Stage II was 350 points). Tasks are ordered by intended increasing difficulty. 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.