К обычному разбору
Тренировка по собеседованиюТехническое собеседованиеMayflower2025-01-25

Mayflower: Python-агрегации, ML System Design про возврат пользователя и RecSys-рантайм

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

Шагов
5
Вопросов
4
Задач
1
1ЗадачаMedium

Агрегации заказов по пользователям и категориям

Условие

На собеседовании дают две таблицы: заказы и товары в заказах.

Нужно реализовать функцию build_order_features(orders, items), которая вернет словарь с тремя блоками:

  • category_spend: сколько денег каждый пользователь потратил в каждой категории;
  • order_user_total_spend: для каждого заказа суммарные траты этого пользователя по всем его заказам;
  • weekday_spend: для каждого пользователя вектор из 7 чисел, где индекс 0 соответствует понедельнику, а индекс 6 - воскресенью.

Цена строки считается как price * amount. Если в заказе несколько товаров одной категории, их нужно просуммировать.

Решение прямо на странице

Напишите код, запустите проверки и только потом открывайте разбор.

Проверка решения

Нажмите «Запустить проверки» или Ctrl+Enter.

Показать разбор

Идея решения

Сначала джойним строки товаров с заказами через order_id, считаем стоимость строки как price * amount, затем делаем три независимые агрегации: по пользователю и категории, по суммарным тратам пользователя и по дню недели. В pandas это были бы merge, groupby, transform и pivot_table, но в тренажере задача оставлена как чистый Python, чтобы тесты были детерминированными.

Эталонный код

from collections import defaultdict
from datetime import datetime


def build_order_features(orders, items):
    order_by_id = {order["order_id"]: order for order in orders}
    order_spend = defaultdict(int)
    category_spend = defaultdict(lambda: defaultdict(int))
    weekday_spend = defaultdict(lambda: [0] * 7)

    for item in items:
        order = order_by_id[item["order_id"]]
        user_id = order["user_id"]
        value = item["price"] * item["amount"]

        order_spend[item["order_id"]] += value
        category_spend[user_id][item["category"]] += value

        weekday = datetime.fromisoformat(order["created_at"]).weekday()
        weekday_spend[user_id][weekday] += value

    user_total = defaultdict(int)
    for order_id, value in order_spend.items():
        user_total[order_by_id[order_id]["user_id"]] += value

    return {
        "category_spend": {user: dict(values) for user, values in category_spend.items()},
        "order_user_total_spend": {
            str(order["order_id"]): user_total[order["user_id"]]
            for order in orders
        },
        "weekday_spend": dict(weekday_spend),
    }
Сложность
Время: O(n + m). Память: O(n + m).
Где n - число заказов, m - число строк товаров. Мы один раз строим индекс заказов и один раз проходим по товарам.
2Кейс10 мин

Как поставить задачу предсказания возврата пользователя

Бизнес хочет понимать, вернется ли пользователь и стоит ли давать ему скидку. Как сформулировать ML-задачу, таргет и признаки?

Ответьте без подсказки

Сначала проговорите ответ вслух или тезисами.

Запишите черновик

Формулы, план решения, риски и примеры.

Сравните с разбором

Откройте разбор только после своей попытки.

Показать разбор

Короткий ответ

Сначала фиксируем действие и горизонт: например, купит ли пользователь в следующие 7 дней. Затем строим point-in-time датасет без заглядывания в будущее: история активности, категории, частоты, давность действий, прошлые покупки и доступные пользовательские признаки.

Подробный разбор

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

Датасет собирается point-in-time: признаки берутся только из прошлого относительно даты скоринга, а таргет - из будущего окна. Признаки: давность последнего визита, число сессий, покупки и просмотры по категориям, средний чек, реакция на прошлые промо, сегмент пользователя, устройство, регион, сезонность. Базовая модель - бустинг или логистическая регрессия, дальше уже можно усложнять.

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

Типичные ошибки

  • Сразу выбирать модель, не зафиксировав окно предсказания.
  • Смешивать признаки из будущего с историческими признаками.
  • Оптимизировать абстрактную accuracy без учета стоимости скидки.
3Вопрос9 мин

Как выбрать признаки и метрики для модели возврата

После первой модели нужно понять, какие признаки оставить и стала ли модель лучше. Какие offline-метрики и проверки использовать?

Ответьте без подсказки

Сначала проговорите ответ вслух или тезисами.

Запишите черновик

Формулы, план решения, риски и примеры.

Сравните с разбором

Откройте разбор только после своей попытки.

Показать разбор

Короткий ответ

Сначала смотрим качество на временной валидации, затем feature importance/SHAP и slice-метрики. Для скидок обычно важны precision при заданном recall или recall при ограниченном FPR, потому что false positive тратит деньги, а false negative теряет пользователя.

Подробный разбор

Для такой задачи обычная accuracy часто бесполезна: классы могут быть несбалансированы, а ошибки стоят по-разному. Если скидка дорогая, нужен высокий precision среди тех, кому ее дадим. Если потеря пользователя дороже лишней скидки, можно фиксировать допустимый FPR и максимизировать recall.

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

Дополнительно нужны slice checks: новые/старые пользователи, категории, регионы, промо-сегменты. Модель может выглядеть хорошо в среднем и ломаться на важном бизнес-сегменте.

Типичные ошибки

  • Сравнивать модели на случайном split, где будущее перемешано с прошлым.
  • Выкидывать признаки только потому, что SHAP маленький на одном запуске.
  • Забывать про стоимость false positive при промо-скидках.
4Кейс10 мин

Как встроить модель возврата в продукт

Модель уже умеет предсказывать вероятность возврата. Как ее применить в продукте и где хранить признаки?

Ответьте без подсказки

Сначала проговорите ответ вслух или тезисами.

Запишите черновик

Формулы, план решения, риски и примеры.

Сравните с разбором

Откройте разбор только после своей попытки.

Показать разбор

Короткий ответ

Если решение принимается раз в день или неделю, проще batch scoring: пересчитать признаки, посчитать score, записать аудиторию для CRM/push-сервиса. Online endpoint нужен только если решение зависит от свежего действия пользователя.

Подробный разбор

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

Признаки лучше держать рядом с ML-пайплайном или в feature store, где есть offline/online консистентность. Если push-сервису нужен только финальный флаг или score, не надо гонять через сеть большой feature vector. Если же решение должно реагировать на свежую сессию, нужен online store с TTL и endpoint, который быстро достает последние признаки.

После запуска обязательно нужен A/B тест: не только uplift по покупкам, но и стоимость скидок, отписки, жалобы, fatigue от коммуникаций и стабильность latency/ошибок пайплайна.

Типичные ошибки

  • Делать online endpoint там, где достаточно разового batch scoring.
  • Передавать тяжелые признаки между сервисами вместо финального score.
  • Не считать стоимость промо как часть online-метрики.
5Кейс12 мин

Как ускорять тяжелую модель рекомендаций в рантайме

Есть трансформерная модель рекомендаций по истории пользователя. Как сделать так, чтобы она не ломала online-сервис?

Ответьте без подсказки

Сначала проговорите ответ вслух или тезисами.

Запишите черновик

Формулы, план решения, риски и примеры.

Сравните с разбором

Откройте разбор только после своей попытки.

Показать разбор

Короткий ответ

Кэшировать item embeddings и последний user state, пересчитывать только изменившуюся часть истории, отделить генерацию кандидатов от реранжирования, использовать batching/quantization и держать fallback на популярное или старый top-K.

Подробный разбор

Сначала нужно понять, что именно должно быть online. Item embeddings почти всегда можно считать заранее: каталог меняется медленнее пользовательских событий. User representation можно обновлять по событию, по TTL или при входе в секцию, но не пересчитывать всю длинную историю при каждом запросе.

Практичная схема: генерация кандидатов через ANN/векторный индекс или готовые эвристики, затем легкий реранкер по ограниченному числу кандидатов. Для heavy transformer стоит хранить последний user state или хотя бы последние top-K, инвалидировать его при новых важных событиях и пересчитывать асинхронно. Если нужен sync endpoint, он должен иметь жесткий timeout и fallback.

Инференс ускоряют batching, quantization FP16/INT8, ONNX/TensorRT/Triton, precomputed item vectors, cache hit rate monitoring и autoscaling. Но оптимизация не заменяет продуктовый контракт: сколько freshness реально нужно и что показываем, если модель не успела ответить.

Типичные ошибки

  • Каждый раз прогонять всю историю пользователя через трансформер.
  • Не иметь fallback на случай timeout или пустых рекомендаций.
  • Смешивать генерацию кандидатов и тяжелое реранжирование в один дорогой online-запрос.