Checklist OAI 2026 Final (Stage III) · Task 4
SOTA-AI Editorial
Hidden Categories
Predict the set of hierarchical e-commerce category labels of each product from its 256-dimensional collaborative-filtering embedding.
Measured results use the released validation sets and the evaluator logic in each notebook. They do not establish secret-test performance.
Hints
Hint 1
Inspect path lengths and category frequencies before choosing a loss or output rule.
Hint 2
Do the embeddings support local similarity? Compare cosine neighbors.
Hint 3
Use the position in each category path as a hierarchy level.
Hint 4
How should a model decide whether to stop adding deeper categories?
The solution in one line
Use similarity-weighted cosine neighbors to vote independently at each path depth, with a learned stop rule.
The editorial
Dataset analysis and EDA
There are 12,145 training products, 2,603 validation products, 256 features, and 1,095 distinct training labels. Every training row includes the root category Sports & Outdoors. The label distribution is long-tailed: 248 labels appear once, while 40 validation labels are absent from training. Paths contain 1–7 labels, with a mean length of about 4.61; most have four or five.
The embedding norms are tightly concentrated (median about 0.543; interquartile range about 0.515–0.580), so cosine neighbors are a useful similarity measure. Category paths preserve their hierarchy by list position, which makes votes by depth more appropriate than an unstructured global threshold.
Solution strategy and experiments
Normalize training and query embeddings, retrieve the 80 highest-cosine neighbors, and weight each neighbor by similarity⁴. At each category depth, accumulate votes for every label and a STOP outcome for neighbors whose path ends before that depth. Predict the strongest category when its vote exceeds the depth-specific STOP vote multiplied by [0, 1, 1, 3, 2, 1, 1].
| Method | Validation mean IoU |
|---|---|
| Ridge multi-label scores, top three labels | 0.441 |
| Cosine k-NN votes, global top three labels | 0.472 |
| Depth-wise cosine votes, unadjusted stop decision | 0.472 |
| Depth-wise votes with calibrated stop bias | 0.483 |
The selected method reaches 0.483 mean IoU, above the notebook’s 0.46 full-credit threshold. The 40 validation-only categories are unseen classes; a train-vocabulary classifier cannot predict them.
Implementation and compute footprint
The YourSolution class uses NumPy only and fits solely on the training embeddings and labels. Inference requires a cosine-similarity matrix for the query batch and 80-neighbor votes. It runs on CPU and stays within the notebook’s five-minute limit for the provided sizes.
Other candidates include one-vs-rest ridge/logistic models, global label thresholds, and voting for complete paths. Ridge underperformed the depth-wise neighbor approach; complete-path voting discarded useful agreement at individual hierarchy levels.
Code
Solution code
import numpy as np
######################### TUTAJ JEST MIEJSCE NA TWOJE FINALNE ROZWIĄZANIE ##########################
class YourSolution:
"""Cosine-neighbor multilabel classifier that respects taxonomy depth."""
def __init__(self):
self.n_neighbors = 80
self.similarity_power = 4
# A depth-specific STOP bias, tuned on the released validation set.
self.stop_bias = np.array([0, 1, 1, 3, 2, 1, 1], dtype=np.float32)
def fit(self, X, y):
X = np.asarray(X, dtype=np.float32)
self.X_train = X / (np.linalg.norm(X, axis=1, keepdims=True) + 1e-12)
self.paths = [list(path) for path in y]
self.max_depth = max((len(path) for path in self.paths), default=0)
self.classes_by_depth = []
self.train_codes_by_depth = []
for depth in range(self.max_depth):
classes = sorted({path[depth] for path in self.paths if len(path) > depth})
class_to_code = {label: index for index, label in enumerate(classes)}
stop_code = len(classes)
codes = np.full(len(self.paths), stop_code, dtype=np.int32)
for row_index, path in enumerate(self.paths):
if len(path) > depth:
codes[row_index] = class_to_code[path[depth]]
self.classes_by_depth.append(classes)
self.train_codes_by_depth.append(codes)
return self
def predict(self, X):
X = np.asarray(X, dtype=np.float32)
if X.ndim == 1:
X = X.reshape(1, -1)
X = X / (np.linalg.norm(X, axis=1, keepdims=True) + 1e-12)
similarities = X @ self.X_train.T
k = min(self.n_neighbors, len(self.X_train))
neighbor_indices = np.argpartition(similarities, -k, axis=1)[:, -k:]
neighbor_similarities = np.take_along_axis(similarities, neighbor_indices, axis=1)
order = np.argsort(neighbor_similarities, axis=1)[:, ::-1]
neighbor_indices = np.take_along_axis(neighbor_indices, order, axis=1)
neighbor_similarities = np.take_along_axis(neighbor_similarities, order, axis=1)
weights = np.maximum(neighbor_similarities, 0.0) ** self.similarity_power
predictions = [[] for _ in range(len(X))]
row_indices = np.arange(len(X))
for depth, classes in enumerate(self.classes_by_depth):
votes = np.zeros((len(X), len(classes) + 1), dtype=np.float32)
train_codes = self.train_codes_by_depth[depth]
for rank in range(k):
codes = train_codes[neighbor_indices[:, rank]]
votes[row_indices, codes] += weights[:, rank]
best_codes = votes[:, :-1].argmax(axis=1)
best_votes = votes[row_indices, best_codes]
stop_votes = votes[:, -1]
bias = self.stop_bias[depth] if depth < len(self.stop_bias) else 1.0
use_category = best_votes > bias * stop_votes
for row_index in np.flatnonzero(use_category):
label = classes[best_codes[row_index]]
if label not in predictions[row_index]:
predictions[row_index].append(label)
return predictions
Written by Gonçalo Franco; added here on 4 October 2026. Editorials are written by members of the SOTA community, not by the olympiad. The official statement and scoring win wherever an editorial differs.