🎓 Rao-Blackwellized Monte-Carlo
Это математика оценщика вероятности, который используется в Position Equity — оценка вероятности, когда сыгранных партий мало. Исходник — MonteCarloEquity.scala в dicechess-engine-scala (Scala 3, кросс-компилируется в WASM и исполняется в браузере).
Что такое один rollout
Rollout — это одна смоделированная партия от текущей позиции до развязки, которую прогоняет движок. Это не реальная сыгранная партия и не один бросок кубиков. Движок из позиции многократно «бросает» 3 кубика, доигрывает вперёд (до maxPlies полуходов) и фиксирует, кто захватил короля.
Цифра «64 rollouts» на панели означает, что текущая оценка усреднена по 64 таким доигровкам (первый батч); дальше счётчик растёт 64 → 128 → … к цели, а доверительный интервал сужается.
Rao-Blackwellization: почему «64» достаточно
Наивный Monte-Carlo на каждом rollout получил бы бинарный исход (0 или 1) и усреднял бы их — высокая дисперсия. Наш оценщик вместо этого аналитически интегрирует на каждом полуходе точную вероятность взятия короля по всем 216 комбинациям трёх кубиков (KingCaptureProbability):
survive = 1
для каждого полухода (ходит сторона S):
p = P(S берёт короля соперника этим броском) # точно, по всем 216 броскам
winsOf(S) += survive * p # вклад победы — аналитический, не 0/1
survive *= (1 - p) # масса «дожили дальше»
перейти в случайное выжившее продолжение # сэмплируется ход БЕЗ взятия короляТо есть вероятность победы стороны — это сумма по её полуходам произведения «дожили до сюда» на «берём короля здесь»:
Остаточная масса выживания на горизонте maxPlies → undecided. Три величины whiteWin + blackWin + undecided = 1.
Так как победная масса добавляется аналитически (это и есть Рао-Блэквеллизация), а не как сэмпл Бернулли, дисперсия на один rollout во много раз меньше. Оценщик сам это измеряет:
— во сколько раз дисперсия меньше, чем у наивного 0/1 MC с тем же средним. Значения — выигрыш; — оценка точная (позиция решается на первом броске).
Стандартная ошибка и сходимость
Стандартная ошибка оценки White-win:
CI сужается как : чтобы уполовинить ошибку, нужно в 4 раза больше rollouts. Дисперсия считается онлайн по алгоритму Уэлфорда (один проход, без хранения всех значений).
| rollouts | типичный CI (для ) |
|---|---|
| 64 | широкий (≈ ±12 п.п.) — «Low confidence» |
| ~250 | ≈ ±5 п.п. — обычно уже показываем вердикт |
| 4000 | ≈ ±1.5 п.п. — уверенно |
Поэтому в UI вердикт по кубу показывается только когда ширина CI ≤ MC_VERDICT_MAX_CI_WIDTH (0.1).
Пулинг батчей (на стороне клиента)
Движковый estimateEquity — одноразовый: каждый вызов делает фиксированное число rollouts с нуля. Чтобы уточнять прогрессивно без пересчёта, Web Worker зовёт его батчами по 64 (меняя seed) и пулит результаты в pool.ts. Каждый батч — i.i.d. выборка той же позиции, поэтому объединение точное.
Объединённое среднее по всем rollouts:
Объединённая дисперсия — через закон полной дисперсии (внутригрупповая + межгрупповая суммы квадратов):
где — восстановленная по-rollout-ная дисперсия батча . Так оценка тикает 64 → 128 → …, а CI монотонно стягивается.
Отличие от C++-референса
Эта Scala-версия — порт C++-движка (rabestro/dice-chess-engine), но с одним отличием. Референс продвигает rollout в любой сэмплированный ход, включая взятие короля; взятие терминально, поэтому за ним получается доска без короля, по которой (некорректно) играют дальше — второпорядковое смещение. Будучи источником истины по правилам, наш оценщик обусловливает продолжение на выживании: продвигается только через ходы без взятия короля (событие с вероятностью , которое уже учтено в survive). Аналитические по-полуходные члены — и значит самопроверка дисперсии — при этом не меняются.
Параметры
| Параметр | По умолчанию (движок) | В UI | Смысл |
|---|---|---|---|
rollouts | 500 | 64 на вызов (батч) | жёсткий лимит rollouts |
maxPlies | 1000 | 60 | горизонт одного rollout |
targetError | 0.0 (выкл) | — | адаптивная остановка по достижению SE |
minRollouts | 128 | — | минимум до адаптивной остановки |
SurvivalEpsilon | 1e-5 | — | масса выживания, ниже которой rollout считается решённым |
MaxRollRetries | 64 | — | попыток найти бросок с выжившим продолжением |
Детерминизм
При фиксированном
rollouts/targetErrorи seed оценка воспроизводима. Путь с дедлайном (для тайм-бюджетного бота) — недетерминированный (зависит от скорости машины), поэтому для тестов не используется.
Связанные заметки
- Position Equity — оценка вероятности — где и как это показывается в UI.
- WebAssembly — почему движок исполняется как WASM в браузере.