Всем привет! Меня зовут Аня Шатшнайдер, я старший BI-разработчик в команде антифрода Авито. Мы пользуемся сотнями ИИ-моделей, чтобы бороться с действиями недобросовестных пользователей. Но иногда модели работают не так эффективно, как следует. Поэтому моя команда решила найти аутсайдеров и передать их на доработку DS-инженерам. Расскажу, как мы пересматривали подход к оценке моделей.

Статья будет полезна аналитикам и DS, которые работают с несколькими ML-моделями в проде и хоть раз озадачились вопросом, как сравнивать их между собой.

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

Изменения в антифроде мы запускаем быстро, поскольку это помогает опережать недобросовестных пользователей. В результате за всё время накопилось много моделей, из-за чего простой вопрос «какая из них вносит наибольший вклад, и нет ли дублей?» превращается в отдельное исследование и съедает заметную часть аналитического ресурса.

Мы не могли найти лучшую модель

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

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

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

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

Обеления ломали любую наивную метрику. Чтобы не наказывать пользователей зря, у нас работает механизм обелений: в некоторых случаях санкция не применяется, даже если модель настаивает. Это хорошо для пользователей, но плохо для аналитики — без сквозного ID связать обеление с предсказанием конкретной модели тоже было сложно.

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

AUC не работает. Это площадь под ROC-кривой — стандартная метрика качества классификатора. Она показывает, насколько хорошо ваша модель способна отличать один класс от другого. Но классический ROC-AUC требует таргета: мы должны знать, реально ли пользователь был фродером. А после запуска модели в прод у нас этого знания нет, потому что получить таргет можно только через ручную разметку, что довольно дорого. Значит, офлайн-метрики качества классификации нам мало что скажут про реальную пользу модели.

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

Тут еще больше контента

Переформулировали задачу и выделили слои работы: данные, метрики и визуализация

Изначально задача пришла к нам в таком виде: «Разработать инструмент для оценки эффективности моделей».

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

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

К этой задаче мы подошли с нестандартным подходом:

1️⃣ Заранее зафиксировали измеримый результат. Ещё до старта договорились, что считаем успехом — health score дашбордов не ниже жёлтого, а оценка от пользователей — 4,5+ (минимум 5 оценок). 

Health score — это метрика здоровья разных аналитических объектов — дашбордов, датасетов и витрин внутри Авито. Каждый объект находится в зелёной, жёлтой или красной зоне в зависимости от его состояния. Для дашбордов зона зависит от значения health score по шкале от 1 до 100. Чем выше скор, тем лучше: 

? — выше 80, значит, это здоровый объект.

? — от 60 до 80 означает, что дашборду нужны небольшие доработки.

? — ниже 60 значит, что дашборд надо серьёзно переработать.

Сверхрезультат — зелёный health score, мы нашли важные инсайты и запланировали на их основе работы в следующем периоде. Критерий согласовали заранее, а потом честно замерили опросом стейкхолдеров прямо в Redash.

2️⃣ Вовлекли ключевого стейкхолдера в разработку. Мы добавили в контрибьюторы ключевого стейкхолдера — DS-лида и договорились, что он регулярно участвует в разработке вместе с нами. Работали по принципу: «Скажи мне — и я забуду, покажи мне — и я запомню, вовлеки меня — и я пойму». Для BI-задач это нетипичная практика, но именно она помогла попасть в реальные потребности команд.

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

Слой данных из сквозных ID и новых витрин

У нас не было идентификаторов и было неясно, что и где именно не работает. Поэтому мы начали работу именно с внедрения ID.

Ввели единый идентификатор запроса на санкцию — punisher_request_id. Это UUID, который проходит через всю цепочку модель → обеление → санкция и позволяет связать между собой звенья, которые раньше находились в разных таблицах. Этот же ID мы добавили в уже существующие витрины с обелениями, санкциями и скорами. Так быстро и просто соединили всё в единое целое.

Переработанное логирование. В одном из сервисов со скоринговыми моделями заодно отработали технический долг — переделали схему логирования. Теперь всё стало аккуратнее:

Было

Стало

Дублирование информации

Схему упростили, дубли убрали

Нет чёткой связи с санкциями

Добавили сквозной ID и полную цепочку

Часть данных не логировалась

Добавили недостающие поля

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

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

Реализовали 7 новых витрин в DWH на Trino. Это каркас, на котором держится остальная аналитика и отчётность:

Витрина

Что внутри

af_user_model_predictions

Все предсказания моделей по новой схеме логирования одного из сервисов

af_user_model_actions

Санкции от моделей одного из сервисов

antifraud_scores

Скоры всех моделей по пользователям, объявлениям или чатам из разных сервисов скоринга

antifraud_model_actions

Санкции от моделей из разных сервисов

punisher_requests

Запросы на наложение санкций в систему санкций и обелений (ОРР)

punisher_requests_params

Параметры запрошенных санкций

punisher_requests_settings

Настройки запрошенных санкций

Добавили одно событие в ClickStream — «Запрос в ОРР об отправке пользователя на санкцию». Теперь каждый такой запрос фиксируется независимо от его результата.

Главное, связали все действия моделей. Теперь по одному punisher_request_id можно восстановить всю цепочку: «Модель X предсказала фрод, отправила в ОРР запрос на санкции Y и Z, пользователь был обелён для Y, а санкция Z в итоге применилась».

? Вывод 2: аналитика начинается с логов, а не с дашборда. Сколько бы красивых графиков вы ни нарисовали поверх кривых данных, они останутся графиками поверх кривых данных. Мы потратили значительную часть времени именно на слой с логами и не жалеем.

? И сразу вывод 3: В антифроде, да и не только, модель живёт в связке с тем, что происходит после предсказания: обелениями, санкциями, прохождением. Оценивать имеет смысл всю цепочку, а не только «угадала или не угадала».

Жми сюда!

Слой MES из метрик эффективности моделей

Когда данные, наконец, стали связными, появилась возможность нормально считать. ROC-AUC мы использовать не можем, потому что таргета нет. Значит, надо смотреть, что реально произошло с пользователем после срабатывания модели. Так появилась MES (Model Effectiveness Score) — сводная метрика из шести компонентов:

Компонент

Что измеряет

Вес

Формула

Конверсия в санкцию

Доля предсказаний, которые привели к реальному наложению санкции

1

# санкций / # предсказаний

1 − Прохождение санкции пользователем

Доля пользователей, которые успешно прошли санкцию

2

1 − # пройденных / # наложенных

1 − 2 × Конверсия в блокировку

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

1

1 − # блокировок / # пройденных санкций

1 − False Positive Rate

Доля избыточных санкций

1

1 − # избыточных / # всех санкций

Уникальность

Доля предсказаний, в которых никто не сработал кроме этой модели

0.5

# уникальных предсказаний / # всех предсказаний модели

1 − Степень неуникальности / n

Среднее число других моделей, сработавших вместе с этой. Деление на n нужно для нормировки

0.5

1 − (# совместных срабатываний / # срабатываний модели) / n

MES = Σ(Компонент × Вес) / 6

Чем выше MES, тем эффективнее модель в связке с санкцией.

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

Три конверсии (в санкцию, прохождение или блокировку) — реальная оценка вместо ROC-AUC. Они показывают, доводит ли модель дело до конца. Ведь есть вероятность, что она просто выдаст флаг и на этом успокоится.

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

False Positive Rate — модель не должна зарабатывать очки за счёт того, что наказывает всех подряд. Мы постоянно работаем над снижением ложных отправок пользователей на санкции.

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

? Вывод 4: Частота срабатываний ≠ полезность. Модель, которая 1000 раз триггерится на одном пользователе, не в 1000 раз лучше модели, сработавшей один раз. Учитывайте уникальность срабатываний как по пользователю, так и между моделями.

Слой дашбордов из 10 штук

Все доски находятся в Redash — это стандартный стек для Авито из Trino и Redash. Всего получилось 10 дашбордов, объединённых в один отчёт. Я специально сделала их много, потому что один универсальный дашборд на всё обычно превращается в кашу, которую никто не читает.

Дашборд

Зачем он нужен

Основной — MES по всем моделям

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

Воронка и конверсии

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

4 дашборда по шагам воронки

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

Уникальность моделей

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

Покрытие Proxy Fraud 2.0

Обратный взгляд: берём пользователей, которых мы в итоге заблокировали за недобросовестное поведение и смотрим, как на них срабатывали модели. Это проверка на полноту.

False Positive по моделям

Доля избыточных наложений санкций по каждой модели. Модели с высоким FP уходят в проработку

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

Что получилось в итоге

? Модели из разных сервисов и команд теперь можно сравнивать по одной метрике, с учётом нескольких факторов, а не только конверсии.

? 8 неэффективных моделей нашлись сразу. Одну взяли в переработку одновременно с выходом отчёта, ещё 4 уже отключили или переработали, а остальные запланированы на следующие периоды.

? Теперь MES подсвечивает проблемные модели. Именно на неё смотрят при решениях о переработке.

? 7 витрин живут отдельной жизнью. Их используют в дашбордах, а также в обычных задачах аналитиков и DS. Оказалось, что нормальные логи нужны всем.

? Ручной анализ схлопнулся, и там, где раньше уходили дни, теперь хватает одного отчёта.

? Мы достигли и перевыполнили цель. Итоговая оценка дашбордов от пользователей — 5.0, health score — зелёный, а среди моделей нашлись те, что мы отправили в переработку. Это даже выше планки, которую мы ставили себе на старте.

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

А если вы хотите узнать больше о работе аналитиков в Авито — подписывайтесь на канал «Коммуналка аналитиков»

Кликни здесь и узнаешь

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