- PVSM.RU - https://www.pvsm.ru -

История первого места на ML Boot Camp VI

Mail.ru уже не первый год проводит чемпионаты по машинному обучению, каждый раз задача по-своему интересна и по-своему сложна. Я участвую в соревнованиях четвертый раз, мне очень нравится платформа и организация, и именно с буткемпов начался мой путь в соревновательный machine learning, но первое место удалось занять впервые. В статье я расскажу как показать стабильный результат, не переобучившись ни на публичный лидерборд, ни на отложенные выборки, если тестовая часть существенно отлична от тренировочной части данных.

Задача

Полный текст задачи доступен по → ссылке [1]. Вкратце: есть 10 гб данных, где каждая строка содержит три json'а вида «ключ: счетчик», некая категория, некая временная метка и идентификатор пользователя. Одному пользователю может соответствовать множество записей. Требуется определить к какому классу относится пользователь, первому или второму. Метрикой качества для модели является ROC-AUC, о котором отлично написано в блоге Александра Дьяконова[1] [2].

Пример записи в файле

00000d2994b6df9239901389031acaac	5 {"809001":2,"848545":2,"565828":1,"490363":1} 
{"85789":1,"238490":1,"32285":1,"103987":1,"16507":2,"6477":1,"92797":2}	{}	39

Решение

Первая идея, которая возникает у data scientist'а, успешно скачавшего датасет — превратить json-колонки в разреженную матрицу. На этом месте многие участники испытали проблемы с недостатком оперативной памяти. При разворачивании даже одной колонки в питоне потребление памяти было выше, чем это доступно в среднем ноутбуке.

Немного сухой статистики. Количество уникальных ключей в каждой колонке: 2053602, 20275, 1057788. При этом и в трейн-части и в тест части всего лишь: 493866, 20268, 141931. 427994 уникальных пользователей в трейн и 181024 в тест части. Примерно 4% класса 1 в тренировочной части.

Как видим, признаков у нас очень много, использовать их всех это явный путь к оверфиту на трейн, потому что, например, решающие деревья используют сочетания признаков, а уникальных сочетаний такого большого количества признаков еще больше и практически все из них существуют только в тренировочной части данных или в тестовых. Тем не менее, одной из базовых моделей у меня был lightgbm с colsample ~0.1 и очень жесткой регуляризацией. Тем не менее, даже не смотря на огромные параметры регуляризации, оно показывало не стабильный результат на паблик и прайват части, как выяснилось после окончания соревнования.

Второй мыслью человека, который решил участвовать в этом соревновании наверняка было бы собрать трейн и тест, агрегируя информацию по идентификаторам. Например, суммой. Или максимумом. И тут выясняется две очень интересные вещи, которые придумал для нас Mail.ru. Во-первых, тест можно классифицировать с очень высокой точностью. Даже по статистикам о количестве записей для cuid и количестве уникальных ключей в json тест значительно превосходит трейн. Базовый классификатор давал 0.9+ roc-auc в распознавании теста. Во-вторых, счетчики не имеют практически никакого смысла, практически все модели становились лучше от перехода от счетчиков к бинарным признакам вида: есть/нет ключ. Даже деревья, которым по идее не должно быть хуже от того, что вместо единицы стоит какое-то число, похоже переобучались на счетчики.

Результаты на публичном лидерборде сильно превосходил таковые на кросс-валидации. Это было связано, видимо, с тем, что модели было проще построить ранжирование двух записей в тесте, чем в трейне, потому что большее число признаков давало больше слагаемых для ранжирования.

На этом этапе стало окончательно ясно, что валидация в этом соревновании вещь совершенно не простая и ни паблик, ни информация о CV других участников, которую можно было хитростью выманить у других участников в официальном чате[2] [3]. Что же делать? Похоже, что трейн и тест разделены по времени, что позже подтвердили организаторы.

Любой опытный участник kaggle сразу скажет Adversarial validation[3] [4], но все не так просто. Несмотря на то, что точность предсказания теста близка к 1 по метрике roc-auc, но похожих на тест записей в трейне не так много. Я пробовал суммировать агрегированные по cuid семплы с одинаковым таргетом, чтобы увеличить количество записей c большим числом уникальных ключей в json, но это давало просадки и на кросс-валидации, и на паблике, и я побоялся использовать такие модели.

Есть два пути: искать вечные ценности с помощью unsupervised learning или пробовать брать более важные именно для теста фичи. Я пошел обоими путями, используя TruncatedSVD для unsupervised и отбор фич по частоте в тесте.

Первым, шагом, впрочем, я сделал глубокий autoencoder, но ошибся, взяв два раза одну и ту же матрицу, исправить ошибку и использовать полный набор признаков не удалось: входной тензор не вмещался в память GPU ни при каком размере dense-слоя. Ошибку я нашел и в дальнейшем уже не пробовал кодировать фичи.

SVD я генерировал всеми способами на которые хватало фантазии: на оригинальном датасете с cat_feature и последующим суммированием по cuid. По каждой колонке отдельно. По tf-idf на json как bag-of-words[4] [5] (не помогало).

Для большего разнообразия я пробовал отбирать небольшое число фич в трейне, использую A-NOVA по трейн части каждого фолда на кросс-валидации.

Модели

Основные базовые модели: lightgbm, vowpal wabbit, xgboost, SGD. Кроме этого я использовал несколько архитектур нейронных сетей. Находившийся на первом месте публичного лидерборда участник Дмитрий Никитко советовал использовать HashEmbeddings [6], эта модель после некоторого подбора параметров показала неплохой результат и улучшила ансамбль.

Еще одна модель нейронной сети с поиском интеракций (factorization machine style) между 3-4-5 колонками данных (три левых входа), численными статистиками (4 вход), SVD матрицы (5 вход).

История первого места на ML Boot Camp VI - 1

Ансамбль

Все модели я считал по фолдам, усредняя предсказания теста от моделей, тренированных на розличных фолдах. Предсказания трейна использовались для стекинга. Лучший результат показал стек 1 уровня с помощью xgboost на предсказаниях базовых моделей и по 250 признаков из каждой json-колонки, отобранные по частоте, с которой встречался признак в тесте.

На решение я потратил ~30 часов своего времени, считал на сервере с 4 ядрами core-i7, 64 гигабайтами оперативной памяти, и одним GTX 1080. В итоге мое решение оказалось довольно стабильным и я поднялся с третьего места публичного лидерборда на первое приватного.

Существенная часть кода доступна на битбакете в виде ноутбуков[5] [7].

Хочу поблагодарить Mail.ru за интересные контесты и других участников за интересное общение в группе!

[1] ROC-AUC в блоге Александрова Дьяконова [2]
[2] Официальный чат ML BootCamp official [3]
[3] Adversarial validation [4]
[4] bag-of-words [5]
[5] исходный код большинства моделей [7]

Автор: sergeif

Источник [8]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/python/287705

Ссылки в тексте:

[1] ссылке: https://mlbootcamp.ru/round/14/sandbox/

[2] [1]: https://alexanderdyakonov.wordpress.com/2017/07/28/auc-roc-%D0%BF%D0%BB%D0%BE%D1%89%D0%B0%D0%B4%D1%8C-%D0%BF%D0%BE%D0%B4-%D0%BA%D1%80%D0%B8%D0%B2%D0%BE%D0%B9-%D0%BE%D1%88%D0%B8%D0%B1%D0%BE%D0%BA/

[3] [2]: https://t.me/mlbootcamp

[4] [3]: http://fastml.com/adversarial-validation-part-one

[5] [4]: https://en.wikipedia.org/wiki/Bag-of-words_model

[6] HashEmbeddings: https://github.com/Puzer/hashembedding

[7] [5]: https://bitbucket.org/sergeif/bootcampvi/src/master/

[8] Источник: https://habr.com/post/418743/?utm_campaign=418743