В этой статье описывается решение, предложенное командой POLINKS, занявшей шестое место в RecSys Challenge 2020. Этот вызов является одним из самых важных соревнований в области рекомендательных систем. В этом году конкурс был организован Twitter, который предоставляет действительно большой набор данных с почти 80 миллионами уникальных твитов.

Команду поддержали FITEC srl, LINKS Foundation и Politecnico di Torino.

Цель челленджа: предсказать помолвку

Твиттер стремится предоставлять всегда привлекательный контент, адаптированный к предпочтениям пользователей. Для этого они должны постоянно улучшать свои рекомендательные механизмы, и это то, что пытаются делать команды, участвующие в конкурсе.

Давайте посмотрим на пример, чтобы было понятнее.

В типичном взаимодействии в Твиттере у нас есть два типа действующих лиц:

  • Автор твита
  • Пользователь читает твит

Автор пишет твит, который, в зависимости от его популярности, может увидеть широкий круг пользователей. Каждый заинтересованный пользователь может выполнять разные действия:

  • Ответить: написать комментарий к твиту.
  • Ретвитнуть: поделиться содержимым твита.
  • Ретвитнуть с комментарием: поделиться контентом вместе с личным комментарием.
  • Нравится: очень известное действие.

Цель Recsys Challenge 2020 — обеспечить вероятность позитивного взаимодействия для каждого вида действия.

Метрики оценки

Результаты в общедоступной таблице лидеров оцениваются с помощью двух разных показателей:

  • PRAUC (площадь под кривой точности-отзыва)
  • RCE (относительная перекрестная энтропия): это показатель, немного отличающийся от классической перекрестной энтропии. Это просто перекрестная энтропия, деленная на CTR (коэффициент кликабельности).

Давайте посмотрим на пример CTR, чтобы немного прояснить этот показатель.

Он просто представляет собой количество положительных действий, деленное на общее количество взаимодействий.

Первый подход: повышение градиента

Довольно распространенное решение, которое обычно имеет большой успех во время соревнований по науке о данных, — это повышение градиента.

Поскольку мы сосредоточены на том, чтобы занять хорошую позицию в таблице лидеров, это наша первая попытка, но если вы думаете, что именно такой подход привел нас к шестому месту, вы будете разочарованы.

Разработка функций

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

Мы создаем 59 функций, которые можно разделить на шесть различных категорий:

  1. Функции набора данных (12 функций): задаются непосредственно набором данных. Они могут быть включены в модель практически без изменений. (например, количество хэштегов, язык твита)
  2. Функции авторов (18 функций): полезны для профилирования каждого автора обучающего набора. Они включают в себя некоторые предварительно вычисленные функции, подробно описывающие особое поведение каждого автора в течение истории, задокументированной набором данных. Две наиболее важные особенности этой категории:

Коэффициент вовлеченности авторов:

Представляет количество действий определенного типа, полученных авторскими твитами, деленное на общее количество опубликованных твитов.

Количество полученных взаимодействий:выражает общее количество взаимодействий, полученных автором для каждого типа взаимодействия (лайк, ретвит, ответ, комментарий).

3. Пользовательские функции (18 функций): аналогичны тем, которые применяются для авторов, но они рассчитываются с учетом взаимодействия пользователя с твитом. (например, общее количество лайков, поставленных пользователем, или общее количество действий)

4. Разговорный язык (1 функция): основная интуиция, стоящая за этой функцией, заключается в том, что понимание языка твита играет ключевую роль в возможном взаимодействии. По этой причине мы подсчитали все языки, на которых говорит каждый пользователь, чтобы определить, способен ли он понять текст твита.

5. Предыдущее действие (4 функции): мы выполняем еще одно предварительное вычисление, чтобы понять историю предыдущих взаимодействий между пользователями и авторами.

6. Функции, связанные со временем (6 функций): некоторые дополнительные функции, связанные с отметкой времени твита, такие как время суток, день недели и т. д.

Проблемы масштабируемости

Теперь мы определили множество функций, чтобы помочь нашему алгоритму повышения градиента принимать правильные решения, и нам предстоит столкнуться с самой большой проблемой этой задачи: масштабируемость.

Учебный набор действительно огромен (около 70 ГБ) и содержит 148 миллионов строк вместе с 60 столбцами, которые мы подробно описали ранее, представляющими функции.

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

К счастью, библиотека XGBoost, которую мы использовали для реализации, предоставляет хорошее решение для такой ситуации: версия для внешней памяти.

Поскольку этот вид реализации малоизвестен, мы делимся фрагментом кода, чтобы помочь другим людям, столкнувшимся с той же проблемой.

  1. Запишите набор данных вместе со сгенерированными функциями в файл csv. В нашем решении мы записали на диск три разных файла: обучающий, тестовый и проверочный.
  2. Сохраните названия столбцов, они будут очень полезны, например, при построении графика важности каждой функции. В противном случае вместо имени вы получите просто номер.
  3. Напишите метки в столбце CSV, помня об индексе столбца меток, поскольку вы потеряете заголовки.
  4. Импортируйте CSV-файл внешней памяти в структуру данных DMatrix :

Важно включить:

#dtrain.cache

Таким образом мы указываем XGBoost не загружать весь набор данных в память.

5. Обучите модель XGBoost. Чтобы сделать модель еще более устойчивой к огромному размеру данных, мы лично предлагаем использовать метод дерева gpu_hist и поиграть с параметром subsample, если у вас все еще есть проблемы:

Результаты

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

Лучшее решение: константа CTR с оптимизацией

После разочарования, вызванного результатами модели повышения градиента, мы понимаем, что нам нужно лучше понимать показатели, чтобы подняться в таблице лидеров.

Стартовый вопрос, который по иронии судьбы стал лучшим решением, звучал так:

Какая наилучшая константа может обеспечить хороший баланс между оценкой двух показателей?

Чтобы ответить на этот вопрос, мы делаем несколько шагов:

  1. Мы присваиваем одно и то же число, извлеченное случайным образом, всем возможным парам пользователь-твит.
  2. Мы рассчитываем обе метрики, чтобы настроить это число, чтобы максимизировать оценку.

Результаты этого исследования подробно представлены в следующей таблице:

Первое, что мы заметили, это то, что у нас разное оптимальное значение в зависимости от вовлеченности. В частности, у нас гораздо более высокий оптимум для Нравится, потому что это, безусловно, самое популярное действие.

Из этого исследования мы получаем два полезных результата:

  • PRAUC: любая константа дает одинаковый эффект с точки зрения оценки.
  • RCE: имеет разные оптимальные значения в зависимости от типа взаимодействия.

Очевидно, что мы должны найти значения, оптимизирующие RCE.

Распределение действий

RCE тесно связан с коэффициентом кликабельности (CTR), а CTR зависит от различного распределения действий в тренировочном наборе.

Например, RCE для действия Like дает лучший результат для большего числа по сравнению с другим действием, потому что это наиболее распространенное действие.

На данный момент очевидно, что лучшей константой для каждого типа действия был сам CTR. В следующей таблице показаны числовые значения CTR для каждого типа взаимодействия:

Сравнивая эти результаты с предыдущей таблицей, мы понимаем, почему 0,5 дает хороший результат для лайка, а для других действий лучшая константа — 0,1. Лучшее значение — это просто самое близкое к CTR.

Окончательное решение основано на константе CTR, выполнив следующие шаги:

  • Рассчитайте CTR для каждого вида действия

  • Прогнозируемой вероятностью для каждого вида действия будет сам CTR, воспроизведенный для всех пар пользователь-твит. Например:

Результаты

Несмотря на то, что этот подход действительно прост и требует нескольких строк кода, он дает потрясающие результаты в таблице лидеров, вероятно, потому, что он использует некоторые недостатки способа расчета рейтинга.

Окончательный рейтинг рассчитывается в несколько этапов:

  • Средний балл PRAUC по четырем заданиям
  • Средний балл RCE по четырем заданиям
  • Вычисление ранжирования для обеих метрик
  • Сумма двух полученных рейтингов

Этот тип вычислений отдает предпочтение решению с хорошей оценкой по наименее конкурентной метрике.

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

Для этого мы разделили тренировочный набор на разные части с одинаковым размером официального проверочного набора, чтобы определить, как на оценку RCE влияет различное распределение действий.

Результаты представлены на следующей картинке:

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

Окончательное рассмотрение

Урок, который мы извлекли из этой задачи, заключается в том, что соревнования по науке о данных могут сильно отличаться от реальной жизни.

Предоставленный набор данных был очень хорошо структурирован, а Twitter является одним из наиболее распространенных источников данных для исследователей, поэтому несправедливо считать такого рода проблемы искусственными.

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

В реальном сценарии решение, основанное на константе, будет совершенно бесполезным, и мы, вероятно, потратили бы больше времени на улучшение решения повышения градиента с большей функциональностью и лучшей масштабируемостью.

Если вам интересно, код обоих решений можно найти в нашем github-репозитории.