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

推荐订阅源

N
News and Events Feed by Topic
Malwarebytes
Malwarebytes
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
Cybersecurity and Infrastructure Security Agency CISA
F
Future of Privacy Forum
C
Cisco Blogs
T
The Exploit Database - CXSecurity.com
A
Arctic Wolf
S
Securelist
K
Kaspersky official blog
S
Schneier on Security
T
ThreatConnect
T
Tenable Blog
Spread Privacy
Spread Privacy
T
True Tiger Recordings
AWS News Blog
AWS News Blog
F
Fox-IT International blog
量子位
T
Threatpost
V
Vulnerabilities – Threatpost
C
CERT Recently Published Vulnerability Notes
Cisco Talos Blog
Cisco Talos Blog
GbyAI
GbyAI
宝玉的分享
宝玉的分享
腾讯CDC
G
Google Developers Blog
aimingoo的专栏
aimingoo的专栏
Cyberwarzone
Cyberwarzone
有赞技术团队
有赞技术团队
S
SegmentFault 最新的问题
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
U
Unit 42
雷峰网
雷峰网
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
O
OpenAI News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
The Register - Security
The Register - Security
MyScale Blog
MyScale Blog
小众软件
小众软件
A
About on SuperTechFans
Last Week in AI
Last Week in AI
Y
Y Combinator Blog
博客园 - 三生石上(FineUI控件)
美团技术团队
Google Online Security Blog
Google Online Security Blog
P
Proofpoint News Feed
MongoDB | Blog
MongoDB | Blog

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

Восстание масс в обществе спектакля и отчуждение труда в царстве количества: что делать во времена всеобщего упадка? Не умеешь работать с ИИ? Тебя заменит тот, кто умеет Как интеллект становится уязвимостью под давлением Не надо так: три типичные ошибки, которые приводят ко взлому Заметки про код-стайл в C++ Забытый мультиколор (часть 1) Культура ест стратегию на завтрак: почему не работает долгосрочное планирование Советское ИИ: Забытые гении Как оплатить iCloud в России в 2026 году без смены региона Apple ID Глубокая интеграция месседжинга с бизнес процессами в фреймворке NodaLogic Контекстные менеджеры в Python за пределами with open(): пишем свои и упрощаем код Пароль против уборщицы Выяснились детали мега-IPO SpaceX, а также первый прибыльный квартал Anthropic Люди с психическими расстройствами – новая нефть? Когда нейросети перестанут галлюцинировать? И почему на «что за дичь» они несут ещё большую дичь? Мессенджер HalChat теперь в Google Play: 3 года разработки, ИИ в браузере и квест с модерацией Реверс-инжиниринг Xiaomi Smart Band 10 Когда памяти мало Среда повседневности как объект проектирования: что общего у горца, серотониновой ямы и митохондрий AGENTS.md создавали, чтобы помогать агентам. Я использую его, чтобы их вычислять Почему устанавливают join_collapse_limit = 20 Почему устанавливают join_collapse_limit = 20 Эрик Рис, автор Lean Startup: Почему хорошие компании становятся плохими после IPO Context-driven Reusable Form Pattern: Масштабируемая архитектура для Create / Edit / Create-from-Source Пузырьковая сетка, кошачья стая и не только — неожиданные источники вдохновения для QoS-алгоритмов ___, или «Заголовок намеренно оставлен пустым» ИИ-боты сканируют даже логи TLS-сертификатов. Любая информация используется для обучения LLM Нейросеть оживить фото ИИ: Как оживить фото нейросетью в 2026 году? Разбираемся в ML без воды: от базы до Attention. Часть 5: Метрики качества В поисках «кофейного Грааля». Как человечество пытается сварить идеальный кофе и какие рецепты предлагают…математики Программатик: Часть 2 — OpenRTB Интернет до бесконечных лент: каким был 2010 год Перезапуск TrueIndex: что изменилось в рейтинге языков программирования Проектный холст: как менеджеру подбирать «краски» управления под разные команды «Метафизика в формулах: математическое ядро «Веры Паломника — Исход» Java и постквантовый TLS Marcli: Markdown Терминал Кнопочный смартфон с 5G за 2800 рублей — разбираем и изучаем китайскую диковинку Где неприятности — там и жизнь Разворачивайте платформы: stackfile Мой путь в Microsoft Мобильная разработка за неделю #631 (18 — 24 мая) Что не так с Mixtape, и почему не все довольны новой игрой? Стоматология каменного века. Как неандертальцы лечили зубы 59 тысяч лет назад Почему классическое управление проектами часто не работает в IT-продуктах Строительство Саркофага. Часть 2. Бетонные реки и стальные берега РАЗРАБОТКА ПАРАМЕТРИЗИРУЕМОГО МОДУЛЯ CORDIC-АЛГОРИТМА НА SYSTEM VERILOG Вариационное исчисление как метафора свободы выбора: от градиентного спуска к онтологии пути Ekahau Sidekick и RSSI‑offset: физические ограничения метода и пять независимых причин неточности клиентской модели Колесо потока против раскола Обзор интересных особенностей переворачивающихся при умножении чисел В С неопределённое поведение повсюду MCP-агрегатор: объединяем инструменты для LLM в один сервер Дата-центры в космосе: как Google и SpaceX готовят новую инфраструктуру для ИИ Google готовит замену Chromebook: какими будут ноутбуки Googlebook Пользователь пишет issue, агент меняет сайт. Да, я это сделал Корпоративные конфликты в ИТ-секторе: механика судебной защиты активов и субсидиарных рисков Цена одной опечатки: Как три неверные буквы сорвали киберограбление на миллиард долларов Как я победил спам в своих email аккаунтах Whitepaper Сбера «AI-Disrupt PDLC»: разбор для тех, кто пишет код RustDesk Pro в России не купить. После долгих лет администрирования мы собрали своё честное решение Не пики, а бассейны: почему эволюция — это блуждание по графу жизни Как Gemini 3.5 Flash сломали ради красивых графиков (и почему она обходит 3.1 Pro только на бумаге) Вредоносная атака на Laravel-Lang meta-attention is all you need Как перестать путаться в IP-адресах серверов Сколько стоят ошибки в арбитраже: декомпозиция ценообразования на судебные услуги в Москве Разбираемся в ML без воды: от базы до Attention. Часть 4: kNN Vortex: фреймворк для тех, кого задолбала итальянская кухня в репозитории Использование тепла ЦОД в мире и РФ Часть 4. Скорость света — технические детали Не цитируй мне нейросеть Что сейчас с Project Loom? Примеры и код Рождённые в Сумерках Meta 1 мая показала как они хранят ключи от ваших бэкапов WhatsApp. Разбираю архитектуру и сравниваю Линт проектов: собираем ESLint, Prettier и Stylelint в один пакет Reasoning-модели сломали мой промпт-инжиниринг. Год переучиваюсь РБМК: enfant terrible Как я собеседую менеджеров AI-продуктов для крупного Enterprise Парадокс рынка труда: конкуренция выросла, но не везде, нанимать легче, но не везде Модификаторы в Blender: осваиваем Boolean «Бесплатно» — это красный флаг: почему мы доверяем не тем (опрос) Стратегия выживания в эпоху ИИ Новая теория обещает переписать фундамент всей математики MTP у Qwen3.6 в llama.cpp обещает ×2 по скорости. Я прогнал ту же модель через своего агента — и получил обратное [Перевод] Соль и перец в безопасности паролей Что такое «статьи-зомби» CodeGraph: граф кода для Claude Code вместо grep по файлам. Разбираю архитектуру и проверяю бенчмарки Мессенджер Ласточка. Часть 3 Google представила Gemini Omni — универсальную ИИ-модель. Роботы работают, счастлив человек Что у SpaceX с патентным портфелем перед IPO? Делегирование, которому можно научиться у промпт‑инженеров Feature Based Clean Architecture. Часть 5: Масштабирование FBCA и теоретико-графовый анализ зависимостей Настройка типизации формы React Hook Form (≥ v7.44.0) + Zod с разными входными и выходными типами Feature Based Clean Architecture. Часть 4: FBCA: формализация границ ответственности в NestJS-модуле Корпорация «Святые Технологии». Работа мечты (рассказ) CyLab Security Academy: как Carnegie Mellon превратила CTF в полноценную обучающую платформу Feature Based Clean Architecture. Часть 3: Архитектурный риск циклов в NestJS: ROI решений на горизонте пяти лет Домашний сервер без белого IP: безопасная публикация сервисов через VPS, обратный SSH-туннель и Caddy Почему не взлетели дирижабли? Часть 22: Митягина, Эйхенвальд и Ховрина, первый в истории женский экипаж дирижабля
Шахматные программы V. Оценочная функция
Rom77 · 2026-05-25 · via Все публикации подряд на Хабре

Уровень сложностиСредний

Время на прочтение14 мин

Охват и читатели11

FAQ

В этой части мы рассмотрим вторую важную составляющую шахматных программ - оценочную функцию. Долгие годы она находилась в роли "пасынка" для процесса разработки. Но на то существовали объективные причины.

< К предыдущей части

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

Используемая программами "рукописная" оценочная функция на основе выделенных параметров состояла из материальной и позиционной составляющей. Первая заключается в подсчете количества фигур на доске, своих и соперника. Вторая учитывает их взаимное расположение.

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

Уровень оценки резко подрос в недавние годы с появлением нейросети NNUE, которая заменила "ручную" оценочную функцию. Новая структура оценки была заимствована из японской игры сеги, где разработчики программ наткнулись на идею принципиально иной архитектуры на базе нейросети, тонко адаптированной к текущим возможностям программ и железа.

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

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

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

При подсчете позиционных факторов наиболее важными являются таблицы ценности полей (Piece-Square Tables, PST, PSQ, PSQT). Они назначают бонус или штраф к цене фигуры в зависимости от ее положения. Для примера, конь на краю доски получает минус к оценке позиции. В простейших программах PST, это обычно несколько таблиц, с коэффициентами на определенных координатах, для каждого типа фигуры.

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

Реализацию классической оценочной функции в предыдущих версиях Стокфиша можно посмотреть на странице:

https://hxim.github.io/Stockfish-Evaluation-Guide/

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

Оценочная функция тюнинговалась вручную или с помощью метода SPSA (Simultaneous Perturbation Stochastic Approximation), путем проведения множества партий программы с самой собой и постепенного подтягивания весов каждого параметра к оптимальным значениям. Для оптимизации параметров поиска этот метод используется и сейчас.

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

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

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

Такая нейросеть имеет достаточно простую структуру. Она состоит всего из четырех полносвязных слоев, три из которых очень невелики. На вход сети слева подается позиция, которую требуется оценить, и после преобразований на каждом слое на выходе справа мы получаем ее оценку, которую можно использовать в программе.

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

Далее единицы и нули со входа перемножаются на линиях на коэффициенты (веса) нейросети, а результаты суммируются по сходящимся направлениям во втором столбце ячеек. Каждой линии соответствует одно число нейросети. Таким образом из 80 тысяч ячеек с закодированной позицией слева мы получаем группу вычисленных параметров в 512 ячейках справа. Соединяющая их группа линий представляет собой входной слой нейросети.

После небольшого преобразования чисел во втором столбце ячеек, мы аналогичным образом производим вычисления на линиях следующего слоя нейросети и на выходе из него получаем 32 числа в третьем столбце ячеек (из 512 во втором). Этот слой называется первым скрытым слоем.

Аналогичным образом производятся вычисления во втором скрытом слое - из 32 чисел в третьем столбце мы получаем 32 числа в четвертом.

На выходном (четвертом по счету) слое все 32 числа аналогичным образом сводятся в единую оценку. На схеме она обозначена круглой ячейкой справа. После небольшого преобразования этого числа мы получаем "готовую к употреблению" оценку позиции. Эта оценка идет в дерево перебора, где применяется, как результат вычислений функции оценки позиции - evaluate(pos). О том, каким образом она используется в процессе поиска по дереву вариантов, рассказывается в предыдущих и следующих частях статьи.

*****
Успеху NNUE способствовала очень тщательная адаптация к архитектуре современных CPU. Для того чтобы подобные сети работали быстро требуется использовать специальные технические решения. NNUE "играет" на некоторых нюансах в отношении тонкостей кодирования и преобразования данных, которые предоставляют значительные преимущества и экономят вычислительные ресурсы для данной архитектуры.

Большая часть оптимизаций NNUE сосредоточена на входном слое. Давайте на базе приведенной выше схемы рассмотрим подробнее, как они приносят выгоду нейросети такого формата.

В нейросеть архитектуры NNUE позиция подается на вход (на схеме с левой стороны) не так, как это обычно принято. В отличие от записи позиции стандартной строкой FEN, здесь напротив ценится не компактность, а разреженность.

Ячейки на входе, в которые заносится позиция, представлены для всех возможных сочетаний координат и типов фигур. В упрощенном представлении, для каждого из шести типов фигур двух цветов существует по 64 возможных расстановки на доске. То есть, для всех возможных положений фигур всего будет 2х6х64 = 768 ячеек. Единица в каждой из них будет свидетельствовать, что данная фигура находиться на данном поле, а ноль, что отсутствует.

В реальной сети NNUE идут еще дальше. Здесь ячейки свидетельствует о наличии по определенным координатам двух фигур - короля одного из цветов и какой-то другой фигуры, любого цвета. То есть каждая ячейка является "именной" и привязана к определенной координате и типу фигур. Таким образом, когда на входе позиция заносится в ячейки, всего доступно (2х64) х (5х2х64) = 80 000 ячеек - для двух королей, при 64-х возможных координат позиции короля на доске, и 5 типов других фигур, черных и белых, с 64 возможными положениями на доске каждой.

В ячейку проставляется единица, только если обе фигуры находятся на координатах ячейки. Например (см. на схеме) если в позиции черный король находится на поле e8, а ферзь на поле d8, то в ячейке будет единица, а все ячейки для иных их расположений, где хотя бы одна из фигур отсутствует, заполняются нулями. Такое бинарное (0 или 1) представление позиции на входных ячейках имеет свои преимущества, о чем будет рассказано ниже.

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

Понятно, что в большинстве входных ячеек находятся нули, поскольку либо одна, либо вторая фигура по координатам ячейки отсутствуют. Король и фигура находятся вместе для каждого возможного сочетания координат в очень редких случаях. По факту заполняются единицами максимум 2х30 входных ячеек - это наиболее возможное количество фигур на доске по обоим путям, за вычетом королей. Таким образом, количество "единичных" сочетаний король-фигура будет равно количеству фигур на доске.

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

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

Получается, сеть сильно сэкономит вычислительные ресурсы и в то же время у каждой позиции будет свой оригинальный набор вычисляемых параметров. Это обеспечит, в некотором смысле, разную "нейросеть" на входном слое для каждой позиции. Имея 80 000 входных условий, сеть может гибко подстроиться под каждую позицию, оперативно адаптируя, таким образом, свою "базу знаний".

Избыточное количество знаний (коэффициентов) позволяет отчасти компенсировать малое количество вычислений, которое критично для относительно слабосильных, в сравнении с видеокартами, векторов центральных процессоров.

Кроме всего прочего, используя связки король-фигура, мы как бы "подсказываем" нейросети важные сочетания фигур, которые сеть вследствие своей небольшой глубины не успевает/не имеет возможности вычислить самостоятельно. В каком-то смысле здесь мы заранее "вычислили" для нее важные признаки позиции.

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

На каждой линии выходящей из входной ячейки находится один множитель/коэффициент нейросети, поэтому всего существует 10 млн. коэффициентов или 2х40000х256 = 20 млн. возможных вычислений (верхний и нижний путь используют одинаковые коэффициенты). В то же время, реальное количество вычислений для подаваемой на вход позиции на "линиях" входного слоя нейросети не превысит 2х30х256 = 15 000 умножений и столько же сложений полученных результатов. Конечно, при сокращении количества фигур на доске в процессе игры, количество "единиц" и соответственно вычислений станет еще меньше.

По большому счету, на входном слое не обязательно производить любые умножения. Задавая на входе позицию лишь нулями и единицами, можно избежать операции умножения на 1, поскольку она ничего не даст кроме самого числа. Соответственно на входном слое можно выполнять лишь операции суммирования в ячейки второго столбца по сходящимся линиям от "единичных" коэффициентов нейросети.

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

То есть нужно из сумм в ячейках второго столбца (аккумулятора) вычесть веса предыдущего положения фигуры и прибавить веса ее нового положения. В некоторых случаях, например при взятиях, придется еще удалить значения по линиям от снятой фигуры, так как в ее ячейке теперь ноль. И только при движении короля изменится положение каждой "единичной" связи, поэтому придется пересчитать все суммы, но только по одному пути, то есть 30х256 коэффициентов.

Это основные оптимизации на входном слое, делающие архитектуру NNUE такой удачной, при ее в общем-то небольшом размере и простоте.

В конечном итоге, сохраняя в некотором отношении гибкость большой нейросети, NNUE имеет объем вычислений в тысячи раз меньше. Разумеется не без последствий, но тем не менее со своей выгодой.

*****
Давайте перейдем к более предметному описанию нейросети NNUE в том виде, в котором она была первоначально реализована в Стокфише.

За входной слой отвечает Feature Transformer. С приложением всех оптимизаций, вычисления на нем сводятся к простому суммированию во второй столбец ячеек (accumulator) "активированных" весов сети (weights), т.е. весов с линий из ячеек с единицами (features).

Как уже отмечалось выше, такие вычисления можно проводить двумя способами - полное (refresh), с пересчетом для всех активных линий или частичное (update), только для изменившей свое положение фигуры. Во втором случае используются сохраненные ранее состояния аккумулятора из стека предыдущих позиций. Примерный код реализации можно посмотреть здесь:

https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md#feature-transformer-2

Полученные значения во всех ячейках складываются с еще одним отдельным числом для каждой ячейки (Bias). Если итоговый результат в ячейке выходит за пределы допустимого диапазона значений, то есть итог вычислений перейдет через верхний или нижний предел, то он "подрезается" до ближайшего разрешенного значения с помощью Clipped ReLU.

Далее вектор во втором столбце ячеек из 256 чисел белого короля, и аналогичный вектор из 256 чисел для черного, стыкуются в единый вектор из 512 чисел в порядке очереди хода (с каждым ходом обе части меняются местами). Попутно производится преобразование (quantisation) целых чисел входного слоя из 16-битного формата int16 к более простому 8-битному представлению int8. Он будет использоваться для остальных слоев.

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

Далее вычисления подхватывает первый скрытый слой (Linear layer 1). Входной вектор из 512-ти чисел для этого слоя (второй столбец ячеек на схеме) перемножают на матрицу первого скрытого слоя нейросети (таблица чисел) с помощью аффинного преобразования. Оно заключается в ротации (повороте) блоков чисел для более удобного вычисления и позволяет быстро и эффективно выполнять умножение вектора на матрицу, то есть перемножать строки чисел на столбцы таблицы чисел.

https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md#linear-layer-4

Аналогично, на выходе слоя к суммам в ячейках добавляют bias и производится обрезка полученных значений с помощью Clipped ReLU.

https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md#clippedrelu-2

Пройдя по всем оставшимся слоям подобным образом, мы получаем оценку позиции.

https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md#putting-it-together

В целом, большинство из оптимизаций входного слоя не применимы на скрытых слоях. Поэтому объем вычислений на них мог бы многократно возрасти. Чтобы удержать его в разумных пределах размерность таких слоев приходится сильно уменьшать. Сокращение числа вычисляемых параметров необходимо еще потому, что вычислительные мощности векторных блоков процессоров гораздо скромнее, чем у тензорных ядер видеокарт. А перемножать приходится в полносвязных сетях, что дополнительно увеличивает объем вычислений.

На первом скрытом слое количество вычисляемых параметров уменьшается с 512-ти до всего 32-х чисел. На втором скрытом слое их количество остается таким же небольшим.

Соответственно, на первом скрытом слое производится 512 х 32 = 16 384 умножений чисел и затем столько же сложений полученных результатов. Плюс указанные выше преобразования. На втором скрытом слое производится 32 х 32 = 1024 умножений и столько же сложений. Выходной слой содержит еще меньше вычислений.

Для перемножения и сложения этих чисел используются специальные блоки центральных процессоров. В отличие от тех же ALU, они перемножают сразу группы чисел, собранных в определенном порядке - так называемые вектора. Поэтому каждое перемножение пары 256-битных векторов AVX2 позволяет производить за один такт сразу 32 умножения 8-битных целых чисел. При использовании FMA в тот же единственный такт укладывается еще и сложение полученных результатов. А учитывая, что в ядре процессора содержится сразу несколько векторных блоков, то соответственно каждое ядро процессора производит за такт еще кратно больше вычислений.

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

*****
Приведенная выше схема вычисления оценки позиции примерно соответствует тому виду, в котором она была перенесена в шахматную программу Стокфиш из сеги в августе 2020 года. Первый официальный релиз Стокфиша 12 с нейросетью, состоялся в сентябре и не слишком отличается от рабочих версий программы.

Исходный код для структуры нейросети, которой посвящена эта часть статьи, можно найти в папке nnue программы Стокфиш 12. В современном Стокфише 18 общая структура нейросети изрядно усложнилась. Тем не менее, базовая архитектура и общее количество слоев (то есть глубина сети), введенная когда-то в сеги, остались прежними. Это все еще полносвязная нейросеть "мелкого заложения".

Более подробный обзор ранней и современной реализации структуры нейросети NNUE, применительно к практике, можно найти на отдельной страничке Стокфиша на гитхабе. На нее уже делались отсылки выше:

https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md

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

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

Помимо этого, в некоторых слоях вычисления были разбиты на несколько этапов. Добавлены так же различные остаточные (residual) связи.

На выходых слоях нейросети появилось несколько путей вычислений (buckets), из которых выбирается только один, в зависимости от количества материала на доске. Такая "развилка" вычислений позволяет до некоторой степени адаптировать оценку к позициям из разных стадий партии.

Кроме исходных текстов самой программы Стокфиш 18, за подробной информацией по всем вопросам связанным с общей архитектурой, получением данных для обучения и режимах тренировки сети следует обращаться к wiki Стокфиша на гитхабе. Здесь все расписано гораздо подробнее и применительно к реальной программе, в том числе по всем темам, которых мы лишь кратко касались выше.

https://github.com/official-stockfish/nnue-pytorch/wiki

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

Продолжение следует...

Все части цикла: