Один кейс, три процесса: как дефект LLM-классификатора превращается в ложный A/B-результат
Представьте: вы запустили A/B-тест нового алгоритма ранжирования на маркетплейсе. Через две недели — выручка на пользователя выросла на 20.9%, p-value практически нулевой, доверительный интервал далеко от нуля. Решение очевидно: выкатываем на всех. Но если посмотреть на этот же эксперимент через три процесса сразу — A/B-методологию, монетизацию и оценку качества ML-модели, — вывод меняется на противоположный. Прирост частично фальшивый, и чинить нужно не алгоритм ранжирования, а совсем другую вещь.
Это разбор синтетического кейса. Данные я сгенерировал сам — они не из реального проекта, а построены так, чтобы воспроизвести механику, с которой сталкиваешься на платформах с рекламной монетизацией и ML-компонентами. Весь код генерации и анализа приложен в конце: можно скачать и прогнать, числа в статье — настоящий вывод этого кода, не иллюстрация.
Кейс
Маркетплейс. Товары автоматически категоризирует LLM-классификатор. Категория влияет на ранжирование в выдаче. Команда выкатывает новый алгоритм ранжирования, который сильнее опирается на предсказанную категорию товара, и тестирует его через A/B: половина пользователей на старом ранжировании, половина на новом. Метрика успеха — выручка на пользователя.
В этом кейсе участвуют три процесса, и обычно за каждый отвечает свой человек или команда: аналитик эксперимента смотрит A/B, продуктовая аналитика следит за монетизацией и retention, ML-команда отвечает за качество классификатора. Когда они смотрят порознь, каждый видит свою правду. Сила — в том, чтобы посмотреть на все три сразу.
Процесс 1. A/B-эксперимент
Первый взгляд — на эффект. Выручка на пользователя: 11.16 в контроле, 13.49 в тесте, прирост +20.9%, Welch t-test даёт t≈11.2 при p<0.001. Сигнал сильный.
Здесь стоит применить CUPED — снижение дисперсии через предэкспериментальную ковариату (исторический ARPU пользователя). Идея простая: часть разброса выручки объясняется тем, что одни пользователи в принципе тратят больше других, и это видно ещё до теста. Вычитая предсказуемую часть, мы сужаем доверительный интервал, не сдвигая оценку эффекта.
theta = np.cov(Y, X)[0,1] / np.var(X) # X — pre-period ARPU
Y_adj = Y - theta * (X - X.mean()) # скорректированная метрика
На этих данных CUPED снижает дисперсию на 14.4%: оценка эффекта та же, но t-статистика растёт с 11.2 до 12.1 — интервал уже, вывод увереннее.
И сразу — методологическая дисциплина. Соблазн «подглядывать» в результат каждый день и остановиться, как только увидел значимость, ломает тест. Я прогнал A/A-проверку: взял контрольную группу, случайно разбил её надвое (истинного эффекта нет), и десять раз «подглядывал» по мере накопления данных. Наивный тест хотя бы раз пересекал p<0.05 в 18% случаев — вместо номинальных 5%. Подглядывание раздувает ложные срабатывания почти вчетверо. Лечится это либо фиксированным размером выборки, либо последовательными границами (например, O'Brien–Fleming).
Пока всё выглядит хорошо: значимый прирост, корректная методология. Аналитик A/B готов писать «выкатываем».
Процесс 2. Монетизация
Теперь — продуктовый взгляд. Выручка выросла, но за счёт чего?
| метрика | control | treatment | дельта |
|---|---|---|---|
| ARPU | 11.16 | 13.49 | +20.9% |
| показы рекламы | 30.0 | 36.0 | +20.1% |
| retention D7 | 0.726 | 0.686 | −5.6% |
Картина проясняется. Прирост выручки идёт в основном из роста показов рекламы (+20%). А retention седьмого дня упал на 5.6% — и это статистически значимо (p<0.001). Это классический конфликт «выручка против удержания»: можно показать больше рекламы и заработать сегодня, заплатив оттоком завтра. Продуктовая аналитика бьёт тревогу там, где A/B-аналитик видел успех.
Но почему новый алгоритм вообще роняет retention? Здесь нужен третий взгляд.
Процесс 3. AI-evals
Качество LLM-классификатора, который категоризирует товары. Наивная оценка — средняя точность по всем товарам: 86.4%. Выглядит достойно, «модель готова к проду».
Но средняя точность на несбалансированных данных обманчива. Стратифицируем по категориям — и картина другая:
| категория | точность | доля товаров |
|---|---|---|
| electronics (частая) | 0.937 | 21.8% |
| clothing | 0.915 | 18.7% |
| … | … | … |
| crafts (редкая) | 0.630 | 1.4% |
| vintage (редкая) | 0.653 | 0.8% |
Классификатор отлично работает на частых категориях и проваливается в «длинном хвосте»: на пяти самых редких категориях средняя точность 67.7%. Разрыв между micro-точностью (взвешенной по частоте, 86.4%) и macro-F1 (где все категории равны, 81.1%) — пять пунктов, и именно micro прячет провал в хвосте. На бенчмарке со сбалансированными классами этот дефект не виден; он проявляется только на реальном распределении трафика.
# наивно — одна цифра прячет всё:
accuracy_score(y_true, y_pred) # 0.864
# стратифицированно — виден провал в хвосте:
products.groupby("true_category")["correct"].mean()
Три взгляда дали три разные правды: A/B — «успех», монетизация — «retention падает», evals — «классификатор врёт в хвосте». По отдельности они не объясняют друг друга. Вместе — складываются в одну цепочку.
Связка
Соединим. Новый алгоритм ранжирования сильнее доверяет предсказанной категории. Там, где классификатор надёжен (частые категории), это работает хорошо. Там, где он врёт (хвост), новый алгоритм показывает нерелевантную выдачу — пользователь дольше скроллит в поисках нужного, видит больше рекламы (выручка растёт!), но уходит недовольным (retention падает).
Разрез treatment-группы по сегменту пользователя подтверждает:
| сегмент | ARPU | retention D7 |
|---|---|---|
| head-категории | 13.76 | 0.692 |
| tail-категории | 10.00 | 0.603 |
В хвосте, где классификатор ошибается, retention заметно ниже. Вред сконцентрирован ровно там, где врёт eval-модель.
Финальная проверка — контрфактуал. Каким был бы эффект, если бы классификатор не ошибался? Оценим эффект только на head-сегментах, где он надёжен:
| выручка | retention | |
|---|---|---|
| все сегменты (наблюдаемое) | +20.9% | −5.6% |
| только head (классиф. надёжен) | +27.4% | −3.8% |
Вот разгадка. На сегментах, где классификатор не врёт, прирост выручки даже выше (+27%), а retention почти не страдает. Значит вред от нового алгоритма — это не его собственное свойство, а артефакт мисматча категорий. Алгоритм ранжирования хорош; плох классификатор в хвосте.
Что показывает этот кейс
Наивный путь: A/B-аналитик видит +20.9% выручки и выкатывает. Через квартал команда обнаруживает просевший retention и начинает откатывать или чинить алгоритм ранжирования — то есть лечит не ту болезнь.
Сквозной путь: связав три процесса, мы видим, что корень — в классификаторе. Правильное действие — не трогать алгоритм ранжирования (он работает), а дообучить классификатор на хвосте категорий. После этого новый алгоритм даст свой честный +27% без потери retention.
Этот вывод невозможно получить, глядя на один процесс. A/B-методология ловит, что эффект значим, но не объясняет цену. Монетизация ловит конфликт «выручка против retention», но не видит причину. Evals ловит дефект классификатора, но не связывает его с выручкой. Ценность — в том, чтобы держать три процесса в голове одновременно и увидеть причинную цепочку: дефект оценки модели → искажение эксперимента → ложный сигнал монетизации.
Весь код — генерация данных и три анализа — приложен ниже. Данные синтетические, но механика реальная: именно так скрытый дефект ML-компонента просачивается в бизнес-метрики через эксперимент, и именно поэтому A/B нельзя читать в отрыве от того, что происходит под капотом продукта.