Зачем нужно ускорять A/B-тест
Типичный продуктовый A/B длится 2–4 недели. Из них первая — пилот на маленьком трафике, потом раскатка, потом ждём, пока наберётся MDE. На сложных метриках (revenue, session_time, retention) выборка нужна большая, а поток юзеров не бесконечный — приходится сидеть и ждать.
Цена ожидания — реальная. Пока твой тест крутится:
CUPED (Controlled-experiment Using Pre-Experiment Data) — способ снизить шум в данных, не трогая дизайн эксперимента. Меньше шум — раньше видно эффект — короче тест. Метод придуман в Microsoft в 2013, описан в оригинальной статье Deng et al., и сейчас включён по умолчанию в A/B-платформах Microsoft, Netflix, Booking, Авито, Яндекса.
Идея в одном предложении
Из метрики во время эксперимента вычитается её предсказуемая часть — то, что мы могли знать о юзере до теста. Остаётся «чистый сигнал»: эффект самого эксперимента + случайный шум, но без шума, который мог быть предсказан.
Аналогия. Хочешь измерить, насколько новая пробежка ускоряет бегунов. Просто замерить время после — шумно: одни от природы быстрые, другие медленные. Замерить разницу «время после − среднее время до» — точнее. Ты убрал индивидуальные различия, осталось только то, что добавила тренировка.
CUPED делает примерно то же самое, но в более общей форме: вычитает не «среднее до», а оптимально подобранную поправку, которая учитывает связь между preperiod-поведением и поведением во время эксперимента.
Формула без ужаса
CUPED трансформирует исходную метрику Y (поведение во время эксперимента) с помощью ковариаты X (поведение до эксперимента) в новую метрику Y_cuped:
Параметр θ (тета) — это не магия, а оптимальное значение, которое максимально снижает дисперсию. Считается через ковариацию и дисперсию:
Дальше — главное. Сколько шума мы вычитаем? Ответ:
Из этой формулы и берётся вся практическая магия CUPED. Чем сильнее метрика юзера сегодня связана с его метрикой месяц назад — тем больше шума мы вычитаем — тем меньше выборка нужна.
Сколько даёт практически
Размер выборки в A/B обратно пропорционален квадрату MDE и пропорционален дисперсии. Если дисперсия упала в 1 − ρ² раз — выборка пропорционально уменьшается. Цифры в реальных задачах:
| Корреляция ρ | Снижение дисперсии | Экономия выборки | Где встречается |
|---|---|---|---|
| 0.3 | 9% | ~9% | слабо коррелированные клики |
| 0.5 | 25% | ~25% | session_duration коротких сессий |
| 0.7 | 49% | ~50% | revenue, время в продукте |
| 0.85 | 72% | ~70% | стабильные daily-метрики у активных юзеров |
Когда CUPED работает, а когда нет
CUPED — не серебряная пуля. Эффективность определяется тем, насколько preperiod-поведение юзера предсказывает его поведение во время эксперимента. Если предсказывает плохо — выгоды нет.
Работает хорошо
Работает слабо или не работает
Реализация на Python в 10 строк
Никаких специальных библиотек — достаточно numpy и pandas. Ключевая функция:
import numpy as np import pandas as pd def apply_cuped(df: pd.DataFrame, pre_col: str, exp_col: str) -> np.ndarray: """ Y_cuped = Y - theta * (X - mean(X)) Theta считается на ОБЪЕДИНЁННОЙ группе (control + treatment), иначе появляется bias. """ pre = df[pre_col].values exp = df[exp_col].values theta = np.cov(exp, pre, ddof=1)[0, 1] / np.var(pre, ddof=1) return exp - theta * (pre - pre.mean())
Использование в обычном пайплайне A/B:
# df: одна строка на юзера, колонки: group ('control'/'treatment'), # revenue_pre (за 30 дней до теста), revenue_exp (за период теста) df['revenue_cuped'] = apply_cuped(df, 'revenue_pre', 'revenue_exp') # Дальше — обычный двухвыборочный t-тест на cuped-метрике from scipy import stats control = df[df.group == 'control']['revenue_cuped'] treatment = df[df.group == 'treatment']['revenue_cuped'] t_stat, p_value = stats.ttest_ind(treatment, control, equal_var=False) effect = treatment.mean() - control.mean() print(f"Эффект: {effect:.2f}, p-value: {p_value:.4f}") # Сравнение: дисперсия до и после CUPED ratio = df['revenue_cuped'].var() / df['revenue_exp'].var() print(f"Дисперсия после CUPED: {ratio:.2%} от исходной")
Если на твоих данных ratio получился около 0.5 — CUPED дал двукратное ускорение. Если около 0.9 — эффект минимальный, корреляция между периодами слабая.
CUPED vs стратификация
Стратификация — другой способ снизить шум в A/B: разбиваешь юзеров на группы по дискретным признакам (платформа, гео, тариф), считаешь эффект внутри каждой страты и потом усредняешь. На собесах часто спрашивают, чем отличается от CUPED.
| CUPED | Стратификация | |
|---|---|---|
| Тип ковариаты | Непрерывная (preperiod-метрика) | Дискретная (платформа, гео, тариф) |
| Что использует | Поведение юзера до эксперимента | Признак, известный на момент сплита |
| Снижение дисперсии | 1 − ρ² (зависит от корреляции) | Разница between-strata variance / total |
| Работает на новых юзерах? | Нет (нет preperiod) | Да (платформа известна) |
| Сложность реализации | Низкая (10 строк) | Средняя (правильное взвешивание) |
| Можно ли комбинировать | Да — CUPED поверх страт даёт ещё больший выигрыш | |
Практика: на «зрелой» аудитории с историей — CUPED обычно даёт больше. На новых юзерах или когда метрика бинарная и слабо автокоррелирована — стратификация. На больших платформах применяют оба метода одновременно.
Где CUPED тихо ломается
Метод выглядит безобидным — добавил поправку, дисперсия упала, тест ускорился. Но есть четыре ситуации, когда CUPED даёт неверные результаты, не выдавая ошибки. Их проверяй явно перед запуском.
theta считаешь только на контроле или только на treatment — в неё попадает случайный шум одной выборки. Эффект эксперимента получает смещение. На больших данных оно мало, на маленьких — может развернуть направление.theta на объединённой группе (control + treatment вместе). Преперриод от теста не зависит — bias не появится.theta * 0 = 0 — никакой поправки. CUPED технически отработает, но никакого ускорения не даст. На новых юзерах ты «думаешь, что ускорился», но шум остался прежним.Как валидировать: A/A на CUPED
Прежде чем доверять CUPED-оценкам в продакшене, прогони на исторических данных офлайновый A/A-тест. Случайно разбей юзеров на две группы (никакого реального воздействия), посчитай CUPED-эффект, повтори N раз. Если процент «значимых» результатов на уровне α=0.05 не равен 5% — где-то баг.
import numpy as np from scipy import stats def aa_test(df, n_sim=1000): significant_count = 0 for _ in range(n_sim): # случайный сплит 50/50 df['group'] = np.random.choice(['A', 'B'], size=len(df)) df['cuped'] = apply_cuped(df, 'pre', 'exp') a = df[df.group == 'A']['cuped'] b = df[df.group == 'B']['cuped'] _, p = stats.ttest_ind(a, b, equal_var=False) if p < 0.05: significant_count += 1 return significant_count / n_sim # Должно быть в окрестности 0.05 (5%) print(aa_test(historical_data))
Если получаешь 7–10% — значит, в трансформации или в сборе данных есть систематическое смещение. Если 2–3% — где-то лишний консерватизм, оценка дисперсии завышена. Идеально — 4–6%.
CUPED в кейс-интервью
На middle/senior-собесах в продуктовой аналитике CUPED — практически обязательный пункт. Его не всегда проверяют формулой, но обязательно — пониманием идеи и границ применения.
- Объясни CUPED простыми словами — без формул.
- Какая корреляция нужна, чтобы CUPED дал двукратное ускорение?
- Почему θ считают на объединённой группе, а не отдельно по контролю?
- CUPED применим к конверсии? Если нет — что с ней делать?
- Чем CUPED отличается от стратификации? В каком случае что выбрать?
- Что произойдёт, если в preperiod-окне окажется пилот эксперимента?
- Как ты проверишь, что CUPED не сломал дисперсионную оценку?
Y_cuped = Y − θ(X − E[X]), что значат буквы — (3) сколько даёт практически (зависит от ρ, на нормальных продуктовых метриках 25–60% экономии) — (4) где не работает (новые юзеры, ratio в лоб). Этого достаточно на 95% собесов.
Частые вопросы
Что такое CUPED простыми словами?
Когда CUPED не работает?
CUPED увеличивает чувствительность или сокращает время теста?
ρ=0.7 даёт примерно 50% экономии, ρ=0.5 — около 25%, ρ<0.3 — почти ничего.Чем CUPED отличается от стратификации?
Почему θ нужно считать на объединённой группе?
Какой preperiod выбирать — 7 / 14 / 30 дней?
14–30 дней. Меньше — слишком шумная ковариата, корреляция упадёт. Больше — велик риск, что пользователь успел измениться, поведение «давно» не похоже на «сейчас». Если активность сильно сезонная (выходные/будни) — обязательно бери целое число недель.Связанные материалы
Главное про CUPED
CUPED — это бесплатное ускорение A/B-теста: ничего не меняешь в дизайне эксперимента, добавляешь 10 строк кода — и тест завершается на 25–60% быстрее. Цена входа минимальная, выгода реальная.
Запомни три ограничения: (1) метрика должна автокоррелировать с собой между периодами (revenue — да, бинарные клики на новый элемент — нет), (2) у юзера должна быть история (новые юзеры — мимо), (3) для ratio-метрик — отдельная техника (delta-method или линеаризация).
Следующий шаг: возьми любой свой прошлый A/B-тест с revenue-метрикой, рассчитай CUPED-версию и сравни ширину доверительного интервала. Обычно — впечатляет. И сразу понятно, почему методу 12 лет, а его всё ещё активно внедряют.