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

推荐订阅源

B
Blog
V
Vulnerabilities – Threatpost
P
Proofpoint News Feed
Google DeepMind News
Google DeepMind News
Y
Y Combinator Blog
V
Visual Studio Blog
阮一峰的网络日志
阮一峰的网络日志
腾讯CDC
月光博客
月光博客
T
Troy Hunt's Blog
博客园_首页
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
Netflix TechBlog - Medium
Microsoft Security Blog
Microsoft Security Blog
Recorded Future
Recorded Future
Blog — PlanetScale
Blog — PlanetScale
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Scott Helme
Scott Helme
T
Threat Research - Cisco Blogs
P
Palo Alto Networks Blog
T
The Exploit Database - CXSecurity.com
Simon Willison's Weblog
Simon Willison's Weblog
Know Your Adversary
Know Your Adversary
SecWiki News
SecWiki News
Security Archives - TechRepublic
Security Archives - TechRepublic
T
Threatpost
Forbes - Security
Forbes - Security
S
Schneier on Security
P
Proofpoint News Feed
T
Tor Project blog
Cyberwarzone
Cyberwarzone
The Hacker News
The Hacker News
Cloudbric
Cloudbric
S
Security @ Cisco Blogs
Webroot Blog
Webroot Blog
Attack and Defense Labs
Attack and Defense Labs
Hacker News: Ask HN
Hacker News: Ask HN
Google DeepMind News
Google DeepMind News
Hacker News - Newest:
Hacker News - Newest: "LLM"
C
CERT Recently Published Vulnerability Notes
The Last Watchdog
The Last Watchdog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
S
SegmentFault 最新的问题
V
V2EX
量子位
B
Blog RSS Feed
宝玉的分享
宝玉的分享
T
The Blog of Author Tim Ferriss
罗磊的独立博客
J
Java Code Geeks

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

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет 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 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Конформные предсказания: интервалы с гарантией покрытия без предположений о распределении
artem · 2026-06-16 · via Все публикации подряд на Хабре

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

Сложный

10 мин

135

Модель регрессии выдаёт число, модель классификации — вектор softmax-вероятностей, и оба молчат о том, насколько им можно верить на конкретном объекте. softmax-вероятность 0.9 не означает, что в 90% таких случаев ответ верен, а точечный прогноз цены ничего не говорит о том, в каком диапазоне реально лежит истина. Обычные способы добавить неопределённость опираются на предположения: доверительные интервалы линейной регрессии — на нормальность остатков, байесовские — на корректность априорных, bootstrap — на репрезентативность выборки.

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

Идея и точная гарантия

Метод в базовой split-форме откладывает калибровочную выборку, которую модель не видела при обучении. Для каждого её объекта считается оценка нонконформности — насколько предсказание «удивлено» истинным ответом; для регрессии это модуль остатка. Берётся эмпирический квантиль этих оценок, и он становится порогом: в предсказание нового объекта попадает всё, что в порог укладывается.

Гарантия — следствие чистой комбинаторики, а не свойств модели. Если новый объект и калибровочные обмениваемы, то ранг нонконформности нового объекта среди n калибровочных распределён равномерно по позициям от 1 до n+1. Поэтому, взяв в качестве порога значение под номером ⌈(n+1)(1−α)⌉ среди отсортированных калибровочных оценок, получаем точную двустороннюю границу покрытия:

1 − α ≤ P(Y ∈ C(X)) ≤ 1 − α + 1/(n+1)

Нижняя граница держится всегда, верхняя — когда оценки почти наверное различны. Отсюда же требование к размеру калибровки: если ⌈(n+1)(1−α)⌉ больше n, порога не существует и множество становится тривиальным, так что для нетривимой гарантии нужно примерно 1/α точек минимум — около ста при α = 0.01. Поправка (n+1)/n и квантиль «сверху» — это и есть перевод равномерности ранга в строгое неравенство, а не косметика.

Регрессия: рабочий пример с проверкой покрытия

Возьмём задачу с гетероскедастичным шумом — он растёт вместе с X, что станет важно дальше. Данные делятся на три части: обучение модели, калибровка, тест.

import numpy as np
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import train_test_split

rng = np.random.default_rng(0)
X = rng.uniform(0, 10, size=(8000, 1))
y = np.sin(X[:, 0]) + rng.normal(0, 0.2 + 0.4 * X[:, 0], size=8000)   # шум растёт с X

X_train, X_rest, y_train, y_rest = train_test_split(X, y, test_size=0.5, random_state=0)
X_cal, X_test, y_cal, y_test = train_test_split(X_rest, y_rest, test_size=0.5, random_state=0)

model = GradientBoostingRegressor(random_state=0).fit(X_train, y_train)

alpha = 0.1
n = len(y_cal)
resid = np.abs(y_cal - model.predict(X_cal))                          # нонконформность
q = np.quantile(resid, np.ceil((n + 1) * (1 - alpha)) / n, method="higher")

pred = model.predict(X_test)
lower_base, upper_base = pred - q, pred + q
print(np.mean((y_test >= lower_base) & (y_test <= upper_base)))       # ~0.90

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

Конечная выборка в коде: при малом n покрытие плывёт вверх

Верхняя граница 1 − α + 1/(n+1) не абстракция — на маленькой калибровке метод заметно перекрывает и сильно колеблется. Прогоним базовую схему при разных размерах калибровки, по 200 случайных разбиений каждый:

def split_coverage(cal_size, seed):
    Xc, Xt, yc, yt = train_test_split(X_rest, y_rest, train_size=cal_size, random_state=seed)
    m = len(yc)
    r = np.abs(yc - model.predict(Xc))
    qq = np.quantile(r, min(np.ceil((m + 1) * (1 - alpha)) / m, 1.0), method="higher")
    p = model.predict(Xt)
    return np.mean((yt >= p - qq) & (yt <= p + qq))

for cs in (20, 100, 1000):
    cov = np.array([split_coverage(cs, s) for s in range(200)])
    print(cs, round(cov.mean(), 3), round(cov.std(), 3))
# 20    0.93   0.05    — перекрытие и большой разброс
# 100   0.905  0.02
# 1000  0.901  0.006   — у 0.90, разброс мал

При n = 20 верхняя граница допускает до 0.90 + 1/21 ≈ 0.95, и среднее покрытие действительно уходит выше 0.90, а разброс отдельных прогонов огромен. С ростом калибровки и среднее садится на 0.90, и разброс сжимается — это единственный параметр, которым тут стоит управлять.

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

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

Где базовый интервал проседает

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

low  = X_test[:, 0] < 2
high = X_test[:, 0] > 8
cov_low  = np.mean((y_test[low]  >= lower_base[low])  & (y_test[low]  <= upper_base[low]))
cov_high = np.mean((y_test[high] >= lower_base[high]) & (y_test[high] <= upper_base[high]))
print(round(cov_low, 2), round(cov_high, 2))     # например, 0.99 и 0.80 при среднем 0.90

В спокойной области интервал избыточно широк и покрывает почти всё, в шумной слишком узок и промахивается чаще обещанного, а в среднем выходит ровно 0.90. Маргинальная гарантия соблюдена, условная — нет, и это фундаментальное свойство фиксированной ширины. Дальше — два способа сделать ширину переменной.

Адаптивность через квантильную регрессию (CQR)

CQR обучает две квантильные модели на уровни α/2 и 1 − α/2, которые сами дают переменный коридор, а нонконформность измеряет, насколько истина вылезает за него. Калибровочный квантиль этой величины поправляет границы ровно настолько, чтобы вернуть покрытие.

lo = GradientBoostingRegressor(loss="quantile", alpha=alpha / 2, random_state=0).fit(X_train, y_train)
hi = GradientBoostingRegressor(loss="quantile", alpha=1 - alpha / 2, random_state=0).fit(X_train, y_train)

score = np.maximum(lo.predict(X_cal) - y_cal, y_cal - hi.predict(X_cal))   # выход за коридор
q = np.quantile(score, np.ceil((n + 1) * (1 - alpha)) / n, method="higher")

lower, upper = lo.predict(X_test) - q, hi.predict(X_test) + q
print(round(np.mean((y_test >= lower) & (y_test <= upper)), 3))            # ~0.90
print(round((upper - lower)[low].mean(), 2), round((upper - lower)[high].mean(), 2))  # узко / широко

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

width = upper - lower
edges = np.quantile(width, [0, .25, .5, .75, 1.0])
for i in range(4):
    b = (width >= edges[i]) & (width <= edges[i + 1])
    cov = np.mean((y_test[b] >= lower[b]) & (y_test[b] <= upper[b]))
    print(f"ширина {edges[i]:.2f}-{edges[i+1]:.2f}: покрытие {cov:.3f}")

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

Адаптивность через нормализацию остатков

Второй путь к переменной ширине не требует квантильных моделей. Обучается отдельная модель масштаба ошибки σ̂(x), и нонконформность нормируется на неё: |y − ŷ| / σ̂(x). Интервал получает ширину, пропорциональную ожидаемой ошибке в этой точке.

mu = GradientBoostingRegressor(random_state=0).fit(X_train, y_train)
res = np.abs(y_train - mu.predict(X_train))
sigma = GradientBoostingRegressor(random_state=1).fit(X_train, res)        # модель масштаба ошибки

sig_cal = np.clip(sigma.predict(X_cal), 1e-3, None)
s = np.abs(y_cal - mu.predict(X_cal)) / sig_cal
q = np.quantile(s, np.ceil((n + 1) * (1 - alpha)) / n, method="higher")

sig_test = np.clip(sigma.predict(X_test), 1e-3, None)
half = q * sig_test
lower, upper = mu.predict(X_test) - half, mu.predict(X_test) + half
print(round(np.mean((y_test >= lower) & (y_test <= upper)), 3))            # ~0.90, ширина переменная

Модель σ̂ обучена на тех же остатках, что и μ, поэтому масштаб лучше оценивать на отдельной части данных, иначе он оптимистично занижен. CQR обычно даёт более точную форму интервала, нормализация дешевле и не требует квантильной модели — выбор между ними определяется тем, есть ли у вас удобный квантильный регрессор.

Классификация: LAC и APS с пококлассовым покрытием

Для классификации простейшая нонконформность — единица минус вероятность истинного класса (вариант LAC): в множество попадают все классы с вероятностью не ниже порога.

from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier

Xc, yc = make_classification(n_samples=8000, n_classes=5, n_informative=8,
                             n_features=20, random_state=0)
Xtr, Xr, ytr, yr = train_test_split(Xc, yc, test_size=0.5, random_state=0)
Xcal, Xte, ycal, yte = train_test_split(Xr, yr, test_size=0.5, random_state=0)
clf = RandomForestClassifier(random_state=0).fit(Xtr, ytr)
m = len(ycal)

# LAC
s_lac = 1 - clf.predict_proba(Xcal)[np.arange(m), ycal]
q_lac = np.quantile(s_lac, np.ceil((m + 1) * (1 - alpha)) / m, method="higher")
lac_sets = clf.predict_proba(Xte) >= (1 - q_lac)
print(round(lac_sets[np.arange(len(yte)), yte].mean(), 3), round(lac_sets.sum(1).mean(), 2))

Покрытие около 0.90, а средний размер множества — честный сигнал неопределённости: на простых объектах множество сжимается до одного класса, на спорных разрастается. У LAC покрытие ровное в среднем, но неравномерное по классам. APS это выравнивает: его оценка — накопленная масса отсортированных по убыванию вероятностей до истинного класса включительно.

def aps_scores(proba, y):
    order = np.argsort(-proba, axis=1)
    sorted_p = np.take_along_axis(proba, order, axis=1)
    cum = np.cumsum(sorted_p, axis=1)
    rank = (order == y[:, None]).argmax(axis=1)
    return cum[np.arange(len(y)), rank]

q_aps = np.quantile(aps_scores(clf.predict_proba(Xcal), ycal),
                    np.ceil((m + 1) * (1 - alpha)) / m, method="higher")

proba_t = clf.predict_proba(Xte)
order_t = np.argsort(-proba_t, axis=1)
sorted_t = np.take_along_axis(proba_t, order_t, axis=1)
cum_before = np.cumsum(sorted_t, axis=1) - sorted_t        # масса до добавления класса
take = cum_before < q_aps                                  # включаем до пересечения порога
aps_sets = np.zeros_like(proba_t, dtype=bool)
np.put_along_axis(aps_sets, order_t, take, axis=1)
print(round(aps_sets[np.arange(len(yte)), yte].mean(), 3), round(aps_sets.sum(1).mean(), 2))

Теперь сравним покрытие по каждому классу — здесь разница LAC и APS видна напрямую:

for c in range(5):
    mask = yte == c
    print(c, round(lac_sets[mask, c].mean(), 2), round(aps_sets[mask, c].mean(), 2))

У LAC покрытие гуляет от класса к классу, у APS держится ровнее ценой чуть большего среднего размера множества. Выбор между ними — это выбор между компактностью и равномерностью покрытия по объектам.

Без отдельной калибровки: jackknife+ и CV+

Split-схема отдаёт калибровочную выборку и не использует её для обучения, что дорого на малых данных. Jackknife+ обходит это: для каждого объекта обучается модель без него, считается leave-one-out остаток, и интервал нового объекта строится из предсказаний этих моделей, сдвинутых на соответствующие остатки.

Гарантия чуть слабее — не ниже 1 − 2α, — но данные используются полностью, а на практике покрытие обычно близко к 1 − α. CV+ — это K-фолдовая, вычислительно посильная версия того же.

from sklearn.model_selection import KFold

X_all = np.vstack([X_train, X_cal]); y_all = np.r_[y_train, y_cal]
kf = KFold(n_splits=10, shuffle=True, random_state=0)
fold_models, loo_res = [], np.empty(len(y_all))

for tr, va in kf.split(X_all):
    mdl = GradientBoostingRegressor(random_state=0).fit(X_all[tr], y_all[tr])
    fold_models.append(mdl)
    loo_res[va] = np.abs(y_all[va] - mdl.predict(X_all[va]))    # остатки на отложенном фолде

# для нового x: нижняя/верхняя границы — квантили объединённых {pred_k(x) ∓ loo_res_i}
preds = np.array([mdl.predict(X_test) for mdl in fold_models])  # (folds, n_test)

Дальше границы CV+ берутся как нижний α- и верхний (1−α)-квантили по объединённым предсказаниям фолд-моделей, сдвинутым на LOO-остатки. Это компромисс между расходом данных и стоимостью обучения, и на средних выборках он выгоднее одиночного split.

Под сдвигом распределения: взвешенный конформный и ACI

Обмениваемость ломается в реальных системах первой, и тогда покрытие плывёт. Если меняется только распределение признаков (сдвиг ковариат), а зависимость ответа прежняя, работает взвешенный конформный метод: калибровочные оценки перевзвешиваются на отношение плотностей прод-к-калибровке, которое оценивается классификатором «калибровка против прода» — тем же приёмом, что и в adversarial validation.

from sklearn.linear_model import LogisticRegression

d = LogisticRegression(max_iter=1000).fit(
    np.vstack([X_cal, X_prod]), np.r_[np.zeros(len(X_cal)), np.ones(len(X_prod))])
p = d.predict_proba(X_cal)[:, 1]
w = p / (1 - p)        # вес калибровочной точки, дальше — взвешенный квантиль остатков

Под произвольным дрейфом этого мало, и тогда работает адаптивная конформная инференция (ACI): уровень подстраивается онлайн по тому, попадали мы в интервал или нет. После каждого шага α корректируется на ошибку покрытия, и долгосрочная доля промахов сходится к α при любом, даже состязательном, сдвиге.

gamma, alpha_t = 0.02, alpha
misses = []
for t in range(len(y_stream)):
    lvl = np.clip(1 - alpha_t, 0.0, 1.0)
    q_t = np.quantile(recent_scores(t), lvl, method="higher")   # остатки в скользящем окне
    lo_t, hi_t = pred_stream[t] - q_t, pred_stream[t] + q_t
    miss = not (lo_t <= y_stream[t] <= hi_t)
    misses.append(miss)
    alpha_t = alpha_t + gamma * (alpha - miss)                  # промах сужает уровень, попадание расширяет
# np.mean(misses) -> около alpha независимо от характера дрейфа

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


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

Валидность даётся даром при любой оценке нонконформности, поэтому вся работа уходит в эффективность: CQR и нормализация выравнивают условное покрытие переменной шириной, APS делает покрытие ровным по классам, size-stratified coverage служит честной диагностикой адаптивности, jackknife+ и CV+ возвращают отданные под калибровку данные, а взвешенная схема и ACI удерживают покрытие, когда обмениваемость ломается. Цена — отдельная калибровочная выборка и более широкий ответ там, где модель не уверена.


Разобраться с конформными интервалами проще, когда уверенно чувствуешь себя в базовых задачах регрессии и понимаешь, как работает модель под капотом. На бесплатных открытых уроках OTUS можно пройти этот путь от градиентного бустинга до практического решения задачи на Python, познакомиться с преподавателями‑практиками и задать вопросы по формату обучения.

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