Осталось менее трех дней до окончания конкурса «Оценка производительности». Возможно, данная статья кому-то поможет улучшить свое решение. Суть задачи — предсказать время умножения двух матриц на разных вычислительных системах. В качестве оценки качества предсказания берется наименьшая средняя относительная ошибка MAPE.

На текущий момент первое место — 4.68%. Ниже хочу описать свой путь к 6.69% (а это уже 70+ место).

Итак, у нас имеются данные для обучения в виде таблицы c 951 колонкой. Такое огромное количество признаков даже нет смысла начинать анализировать «вручную». Поэтому попробуем применить какой-нибудь стандартный алгоритм «не глядя», но с небольшой подготовкой данных:

Попытка №1


  • Пропуски есть только в memFreq, около 11%. Заменим пропуски на среднее значение;
  • Удалим неинформативные колонки (в которых одно значение признака);
  • Применим ExtraTreesRegressor.

Данные манипуляции дают mape = 11.22%. А это 154 из 362 место. Т.е. лучше, чем половина участников.

Попытка №2


Для применения линейных алгоритмов необходимо масштабировать признаки. Кроме этого иногда помогает добавление новых признаков на основании уже имеющихся. Например, с помощью PolynomialFeatures. Поскольку вычислить полином для всех 951 признака крайне ресурсоёмко, то разобьём все признаки на две части:
  • связанные с производительностью;
  • связанные с самой матрицей (m k n);

И будем вычислять полином только на матричных признаках. Кроме этого, вектор откликов (y) перед обучением прологарифмируем, а при расчете ответов вернем прежний масштаб.

Пару нехитрых манипуляций уже дают mape = 6.91% (место 80 из 362). Стоит обратить внимание, что модель RidgeCV() вызывается со стандартными параметрами. В теории её можно еще потюнинговать.

Попытка №3


Самый лучший результат mape = 6.69% (72/362) дало «ручное» добавление признаков. Добавил три признака m*n, m*k, k*n.
Така же добавил отношение максимального измерения матрицы к минимальному измерению для обоих матриц.

Код для воспроизведения результата
import numpy as np
import pandas as pd
from sklearn import linear_model

def write_answer(data, str_add=''):
     with open("answer"+str(str_add)+".txt", "w") as fout:
        fout.write('\n'.join(map(str, data)))
        
def convert_cat(inf,inf_data):
    return inf_data[inf_data == inf].index[0]
X = pd.read_csv('x_train.csv')
y = pd.read_csv('y_train.csv')
X_check = pd.read_csv('x_test.csv')

#Преобразуем memFreq в число. Пустые значения заполним средним
X.memFreq = pd.to_numeric(X.memFreq, errors = 'coerce')
mean_memFreq = 525.576
X.fillna(value = mean_memFreq, inplace=True)

X_check.memFreq = pd.to_numeric(X_check.memFreq, errors = 'coerce')
X_check.fillna(value = mean_memFreq, inplace=True)

# удаляем неинформативные колонки
for c in X.columns:
    if len(np.unique(X_check[c])) == 1:
        X.drop(c, axis=1, inplace=True)
        X_check.drop(c, axis=1, inplace=True)

# Преобразуем категориальные признаки
cpuArch_ = pd.Series(np.unique(X.cpuArch))
X.cpuArch = X.cpuArch.apply(lambda x: convert_cat(x,cpuArch_))
X_check.cpuArch = X_check.cpuArch.apply(lambda x: convert_cat(x,cpuArch_))

memType_ = pd.Series(np.unique(X.memType))
X.memType = X.memType.apply(lambda x: convert_cat(x,memType_))
X_check.memType = X_check.memType.apply(lambda x: convert_cat(x,memType_))

memtRFC_ = pd.Series(np.unique(X.memtRFC))
X.memtRFC = X.memtRFC.apply(lambda x: convert_cat(x,memtRFC_))
X_check.memtRFC = X_check.memtRFC.apply(lambda x: convert_cat(x,memtRFC_))

os_ = pd.Series(np.unique(X.os))
X.os = X.os.apply(lambda x: convert_cat(x,os_))
X_check.os = X_check.os.apply(lambda x: convert_cat(x,os_))

cpuFull_ = pd.Series(np.unique(X.cpuFull))
X.cpuFull = X.cpuFull.apply(lambda x: convert_cat(x,cpuFull_))
X_check.cpuFull = X_check.cpuFull.apply(lambda x: convert_cat(x,cpuFull_))

# Признаки связанные с производительностью
perf_features = X.columns[3:]

# Признаки матриц
X['log_mn'] = np.log(X.m * X.n)
X['log_mk'] = np.log(np.int64(X.m*X.k)) 
X['log_kn'] = np.log(np.int64(X.k*X.n))

X['min_max_a'] = np.float64(X.loc[:, ['m', 'k']].max(axis=1)) / X.loc[:, ['m', 'k']].min(axis=1)
X['min_max_b'] = np.float64(X.loc[:, ['n', 'k']].max(axis=1)) / X.loc[:, ['n', 'k']].min(axis=1)

X_check['log_mn'] = np.log(X_check.m * X_check.n)
X_check['log_mk'] = np.log(np.int64(X_check.m*X_check.k)) 
X_check['log_kn'] = np.log(np.int64(X_check.k*X_check.n))

X_check['min_max_a'] = np.float64(X_check.loc[:, ['m', 'k']].max(axis=1)) / X_check.loc[:, ['m', 'k']].min(axis=1)
X_check['min_max_b'] = np.float64(X_check.loc[:, ['n', 'k']].max(axis=1)) / X_check.loc[:, ['n', 'k']].min(axis=1)

model = linear_model.RidgeCV(cv=5)
model.fit(X, np.log(y))
y_answer = np.exp(model.predict(X_check))
write_answer(y_answer.reshape(4947), '_habr_RidgeCV')



Послесловие


Признаюсь, что матрицы умею умножать только с помощью Википедии. А методы Штрассена и алгоритмы Виноградова, указанные в описании к заданию, для меня что-то нереальное к освоению. Для меня это первое участие в соревновании по машинному обучению. И чувство собственной гордости повышает тот факт, что полученный результат неплохо смотрится на фоне работы, на которую ссылаются авторы конкурса — А. А. Сиднева, В. П. Гергеля «Автоматический выбор наиболее эффективных реализаций алгоритмов».
Поделиться с друзьями
-->

Комментарии (5)


  1. Koncopd
    19.07.2016 02:08
    +1

    Разбиение датасета на 89 категорий (по признаку ос+процессор+размер памяти) и построение взвешенной по признаку time lad регрессии дало ошибку в 5.4% и 21 место. Знаю, что человек на третьем месте также использовал этот вариант, но с несколькими дополнениями.


    1. Belov
      19.07.2016 04:29

      Это отлично. Для меня это был первый опыт. Узнал много нового.
      Но больше удивило, как люди получали более 15-20%. Когда варианты «в лоб» давали лучший результат.


    1. polar_winter
      19.07.2016 10:51

      Почти тоже самое: разбиение датасета на категории(по признаку ос+процессор+размер памяти), логарифмизация времени и сложности (m*n*k), RANSAC (от времени и сложности) — 5.8% 58 место


  1. kenoma
    19.07.2016 07:46

    Ради интереса решил поучаствовать, чтобы испытать xgboost на деле. Сходу, после предобработки данных выхлоп составил ~10%, после подгонки параметров модели ~8.8%. Ну а дальше следовало строить стекованные модели, но внятных результатов получить не успел.


  1. makiiim
    19.07.2016 11:45

    я использовал идею описанную в А. А. Сиднева, В. П. Гергеля «Автоматический выбор наиболее эффективных реализаций алгоритмов», с некоторыми модификациями, получил итоговые 5.7% и 44 место