В субботу завершился месячный конкурс по машинному обучению от mail.ru ML bootcamp 5. я занял в нем 14ое место. Это уже третий мой конкурс, в котором я выиграл одежду и за время участия у меня сформировался фреймворк (который я, недолго думая, назвал QML, сокращение от ника и machine learning) для помощи в подборе решения в подобных соревнованиях. На примере решения ML bootcamp 5 я опишу как им пользоваться.
Как полагается, сперва покажу товар лицом :)
- Сохранение промежуточных результатов вычисления моделей для дальнейшего использования в метамоделях (в т.ч. результатов кроссвалидаций)
- Модели для различных усреднений и стэкинга
- Вспомогательные скрипты для отбора признаков
Итак, начнем решать конкурс (целиком скрипты решения можно посмотреть здесь).
Для начала склонируем репозиторий фреймворка, установим недостающие пакеты описанные в файле install и укажем настройки в config.py:
Далее создадим нужные таблицы в БД из файла со схемой qml/db/schema.sql
, создадим файлы с id train и test set (qml_workdir/data/ids_test.csv
, qml_workdir/data/ids_train.csv
) и файлы с первой версией данных (qml_workdir/data/v0001_test_x.csv
, qml_workdir/data/v0001_train_x.csv
, qml_workdir/data/train_y.csv
). Как это сделать оставлю за рамками этой статьи.
Теперь создадим файл work.py, в котором запишем код ниже и запустим его
# imports
cv = QCV(qm)
print(cv.cross_val(1, 1))
qm.qpredict(1, 1)
1 0.545776154551 2 0.545405471315 3 0.543444368229 4 0.539330473549 5 0.537107693689 0.542212832267
Тремя строчками кода мы:
- провели кроссвалидацию по 5 фолдам (хардкод?) модели №1 из файла models.py
- зафиксировали разбиение на фолды, чтобы в дальнейшем было удобно сравнивать результаты
- увидели результат каждого из фолдов и усредненный результат по фолдам
- сохранили результаты кроссвалидации в БД (см таблички
qml_results
иqml_results_statistic
) - получили файл с предсказаниями для test set (
qml_workdir/data/res/m0000001_d001__tr_70000_ts_30000__h_29c96aaed585f02e30096f265faca72c_s_1000.csv
)
Удалим колонку с id и заголовок и отошлем на сайт конкурса. Результат будет примерно таким:
С таким результатом на public board мы были бы на 497 месте, а на private board на 484 месте.
Ну что ж, маловато. Самое время взглянуть на данные.
Видим, что в полях weight
, height
, ap_hi
и ap_lo
много непонятных данных, чистим их, добавляем индекс массы тела и классы ИМТ из таблички.
В test set некоторые субъективные признаки были изъяты, посмотрев на распределение значений этих колонок заменим NaN
в active на 1, в smoke и alco на 0. Итого у нас уже есть 6ая версия данных.
Кстати, пусть вас не удивляет такая активность у людей такого возраста, для того, чтобы во время диспансеризации вас записали в активные, нужно ходить всего лишь больше получаса в день.
# imports
cv = QCV(qm)
print(cv.cross_val(1, 6))
qm.qpredict(1, 6)
1 0.545776154551 2 0.545405471315 3 0.543444368229 4 0.539330473549 5 0.537107693689 0.542212832267
Результат:
477 на public board и 470 на private, С — стабильность :) Но нам стабильности мало, в это жаркое лето нужно ходить в клевых черных футболках.
Оба раза мы прогоняли данные через xgboost с параметрами, поставленными наугад. Подберем оптимальные параметры модели с помощью hyperopt.
Пример подбора параметров. Из интересного в скрипте:
DATAS = [6]
— можно тюнить сразу на нескольких версиях данных- Параметр
early_stop_cv
останавливает кроссвалидацию с текущими параметрами и возвращает результат текущего фолда, если callback вернетTrue
, удобно вручную отсортировать фолды в сохраненных разбиениях, чтобы первым шел фолд с наибольшим CV score. Тогда можно отсеивать неудачные модели не дожидаясь валидации по всем фолдам num_boost_rounds
иeta
перебираем с шагом в 1.3model_id = qm.add_by_params(..
добавляем в базу модель с такими параметрами, чтобы ее в дальнейшем можно было загрузить по номеруtree_method='hist'
— для хейтеров продуктов Microsoftseed=1000
— прогоняем каждую модель по 8 сидам, чтобы выявить стабильность- результаты предсказаний каждого фолда из разбиений для каждого сида сохраняются, что позже нам пригодится для метамоделей
Оставив на ночь подбор параметров утром можно посмотреть, что у нас натюнилось с помощью
select
data_id,
model_id,
max(cv_diff),
max(std),
sum(sum_sc)/sum(cnt),
sum(cnt),
r.cv_time,
m.cls,
m.level,
m.params
from
(
select
model_id,
fold,
data_id,
sum(cv_score) as sum_sc,
std(cv_score) as std,
max(cv_score)-min(cv_score) as cv_diff,
count(*) as cnt
from
qml_results_statistic
group by
model_id,
data_id,
fold
) q
left join qml_results r using (model_id, data_id)
left join qml_models m using (model_id)
group by
data_id,
model_id
order by
sum(sum_sc)/sum(cnt)
limit 10000;
Примерный результат:
Видим, что модель 1255 лучше по CV, но и разброс одного фолда среди 8 сидов больше. Дальше для проверки добавления признаков будем использовать модель 1395, как наиболее стабильную из найденных.
Т.к. CV score заметно улучшился, попробуем отправить:
О, 161 место на public leaderboard и 164 на private! Лучше, чем Лев Литроводочкин, который был на паблике седьмым, Владимир Омельченко (десятый) и Андей Парков (четырнадцатый).
Самое время обогнать Валерия Бабушкина!
Создадим двадцатую версию данных, в которой добавим колонки с поочередным сложением, вычитанием, умножением и делением комбинаций из двух и трех из имеющихся колонок.
Если прогнать нашу модель на новых данных, то результат будет хуже, чем без них (Если бы я об этом знал во время bootcamp 3..). необходимо добавлять новые колонки по одной, запускать кроссвалидацию и оставлять, только если результат улучшился.
Скормим получившиеся данные подобному скрипту, указав третьим параметром имеющиеся колонки, четвертым колонки, которые нужно проверить.
Из интересного:
QAvgOneModelData(model_id, 8)
— прогоняем кроссвалидацию не исходной модели, а усреднения предсказаний исходной модели с 8ю разными сидами для большей стабильностиearly_stop_cv
— опять не гоняем все разбиенияlog_file='qml_workdir/logs/feat12.txt'
— результат работы пишем в лог файл, отсортировав который можно легко получить лучшую колонку текущего прогона.
В результате выяснили, что добавив колонки ниже (47 версия данных), CV улучшается:
x__age__gluc_all
x__ap_hi__cholesterol_all
div6__height__gluc_all__imt
—1/height/gluc_all*imt
plus__age_norm__ap_hi_norm__gluc_all_norm
x__age__weight
div1__age__weight__cholesterol_all
—age*weight/cholesterol_all
div6__age__weight__cholesterol_all
—1/age/weight*cholesterol_all
plus__height_norm__weight_norm__gluc_all_norm
div1__ap_hi__ap_lo__cholesterol_all
—ap_hi*ap_lo/cholesterol_all
div6__ap_hi__ap_lo__cholesterol_all
—1/ap_hi/ap_lo*cholesterol_all
plus__age_norm__gluc_all_norm__imt_norm
minus6__ap_hi_norm__ap_lo_norm__cholesterol_all_norm
minus1__ap_hi_norm__ap_lo_norm__cholesterol_all_norm
minus6__age_norm__ap_lo_norm__cholesterol_all_norm
minus1__age_norm__ap_lo_norm__cholesterol_all_norm
div6__height__weight__ap_lo
—1/height/weight*ap_lo
div2__ap_lo__cholesterol_all__gluc_all
x__age__ap_hi__gluc_all
div5__ap_lo__cholesterol_all__gluc_all
Хорошо, хоть в этом списке не оказалось колонки imt * height * height.
Прогоним нашу модель на новых данных:
Ооо, 40 место на public и 81 на private. Валерий далеко позади, но впереди маячит нефтяные гипнотизеры чата. Нужно догонять.
Садимся устраивать мозговой штурм:
- Берем KMeans из настольной книги
- Изучаем предметку и находим шкалу SCORE
- Гуглим, как еще можно генерировать фичи, находим, как люди это делают в экселе
Реализуем все это (66 версия данных) и еще раз прогоняем тюнинг модели на итоговых данных.
{"alpha": 0.008, "booster": "gbtree", "colsample_bylevel": 0.6, "colsample_bytree": 1.0, "eta": 0.004, "eval_metric": "logloss", "gamma": 0.2, "max_depth": 4, "num_boost_round": 2015, "objective": "binary:logistic", "subsample": 0.7, "tree_method": "hist"}
После этого поочередно выкидываем по одной колонке и выясняем, что без колонки div6__height__weight__ap_lo
CV score лучше и нещадно ее выкидываем (69 версия).
Прогоним новую лучшую одиночную модель на исходных данных:
Public — 26, private — 50. Ура! Гипнотизеров обошли, футболка наша!
Вроде бы пора остановиться, но хочется большего. Отправим усреднение по 8 сидам этой модели:
Public — 21 место (эти усреднения были выбраны в качестве одного из решений, можно найти под №0109872 из дампа), private — 34 место. Гипнотизеров обошли и на паблике.
Хорошо, но мало. Нагенерируем различных моделей и еще чуть-чуть. Для некоторых моделей выполнена нормализация и заполнение NaN.
Усредняем и стэкаем лучшие модели на различных данных. После этого еще раз усредняем лучшие модели второго уровня.
Из интересного:
- Т.к. результаты кроссвалидации моделей сохраняются, то просчет усреднений происходит
мгновенно - Первый просчет стэкинга для модели занимает какое-то время, дальше сохраненные результаты переиспользуются
- Kaggle Ensembling Guide
- Cтекинг (Stacking) и блендинг (Blending)
- Изначально я в качестве моделей второго уровня брал xgboost и LogisticRegression, но результата это не дало. Потом я вспомнил, что Валерий Бабушкин в чате упоминал гребневую регрессию, когда рассказывал про мерседес, погуглил, нашел статью из пункта выше, стал ее использовать в качестве модели второго уровня и все получилось. А я его обошел одиночным xgboost. Нехорошо получилось.
Моя вторая итоговая модель — это усреднение 7 стэкингов на разном количестве моделей с разными данными. Если интересно расковарять, то это модель №109639 из дампа
На public board эта модель давала результат хуже, чем просто усреднение по 8 сидам различных одиночных xgboost, но я верил кроссвалидации и выбрал ее. Из усреднений xgboost я выбрал такое усреднение, чтобы результаты CV на фолдах отличались от стэкинговой модели, т.е. на некоторых фолдах стэкинговая модель была заметно лучше (-0.0001), на других — заметно хуже
Да уж, заголовок получился желтым, получилось не так легко как у некоторых победителей:
1 Никита Чуркин
2 Рим Шаяхметов
3 Иван Филонов
4 Александр Я-МОРТИДО Киселев
9 Иван Тямгин (осторожно, Р-р-р)
kirikch
Да, статья любопытная.
Но из-за QML в заголовке я ожидал увидеть нечто другое.
quantum
Да, надо было подольше повыбирать название :) Сейчас это производное от ника и machine learning
ilnuribat
я тоже сюда зашел задавать вопросы по Qt/QML