🎯 Вероятности при малом числе партий: prior + сглаживание вместо Монте-Карло

О чём это

Обсуждение 2026-08-08. Аналитика показывает win-rate по ключу normalized_fen + dice_sorted. Для частых позиций хватает эмпирики из БД, а для редких сегодня предполагается фолбэк на MonteCarloEquity — он медленный и отвечает не на тот вопрос. План: заменить «MC при малом N» на калиброванный prior от модели + байесовское сглаживание, MC оставить валидатором. Контекст по данным: собрано уже 1,5 млн партий (DiceChess.com + play.jc.id.lv + немного Beturanga.com), окно вычислений — Azure-кредит €150 / 20 дней (до ~2026-08-28) либо локальный MacBook Pro.

Связанные: what-is-lightgbm · what-is-nnue · value-model-improvement-plan · model-improvement-results-and-oracle-bots-roster · monte-carlo-vs-aggressive-analysis · opening-book-as-built

Замерено 09.08.2026: ядро этого плана не подтвердилось — читать вместе с журналом

Brier-бенчмарк на холдауте из 2 млн ходов (журнал, запись «ночь 09.08») опроверг два пункта ниже:

  • сглаживание (w + α·p_prior)/(n + α) не нужно — смесь проигрывает чистой модели во всех сегментах, а α подбирается в максимум сетки, то есть «эмпирику почти игнорировать». Модель точнее эмпирики даже там, где n ≥ 10;
  • изотоническая пост-калибровка не нужна — модель уже откалибрована (0.2132 против 0.2132 по Brier).

Причина, которую план не учёл: 97% позиций встречаются ровно один раз, а 0,26% дебютных покрывают 25,8% ходов. Эмпирика существует только в голове распределения, и даже там она хуже модели. Формулировка «эмпирика для частых, prior для редких» перевёрнута: модель — основной оценщик везде.

Что из плана подтвердилось: отказ от Монте-Карло (замер: MAE 4,5 пп, сохраняет 43% сигнала, 25 с/позицию) и выбор LightGBM на rich+KCP как оценщика. Разделы 1 и 3 остаются в силе; разделы 2 (шаги 2–3) читать как «идея, проверенная и отклонённая».


1. Почему Монте-Карло здесь плох — и почему это не лечится

MonteCarloEquity (движок, порт C++-референса) — Rao-Blackwell-роллауты: на каждом ply аналитически интегрируется точная вероятность взятия короля (KingCaptureProbability, все 216 исходов), а дальше позиция продвигается случайным ходом. У него две независимые проблемы:

Проблема 1 — смещение случайной политики. Rao-Blackwellization честно убирает дисперсию (и это красиво сделано), но оценка отвечает на вопрос «кто выиграет, если обе стороны ходят случайно». Случайная игра размывает материальное и позиционное преимущество: всё сводится к «у кого чаще выпадает удар по королю». Это систематическая ошибка — добавление роллаутов её не уменьшает, только сужает доверительный интервал вокруг смещённого ответа. Дельты против БД в mise run eval:mc — ровно это.

Проблема 2 — цена. Каждый ply роллаута = один DFS KingCaptureProbability по 216 исходам (~10–15 мс, см. замеры в model-improvement-results-and-oracle-bots-roster) плюс generateAllLegalTurnPaths для случайного продолжения. При 200 роллаутов × десятки ply это порядка минут на одну позицию — в горячий путь API не ставится в принципе.

Вывод

MC — не «медленный, но точный», а «медленный и смещённый». Ускорять его бессмысленно: смещение политики останется. Нужен другой каркас.


2. Предлагаемый каркас: эмпирика + калиброванный prior

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

Шаг 1. Калибровка: оценка модели → вероятность

Любая оценка позиции (LightGBM-скор, выход сети, даже HunterEval) — это ещё не вероятность. Одноразово подгоняем отображение eval → P(win) на исходах реальных партий: логистическая регрессия (или изотоническая, если форма кривой нестандартная). Это ровно то, что Stockfish делает с centipawn → win% и Leela со своей WDL-головой. После калибровки модель для любой позиции выдаёт честную вероятность p_prior за микросекунды-миллисекунды.

На ком калибруем = чью вероятность показываем

Эмпирика по всем игрокам отвечает «как выигрывают в среднем», а domain-conventions.md аналитики фиксирует референс: win-rate сильных игроков. Калибровать надо на партиях сильных (фильтр/взвешивание по рейтингу), иначе prior и эмпирика будут мерить разное, и сглаживание начнёт их «спорить». С тремя источниками (DiceChess.com, play.jc.id.lv, Beturanga) это важно вдвойне: пулы игроков разные — источник и рейтинг должны быть фильтрами калибровки.

Шаг 2. Байесовское сглаживание (empirical Bayes / shrinkage)

Показываемая вероятность:

P̂ = (wins + α · p_prior) / (n + α)
  • n — партий в БД для этого узла, wins — из них выигранных (ничья = 0.5);
  • p_prior — калиброванная вероятность от модели;
  • α — «вес prior’а в эквивалентных партиях», обычно 10–50.

При n = 0 показываем чистый prior, при n ≫ α — чистую эмпирику, между ними — плавный переход без порогов и швов. α не выдумывается, а подбирается минимизацией log-loss/Brier на отложенной выборке с временны́м сплитом по партиям (обучение до даты T, проверка после — позиции одной партии не должны попадать в обе стороны).

Шаг 3. Иерархия для ключа (позиция, бросок)

Ключ explorer’а normalized_fen + dice_sorted в ~56 раз реже, чем сама позиция (56 мультимножеств броска). Двухуровневое сглаживание:

эмпирика (pos, dice)  →  сглаживается к  эмпирике (pos)  →  та к  p_prior

Prior при этом может быть roll-aware: ценность лучшего хода при данном броске, если модель/поиск умеет оценивать после броска (наши _nodice-модели оценивают до броска — обе точки зрения полезны).

Шаг 4. Честный UI

При малых n любая «точная» цифра — иллюзия: биномиальная ошибка при n = 25 — это ±20 пп (95% CI), при n = 400 — ±5 пп. Показывать рядом с вероятностью счётчик партий и/или интервал.


3. Чем заполнить prior: меню быстрых оценок

Ключевая поправка после замеров KCP (см. model-improvement-results-and-oracle-bots-roster): стоимость prior’а — это стоимость его фичей, а не модели. Сам LightGBM-инференс — микросекунды, но KCP-фичи к нему стоят ~40–60 мс на позицию.

ВариантЦена/позициюДанных нужноСтатус у насВердикт
LightGBM на material+dice~мксмало✅ baseline обученнижняя планка, слеп позиционно
LightGBM на rich-фичах~мс1,5M партий — с запасом✅ df_rich.onnxфаворит для массового прекомпьюта
LightGBM на KCP-фичах~40–60 мс (фичи!)с запасом✅ kcp_nodice.onnxточнее (AUC 0.711), но только голова/on-demand
Raw-board сеть (value)~мс CPUдесятки млн позиций — теперь есть✅ rawboard_l000.onnxтёмная лошадка, см. ниже
NNUE-style сетка~мксдесятки млн + лучше с дистилляцией❌ нетотдельная история → what-is-nnue
Дистилляция Hunter → студент~мкс после обучениягенерируется бесплатно🟡 distill.py естьприватный трек (см. open-core)
Expectimax d1–2 поверх KCP~десятки мсне нужнычастично в движкеточная «тактическая» фича/страховка

Policy ≠ value: почему raw-board рано хоронить

Гейты ev#16 мерили raw-board как игрока (пре-ранкер/политика) против oracle-3 — и там она проигрывает. Аналитике нужен не игрок, а калиброванный value: планка «полезный prior с весом α≈20 партий» на порядки ниже планки «обыграть oracle-3». Включить rawboard_l000.onnx в бенчмарк стоит — это бесплатно. Тем более что данных стало в 10 раз больше: главный ограничитель сырых досок ослаб.

Open-core

Модели, обученные на исходах партий, чисты для публичных репо. Веса, дистиллированные из Hunter, — де-факто публикация закрытого eval’а: такие артефакты держим приватно (прецедент: модель в приватном GHCR-образе у house-ботов).


4. Что меняет масштаб 1,5 млн партий

  • Позиций-строк — десятки миллионов (при ~30–50 ходов на партию). Для LightGBM это комфортная зона уже давно; для сырых досок и NNUE-класса сетей — впервые реалистичная.
  • Сплит только по партиям (все ходы партии делят исход; по-строчный сплит = утечка). В dicechess-ev/data.py это уже сделано правильно.
  • Гетерогенность источников — фича, а не шум: source × rating как фильтры калибровки; сильные игроки DiceChess.com — референс, партии наших ботов с play.jc.id.lv — отдельный срез (боты играют не как люди).
  • Эмпирическая голова стала плотнее, но хвост остался хвостом: комбинаторика позиций растёт быстрее любой коллекции. Каркас prior+сглаживание нужен при любом размере БД.

5. Вычислительный план: MacBook против Azure (€150 / 20 дней)

MacBook Pro (бесплатно, часы):

  • свежий экспорт db:export-training c 1,5M партий, rich-обогащение;
  • LightGBM-переобучение, калибровка, подбор α, Brier-бенчмарк — всё это CPU-минуты/часы;
  • первые итерации сетей (PyTorch/MPS) — тоже потянет.

Azure-кредит — на то, что реально параллелится или требует GPU:

  • KCP-обогащение новой выгрузки — «the cloud-CPU run»: при ~50 мс/позицию миллион позиций ≈ 14 CPU-часов, т.е. многоядерная ВМ прожёвывает голову БД (несколько млн уникальных позиций для прекомпьюта книги) за считанные дни;
  • GPU-ВМ для raw-board/NNUE-экспериментов, если бенчмарк покажет, что сети того стоят.

Гочи кредитной подписки (уже наступали)

На кредитной подписке currentSpend = 0.00 — бюджетный алерт не сигналит о конце кредита. При круглосуточной ВМ прошлый замер давал ~€9.4/день ⇒ €150 ≈ 16 дней, меньше календарного окна. Правила: ВМ гасить после прогона, тяжёлое — батчами, прогресс — чекпойнтами, дедлайн ~2026-08-28 держать в голове.


6. План действий

  1. Свежий экспорт на 1,5M партий + rich-обогащение (MacBook), KCP-обогащение головы (Azure CPU).
  2. Brier-бенчмарк на временнóм holdout — сравнить оценщики: сырая эмпирика / MC / kcp_nodice / df_rich / rawboard_l000 (+ Hunter статикой, приватно). Harness: расширить EvaluateMonteCarloApp или скрипт в dicechess-ev (evaluate.py уже умеет калибровочные таблицы).
  3. Выбрать prior по log-loss за единицу стоимости серва; откалибровать на сильных игроках; подобрать α (и α₁/α₂ для иерархии).
  4. Прекомпьют prior-колонки для головы (opening book / частые позиции), on-demand — для хвоста.
  5. MC перевести в валидаторы (eval:mc остаётся регрессионным тестом каркаса). Если когда-то захочется роллаутов для книги — политикой Hunter с температурой, офлайн, с кэшем, не случайной.

Ожидание: побеждает LightGBM на rich/KCP-фичах как prior сейчас; raw-board/NNUE — кандидаты на апгрейд prior’а после (данных уже хватает), причём каркас (калибровка + сглаживание) при смене prior’а не меняется.