← Back to the problem

IOAI 2024 · Solution

Lost in Hyperspace

This page gives the expected approach. Read it once you have searched.

Formatted by IOAI Training from the original (IOAI-official/IOAI-2024, licence CC-BY-4.0). The original is authoritative.

IOAI 2024 (Burgas, Bulgaria), On-Site Round

Lost in a Hyperspace: Baseline Solution

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
import matplotlib.pyplot as plt

SCALING_WEIGHTS = [100/15, 100/8, 100/100]
data = pd.read_pickle('../training_set/ml_data_onsite_start.pickle')
for key in data.keys():
  print(key)
for key in data['X'].keys():
  print(key)
for key in data['y'].keys():
  print(key)
X_train = data['X']['train']
y_train = data['y']['train']

X_val = data['X']['val']
y_val = data['y']['val']

X_test = data['X']['live_test']
X_train.shape, y_train.shape, X_val.shape, y_val.shape, X_test.shape
def vis(arr):
  plt.figure(figsize=(8, 8))

  cnt = 1
  for z in range(5):
    for q in range(6):
      plt.subplot(5, 6, cnt)
      plt.imshow(arr[:, :, z, q], vmin=-40, vmax=40, cmap='hsv')
      plt.grid()
      plt.axis('off')
      cnt += 1
  plt.tight_layout()
vis(X_train[0])

Functions for result evaluation / writing predictions

Do not change it!

def test_solution(X_train, y_train, X_val, y_val, feature_num=0):
    assert X_train.shape[-1] <= 300, "Too many features! Should be less than 300"
    assert X_val.shape[-1] <= 300, "Too many features! Should be less than 300"

    model =  LinearRegression().fit(
        X_train,
        y_train[:, feature_num]
    )
    predictions = model.predict(X_val)
    rmse = mean_squared_error(
        predictions,
        y_val[:, feature_num]
    )**.5
    normalized_rmse = rmse * SCALING_WEIGHTS[feature_num]
    print(f"Property #{feature_num}:    raw RMSE={rmse:.6f}")
    print(f"Property #{feature_num}: scaled RMSE={normalized_rmse:.6f}")
    return round(normalized_rmse, 6)

Let's try a baseline solution

def dummy_feature_extractor(X):
    X_new = X.reshape((X.shape[0], -1)) # ravel
    X_new = X_new[:, :300] # pick first 300 features
    return X_new
 dummy_feature_extractor(X_train).shape
%%time
total_score = 0
for feature_number in range(3):
  total_score += test_solution(
      dummy_feature_extractor(X_train),
      y_train,
      dummy_feature_extractor(X_val),
      y_val,
      feature_num=feature_number
  )
  print()
total_score /= 3
print('='*16)
print(f"Total score = {total_score:.6f}")

How to prepare the answer files

def generate_predictions(X_train, y_train, X_test, feature_num=0):
    assert X_train.shape[-1] <= 300
    assert X_test.shape[-1] <= 300

    model =  LinearRegression().fit(
        X_train,
        y_train[:, feature_num]
    )
    predictions = model.predict(X_test)
    return predictions


## Generate solutions and write to the file
combined = {'ID': np.arange(X_test.shape[0])}

for feature_number in range(3):
    predictions = generate_predictions(
        dummy_feature_extractor(X_train),
        y_train,
        dummy_feature_extractor(X_test),
        feature_num=feature_number
    )

    combined[f'y{feature_number+1}'] = predictions

pd.DataFrame(combined).to_csv('predictions.csv', index=False)
# load the test dataset
loaded = pd.read_pickle("./test_set/ml_data_onsite_final_test.pickle")
X_test_final = loaded['X']['final_test']


# make final predictions
combined = {'ID': np.arange(X_test_final.shape[0])}

for feature_number in range(3):
    predictions = generate_predictions(
        dummy_feature_extractor(X_train),
        y_train,
        dummy_feature_extractor(X_test_final),
        feature_num=feature_number
    )

    combined[f'y{feature_number+1}'] = predictions

pd.DataFrame(combined).to_csv('final_predictions.csv', index=False)

Author Solution

  • Replicate the data 5 more times, swapping axes (number of permutations for three axes)

  • (Optionally) Delete duplicate columns

  • Use PCA, it helps

  • (Author's knowledge) One of the features is from the previous task, gives biggest improvement, so home-task knowledge definitely helps

Most successful teams combined some replication(=augmentation), features from home task, and PCA. Validation set should be a good indicator whether particular feature is a good idea to add.

def symmetrize_x(X_tr, y_tr):
    xxx = [
        X_tr,
        X_tr.swapaxes(1, 2),
        X_tr.swapaxes(1, 3),
        X_tr.swapaxes(2, 3),
        X_tr.swapaxes(1, 3).swapaxes(1, 2),
        X_tr.swapaxes(1, 3).swapaxes(2, 3),
    ]
    return np.concatenate(xxx), np.vstack([y_tr]*6)
def ravelize(X):
    return X.reshape((X.shape[0], -1))
def make_bg(X):
    bgs = []
    for _x in X:
        bg = _x[:,:,:,3].ravel().min() - _x[:,:,:,2].ravel().max()
        bgs.append(bg)
    bgs = np.array(bgs)
    return bgs[:, None]
X_train_symm, y_train_symm = symmetrize_x(X_train, y_train)
X_train_bg = make_bg(X_train_symm)
X_val_bg = make_bg(X_val)
from sklearn.decomposition import PCA
pca = PCA(n_components=299)
X_train_pca = pca.fit_transform(ravelize(X_train_symm))
X_val_pca = pca.transform(ravelize(X_val))
%%time
total_score = 0
for feature_number in range(3):
  total_score += test_solution(
      X_train_pca,
      y_train_symm,
      X_val_pca,
      y_val,
      feature_num=feature_number
  )
  print()
total_score /= 3
print('='*16)
print(f"Total score = {total_score:.6f}")
%%time
total_score = 0
# something close to the best possible solution
for feature_number in range(3):
  total_score += test_solution(
      np.concatenate([X_train_pca, X_train_bg], axis=-1),
      y_train_symm,
      np.concatenate([X_val_pca, X_val_bg], axis=-1),
      y_val,
      feature_num=feature_number
  )
  print()
total_score /= 3
print('='*16)
print(f"Total score = {total_score:.6f}")