- BrainTools - https://www.braintools.ru -
Для запуска А/В теста необходимым минимумом является фиксация ошибок первого и второго рода, расчет MDE (минимальный наблюдаемый эффект). Однако при расчете результатов теста далеко не всегда получается достичь MDE заданного размера, в таком случае вероятность достижения значимости значительно уменьшается. Помимо этого даже при статистически значимом результате существует вероятность ошибки [1], что наши результаты являются выбросом или просто случайностью [2]. В таких случаях необходимо применить дополнительный арсенал инструментов для работы с данными.
В этой статье я фокусируюсь на error Type-M/Type-S. Но для начала давайте вспомним, что из себя представляют статистические ошибки первого и второго рода.
Рассматривая классическое тестирование гипотез (подсчет результатов А/В теста): есть нулеваяи альтернативная
.
Ошибка первого рода (Type I): отклоняем, когда на самом деле
верна.
Например: аналитик ложно замечает разницу описательных статистиках выборок.
Вероятностью наступления такого события называется –уровень значимости:
Ошибка второго рода (Type II): не отклоняем, когда на самом деле верна
.
Тогда мощностью теста является:
Чем выше мощность, тем меньше риск пропустить реальный эффект.
Для ML/DS специалистов эти типы ошибок (в рамках бинарной классификации) обозначаются:
Type I – False Positive (FP) (ложно положительный результат, ложная тревога)
Type II – False Negative (FN) (ложно отрицательный результат, пропустили реальный эффект)
Если мощность низкая, то среди «значимых» результатов:
Знак эффекта может быть неверным (мы «ловим» эффект не в ту сторону).
Оценённая величина эффекта условно на значимости часто сильно завышена.
Gelman и Carlin предложили смотреть не только на мощность и ошибки I/II рода, но и на ошибки типа S (Sign) и типа M (Magnitude), которые описывают, насколько часто значимый эффект имеет неверный знак и насколько условно значимые оценки систематически завышают истинную величину эффекта [1] [3]. В контексте дизайна экспериментов их подход показывает, что при низкой мощности классический фокус на p‑value приводит к «экстремальным» значимым результатам, и поэтому уже при планировании теста нужно оценивать не только вероятность обнаружить эффект, но и риск перепутать его направление и масштаб.
Давайте представим что мы тестируем гипотезу о равенстве истинного эффекта нулю против альтернативной гипотезы с наличием реального эффекта, в двустороннем тесте. Это утверждение можно записать следующим образом:
Так же предполагается, что тестовая статистика имеет нормальное распределение. При обработке результатов A/B теста обычно берется разница между тестовой и контрольной группой. Для сравнения качества ML моделей берется разница метрик. Так же можно рассматривать данные ошибки в контексте весов моделей регрессии.
Для определения новых понятий понадобятся следующие обозначения: пусть– истинный эффект,
– оценка эффекта, а также есть правило определения значимости (например можно взять
)
Type S error (Sign): вероятность того, что при статистически значимом результате знак оценённого эффекта неверен.
Type M error (Magnitude): ожидаемый коэффициент завышения (или занижения) размера эффекта (exaggeration ratio), другими словами насколько условно значимый эффект завышает истинный по модулю.
В качестве примера я предлагаю рассмотреть кейс сравнения двух ML моделей, однако все вычисления можно перенести на подсчет эффекта в рамках A/B теста.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, accuracy_score
from scipy.stats import norm
# Загрузка данных
data_bc = load_breast_cancer(as_frame=True)
X = data_bc.data
y = data_bc.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
# Обучение моделей с разными параметрами
# Модель А
model_A = LogisticRegression(max_iter=1000, random_state=33)
model_A.fit(X_train, y_train)
# Модель В
model_B = LogisticRegression(C=0.1, max_iter=1000, random_state=33)
model_B.fit(X_train, y_train)
proba_A = model_A.predict_proba(X_test)[:, 1]
proba_B = model_B.predict_proba(X_test)[:, 1]
# Функция для рассчета ошибок
def error_table(threshold, y_true, proba):
y_pred = (proba >= threshold).astype(int)
tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel()
fpr = fp / (fp + tn) # эмпирическая alpha
fnr = fn / (fn + tp) # эмпирическая beta
return pd.Series(
{
"threshold": threshold,
"TN": tn,
"FP (Type I)": fp,
"FN (Type II)": fn,
"TP": tp,
"[alpha] FPR = FP / (FP+TN)": fpr,
"[beta] FNR = FN / (FN+TP)": fnr
}
)
В данной статье, для простоты и наглядности я не буду оптимизировать границу отсечения классов по скору.
threshold = 0.5
pred_A = (proba_A >= threshold).astype(int)
pred_B = (proba_B >= threshold).astype(int)
errors_A = error_table(threshold, y_test, proba_A)
errors_B = error_table(threshold, y_test, proba_B)
print("Ошибки I/II рода для модели A:n", errors_A)
print("nОшибки I/II рода для модели B:n", errors_B)
acc_A = accuracy_score(y_test, pred_A)
acc_B = accuracy_score(y_test, pred_B)
effect_obs = acc_B - acc_A
print("nНаблюдаемый эффект (accuracy_B - accuracy_A):", effect_obs)
Ошибки I/II рода для модели A:
threshold 0.5
TN 57
FP (Type I) 7
FN (Type II) 2
TP 105
[alpha] FPR = FP / (FP+TN) 0.109375
[beta] FNR = FN / (FN+TP) 0.018692
dtype: float64Ошибки I/II рода для модели B:
threshold 0.5
TN 59
FP (Type I) 5
FN (Type II) 2
TP 105
[alpha] FPR = FP / (FP+TN) 0.078125
[beta] FNR = FN / (FN+TP) 0.018692
dtype: float64Наблюдаемый эффект (accuracy_B – accuracy_A): 0.011695906432748537
rng = np.random.default_rng(0)
n = len(X_test) // 2
B = 100000
threshold = 0.5
pA = model_A.predict_proba(X_test)[:, 1]
pB = model_B.predict_proba(X_test)[:, 1]
predA = (pA >= threshold).astype(int)
predB = (pB >= threshold).astype(int)
y = y_test.to_numpy()
corrA = (predA == y).astype(int)
corrB = (predB == y).astype(int)
idx = rng.integers(0, n, size=(B, n))
boot_effects = corrB[idx].mean(1) - corrA[idx].mean(1)
true_effect_hat = boot_effects.mean()
se_hat = boot_effects.std(ddof=1)
print("nОценка эффекта по бутстрэпу:", true_effect_hat)
print("Оценка SE эффекта по бутстрэпу:", se_hat)
Оценка эффекта по бутстрэпу: 0.023563647058823543
Оценка SE эффекта по бутстрэпу: 0.01638026036908619
def simulate_type_s_m(true_effect, se, alpha=0.05, n_sims=100000, random_state=13):
rng = np.random.default_rng(random_state)
sims = rng.normal(loc=true_effect, scale=se, size=n_sims)
z = sims / se
p_values = 2 * (1 - norm.cdf(np.abs(z)))
significant = p_values < alpha
sims_sig = sims[significant]
sign_hat = np.sign(sims_sig)
true_sign = np.sign(true_effect)
type_s = np.mean(sign_hat != true_sign)
exaggeration_ratio = np.abs(sims_sig) / abs(true_effect)
type_m = exaggeration_ratio.mean()
power = significant.mean()
return sims, significant, type_s, type_m, power
sims, significant, type_s, type_m, power = simulate_type_s_m(true_effect_hat, se_hat)
print("nОценки Type S/M и мощности:")
print("power =", round(power, 3), ", Type S =", round(type_s, 5), ", Type M =", round(type_m, 3))
Оценки Type S/M и мощности: power = 0.302 , Type S = 0.00109 , Type M = 1.803
plt.style.use("seaborn-v0_8-whitegrid")
# Распределения скоров для моделей A и B
plt.figure(figsize=(8, 4))
plt.hist(proba_A, bins=30, alpha=0.6, label="Model A", density=True)
plt.hist(proba_B, bins=30, alpha=0.6, label="Model B", density=True)
plt.axvline(threshold, color="red", linestyle="--", label=f"threshold = {threshold}")
plt.title("Распределение предсказанных вероятностей (модели A и B)")
plt.xlabel("Предсказанная вероятность положительного класса")
plt.ylabel("Плотность")
plt.legend()
plt.tight_layout()
# Бутстрэп-распределение эффекта (accuracy_B - accuracy_A)
plt.figure(figsize=(8, 4))
plt.hist(boot_effects, bins=30, alpha=0.7, density=True, color="steelblue")
plt.axvline(true_effect_hat, color="black", linestyle="-",
label=f"mean effect = {true_effect_hat:.4f}")
plt.axvline(0, color="red", linestyle="--", label="нулевой эффект (0)")
plt.title("Бутстрэп-распределение эффекта (accuracy_B - accuracy_A)")
plt.xlabel("Размер эффекта (uplift по accuracy)")
plt.ylabel("Плотность")
plt.legend()
plt.tight_layout()
# Распределение симулированных оценок эффекта и выделение значимых
plt.figure(figsize=(8, 4))
plt.hist(sims, bins=40, alpha=0.4, density=True, label="все симулированные эффекты")
plt.hist(sims[significant], bins=40, alpha=0.7, density=True, label="значимые эффекты")
plt.axvline(true_effect_hat, color="black", linestyle="-", label="true_effect_hat")
plt.axvline(0, color="red", linestyle="--", label="нулевой эффект (0)")
plt.title(
f"Симулированные оценки эффекта (Type S/M)n"
f"power={power:.2f}, Type S={type_s:.3f}, Type M={type_m:.2f}"
)
plt.xlabel("Размер эффекта (симуляции)")
plt.ylabel("Плотность")
plt.legend()
plt.tight_layout()
plt.show()




Наблюдаемый uplift accuracy_B − accuracy_A ≈ 0.0117, а бутстрэп‑оценка эффекта ≈ 0.0236 с SE ≈ 0.0164. Это означает, что реальный прирост по accuracy небольшой, но заметный. Для бизнеса это означает небольшой прирост в качестве (значит экономия), за счет снижения FP.
Мощность ≈ 0.30: при «истинном» приросте качества такого размера, примерно в 30% можно наблюдать статистически значимое отличие, в остальных случаях эффект не был бы обнаружен.
Type S ≈ 0.001: на множестве всех, стат. значимых, результатов вероятность перепутать направление эффекта практически нулевая. Направление (знак) эффекта хорошо определено.
Type M ≈ 1.8: на множестве всех стат. значимых экспериментов оценённый uplift в среднем почти в 1.8 раза больше истинного. Это означает, что прирост в качестве на стат. значимых результатах систематически завышается.
Автор: O_oscar
Источник [6]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/25636
URLs in this post:
[1] ошибки: http://www.braintools.ru/article/4192
[2] случайностью: http://www.braintools.ru/article/6560
[3] [1]: https://library.virginia.edu/data/articles/assessing-type-s-and-type-m-errors
[4] обучение: http://www.braintools.ru/article/5125
[5] Image: https://sourcecraft.dev/
[6] Источник: https://habr.com/ru/articles/996474/?utm_campaign=996474&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.