🗺️ План усиления value-модели: богатые фичи + Oracle

О чём это

Мы уже выжали, что могли, из поиска (2-ply expectimax, 🎓 2-ply Expectimax поверх value-модели): ~52% против aggressive, балансируя на пороге. Следующий рычаг — сама оценка. Сейчас модель видит только материал (7 фич), потому что python-пайплайн не умеет генерировать ходы. План: считать богатые фичи в движке (единый источник и для обучения, и для игры), с честным учётом их стоимости на инференсе, и использовать под это грант Oracle ($300/30 дней) — на CPU, не на GPU.

Связанные: 🎓 2-ply Expectimax поверх value-модели · Что такое LightGBM · 03 Идеи по улучшению алгоритмов


1. Корень проблемы (напоминание)

dicechess-ev/features.py честно пишет в шапке: «features computable from FEN + dice alone. No move generator». Отсюда все фичи — материальные (MATERIAL_COLUMNS = 7) + состав костей (DICE_COLUMNS = 6). Модель не отличает две позиции с одинаковым материалом — мобильность, безопасность короля, угрозы для неё не существуют. Это и есть потолок: 1-ply ≡ greedy.

Позиционные фичи требуют генерации ходов. Генератор ходов есть только в движке (Scala, source of truth). Значит богатые фичи считаем там.


2. Железное правило: train == serve

Сейчас материальные фичи продублированы вручную в двух местах: features.py (Python, обучение) и OnnxFeatures.extract (Scala, инференс). Совпадают «по договорённости» — хрупко. С богатыми фичами дублировать формулы на двух языках — гарантированный источник train/serve-skew.

Единый экстрактор фич в движке

Фичи вычисляет один объект в движке (расширенный OnnxFeatures / новый FeatureExtractor). Он используется:

  1. при обогащении обучающих данных (enrichment-шаг гоняет его по каждой строке CSV → фича-колонки);
  2. при инференсе в OnnxEvalSearch / OnnxExpectimaxSearch.

Python больше не считает эти фичи — он читает готовые колонки из обогащённого CSV. Совпадение train/serve гарантировано тем, что это буквально один и тот же Scala-код.


3. Какие фичи добавить — и чего они стоят

Ключевая тонкость, которую нельзя игнорировать: фича платится на каждом листе поиска. У 2-ply на ход ~360k листьев (K=8 × 56 бросков × сотни ответов). Фича в 1 мс × 360k = 6 минут на ход — нежизнеспособно. Поэтому фичи делятся по стоимости:

ФичаИсточник в движкеСтоимостьГде применима
материал (7, есть)OnnxFeatures.extract~µsвезде
мобильность (число псевдо-легальных ходов / атакованных клеток)MoveGenerator.generateMoves~µs–десятки µsвезде, в т.ч. листья 2-ply
king safetyEvaluator.evaluateKingSafety~µsвезде
P(захват короля) короля/ферзя, своя и соперникаKingCaptureProbability.{king,queen}CaptureProbability~мс (DFS по 216 броскам)дорого для листьев; см. ниже

Главная идея — распределение бюджета: богатая оценка vs глубокий поиск

KingCaptureProbability — почти идеальный предиктор в dice chess (условие победы = захват короля; фича прямо кодирует «насколько я близок к потере короля», интегрируя по будущему броску соперника). Но она дорогая. Отсюда два взаимоисключающих режима на одну и ту же модель:

  • 1-ply + дорогие фичи (KCP): сотни оценок на ход × мс = сотни мс/ход — приемлемо. KCP даёт зрение, ради которого мы городили lookahead, но статически и дёшево по числу вызовов.
  • 2-ply + только дешёвые фичи (мобильность, king-safety): сотни тысяч листьев × µs — тоже приемлемо.

Самый интересный эксперимент: 1-ply+KCP против 2-ply+материал. Гипотеза — 1-ply с KCP догоняет или обгоняет 2-ply на материале, будучи проще. Это прямой замер «где компуют полезнее: в оценке или в глубине».

На обучении стоимость не проблема: KCP × 1.3M строк один раз (часы) — ровно то, что параллелится на Oracle CPU (см. §5).


4. Архитектура пайплайна

graph LR
    DB[(prod DB<br/>dexus)] -->|"ExportTrainingDataApp<br/>(как сейчас)"| RAW["raw CSV<br/>fen, dice, result…"]
    RAW -->|"enrichment (Scala, движок)<br/>FeatureExtractor по строкам"| RICH["enriched CSV<br/>+ mobility, king-safety, KCP…"]
    RICH -->|"features.py читает колонки"| TRAIN["LightGBM train<br/>(dicechess-ev)"]
    TRAIN --> MODEL["oracle-N.onnx"]
    MODEL -->|"тот же FeatureExtractor на инференсе"| BOT["OnnxEvalSearch /<br/>OnnxExpectimaxSearch"]

Enrichment вынесен отдельным шагом на CSV (не внутрь экспортёра), чтобы CPU-тяжёлую часть отвязать от хоста БД (dexus тесен по RAM) и гонять где угодно — в т.ч. на Oracle. Вход/выход — файл, зависимость — движок (JVM). Место: раннер в dicechess-analytics (уже зависит от движка) или маленький модуль движка.


5. Как использовать Oracle ($300 / 30 дней) — CPU, не GPU

Пошаговый how-to

Провижнинг, bootstrap и запуск — в отдельном раннбуке: Oracle Cloud — раннбук. Ниже — только зачем и что.

GPU нам не нужен (и, вероятно, недоступен)

LightGBM на табличных фичах учится за ~15 с на CPU; данных (~79k независимых партий) хватает только на маленькую модель, которая и на CPU быстра. GPU не поднимает качество — только скорость итераций, которой у нас и так довольно. Плюс SR на GPU-лимит ранее отклонили → скорее всего доступен только CPU. Это удачно совпадает с тем, что вся тяжёлая работа тут — CPU-bound.

Три CPU-задачи под грант:

  1. Обогащение 1.3M строк фичами (особенно KCP): embarrassingly parallel map по строкам. На многоядерном инстансе — минуты-часы вместо суток на homelab.
  2. Честные большие арены: сначала засеять Random бота в арене (сейчас findBestMove(state)new Random(), из-за чего гейт шумит ±4 п.п.), затем гонять тысячи партий по вариантам (набор фич × 1-ply/2-ply × K) параллельно. $300 CPU = очень много арена-часов.
  3. Свипы гиперпараметров LightGBM параллельно.

Шейпы (ориентир): Free Tier VM.Standard.A1.Flex = 4 OCPU / 24 ГБ (always-free) — уже годится. PAYG под 0.01/OCPU-ч (напр. 64 OCPU × неделя ≈ 0.03/vCPU-ч. Хватает с запасом; можно держать живой инстанс на оба дня хакатона под демо.


6. Порядок работ и метрики успеха

Метрик две (модель нужна и для бота, и для grading в analytics-UI): сила (win-rate в арене) и калибровка/AUC (для оценки ходов игроков).

  1. Починить сид арены — иначе нечем честно мерить. Мелкий фикс в OnnxExpectimaxArenaRunner/BotMatchRunner (передавать засеянный Random).
  2. Единый FeatureExtractor в движке — расширить OnnxFeatures: сперва дешёвые фичи (мобильность, king-safety). Инференс уже через него.
  3. Enrichment-раннер (Scala, CSV→CSV) + прогон на Oracle.
  4. Переобучить (dicechess-ev) на дешёвых фичах → замер AUC/калибровки + арена (1-ply и 2-ply).
  5. Добавить KCP в экстрактор → отдельная ветка модели; эксперимент 1-ply+KCP vs 2-ply+материал на Oracle (много партий).
  6. Победивший вариант → новый oracle-N в парке house-bots (паттерн (модель, поиск), новый релиз/деплой).

Честный риск

Ожидаем, что KCP-фичи дадут заметный скачок (прямое зрение на условие победы). Но это гипотеза — проверяется замером. Дешёвые фичи (мобильность/king-safety) могут дать лишь скромную прибавку. Поэтому порядок: сперва дешёвая быстрая итерация, KCP — как отдельный сильный эксперимент.