惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
V
V2EX
爱范儿
爱范儿
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Martin Fowler
Martin Fowler
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Apple Machine Learning Research
Apple Machine Learning Research
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
宝玉的分享
宝玉的分享
Last Week in AI
Last Week in AI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
罗磊的独立博客
小众软件
小众软件
Vercel News
Vercel News
博客园 - 司徒正美
阮一峰的网络日志
阮一峰的网络日志
V
Visual Studio Blog
J
Java Code Geeks
P
Proofpoint News Feed
MongoDB | Blog
MongoDB | Blog
B
Blog
美团技术团队
量子位

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
Почему +0.3% к ROC‑AUC почти всегда шум, и как это проверить
artem · 2026-06-17 · via Все публикации подряд на Хабре

Средний

6 мин

10

Вы поменяли набор признаков, прогнали кросс‑валидацию, ROC‑AUC вырос с 0.871 до 0.874. Изменение уезжает в продакшен как улучшение, в чате ставят плюсы, через месяц на свежих данных «улучшенная» модель работает не лучше прежней, а иногда хуже. Прирост на третьем знаке утонул в шуме самой процедуры валидации, и отличить его от настоящего сдвига по одному числу было нельзя с самого начала.

Корень в том, что метрика на отложенной выборке — это не свойство модели, а случайная величина: она зависит от того, какие объекты попали в тест, с каким зерном перемешались данные, как нарезались фолды. У этой величины есть разброс, и если прирост меньше разброса, никакого прироста нет. Разберём, откуда берётся шум, как его измерить и как сравнивать модели так, чтобы вывод был воспроизводимым.

Один прогон ничего не говорит о разнице

Одиночный train/test split или одна пятифолдовая кросс‑валидация дают одно число, и по нему невозможно понять, насколько оно устойчиво. Достаточно прогнать ту же модель на той же выборке с разными зёрнами разбиения, чтобы увидеть собственный разброс оценки:

from sklearn.model_selection import RepeatedStratifiedKFold, cross_val_score

cv = RepeatedStratifiedKFold(n_splits=5, n_repeats=10, random_state=0)
scores = cross_val_score(model, X, y, cv=cv, scoring="roc_auc")
scores.mean(), scores.std()      # например, 0.872 ± 0.009

Если стандартное отклонение оценки 0.009, то разница в 0.003 между двумя моделями лежит внутри собственного шума процедуры. Повторённая кросс‑валидация с несколькими разбиениями на разных зёрнах — минимальный способ увидеть этот разброс, а одиночный фолд его просто прячет, выдавая одно число с видимостью точности.

Сравнивать модели нужно на одних и тех же фолдах

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

cv = RepeatedStratifiedKFold(n_splits=5, n_repeats=10, random_state=0)
scores_a = cross_val_score(model_a, X, y, cv=cv, scoring="roc_auc")
scores_b = cross_val_score(model_b, X, y, cv=cv, scoring="roc_auc")

diff = scores_b - scores_a       # на тех же фолдах, поэлементно
diff.mean(), diff.std()

Одно и то же зерно cv гарантирует, что scores_a[i] и scores_b[i] посчитаны на идентичном разбиении, и поэлементное вычитание имеет смысл. Если средняя разность сопоставима со своим стандартным отклонением или меньше него — различия нет.

Здесь подстерегает тонкость со статистическими тестами. Парный t‑тест по фолдам кросс‑валидации соблазнительно применить к diff, но он антиконсервативен: фолды делят между собой обучающие данные, их оценки скоррелированы, а тест считает их независимыми, занижает дисперсию и выдаёт значимость там, где её нет.

Честнее использовать поправку Надо‑Бенжио на скоррелированность повторных прогонов или схему 5×2cv, либо вообще не полагаться на p‑значение, а смотреть на доверительный интервал разности и относиться к нему скептически.

Лотерея случайного зерна

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

aucs = [run_pipeline(seed=s) for s in range(20)]
np.mean(aucs), np.std(aucs), np.max(aucs)   # отчитываться по среднему, не по max

Качество модели — это среднее и разброс по зёрнам, а не максимум. Соблазн взять лучший запуск особенно опасен при сравнении двух моделей: одной достаётся удачное зерно, другой нет, и разница оказывается полностью случайной. Зерно фиксируют ради воспроизводимости, но судят по распределению, а не по единственной удачной точке.

Чем больше вариантов вы перебрали, тем удачливее победитель

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

Защита — разделить выбор и оценку. Гиперпараметры и признаки подбираются на одних данных, а итоговое качество измеряется на финальном тесте, которого перебор не касался; в кросс‑валидации это вложенная схема, где подбор живёт во внутреннем цикле, а замер — во внешнем. Финальный отложенный кусок трогается ровно один раз, иначе он быстро превращается в ещё одну валидацию, по которой вы незаметно начинаете оптимизироваться.

Доверительный интервал вместо одного числа

Метрика на тесте — оценка по конечной выборке, и у неё есть доверительный интервал, зависящий от размера теста. Бутстрап даёт его без предположений о распределении: пересэмплируем тест с возвращением и пересчитываем метрику.

from sklearn.metrics import roc_auc_score

def bootstrap_auc_ci(y_true, y_score, n=2000, alpha=0.05):
    rng = np.random.default_rng(0)
    idx = np.arange(len(y_true))
    aucs = []
    for _ in range(n):
        b = rng.choice(idx, len(idx), replace=True)
        if len(np.unique(y_true[b])) < 2:   # бутстрап выбил один класс
            continue
        aucs.append(roc_auc_score(y_true[b], y_score[b]))
    return np.quantile(aucs, [alpha / 2, 1 - alpha / 2])

Одиночное значение 0.874 на тесте из тысячи объектов легко имеет интервал вроде [0.855, 0.892]. Две модели, чьи интервалы почти полностью перекрываются, на этих данных неразличимы, сколько бы ни отличался третий знак их точечных оценок. Доверительный интервал сразу показывает, есть ли вообще разрешающая способность у вашего теста: если он шире целого процента, спор о трёх десятых долях беспредметен.

Как сравнивать модели честно

Все приёмы сводятся к одному: метрика — случайная величина, и прирост меньше её шума приростом не считается. На практике это означает повторённую кросс‑валидацию вместо одного фолда, сравнение моделей по поэлементной разности на одних и тех же разбиениях, отчёт в виде среднего с разбросом или доверительным интервалом вместо голого числа, фиксацию зерна с оценкой по распределению, а не по лучшему запуску, и финальный тест, которого касаются один раз. Порог простой: прирост, который меньше стандартного отклонения вашей собственной процедуры, считают шумом, пока не доказано обратное.

Последний арбитр живёт вне офлайн‑валидации. Настоящее улучшение должно пережить переход на свежие данные из будущего и подтвердиться A/B‑тестом в проде, потому что именно там модель встречает распределение, которого не было ни в одном фолде. Офлайн‑сравнение лишь отсеивает заведомый шум, чтобы до прода доезжали только кандидаты, у которых эффект больше собственного разброса измерения.

Итого

Чтобы отличить улучшение от шума, сравнивайте модели на одних и тех же фолдах по поэлементной разности, измеряйте разброс повторённой кросс‑валидацией и бутстрап‑интервалом, не отчитывайтесь по лучшему зерну и лучшему из полусотни вариантов, держите финальный тест нетронутым.

Статистические тесты на фолдах применяйте с поправкой на их скоррелированность, а не вслепую. И помните, что единственное окончательное подтверждение — устойчивость эффекта на будущих данных и в A/B, потому что прирост, не переживший смену распределения, приростом не был.

Статьи по теме

Статьи по теме

Когда метрика растёт, важно понимать, улучшилась ли модель или вам просто повезло с выборкой. Разобраться в поведении ML‑алгоритмов и научиться осмысленно оценивать результат помогут бесплатные открытые уроки OTUS.

На занятиях преподаватели‑практики покажут подходы на реальных задачах, познакомят с форматом обучения и ответят на вопросы.

Полный список бесплатных уроков смотрите в дайджесте.