A/B-тесты в рекламе: деление трафика, SRM и подглядывание
Чтобы A/B-тест в рекламе не объявил победителем случайность, мало поделить трафик на контроль и тест и сравнить CTR. Нужны еще четыре вещи: деление по пользователю, а не по запросу, если эффект переносится между показами; дисперсия CTR по дельта-методу; проверка долей групп (SRM) по назначению, а не по показам; решение по always-valid p, а не по обычному p-value.
Единица рандомизации: пользователь или запрос
Первое решение — что именно делится между группами.
| Деление по запросу | Деление по пользователю | |
|---|---|---|
| Что получает группу | Каждый запрос отдельно | Человек целиком, во всех запросах |
| Один человек видит | Обе версии вперемешку | Только свою версию |
| Нужен стабильный ID | Нет | Да |
| Метрики | Простые доли, тест долей | Отношения по пользователям, дельта-метод |
| Подходит для | Технических правок без «памяти»: формат ответа, верстка | Ставок, креативов, частоты показов — всего, что влияет на поведение человека |
Если тест меняет креатив или ставку, деление по запросу «загрязняет» группы. Человек видел оба креатива, кликнул на втором показе после первого — и неясно, какой группе засчитать клик. Деление по пользователю требует надежного ID, и тут важен identity-граф: если один человек приходит под двумя ID, он может попасть в обе группы.
Запросы без идентификатора при делении по пользователю не участвуют в тесте. Нельзя подставлять вместо пользователя ID запроса: тогда один и тот же человек сегодня без ID, завтра с ним окажется в обеих группах.
Детерминированное назначение по хешу
Хранить таблицу «пользователь → группа» не нужно. Группу вычисляют хешем:
корзина = hash(соль + ":" + user_id) mod 10000
Корзины от 0 до 9999 делятся по накопленным весам групп. Например, при весах 50/50: корзины 0–4999 — контроль, 5000–9999 — тест.
Пример (иллюстративный). Для пользователя
ibz_7f3a…и соли9c41e0d2a7b85f13хеш дает корзину 3127. Это контроль, и в любом запросе, на любом сервере, в любой день результат будет тем же.
Свойства такой схемы:
- Стабильность. Тот же пользователь при той же соли всегда в той же группе, без обращений к базе.
- Независимость экспериментов. У каждого теста своя случайная соль. Попадание в тестовую группу одного эксперимента не повышает шанс попасть в тестовую группу следующего.
- Хрупкость весов. Смена 50/50 на 30/70 сдвигает границу с 5000 на 3000: пользователи из корзин 3000–4999 переезжают из контроля в тест. Поэтому у запущенного теста веса, соль и единица рандомизации не меняются. Новое распределение — это новый эксперимент.
- Хеш с хорошим перемешиванием. У некоторых простых хешей (например, FNV-1a) младшие биты результата зависят только от младших бит входа, а остаток от деления на 10 000 это «видит». Нужен хеш с лавинным эффектом во всех битах.
Еще одно правило: один объект (например, кампания) — один эксперимент одновременно. Если показ попадает сразу в два теста, его нельзя честно приписать ни одному.
Метрики-отношения и дельта-метод
CTR — это отношение: клики к показам. При делении по пользователю наблюдения внутри группы не независимы: один активный пользователь дает много показов и кликов, и его поведение коррелировано. Обычная биномиальная формула p(1−p)/N в этом случае занижает дисперсию, и тест долей объявляет значимыми случайные различия.
Правильная дисперсия отношения R = ΣY / ΣX (Y — клики пользователя, X — его показы) считается по пользователям дельта-методом:
Var(R) ≈ ( s²_Y − 2R·s_XY + R²·s²_X ) / ( n · μ_X² )
Здесь n — число пользователей, s² и s_XY — выборочные дисперсии и ковариация по пользователям, μ_X — средние показы на пользователя.
Отношение этой дисперсии к биномиальной называют эффектом дизайна (design effect).
Пример (иллюстративный). Биномиальная стандартная ошибка CTR в группе — 0,010 п.п., по дельта-методу — 0,016 п.п. Эффект дизайна — (0,016 / 0,010)² ≈ 2,6. Значит, нужно примерно в 2,6 раза больше показов, чем дает наивный расчет, а различие, которое выглядело значимым при z = 2,5, на деле дает z ≈ 1,6.
При делении по запросу каждый запрос — независимое испытание, и обычный z-тест двух долей корректен.
SRM: когда группы набраны не по плану
Sample ratio mismatch (SRM) — расхождение фактических долей групп с заданными весами. Проверка — критерий χ² согласия:
χ² = Σ (наблюдено_i − ожидалось_i)² / ожидалось_i, степеней свободы = групп − 1
Пример (иллюстративный). Веса 50/50, в тест попало 100 000 пользователей: 50 900 в контроль, 49 100 в тест. χ² = 900²/50 000 · 2 = 32,4, p ≈ 1,3·10⁻⁸. Перекос всего 0,9 п.п., но случайно он почти невозможен: назначение или учет сломаны.
Порог для флага SRM обычно берут строгим, p < 0,001, иначе он будет срабатывать случайно в каждом двадцатом тесте. Если флаг поднят, сравнивать метрики нельзя, сначала нужно найти поломку.
Почему SRM считают по назначению, а не по показам
Самая частая ошибка — считать доли групп по показам. Если тест меняет ставку, тестовая группа законно выигрывает больше или меньше аукционов. Это эффект, а не поломка.
Пример (иллюстративный). Тест повышает ставку на запросах с высоким прогнозом клика. Назначение — ровно 50/50 по пользователям, пришедшим в запросах. Но тестовая группа выигрывает чаще, и показы делятся 55/45. Проверка по показам кричит «SRM!», хотя разбиение исправно. Проверка по уникальным пользователям во входящих запросах, с группой, вычисленной той же формулой хеша, показывает честные 50/50.
Вторая полезная проверка: группа, записанная в событиях, должна совпадать с группой, которую дает формула от соли и ID. Расхождение значит, что сервер назначает не то или учет пишет не то.
Подглядывание и always-valid p-value
Классический p-value рассчитан на одну проверку в конце теста заранее заданного объема. Если смотреть на него каждый день и остановиться при первом p < 0,05, вероятность ложной победы растет с каждым взглядом. Классический расчет Армитиджа, Макферсона и Роу (Armitage, McPherson, Rowe, 1969) для нормально распределенной метрики дает при пяти равномерных проверках около 14%, при десяти — около 19%, при двадцати — около 25% вместо заявленных 5%. При неравномерных проверках, например частых в начале теста, инфляция может быть еще больше.
Решение — последовательные тесты. Один из самых практичных — mSPRT (mixture sequential probability ratio test), на котором построен always-valid p-value:
Λ = √( V / (V + τ²) ) · exp( τ²·θ̂² / (2V·(V + τ²)) ), p = min(1, 1/Λ)
Здесь θ̂ — наблюдаемая разность метрики между группами, V — дисперсия этой разности, τ² — масштаб ожидаемого эффекта (разумно брать порядка квадрата MDE в абсолютных единицах). Такой p можно смотреть сколько угодно раз: вероятность хоть однажды увидеть p < α при отсутствии эффекта не превышает α.
Пример (иллюстративный). CTR контроля 0,50%, теста 0,55%, стандартная ошибка разности 0,018 п.п. Классический p-value = 0,005, «значимо». Always-valid p при τ = 10% от контроля (0,05 п.п.) = 0,097, «пока нет». Цена возможности подглядывать — тест должен набрать больше данных, прежде чем признать эффект.
Мощность и MDE: сколько ждать
MDE (minimum detectable effect) — минимальный эффект, который тест надежно заметит. Его выбирают до запуска, исходя из того, какой эффект важен для бизнеса. Требуемый объем на группу для доли p при уровне значимости α и мощности 1−β:
n ≈ ( z_{1−α/2}·√(2p̄q̄) + z_{1−β}·√(p₁q₁ + p₂q₂) )² / (p₂ − p₁)², p₂ = p₁·(1 + MDE)
| Базовый CTR | Относительный MDE | Показов на группу (α = 0,05, мощность 80%) |
|---|---|---|
| 0,5% | 5% | ≈ 1 280 000 |
| 0,5% | 10% | ≈ 328 000 |
| 0,5% | 20% | ≈ 86 000 |
| 0,5% | 30% | ≈ 40 000 |
При делении по пользователю эти числа умножают на эффект дизайна. Вывод из таблицы: вдвое меньший эффект требует примерно вчетверо больше данных. Тест, которому не хватает объема, может только «случайно победить». Поэтому, пока объем не набран, решение принимают по always-valid p, а не по классическому.
Чек-лист перед запуском
- Единица рандомизации выбрана по природе изменения (пользователь или запрос).
- Задана ровно одна контрольная группа, веса в сумме дают 100%.
- MDE, α и мощность выбраны заранее, объем посчитан с учетом эффекта дизайна.
- Метрика определена однозначно: клики — только валидные, иначе выиграет группа, которая чаще попадает на мусорный инвентарь.
- Объект теста не участвует в другом эксперименте.
- SRM проверяется по назначению, решение принимается по always-valid p.
A/B-эксперименты в Ibiza
Эксперименты в Ibiza создаются в админке и делят трафик выбранных кампаний (весь трафик — только с явным флагом). Единица рандомизации — пользователь (внутренний профиль identity-графа) или запрос. Групп от 2 до 8, ровно одна контрольная, веса задаются в базисных пунктах и в сумме дают 100%. При создании генерируется случайная соль. Группа вычисляется DSP детерминированно, как XXH64 от строки «соль:ID» по модулю 10 000, без обращений к хранилищу. После запуска группы, соль, единица и фича заморожены, пауза сохраняет назначение, а одна кампания не может участвовать в двух экспериментах. Группа передается в ссылках трекинга и оседает в показах, кликах и конверсиях.
Сейчас эксперименты поддерживают три фичи: ставку по прогнозу CTR (множитель в коридоре), выбор креатива по прогнозу CTR и форму нативного ответа SSP. Отчет показывает по группам показы, валидные клики, конверсии, расход, CTR, CR, CPC и CPM. Сравнение с контролем при делении по пользователю идет дельта-методом, при делении по запросу — z-тестом двух долей. Решение о значимости принимается по always-valid p (mSPRT с масштабом эффекта τ = MDE × контроль). SRM считается по назначению: уникальные единицы во входящих запросах, группа вычисляется той же формулой хеша, что и в DSP. Отдельно проверяется, совпадает ли записанная группа с вычисленной. Отчет также оценивает необходимый объем с учетом эффекта дизайна и предупреждает, если тест еще недобран. Пошаговая настройка — в документации по A/B-тестам.
Частые вопросы
Что делать, если тест показал SRM?
Не делать выводов по метрикам. SRM означает, что группы набраны не по задуманным долям: сломано назначение или учет. Сначала найти и исправить причину, затем перезапустить тест с новой солью.
Можно ли остановить тест, как только p-value стало меньше 0,05?
Для классического p-value — нет: каждая промежуточная проверка увеличивает шанс ложной победы. Останавливать по ходу можно только по always-valid p-value (например, mSPRT), которое рассчитано на многократный просмотр.
Почему нельзя менять доли групп посреди теста?
При детерминированном назначении смена весов сдвигает границы групп, и часть пользователей переезжает из контроля в тест. Их история смешивает эффекты обеих групп. Правильный путь — остановить тест и запустить новый.