Использование последовательного тестирования для уменьшения размера выборки

Привет, Хабр! Я Андрей Корнеев, аналитик команды Origination в Т-Банке. Наша команда работает над тем, чтобы клиент оформил продукт максимально быстро и комфортно, а потом захотел остаться с нами навсегда.

Команда большая, поэтому спектр задач — от развития конкретных продуктов до разработки и внедрения аналитических инструментов.

c3ce9be93689918c9a0dd327f24a3cb7.jpg

Наша команда проводит тысячи тестов в год, причем большинство из них — на бинарных метриках. И часто команда сталкивается с вопросами:

  • Как статистически понять, продолжать тест или нет в случае, когда наблюдаемый эффект сильно выше MDE, но до предрассчитанного размера выборки еще далеко?

  • Как уменьшить среднюю продолжительность теста для бинарных метрик? Конечно, существуют стандартные подходы к сокращению размера выборки, например стратификация и CUPED. Но на бинарных метриках они не дают сильного эффекта — либо нужны хорошие ковариаты, которые далеко не всегда есть.

  • Как понять, техническая метрика действительно просела или это случайный выброс?

Один из ответов на все эти вопросы — Group Sequential Test (GST), о котором и расскажу в статье. По пути обсудим:

  • Классический дизайн теста и почему он именно такой.

  • Концепцию последовательного тестирования.

  • Основные методы последовательного тестирования.

  • Как и почему работает GST.

  • Библиотеку в Python для использования GST.

  • Выводы.

Классический дизайн A/B-теста

Прежде чем перейти к обсуждению последовательного анализа, освежим в памяти дизайн классического A/B-теста.

Пусть\theta— эффект от нашего изменения. Пусть у нас случай двух выборок, двусторонняя альтернатива. То есть

H_0: \theta \leq 0 \\ H_1: \theta > 0» src=«https://habrastorage.org/getpro/habr/upload_files/da5/ffd/68e/da5ffd68e24f95ca56bd552678c9fc40.svg» /></p>

<p>Выбираем<img alt=и \beta, определяем \beta:

  • \alpha — вероятность ошибки первого рода. Вероятность, что мы заметим эффект, когда его нет.

  • β — вероятность ошибки второго рода. Вероятность, что мы не заметим эффект, когда он есть.

  • \delta — минимальный детектируемый эффект.

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

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

\mathbb{P}(Accept H_1|\theta = \delta) = 1 - \beta

А вот вероятность ошибки первого рода мы контролируем, подсматривая ровно один раз за тест. Что будет с вероятностью ошибки первого рода, если мы будем подглядывать много раз?

Количество подглядываний

Вероятность ошибки 1-го рода

1

0,05

2

0,08

5

0,14

10

0,19

20

0,25

100

0,37

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

Поэтому выбирается ровно один момент, когда происходит подсматривание. Подглядывать нельзя. Или, может, стоит просто делать это правильно?

Последовательный анализ — метод, который описывает, как подглядывать правильно.

Концепция последовательного тестирования 

Разбираться, почему возрастает вероятность ошибки первого рода, будем на примере z-теста. 

Пусть α = 0,05. Для двусторонней альтернативы граница, которую мы хотим пересечь, равна z-score = 1,96 для принятия H_1при\theta > 0» src=«https://habrastorage.org/getpro/habr/upload_files/d41/24f/d11/d4124fd11edea6fda1b5db16c00570cf.svg» />и z-score = -1.96 для принятия<img alt=при\theta < 0.

Прогоним 50 А/А на синтетических данных и будем равномерно подглядывать 10 раз на протяжении теста через каждые 200 наблюдений в вариации. Видим, что из 50 границу на одном из подглядываний пересекли 6, то есть вероятность отвергнуть нулевую гипотезу при отсутствии эффекта составила 0.12:

Вероятность ошибки первого рода при обычных подглядываниях

Вероятность ошибки первого рода при обычных подглядываниях

Видим, что вероятность ошибки первого рода не контролируется. Но что, если проблема не в концепции подглядывания, а в границах, по которым нужно принимать решение?

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

Например, так выглядит результат симуляций для метода GST. Видим, что из 50 синтетических A/A-тестов только в двух мы отвергли H_0:

2332ac11bb354908efaed55cdea15716.png

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

1. SPRT — Sequential Probability Ratio Test Вальда. Функция, по которой метод Вальда принимает решение, — это отношение правдоподобий:

\lambda_n = \frac{\mathbb{P}(X_1, X_2, \ldots, X_n \mid H_1)}{\mathbb{P}(X_1, X_2, \ldots, X_n \mid H_0)}

Здесь в числителе и знаменателе вероятность наблюдать полученные данные при условии, что верны H_1 и H_0 соответственно.

А границы:

A = \frac{1 - \beta}{\alpha} \quad \text{and} \quad B = \frac{\beta}{1 - \alpha}

Остановка, если\lambda_n \geq Aили\lambda_n \leq B.

Преимущества:

  • Оптимальный метод с точки зрения ожидаемого размера выборки. Ускорение происходит в том числе на A/A-тестах, что делает метод более универсальным.

  • Не требуется заранее знать необходимый размер выборки.

  • Контроль мощности.

Недостатки:

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

  • Данные должны собираться и анализироваться непрерывно для максимальной мощности и полноценного эффекта ранней остановки.

 Библиотеку с методом реализовал мой коллега из команды Лаборатории прикладной статистики Витя Харламов:

Митап Tinkoff Product Analytics Meetup: A/B-тесты (offline)

meetup.tbank.ru

2. AVI — Always Valid Interference. Можно выделить два вида этой группы методов: mSPRT и GAVI, хотя первый — подвид второго.

Always Valid Inference Tests позволяют проводить непрерывное тестирование во время сбора данных, не устанавливая заранее правило остановки или число промежуточных анализов. Мы представляем mSPRT и GAVI, но mSPRT — это частный случай GAVI, их преимущества и недостатки одинаковы.

Преимущества:

  • Не требуется заранее знать необходимый размер выборки.

  • Поддерживает потоковый и батчевый набор данных, но при батчевом наборе занижается вероятность ошибки первого рода.

Недостатки:

  • Требует от экспериментатора выбора параметров распределения, описывающего эффект в рамках альтернативной гипотезы. Этот выбор влияет на статистические свойства теста и оказывается нетривиальным. Если примерный ожидаемый размер выборки известен, он может быть использован для выбора параметра, но в таком случае теряется преимущество, связанное с отсутствием необходимости знать размер выборки.

3. Group Sequential Tests (GST). В этом методе в качестве функции используется обычная z-статистика, посчитанная на группе, а границы строятся на основе выбранной alpha-spending функции. Alpha-spending функция задает правило, по которому уровень значимостиαрасходуется на каждом этапе анализа, чтобы общий уровень значимости эксперимента не превышал значение\alpha. Как на примере с синтетическими данными выше, мы просто подстраиваем границы так, чтобы не нарушать вероятность ошибки первого рода при последовательном анализе.

Преимущества:

  • Ограниченное сверху время проведения теста, мы не выходим за пределы выборки, рассчитанной для стандартного z-теста.

  • \alphaтратится только в моменты подглядывания, что дает гибкость в этом вопросе.

  • Простая и привычная интерпретация из-за связи с z-тестом.

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

Недостатки:

  • Не дает ускорения на тестах, где \theta = 0.

  • Требует предрасчета размера выборки, как и при стандартном дизайне.

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

Методология GST 

Эта глава посвящена математическому обоснованию метода GST. Как я написал выше, мы просто смотрим на z-статистику на промежуточном анализе и сравниваем ее со скорректированными границами, чтобы сохранялся уровень значимости. 

Опишу, почему и как это работает, для односторонней альтернативы. Для перехода к двусторонней альтернативе нужно заменить\alphaна\alpha/2и зеркально относительно нуля отобразить полученную границу. 

Пусть у нас есть броуновское движение W(t) : 0 < t ≤ 1 и мы хотим посмотреть, в какое время\tauпроцесс пересечет границуb(t) = z_{\alpha/2}в первый раз. Если\alpha^*(t) = \mathbb{P}(\tau \leq t \mid 0 \leq t \leq 1),то известно:

\alpha^*(t) = \begin{cases}     0, & \text{если } t = 0, \\     2 - 2 \cdot \Phi\left(\frac{z_{\alpha/2}}{\sqrt{t}}\right), & \text{если } t > 0, \end{cases}» src=«https://habrastorage.org/getpro/habr/upload_files/d1c/eac/7e3/d1ceac7e31de13c9ceb99cfdaa96f9b9.svg» /></p>

<p> где<img alt=— функция стандартного нормального распределения. Заметим, что\alpha^*(t)— возрастающая функция, такая, что\alpha^*(1) = \alpha.Допустим, мы подсматриваем, пересекло ли броуновское движение границу, только в моменты\leq t_1, t_2, \dots, t_K = 1.Можем назначить кумулятивную вероятность пересечения границы\alpha^*(t_1)точкеt_1,определивb_1так, чтобы она удовлетворяла следующему:

\mathbb{P}(B(t_1) > b_1) = \mathbb{P}(\tau \in [0, t_1]) = \alpha^*(t_1)» src=«https://habrastorage.org/getpro/habr/upload_files/b55/6c7/f9f/b556c7f9f4b6b0216aa3591850030c68.svg» /></p>

<p> Точно так же мы можем определить константы<img alt=так, чтобы:

\mathbb{P}(B(t_j) < b_j, j = 1, \dots, i-1; B(t_i) > b_i) = \mathbb{P}(\tau \in [t_{i-1}, t_i]) = \alpha^*(t_{i}) — \alpha^*(t_{i-1}).» src=«https://habrastorage.org/getpro/habr/upload_files/b74/906/03c/b7490603cbc3d27170b825780db14d8a.svg» /></p>

<p><img alt=находится аналитически, а вотb_2, \dots, b_Kтребуют численных методов интегрирования. Заметим, чтоb_iзависит только от выбора\alpha^*(t)и предыдущихb_j, 1 \leq j  < i.

В примере выше\alpha^*(t)— alpha-spending-функция. 

Идея GST заключается в том, что можно брать другие функции распределения α, которые строго возрастают по t и удовлетворяют условию\alpha^*(1) = \alpha.На основе последовательной траты\alpha(то есть, на основе распределения уровня значимости на каждом промежуточном этапе анализа) и вычисляются границы, чтобы вероятность ошибки первого рода контролировалась.

Результаты на синтетических данных 

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

В случае пересечения тест останавливается, в противном — ждём следующего подглядывания или набранной выборки.

Вот как это выглядит для\alpha^*(t) = \alpha \cdot t^3на синтетических данных:

Визуализация принципа работы GST

Визуализация принципа работы GST

Главные вопросы, на которые точно хочется ответить:

  • Что с вероятностью ошибки первого рода?

  • Что с вероятностью ошибки второго рода?

  • Насколько уменьшается время, необходимое на подведение итогов?  

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

Зависимость ошибки 1 рода от подглядываний

Зависимость ошибки 1 рода от подглядываний

С мощностью ситуация несколько иная. В общем случае чем выше количество подглядываний, тем ниже мощность. Вот какая зависимость мощности от подглядываний при\alpha^*(t) = \alpha \cdot t^3:

Зависимость мощности от подглядываний

Зависимость мощности от подглядываний

Видим, что при 128 подглядываниях теряется всего 4% мощности, что очень и очень неплохо. Размен в скорости за все это:

Ускорение времени, необходимого для принятия решения в долях

Ускорение времени, необходимого для принятия решения в долях

В итоге мы получаем, что, например, при 16 подглядываниях мы размениваем 3% мощности на ускорение в 34%.

Другие виды границ и их отличия

В предыдущих примерах границы были выстроены на основе\alpha^*(t) = \alpha \cdot t^3.Это представитель power-family, где все функции имеют вид:

\alpha^*(t) = \alpha \cdot t^x, x > 0» src=«https://habrastorage.org/getpro/habr/upload_files/f28/d32/ac3/f28d32ac3333a255be7241ca793d9b3e.svg» /></p>

<p>Для<img alt=

Мы тратим только восьмую часть альфы к середине эксперимента. Поэтому мощность особо не теряется, но пересечения границ случаются обычно после первой половины эксперимента. Чем больше x, тем больше будет потеря в мощности и выигрыш в скорости. Но есть и другие типы границ. Например, границы по Pocock имеют следующий вид:

\alpha^*(t) = \alpha \cdot \log\left(1 + \left(\exp(1) - 1\right) \cdot t\right)

При t = 0.5 мы получаем 0,62α. То есть при таких границах мы в среднем раньше останавливаем тест, но тут происходит потеря в мощности:

Потеря мощности при границах по Pocock

Потеря мощности при границах по Pocock

Ускорение времени, необходимого для принятия решения, Pocock

Ускорение времени, необходимого для принятия решения, Pocock

Использование в Python

Реализация на Python хранится на GItHub. Можно импортировать ее и пользоваться в своих целях. Основана на библиотеке расчета границ от Лана и Де Матса, реализованной на Fortran. Эту же реализацию брали за основу Spotify в своей известной статье сравнения методов последовательного тестирования.

В примере ниже показываю, как запустить GST на своем тесте в первый раз. Файлы из примера содержат массив из 0 и 1 по основной бинарной метрике.

from adaptiveGST import AdaptiveGST
import numpy as np

test_array = np.load('1007_13#test.npy')
control_array = np.load('1007_13#control.npy')

#Задаём переменные теста
p_0 = 0.09 #Базовая конверсия, она же историческая
mde = 1.5 # Изменение в процентах, которое ожидаем. То есть, p_test = p_0 * (1 + mde/100)

#Тип границ, которые используем
iuse = 3
phi = 4
#Массив долей выборок, когда были подглядывания. Пустой при первом подглядывании
peeking_array = [] 

gst = AdaptiveGST(
    p_0, 
    mde,
    alpha=0.05, 
    power=0.8,
    test=test_array,
    control=control_array,
    peeking_array=peeking_array,
    iuse=iuse,
    phi=phi
)

result = gst.check_result(gst.test, gst.control)
peeking_array = result[2]

#Вывод результата
print(f'{result[0]}, набралось {result[1]} от необходимой выборки')

После этого сохраняется peeking_array — массив подглядываний, который после первого подглядывания состоит из одного числа. При дальнейших подглядываниях стоит подставлять массив, полученный после последнего подглядывания.

Вот как выглядит результат работы этого метода на одном из реальных тестов:

Пример GST на реальном тесте

Пример GST на реальном тесте

Тест закончился бы спустя только 40% (!) выборки, если бы вместо обычного z-теста использовали этот подход.

Выводы

Когда дело касается принятия решений в бизнесе, скорость зачастую не менее важна, чем точность. Время — это деньги, а затянувшиеся тесты могут приводить к упущенным возможностям. Именно здесь на помощь приходит GST, способный существенно ускорить процесс принятия решения, сохранив при этом статистическую достоверность результатов.

Как и любая методология, GST имеет свои ограничения и особенности, которые важно учитывать. Разберемся, в чем его сильные и слабые стороны и почему его стоит применять, несмотря на некоторые минусы.

Основные недостатки:

  • Не подходит для метрик с долгим периодом созревания. Если результаты метрики становятся доступными только через длительное время (например, показатели возврата клиентов через полгода), последовательное тестирование теряет преимущество, так как требует регулярного пересчета статистики.

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

  • Зависимость от тех же допущений, что и z-тест.

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

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

  1. Экономия времени и ресурсов. В среднем с помощью последовательного тестирования можно ускорить A/B-тесты на 30—35%, а в ряде случаев — сократить их продолжительность на 60—70%. Это огромная разница, особенно для тестов с участием больших выборок.

  2. Возможность принятия решений на ранних этапах. Если в процессе теста наблюдается явное доминирование одной из вариаций, последовательное тестирование позволяет зафиксировать победителя и завершить эксперимент значительно раньше, чем при использовании классических fixed-horizon-методов.

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

  2. Простота интерпретации. В своей основе последовательное тестирование остается вариантом классического z-теста. Если проводить анализ только один раз, в самом конце эксперимента, результат будет идентичен традиционному подходу. Это позволяет использовать привычные метрики и статистики, не путая конечных пользователей.

Почему это важно для вашего бизнеса? Последовательное тестирование становится настоящим инструментом конкурентного преимущества, когда вам нужно:

  • Быстро принимать решения на основе данных.

  • Снижать затраты на тестирование.

  • Максимально эффективно использовать ограниченный трафик или выборку.

  • Сохранять при этом статистическую достоверность. 

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

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

© Habrahabr.ru