Discord

Checklist OAI 2026 Stage II · Task 3

Decision Trees

Polish title: Drzewa decyzyjne

Automatically preprocess two-dimensional datasets and choose decision-tree hyperparameters so that the tree beats the organisers' baseline accuracy on as many of 100 hidden datasets as possible.

  • Tabular
  • AutoML (preprocessing and hyperparameter selection)
  • Polish original · English translation

The task

A decision tree splits feature space into rectangular regions using thresholds on individual features; its effectiveness depends on whether the classes are separable, on the noise, and on hyperparameters such as depth or the minimum number of samples in a leaf.

The contestant writes an algorithm that, from a training set alone, automatically preprocesses the data and selects decision-tree hyperparameters: preprocess_data(X_train, X_test) returns transformed matrices of the same shapes, and get_decision_tree_hyperparameters(X_train, y_train) returns a dictionary of hyperparameters (for example max_depth, min_samples_split, min_samples_leaf, max_features).

Collection A (100 datasets) is provided: each has a training matrix n_train × 2 with 0/1 labels and a test matrix n_test × 2, with test labels and the organisers' baseline accuracies in a separate file. The datasets differ in grid size, overlapping cells, noise, rotation, probabilistic labels and size. Solutions are tested on a hidden collection B with the same format and number of datasets.

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 3981 words and 6 code cells

Decision Trees

drzewo_decyzyjne.jpg

Introduction

In machine learning we like to train models. The work, however, begins much earlier: with the choice of hyperparameters and data transformations. A decision tree divides the feature space into rectangular regions using thresholds on individual features. Its effectiveness depends on, among other things, whether it is able to separate points from different classes in the data and how much noise there is — as well as on its own hyperparameters, such as the depth or the minimum number of samples in a leaf.

Task

Create an algorithm which automatically, on the basis of the training dataset, performs data preprocessing and selects the hyperparameters of a decision tree so that the final tree achieves the best possible score on the preprocessed test set. Provide the functions preprocess_data(X_train, X_test), which returns the transformed X_train and X_test with the same dimensions as the corresponding input matrices, and get_decision_tree_hyperparameters(X_train, y_train), which returns a dictionary of hyperparameters.

Data

A collection of 100 datasets named A is available, in the files:

  • A_input.npz — contains, for each dataset i (from 0 to 99):

    • X_i — the training matrix (n_train_i × 2)
    • y_i — the vector of training labels (n_train_i,), values 0 or 1
    • X_test_i — the test matrix (n_test_i × 2)
  • A_output.npz — contains:

    • y_test_i — the vector of test labels (n_test_i,), 0/1
    • baseline_acc — the vector of baseline results of our method (n_datasets,), floating-point values.

Both files contain the field n_datasets = 100.

The datasets differ from one another: they may have different grid sizes, overlapping cells, noise, rotated data, probabilistic labels, etc. They may also have different numbers of examples.

Your solution will be tested on a hidden collection B, with an identical format and number of datasets. You do not have access to it during the contest.

Scoring Criterion

For each dataset in the collection you can score 0 or 1.2 points, depending on whether the decision tree trained using your hyperparameters and data transformations achieves a higher accuracy than our solution. There are 100 datasets in total, so the maximum number of points available is 120. The final score is the number of points capped at 100.

Constraints

  • Your solution will be tested on the Contest Platform without internet access.
  • The evaluation of the final solution must not take longer than 3 minutes (without a GPU).
  • List of permitted libraries: scikit-learn, numpy, pandas.

Submission Files

This notebook, completed with your solution (see the functions get_decision_tree_hyperparameters and preprocess_data).

Evaluation

During grading, the FINAL_EVALUATION_MODE flag will be set to True. The number of points will be calculated on the secret test set on the Contest Platform. If the solution does not meet the criteria or does not run correctly, you will receive 0 points for it.

Starter Code

In this section we initialise the environment by importing the required libraries and setting the random seed.

######################### DO NOT CHANGE THIS CELL WHEN SUBMITTING ##########################
FINAL_EVALUATION_MODE = False # During grading on the grading system, the flag is automatically changed to True.
# ######################### DO NOT CHANGE THIS CELL WHEN SUBMITTING ##########################

import random
import numpy as np
from sklearn.tree import DecisionTreeClassifier

if not FINAL_EVALUATION_MODE:
    import matplotlib.pyplot as plt


seed = 42
random.seed(seed)
np.random.seed(seed)

Loading the Data

File format:

  • A_input.npz: X_0, y_0, X_test_0, ..., X_99, y_99, X_test_99, n_datasets
  • A_output.npz: y_test_0, ..., y_test_99, n_datasets, baseline_acc

Where:

  • X_i — the matrix of training features (n_train × 2, float32),
  • y_i — the training labels (n_train, int8, values 0/1),
  • X_test_i — the matrix of test features (n_test × 2, float32),
  • y_test_i — the test labels (n_test, int8).
# ######################### DO NOT CHANGE THIS CELL WHEN SUBMITTING ##########################

def load_data(prefix="A"):
    input_data = np.load(f'data/{prefix}_input.npz')
    output_data = np.load(f'data/{prefix}_output.npz')
    n = int(input_data['n_datasets'])
    print(f"\n{prefix}: {n} datasets")
    for i in range(min(n, 3)):
        X_tr = input_data[f'X_{i}']
        y_tr = input_data[f'y_{i}']
        X_te = input_data[f'X_test_{i}']
        y_te = output_data[f'y_test_{i}']
        print(f"  Dataset {i}: train={X_tr.shape}, test={X_te.shape}, "
              f"num_1s={y_tr.sum()}, num_0s={(y_tr == 0).sum()}")
    print("  ...")
    return n, input_data, output_data


n, input_data, output_data = load_data()

Data inspection

A visualisation of selected datasets from collection A. For each selected dataset we train a default decision tree and plot the training points together with the decision regions.

# ######################### DO NOT CHANGE THIS CELL WHEN SUBMITTING ##########################

def extract_regions(tree, feature_names=None, node=0, bounds=None):
    """
    Recursively extracts rectangular regions from a decision tree.
    Returns a list of (bounds, predicted_class) for each leaf.
    """
    if bounds is None:
        n_features = tree.n_features
        bounds = [(-np.inf, np.inf) for _ in range(n_features)]

    if tree.children_left[node] == tree.children_right[node]:
        return [(bounds, np.argmax(tree.value[node]))]

    feature = tree.feature[node]
    threshold = tree.threshold[node]

    regions = []

    left_bounds = bounds.copy()
    left_bounds[feature] = (left_bounds[feature][0], min(left_bounds[feature][1], threshold))
    regions.extend(extract_regions(tree, feature_names, tree.children_left[node], left_bounds))

    right_bounds = bounds.copy()
    right_bounds[feature] = (max(right_bounds[feature][0], threshold), right_bounds[feature][1])
    regions.extend(extract_regions(tree, feature_names, tree.children_right[node], right_bounds))
    return regions


def plot_dataset_with_tree(X_train, y_train, X_test, y_test, tree_params=None, ax=None, title=""):
    """Plots a dataset with the decision regions of a trained tree."""
    if tree_params is None:
        tree_params = {"max_depth": 10, "ccp_alpha": 0.01}
    if ax is None:
        _, ax = plt.subplots(1, 1, figsize=(5, 5))

    tree = DecisionTreeClassifier(**tree_params)
    tree.fit(X_train, y_train)
    acc = tree.score(X_test, y_test)

    regions = extract_regions(tree.tree_)
    colors = ['lightcoral', 'lightblue']
    for bounds, label in regions:
        (x0, x1), (y0, y1) = bounds
        if np.isinf(x0): x0 = X_train[:, 0].min() - 0.5
        if np.isinf(x1): x1 = X_train[:, 0].max() + 0.5
        if np.isinf(y0): y0 = X_train[:, 1].min() - 0.5
        if np.isinf(y1): y1 = X_train[:, 1].max() + 0.5
        ax.fill_between([x0, x1], y0, y1, color=colors[label % 2], alpha=0.25)

    ax.scatter(X_train[:, 0], X_train[:, 1], c=1-y_train, cmap='bwr', s=3, alpha=0.6)
    ax.set_title(f"{title}\nacc={acc:.3f}, nodes={tree.tree_.node_count}", fontsize=9)
    ax.set_aspect('equal')
    ax.tick_params(labelsize=6)
    return acc


if not FINAL_EVALUATION_MODE:
    # Visualisation of 10 selected datasets from collection A
    sample_ids = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
    fig, axes = plt.subplots(2, 5, figsize=(25, 10))
    for j, i in enumerate(sample_ids):
        X_tr = input_data[f'X_{i}']
        y_tr = input_data[f'y_{i}']
        X_te = input_data[f'X_test_{i}']
        y_te = output_data[f'y_test_{i}']
        ax = axes[j // 5, j % 5]
        plot_dataset_with_tree(X_tr, y_tr, X_te, y_te, ax=ax, title=f"Dataset {i}")
    plt.tight_layout()
    plt.show()

Your Solution

Place your solution in this section. Implement the functions

  • get_decision_tree_hyperparameters(X_train, X_test), which returns a dictionary with the hyperparameters of the decision tree,
  • preprocess_data(X_train, X_test), which returns the processed data. The resulting matrices must have the same dimensions as the corresponding input matrices.

Example tree hyperparameters:

  • max_depth — the maximum depth of the tree,
  • min_samples_split — the minimum number of samples required to split a node,
  • min_samples_leaf — the minimum number of samples in a leaf,
  • max_features — the number of features considered at each split.
def preprocess_data(X_train, X_test):
    """
    Data preprocessing.
    """
    # TODO: write your solution here!
    return X_train, X_test


def get_decision_tree_hyperparameters(X_train, y_train):
    """
    Selection of the decision tree hyperparameters.
    """
    # TODO: write your solution here!
    return {"max_depth": 3}

Evaluation

Running the cell below lets you check how many points your solution would score on the datasets from group A, which are available during the contest. Before submitting, make sure that the whole notebook runs from start to finish without errors and without any user intervention after selecting the "Run All" option.

During grading, the model will be scored on the hidden group B of datasets, using an evaluation function that works on a similar principle.

######################### DO NOT CHANGE THIS CELL WHEN SUBMITTING ##########################

if not FINAL_EVALUATION_MODE:
    baseline_acc = output_data['baseline_acc']
    beats_baseline = 0
    accuracies = []
    for i in range(n):
        X_train = input_data[f'X_{i}']
        y_train = input_data[f'y_{i}']
        X_test = input_data[f'X_test_{i}']
        y_test = output_data[f'y_test_{i}']
        X_train_shape = X_train.shape
        X_test_shape = X_test.shape

        X_train_proc, X_test_proc = preprocess_data(X_train, X_test)
        assert X_test_shape == X_test_proc.shape
        assert X_train_shape == X_train_proc.shape

        hyperparameters = get_decision_tree_hyperparameters(X_train_proc, y_train)

        model = DecisionTreeClassifier(**hyperparameters)
        model.fit(X_train_proc, y_train)

        accuracy = model.score(X_test_proc, y_test)
        accuracies.append(accuracy)

        if accuracy >= baseline_acc[i]:
            beats_baseline += 1

    mean_acc = np.mean(accuracies)
    print(f"\nFinal result:")
    print(f"  Analysed:           {n} datasets")
    print(f"  Mean accuracy:      {mean_acc:.4f}")
    print(f"  Beats baseline:     {beats_baseline} / {n}")
    print(f"  Number of points:   {min(beats_baseline * 1.2, 100)}")

Translated by SOTA. The Polish original is the official version and wins wherever the two differ. The data (collection A) are in the data/ folder next to the original notebook. As in the original, the 'Your Solution' section writes get_decision_tree_hyperparameters(X_train, X_test), but the function receives (X_train, y_train); the evaluation code counts a dataset as beaten when your accuracy is greater than or equal to the baseline. 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
data/A_input.npz (X_i, y_i, X_test_i, n_datasets) and data/A_output.npz (y_test_i, n_datasets, baseline_acc) in the task folder.
You submit
This notebook with get_decision_tree_hyperparameters and preprocess_data.
Scoring
For each of the 100 datasets, 1.2 points if the tree trained with the submitted preprocessing and hyperparameters achieves higher test accuracy than the organisers' solution, otherwise 0; the total (maximum 120) is capped at 100.
Rules
  • Tested without Internet access; evaluation must take at most 3 minutes (without a GPU).
  • Allowed libraries: scikit-learn, numpy, pandas.
  • Preprocessed matrices must keep the input shapes.
Format
Stage II (regional, on site in Kraków, Poznań, Warsaw and Wrocław), 13–15 March 2026; two tasks per day in 5-hour sessions. 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.

Details

Year
2026, Kraków, Poznań, Warsaw and Wrocław, Poland
Round
Stage II · Task 3
Language
Polish; English translation by SOTA
License
Not stated by the source