Осталось менее трех дней до окончания конкурса «Оценка производительности». Возможно, данная статья кому-то поможет улучшить свое решение. Суть задачи — предсказать время умножения двух матриц на разных вычислительных системах. В качестве оценки качества предсказания берется наименьшая средняя относительная ошибка MAPE.
На текущий момент первое место — 4.68%. Ниже хочу описать свой путь к 6.69% (а это уже 70+ место).
Итак, у нас имеются данные для обучения в виде таблицы c 951 колонкой. Такое огромное количество признаков даже нет смысла начинать анализировать «вручную». Поэтому попробуем применить какой-нибудь стандартный алгоритм «не глядя», но с небольшой подготовкой данных:
Данные манипуляции дают mape = 11.22%. А это 154 из 362 место. Т.е. лучше, чем половина участников.
Для применения линейных алгоритмов необходимо масштабировать признаки. Кроме этого иногда помогает добавление новых признаков на основании уже имеющихся. Например, с помощью PolynomialFeatures. Поскольку вычислить полином для всех 951 признака крайне ресурсоёмко, то разобьём все признаки на две части:
И будем вычислять полином только на матричных признаках. Кроме этого, вектор откликов (y) перед обучением прологарифмируем, а при расчете ответов вернем прежний масштаб.
Пару нехитрых манипуляций уже дают mape = 6.91% (место 80 из 362). Стоит обратить внимание, что модель RidgeCV() вызывается со стандартными параметрами. В теории её можно еще потюнинговать.
Самый лучший результат mape = 6.69% (72/362) дало «ручное» добавление признаков. Добавил три признака m*n, m*k, k*n.
Така же добавил отношение максимального измерения матрицы к минимальному измерению для обоих матриц.
Признаюсь, что матрицы умею умножать только с помощью Википедии. А методы Штрассена и алгоритмы Виноградова, указанные в описании к заданию, для меня что-то нереальное к освоению. Для меня это первое участие в соревновании по машинному обучению. И чувство собственной гордости повышает тот факт, что полученный результат неплохо смотрится на фоне работы, на которую ссылаются авторы конкурса — А. А. Сиднева, В. П. Гергеля «Автоматический выбор наиболее эффективных реализаций алгоритмов».
На текущий момент первое место — 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')
Послесловие
Признаюсь, что матрицы умею умножать только с помощью Википедии. А методы Штрассена и алгоритмы Виноградова, указанные в описании к заданию, для меня что-то нереальное к освоению. Для меня это первое участие в соревновании по машинному обучению. И чувство собственной гордости повышает тот факт, что полученный результат неплохо смотрится на фоне работы, на которую ссылаются авторы конкурса — А. А. Сиднева, В. П. Гергеля «Автоматический выбор наиболее эффективных реализаций алгоритмов».