Визуализация статистики


Аналитический инструмент для сравнения качества игры шахматных движков

Ситуация и проблема

Новый шахматный движок претендует превзойти по уровню и скорости все существующие. Он показывает прекрасные результаты, но в некоторых ситуациях необъяснимо терпит поражение. Нужно анализировать большое количество данных (записи партий против программ-конкурентов), выявлять аномалии, вносить изменения в алгоритмы движка, подстраивать коэффициенты, запускать тестовые матчи и сравнивать результаты с прошлыми версиями. Анализ данных с помощью ИИ несёт сомнительную пользу – ИИ упускает важные детали, выдвигает необоснованные предположения, советует неэффективные решения – работа замедляется и идёт по ложному следу.

Исследование

Исследование я начал с погружения в предметную область. Для начала попросил Claude Code разобраться в интерфейсах трёх программ-соперников. А именно, научиться выбирать уровень противника, распознавать позицию, последний сделанный ход, время на часах и признаки окончания партии. После пары автоматически сыгранных партий стало ясно, что распознавание позиции ведётся средствами ИИ и тратит много токенов.

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

Отчёт Claude в терминале: статистика по каждой партии уровня 2 Отчёт Claude в терминале: таблицы результатов партий на уровнях 1 и 2

Отчёты о первых пробных партиях я смотрел в виде таблиц, которые печатал Claude. Польза таблиц очевидна – по ним быстро вскрылись некоторые проблемы движка (например, нехватка времени при игре на сильных уровнях, когда каждый ход обдумывался слишком долго). Когда таблиц много, сверять их неудобно, а впереди нас ждали тысячи партий.

Статистически значимый результат получается только при многочисленных повторениях эксперимента, поэтому матчи состояли из 10-60 партий. Движок играл на одном и том же уровне сложности попеременно белым и чёрным цветом с одним и тем же противником. Статистика по каждой партии записывалась в csv-файл, в итоге получалась сводная таблица матча. Информация о каждом ходе в таблицу не попадала – получилось бы слишком много данных. Пока что надо было просто посмотреть на картину в целом и нащупать подходящие инструменты высокоуровнего анализа.

Таблица матча в Apple Numbers и точечный график: победы, поражения и ничьи по уровням сложности

От таблиц к графикам: данные матча я загрузил в Apple Numbers (аналог Microsoft Excel), добавил 4 столбца, приводящих данные к удобному для отображения виду, и построил график. Получилось именно то, что надо! Видна примерная сила игры движка на каждом из семи уровней, расход времени и аномальные партии (такие партии можно разбирать отдельно по логам).

Решение

За 2-3 дня автоматизировать отображение данных в виде графиков и сконструировать инструмент удобного просмотра и сравнения. Для простоты я выбрал такой стек: Claude мониторит партии, собирает статистику и записывает csv-файлы в директорию /runs. Сервер написан на php, он читает файлы из /runs и передаёт фронтенду. Там обычный html/css и по-минимуму JavaScript. На будущее была припасена идея ускорения: рисовать графики прямо в canvas. Но сперва нужно было определить сам вид графиков.

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

Понятно, что просто вывалить такое количество информации на пользователя нельзя. Но я решил доказать это от противного :) Закинул в Claude Design csv-таблицу и график из Apple Numbers, подробно объяснил как такой график получить, описал вёрстку страницы и предложил Claude придумать как отобразить остальные данные. Конечно, я написал “сделай в стиле современных модных дашбордов”. Любой UX-дизайнер посмеётся над таким наивным заклинанием, но именно веселья мне и хотелось. Однако, получилось грустно:

Не рассчитывайте, что ИИ поможет придумать идею и сделать первый шаг – он поведёт в неверном направлении

Согласно моим критериям качества, это настолько плохой UX, что объяснять и исправлять что-либо бесполезно. Не найдя под рукой карандаша, я нарисовал грубый набросок в Figma. Да, выглядит некрасиво, но вполне годится в качестве иллюстрации к техническому заданию, которое я подробно написал для Claude текстом.

Набросок дашборда в Figma: результаты по семи уровням сложности, график времени партий и расход стека

По итогам двухдневной сессии с Claude Design получилось то, что надо. Слева направо на дашборде отображены 7 уровней сложности: на первом уровне движок думает поверхостно, а на седьмом размышления могут достигать максимальной глубины. Наведение на индикатор партии подсвечивает индикаторы в других блоках - это оказалось очень удобным. Нажатие на график приводит к его расширению. Сначала я сделал расширение при наведении, но это оказалось слишком суетно. Автоматическое увеличение высоты я пока оставил – механизм помогает уместить больше информации на маленьких экранах.

Первая версия дашборда

Каждый проект требует индивидуального, оптимального в своём контексте способа отображения информации. Не существует “универсальных дашбордов” и “проверенных решений” – первые обитают в ученических работах на Behance и Dribbble, а вторые подходят лишь в тривиальных случаях. Так что для шахматного проекта я изобрёл два подходящих типа отображения информации.

Эти графики показывают изменение уровня сложности во времени по всем партиям матча. Полупрозрачные линии насыщают поле графика и таким образом выявляют тенденцию (если она есть). Конец партии обозначен резким обрывом линии вниз – благодаря этому возможно выделить курсором конкретную партию (её линия становится непрозрачной). С помощью этой визуализации удалось выявить и решить проблемы проигрыша по времени и падения качества игры при нехватке времени.

Второй график – круговая диаграмма – показывает выделенный на данный уровень бюджет (максимальное количество позиций, которые разрешено просматривать движку на своём уровне сложности) – это серый диск. У разных уровней разный бюджет (можно посмотреть при наведении мыши), но отображение нормировано так, чтобы диски разных уровней имели одинаковый диаметр и символизировали 100% бюджета. Зелёный сектор показывает две величины: процент ходов, которые превысили бюджет (угол сектора) и на сколько процентов перерасходован бюджет (площадь сектора). Перерасход бюджета – это очень хорошо, несмотря на название: означает что программа думала во время хода противника, и успела обдумать больше, чем ей разрешено. Люди тоже так делают.

Помните, я говорил, что нельзя просто взять и вывалить на пользователя всю информацию? Красная часть круговой диаграммы показывает недорасход бюджета. Она находится точно на своём месте: серый сектор как раз соответствует ходам, которые не превысили бюджет. Заметный неизрасходованный бюджет может означать как “хорошо”, так и “плохо”. Ходы слабого противника были предугаданы и движку не пришлось тратить собственное время/бюджет – это хорошо. Партия затянулось, время поджимало, движку приходилось обрывать поиск, чтобы успеть сделать ход, бюджет не полностью потрачен – это плохо.

Крайние случаи, ошибки и пустые статусы в интерфейсе: здесь всё просто – наблюдаемых величин становилось больше и больше, а логи прошлых партий не содержали соответствующих записей, поэтому нужно было научиться работать с неполной информацией. Как только возник первый такой случай, принцип “нет информации – не отображаем блок” был применён ко всему проекту. Однако, в паре случаев недостающую информацию можно было восстановить приблизительно (например, предсказать падение уровня игры в зависимости от количества сделанных ходов и времени на часах). Сразу прорабатывать эти сценарии не имело смысла, потому что набор наблюдаемых величин не был зафиксирован. Ну а статусы типа “идёт загрузка” сделаны по-простому, так как дашборд запускается локально на компьютере с быстрым SSD. В конце концов, моей целью было создать инструмент за 2 дня.

А на третий день я прикрутил к сайту чат-бота, чтобы можно было обсуждать с ним статистику и шахматы вообще. Обычно чат-боты тратят токены владельца сайта. Я придумал другой способ: пользователю предлагается подключить свой Claude или ChatGPT и чатиться за свой счёт. С Claude это работает даже на бесплатном тарифе, так что пользуйтесь на здоровье! С ChatGPT сложней (можно сделать, но в данном случае получится over-engineering).

Компромиссы

Пока что нельзя погрузиться в подробности игры по ходам. Такое редко нужно, и можно обойтись просмотром логов. Если неохота читать логи – на дашборде есть чат-бот, у которого можно узнать все подробности о ходе партии. В будущем хочется добавить индикацию оценки каждого хода: как позицию оценивает движок, как её же оценивает Stockfish, каков материальный перевес. Эти величины похожи по смыслу, их можно отобразить на одном графике. Отдельный график – время, затраченное на ход и сводная гистограмма расхода времени (отдельно на дебют, миттельшпиль и ендшпиль, потому что движок ведёт себя немного по-разному в разные фазы игры).

Дашборд не предназначен для анализа партии игроком, поэтому оценки вроде “число удачных ходов” и “упущен мат в 2 хода” не нужны. В этом состоит ответ на вопрос, вероятно, возникший у вас: “почему в исследовании не смотрел как сделано у конкурентов”. Данный проект решает совершенно другую, уникальную задачу.

Дашборд работает локально, и время загрузки данных ничтожно. Но я выложил онлайн-версию – там уже видна задержка: в сериях по 60 партий дашборд загружает 427 таблиц, и лишь потом рисует графики. В таких случаях у любого UI-дизайнера срабатывает рефлекс: надо добавить анимированную иконку загрузки. Я поступил иначе. Во-первых, оптимизировал отрисовку графики – там, где css и html вычисляли относительные величины типа процентов, но по факту значения были фиксированы, пересчитал их в пиксели. Профайлер браузера показал двукратное ускорение отрисовки. Во-вторых, собираюсь написать скрипт, который загружает данные партий в sql-базу, а не хранит файлами на диске – тогда чтение станет быстрей. И в-третьих, собираюсь читать данные из базы не скопом (420 партий), а постепенно, по одной партии для каждого уровня сложности – тогда можно сразу рисовать графики для этой партии. Таким образом, поле графиков будет наполняться постепенно, по мере чтения данных – это и есть лучшая анимация загрузки! А главное, при работе с большими данными не обязательно дожидаться полной загрузки – можно начинать работать уже с тем, что загрузилось.

Что для меня сделал ИИ

Со времён Photoshop у меня есть привычка давать осмысленные названия всем объектам. В моей Figma никогда не встретишь “Frame 3754629”. Для совместной работы это было исключительно важно. Теперь, в эпоху ИИ, привычка вкладывать смыслы в названия дала многократное ускорение.

Claude Design определил по моему Figma-наброску иерархию шрифтов и применил во всём проекте. Цвета инфографики правильно закрепились за соответствующими статистическими величинами. Мне не пришлось конструировать дизайн-систему – ясные названия элементов макета стали идеальными ответами на вопросы “как, что и почему”. Например, достаточно было объединить текстовые строки Blitz, Rapid и Classical в auto-layout с названием “Tabs”, чтобы Claude понял какое оформление и поведение применить. Столбики и сектора диаграмм я назвал примерно так же, как колонки в таблице с данными, так что Claude сам их связал.

От себя Claude дорисовал горизонтальные отметки на графиках. С первого раза получилось симпатично. А вот с дополнительными цветами пришлось повозиться, вплоть до ручного вмешательства.

ИИ хорошо справился с заданием сделать тёмную тему. Но стандарты контрастности (accessibility) провалил – об этом пришлось договариваться отдельно и выбирать из предложенных вариантов.

С первого раза получились иконки для светлой и тёмной темы. Остальные пришлось переделывать. Всё-таки, у нейросети нет вкуса.

Конечно, Claude написал весь html, css, JavaScript и php. После просьбы поискать неиспользуемый, избыточный и медленный код ИИ уменьшил и ускорил своё творение вдвое.

А ночами агент мониторил скрипты, гоняющие матчи против программ-оппонентов, находил причины зависания партий, улучшал скрипты распознавания, и заново запускал матчи.

Окна с партиями против программ-оппонентов, запущенные агентом одновременно

Результат

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

Один лишь пример: на самом слабом уровне движок сумел выиграть одну партию в рапид против сильного противника. Расход времени почти не отличался от проигранных партий. Случайность и погрешность статистики? Однако, график предсказаний показал заметный выброс – оказывается, движок предугадал больше 41% ходов противника (это очень много). Поэтому ответные ходы делались быстро, время не поджимало, поиск не обрывался, и партия продлилась до 120 хода (проигранные партии шли 30-40 ходов).

Первая версия: график показывает только время партий, выигранная партия по нему не выделяется Результаты матча: по расходу времени выигранная партия не выделяется, график предсказаний показывает выброс
Слева: показывалось только время. Справа: я добавил число ходов

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

Дашборд помог выявить не только недостатки алгоритмов, но и откровенные баги (в аномальных партиях). Благодаря визуализации удалось настроить плавный рост сложности в зависимости от уровня. А главное, принять важные решения и внести изменения в продукт:

  • Добавился разогрев уровня: первые ходы обдумываются слабей, уровень повышается с каждым ходом, пока не достигнет заданного. Это решает две проблемы: UX (не заставляем пользователя долго ждать в начале партии) и таймаут (программа приберегает время на конец партии).
  • Понижение уровня: когда до конца партии остаётся мало времени, программа снижает уровень игры, чтобы не уронить флажок. Если удалось отодвинуть конец партии прибавкой времени за быстро сделанные ходы, уровень автоматически повышается. Это правило действует на уровнях 2-6.
  • Понижение бюджета на уровне 7: из-за разогрева в быстрых партиях уровень 7 не достигался, а в долгих падал слишком рано. Вместо понижения решено оставить все параметры 7-го уровня, а занижать только число просматриваемых ходов. Благодаря накопленным в кэше оценкам позиций это позволяло всё ещё делать сильные ходы за меньшее время.
  • Правило десяти секунд: перебор вариантов прерывается, если на часах осталось 10 секунд. В этом случае исполняется лучший из найденных к этому времени ходов.
  • Отказ от идеи засчитывать обдумывание на ходу противника в бюджет (изначально половину потраченных ресурсов вычитали из общего бюджета, отведённого соответствующему уровню игры). Это не было похоже на игру с живым противником.

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