Решение задачи «Оценка производительности» mlbootcamp.ru

в 15:54, , рубрики: python, sklearn, машинное обучение, регрессия, соревнование

Осталось менее трех дней до окончания конкурса «Оценка производительности». Возможно, данная статья кому-то поможет улучшить свое решение. Суть задачи — предсказать время умножения двух матриц на разных вычислительных системах. В качестве оценки качества предсказания берется наименьшая средняя относительная ошибка 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')

Послесловие

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

Автор: Belov

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js