Привет, Хабр! Меня зовут Дмитрий Лучкин, я управляю развитием одного из видов финансовых продуктов в Сравни, мой соавтор — Марк Мережников, DS нашей команды. Мы решили поделиться с вами опытом использования ML на лендингах финансовых витрин и рассказать, к каким результатам мы пришли. 

В Сравни много ресурсов выделяется на аналитику и ML. В компании работают более 40 аналитиков в разных продуктовых и аналитических командах. Мы используем SnowFlake в качестве DWH, в том числе H2O-решение для ML-задач. Сейчас в трех командах работают датасаентисты — они сфокусированы на решении конкретных задач по росту монетизации и улучшению пользовательского опыта. Сегодня мы хотим поговорить про опыт внедрения ML в наших витринах финансовых продуктов.

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

Это стандартная задача для ML. Самая близкая аналогия для такого типа задачи: как эффективно показывать товары одной категории в поисковой выдаче на e-commerce площадках. Но наша задача не решается просто, потому что целевая метрика — выручка — зависит от суммы в каждой воронке по каждому офферу. Мы используем несколько метрик: клики, заявки, оформленные финансовые продукты. Есть трекинговая система, которая получает обратные данные по результатам успешного прохождения воронки пользователем. Есть фактор точности данных по выручке и всем показателям, также существует влияние маркетинговых затрат и влияние позиции (расположения оффера на витрине из более 80 офферов) на выручку по каждому офферу и целевые метрики продаж. Еще нужно учитывать, является ли пользователь новым или повторным для каждой конкретной финансовой организации, — от этого зависит, сколько мы заработаем за оформленный финансовый продукт.

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

Мы с командой приняли решение ранжировать имеющиеся финансовые организации с целью максимизации выручки компании, но с учетом того, что пользователь должен быстро получать одобрение или неодобрение по заявке на финансовый продукт со стороны конкретной финансовой организации. В первую очередь мы обратились к статистике по пользователям. Оказалось, что 80% пользователей раздела не имели регистрации на платформе, значит, мы видели по ним ограниченную информацию. При этом информация, которой мы не владели, используется для создания базовых решающих правил показа офферов. Например, если пользователь зашел из Москвы, то имеет смысл показывать ему только те финансовые организации, которые работают в этом городе. Однако этой информации недостаточно чтобы делать персонализированные рекомендации пользователю. Для решения проблемы ранжирования нужен item based подход, где item - оффер (вариант финансового продукта или предложение финансового продукта от организации), имеющий определенные свойства, выраженные в метриках.

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

И последнее — данные о том, что пользователь сделал заявку или оформил финансовый продукт (далее будем называть постбэки от финансовой организации) или прошел воронку до конца, приходят к нам в трекинговую систему не моментально. По тем или иным причинам мы можем их получить даже позже, чем через 24 часа с момента совершения клика по офферу. Исходя из этих аспектов, мы решили, что нам необходимо работать с метриками, агрегированными за 1 день — например, количество кликов по офферу за день. Отдельной метрикой обозначим место на лендинге (витрине), на которой находился оффер бó‎льшую часть дня.

Вдобавок к основным метрикам мы использовали производные метрики, отражающие воронку оффера: конверсия из показа в клик (CR1), из клика в заявку (CR2), из заявки в оформленный финансовый продукт (CR3), и различные метрики, отражающие отношение выручки ко всем основным метрикам — выручка за клик (EPC), заявку (EPL), оформленный финансовый продукт (EPAL). В итоге каждый оффер характеризуется метриками, распределенными во времени — временными рядами. Основываясь на этих рядах, мы создавали различные модели для ранжирования предложений.

Сразу отметим, что мы не могли использовать классические модели ранжирования (работающих в формате “запрос-документ”) и модели по типу коллаборативной фильтрации, потому что у нас нет никакой репрезентации пользователя (нет user-вектора и “запроса” соответственно). Модели для предсказания места оффера, основанные на его метриках, тоже были не реализуемы, так как мы не обладали историческими данными по оптимальным местоположениям офферов, а только данными об их фактическом местоположении. Как следствие, одним из вариантов решения проблемы было переформулирование задачи ранжирования в другую задачу, которую можно решить. 

Мы рассмотрим лишь одно из выбранных нами направлений — переформулирование в задачу регрессии, где для каждого оффера на основе его метрик (временных рядов) нужно предсказать следующее значение целевой метрики, и уже по этим значениям отранжировать офферы. Выбор целевой метрики производился, исходя из требований бизнеса.

Для максимизации выручки была выбрана метрика EPC (Earn Per Click), которая показывает выручку за клик по офферу. Эта метрика подходит лучше, чем собственно выручка, потому что позволяет четко определить, когда оффер лучше не поднимать выше (чем выше оффер, тем больше по нему кликов, но выручка может вырасти несоразмерно, и после определенного этапа поднятия оффера EPC вместо роста начнет падать). Выручка же в подавляющем большинстве случаев будет всегда расти при поднятии оффера в списке, и найти оптимальное устойчивое место оффера будет затруднительно. Также, чем выше оффер, тем больше по нему кликов, поэтому ранжирование по EPC приведет и к максимизации выручки, так как офферы будут на оптимальных местах.

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

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

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

  • Завтра будет так же, как за N предыдущих дней в среднем. Прогноз будет более устойчив к волатильности, но все же недостаточно. Возможно, он будет хороший для предсказания среднего значения целевой метрики в следующие N дней, но это ведет к тому, что производить ранжирование придется каждые N дней, что плохо для скорости реагирования, но не факт, что плохо для максимизации выручки на длительном временном промежутке. Что лучше, быстро реагировать с высоким риском или медленно с низким? Ответ отличается для каждого отдельного случая.

  • Усложним предыдущую модель и обратим внимание на изменения целевой метрики, то есть на ее производную. Скажем, что среднее значение метрики за M следующих дней равен среднему значению за N предыдущих дней с учетом среднего изменения метрики за N дней. Получаем простейшую линейную модель со свободным членом в виде усредненной производной. Эту модель уже надо валидировать на исторических данных, ведь временной ряд может иметь точки изменения направления тренда, и если их много, то данная модель будет работать хуже предыдущих.

Следующие методы, это собственно все доступные ML-инструменты для работы с временными рядами — например, ARIMA, prophet, да и весь scikit-learn для работы с таблицей лагов временного ряда. Последний, хоть и требует дополнительных трудозатрат для подготовки данных, — создание таблицы лагов и дальнейшие всевозможные преобразования фичей-лагов — чаще всего наиболее эффективен. Наш кейс не оказался исключением, поэтому дальше речь будет про него. 

Почему мы сразу не перешли к этому пункту и рассматривали простые модели? Вспомним, что мы работаем с рядами ежедневно агрегированных метрик, а значит, что, например, оффер, который находится на лендинге (витрине) ровно год, имеет временные ряды метрик длинной 365. То есть максимум 365 обучающих примеров для модели, очень скромно. Серьезные модели типа градиентного бустинга особо не применишь, остается надеяться на нахождение простой зависимости линейной моделью. А на нашей витрине есть и “старые”, и “молодые” офферы.

Вдобавок, чем дальше модель смотрит в прошлое (чем больше лагов мы используем в качестве фичей), тем меньше становится обучающих примеров. В таких случаях при подборе гиперпараметров как модели, так и трансформеров в scikit-learn, скорее всего оптимальными окажутся параметры, отражающие модель, обученную на малом количестве фичей (лагов ряда), но бó‎льшем количестве обучающих примеров. Это логично, потому что модели, смотрящей далеко в прошлое (то есть способной найти какие-то действительно сложные зависимости) попросту не хватит примеров для обучения, и она покажет себя на валидации хуже простой модели, но лучше обученной. 

В дополнение к этому валидация out-of-time может показывать, и в нашем случае она и показывала, что найденные моделью зависимости между предыдущими и следующими значениями рядов изменяются со временем. Проще говоря, условно хорошая модель, обученная на прошлом месяце, окажется плохой для данных этого месяца. Это еще один аргумент, чтобы не строить смотрящие далеко модели и чтобы не обучать модели на далеких от текущего момента данных. Таким образом, наш скромный датасет уменьшается еще больше. В таких условиях уверенность в том, что ML-модель будет лучше той же представленной выше модели, находится под большим вопросом. Возможно, она окажется даже хуже, если обучалась на данных с другой зависимостью между значениями временного ряда.

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

И эту модель тоже можно модернизировать, правда ценой небольших изменений в формулировке задачи. Вспомним про метрику "место оффера на витрине" — именно ее оптимальное значение мы хотим узнать в контексте максимизации целевой метрики. Если она и использовалась в предыдущих моделях, то только для предсказания целевой метрики. Что если теперь мы будем использовать не только ее лаги, но и будущие значения в качестве фичей? С другими метриками мы так сделать не можем, потому что это утечка неуправляемых нами данных, а место оффера мы можем назначать сами, поэтому утечка данных из будущего может сыграть нам на руку. 

Проще говоря, модель после обучения будет в том числе отражать некоторую зависимость между значением целевой метрики оффера и его будущим местоположением. И тут снова придется пожертвовать частью обучающих данных в зависимости от того, места скольких следующих дней мы хотим рассматривать. Встает вопрос: как сделать предикт на завтра, если нам неизвестно, где будут стоять офферы, а фичи надо чем-то заполнить? Ответ — сделать предсказания для всех возможных сценариев и выяснить, при каких местах будет максимальное предсказание для каждого оффера. Это и есть переформулированная задача. 

Достоинство данного подхода в том, что мы наконец предсказываем оптимальное место оффера, хоть и обходным путем. Но есть и два недостатка. Первый — при ранжировании необходимо разрешать конфликты между офферами с одинаковыми оптимальными местами. Второй недостаток подхода следует из его достоинства: чтобы корректно предсказывать целевую метрику для всех возможных вариаций расположения мест, модель должна быть экстраполируемой, по крайней мере относительно фичей-мест, ведь в исторических данных оффер явно побывал не на всех позициях. Экстраполирумые модели — это GLM модели и LightGBM, которые поддерживают линейную регрессию по листам деревьев. Если хочется использовать другие модели, то выход есть — это стекинг, где можно “настакать” любых моделей. Главное, чтобы финальная модель была экстраполируема и желательно получала фичи-места напрямую. В качестве бонуса - стэкинг дополнительно подсветит возможные проблемы с переобучением и валидацией, особенно out-of-time.

В вышеприведенных моделях мы не учитывали возможное влияние соседних офферов друг на друга, а оно вполне вероятно. Например, метрики оффера могут измениться из-за того, что рядом с ним стоит кликабельный оффер, который забирает часть внимания пользователя. Достоверно оценить это влияние можно только через череду АВ-тестов, но это нам и не нужно. Если эта зависимость есть, нужно позволить ML-модели попытаться ее отыскать. И, наконец, мы подходим к финальной модели в этой статье.

В предыдущем случае мы должны были построить для каждого оффера свою изолированную модель на его данных, и потом агрегировать их результаты для ранжирования. Теперь же мы объединим таблицы лагов всех временных рядов всех метрик всех офферов в единый датасет, включая финт с метрикой места, и будем предсказывать суммарную целевую метрику по всем офферам. А можно предсказать и производную этой метрики, убрав тренд временного ряда предсказуемых значений, который зачастую мешает модели найти зависимости. Теперь в одном обучающем примере будет информация о всех офферах. Однако за возможность найти взаимосвязи между их местоположением и сокращением количества моделей до одной мы платим очень многим. Фичей становится очень много, при, возможно, скромном количестве обучающих примеров — это прямой путь к переобучению. Также для нахождения оптимального порядка офферов нужно получить предсказания модели для всех возможных перестановок офферов, что неадекватно по временным затратам. Но это решается ограничением возможных перестановок. Например, мы можем позволить офферу изменить свою позицию максимум на 10 мест. Нам не надо просчитывать случаи что будет с оффером, который сейчас последний в списке, если поставить его первым.

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

Чего мы достигли в ходе нашего моделирования? Первоначально мы планировали прийти к результату, выбрав в качестве финальной модели последний вариант со стеком Scikit-Learn + CatBoost — использовать стэкинг, чтобы объединить несколько моделей, для каждой из которых создавалась своя таблица лагов в зависимости от того, как далеко модель смотрит в прошлое. Градиентный бустинг и Rigde-регрессия должны были использоваться для выявления линейных и нелинейных низкочастотных трендов во временных рядах, метод ближайших соседей — для высокочастотных недавних изменений. Финальным оценщиком у нас должна была стать Ridge-регрессия из-за требований к экстраполируемости. Во время каждого переранжирования модель должна была полностью переобучаться, включая повторную оптимизацию всех гиперпараметров пайплайна — для этого у нее есть достаточно времени. Переранжирование должно было происходить каждые 3 дня.

Однако после проведения экспериментов со всеми вышеописанными моделями, мы выявили, что проблема недостатка примеров для обучения “серьезных” моделей и непрогнозируемых изменений в поведении временных рядов метрик актуальна для большинства офферов на витрине. Лучше всего себя проявила комбинация моделей в стекинге, каждая из которых смотрит максимум на неделю в прошлое и не пытается выявить низкочастотные тренды во временных рядах. Ridge-регрессия ищет линейную зависимость между лагами ряда, метод ближайших соседей — нелинейную. Финальным оценщиком выступает Ridge-регрессия вследствие тех же требований к экстраполируемости.  Для каждого оффера создается отдельная модель со своими гиперпараметрами, которая обучается на временных рядах всех его метрик. 

Предварительно ряды метрик проходят через статистические анализаторы, определяющие сильные изменения в структуре данных, чтобы уменьшить риски обучения моделей на данных, структура взаимосвязей которых уже не актуальна. Если данных для обучения оказывается недостаточно, используется тот самый подход — “следующие N дней в среднем будут такими же, как и предыдущие”. Каждые 3 дня на основе предсказанных целевых метрик (EPC, выручка на платное действие — клик) происходит переранжирование, для которого полностью переобучаются все модели, включая гиперпараметры пайплайнов — на этот довольно длительный процесс есть достаточно времени. Для реализации данного подхода оказалось достаточно использования библиотеки Scikit-Learn. За счет внедрения модели в процесс ранжирования реферальной витрины, мы не только автоматизировали ручной труд, но и увеличили наши некоторые метрики. В ходе реализации этой модели у нас получилось суммарно увеличить выручку на ~6%. 

В таких задачах даже минимальное улучшение метрик может привести к росту выручки на весомый процент (речь идет о десятках миллионов рублей или более). Аналитики и датасаентисты в Сравни всегда стараются проверить все гипотезы, которые нельзя отвергнуть сразу, чтобы убедиться в этом на 100%.

Комментарии (2)


  1. sunnybear
    30.05.2022 16:21

    Ничего не написали про обучающую/проверочную выборку. А здесь это чуль ли не самое интересное :)


    1. marwkoka
      01.06.2022 10:04

      Работа производилась с временными рядами, так что для разбиения таблиц лагов на трейн/тест выборки использовался соответствующий сплиттер https://scikit-learn.org/stable/modules/cross_validation.html#time-series-split. А в качестве финальной валидационной выборки для проверки обученного пайплайна откладывались самые «свежие» по времени данные. Однако кросс-валидация с KFold/Shuffle сплитами тоже производилась - сравнив результаты можно понять, что модель впринципе не может аппроксимировать зависимость между точками ряда (если все валидации показали плохие результаты), или же конкретно из за того, что эта зависимость меняется со временем (плохие результаты покажет только TimeSeriesSplit).