Да - там все честно. Изначально ракету строил и оптимизировал и вылизывал до блеска все что только можно. Но не в этом счастье, как говорится. Если ваш учебный пример будет не на 3700 эло сразу играть как у меня, а , скажем, на 2600 это ж не будет проблемой?
Я решил переписать (как я это вижу) реализацию и генератора ходов и т.д.
Чтобы у нас не было расхождений, какие моменты нужно учесть?
1 lsb у вас a1 (у меня было а8)
2 что еще?
Это основное. Я бы рекомендовал вам не писать изначально на битбордах. К ним привыкнуть надо да и по сути после того как оценку передают нейросетям их полезность уже под вопросом.
Ну и конечно структуры попроще делайте. Все эти вложенные ссылки в списке ходов , наверное, красиво, но достаточно просто массива где нулевой элемент - счетчик количества в списке.
Не обессудьте
Переписываю потихоньку все.
Но мне нравятся битбоарды и потом будет легче из учебного материала сделать какой ни какой, а движок.
Пусть с натяжкой, но свой Engine
Ну смотрите . На почте у вас лежит очень неплохой мой шаблон для старта. Который, пусть и писал я всего день, но он уже отлажен, рабочий, и в нем как раз нет ничего лишнего и его очень легко будет поддерживать не опасаясь что со временем что-то забудешь, исправишь, а так за ним столько кишок потянется, что уже и не отследишь .
Что касается его пригодности для полноценного движка - так вполне себе. Там пару оптимизаций подскажу, которые сразу реактивную тягу добавляют и которые, к слову,вообще не зависят от того "на битбордах или нет". Кстати можем потом сравнить скорости перфоманса.
достаточно просто массива где нулевой элемент - счетчик количества в списке.
Не очень удобно этим пользоваться.
Сейчас любой язык содержит класс List или Array, где уже все есть
Дело не в том, что там в каком языке уже есть. Драматургия шахматного программирования заключается в том, что 2 главные стихии в нем это Скорость (узлов в секунду) и Точность (оценки в нейросети). Которые друг с другом в противовесе : в одном теряешь, в другом находишь. В этом смысле все эти удобные для программиста "классы" в удобных для программииста языках нафиг, по сути, не нужны и даже вредны : на Точность (оценки) способ хранения ходов не влияет аж вообще никак, а вот в Скорости любое усложенение над простейшим типизированным массивом данных может потерять. А значит - потенциально опасно и не нужно. Не нужны никакие усложнения там где нам нужно в перфомансе полторы сотни миллионов узлов в секунду "вынь и положь". Самое простое для компиллятора = самое быстрое. Даже если програмиисту придется чуть-чуть лоб почесать. Лоб потерпит, а узлы в секунду - нет
В этом смысле все эти удобные для программиста "классы" в удобных для программииста языках нафиг, по сути, не нужны и даже вредны
Какие то вредны
Но в данном случае класс выполняет все те же операции по реаллокации и изменению счетчика и не более.
Притом в коре либе типа STL это сделано вряд ли тупее, чем своя личная разработка
Люди десятилетиями эти вещи напильником пилили.
А вот глюков насовать можно легко
Да - глюки это бывает. С другой стороны люди десятилетиями функционал допиливали, а не скорость. И он наверняка хорош. Но нужен не функционал, а скорость. И ради стакана полезного молока не хочется целую корову тащить за собой . Впрочем это все дело вкуса. Критерием тут Эло является.
Да - глюки это бывает. С другой стороны люди десятилетиями функционал допиливали, а не скорость. И он наверняка хорош. Но нужен не функционал, а скорость. И ради стакана полезного молока не хочется целую корову тащить за собой . Впрочем это все дело вкуса. Критерием тут Эло является.
Для базового функционала пилить уже нечего. Только производительность.
А вот тут много что есть.
Даже память выделять можно по разному.
Впрочем да, это все предмет тестов.
А вот доска не должна становиться вдруг цилиндрической, как когда-то внезапно обнаружил Михал Моисеич
Свою реализацию буду делать параллельно, потому что запутался
Booot помогите в вашей реализации сделать оценку , а я попробую прикрутить UCI, а то нет никакого движения по нейросети
Оценку в моей реализации какую хотите? Что-то простое чтобы пойти в функции перебора или , по взрослому, в нейросети пойдем с прицелом на финальный уже вариант?
В коде шаблона разобрались? Там вроде комментариев я от души накидал. Правда это - вариант без оптимизаций. Там в паре мест можно хорошо прибавить в скорости.
Тогда чуть позже напишу дорожную карту как будем двигаться в сторону нейросети. С нее тогда и начнем. А уже переборные алгоритмы несложные оставим напоследок.
Норм! . А я небольшой баг у себя нашел : неправильно обновляю счетчик 50 ходов. Для перфоманса это вообще неважно, а вот в практической игре может навредить в переборе - ничьи по этому правилу просматривать. У меня он обнуляется только при взятии и превращении. А надо - при ходе пешки (любом) . Поправим потом, когда прикрутим оценку и я пару реактивных ускорителей навешу на генератор ходов перед релизом.
Теперь про дорожную карту : задача стоит масштабная , причем самое противное что маленький баг на каком-то из этапов приводит к полной неработоспособности всей технологии. А там все этапы связаны и пока не получилось все сразу нельзя считать что какой-то из этапов благополучно завершен. Я тут уже собаку сожрал на этом.
Сначала немного теории : NNUE это всего лишь про быстрый многократный проход по нейросети для вычисления оценки. Его основная идея - хранить результаты перемножения матриц первого слоя (самого большого и самого долгого по вычислению). Результаты эти называют "аккумулятор". И потом лишь быстро многократно обновлять его значения в процессе движения в переборе от корня к листьям пользуясь тем, что после каждого хода изменения в нашей строке даннных (там где нули и единицы) очень небольшые и можно обойтись без постоянного полного перемножения заменив его лишь сложением кусочков матрицы.
Как это устроено в математике. Вспоминаем наш проект про неподвижного короля. У нас на входе в нейросеть - строка данных длинной (1,192) состоящая из нулей и единиц, где "1" - фигура стоит на поле. Строка эта при перемножении на веса 256 нейронов первого слоя (192,256) и добавлении 256 соответствующих биасов этих нейронов давала результат на выходе каждого из этих нейронов (1,256).
Именно этот результат и называют аккумулятором в NNUE. Далее мы пропускали эти все значения через функцию активации, потом шли на 2 слой, потом на выходной слой и получали результат (оценку). Теперь смотрим что такое единицы и нолики в плане перемножения матриц. Представим что у нас на доске вообще нет фигур. Все входы - нолики. Так как любое число w*0=0 то выходом первого слоя будет строка (1,256) состоящая лишь из биасов каждого нейрона (которые надо добавлять после перемножения весов на данные). Это и будет нулевым значением нашего аккумулятора (выходов первого слоя).Биасы.
Теперь посмотрим что будет если на нашей пустой доске на каком-то поле появляется фигура (все нули остались а одно значение теперь - единичка). Так как любое число w*1=w, то к каждому из 256 значений "нулевого аккумулятора" нужно будет прибавить теперь соответствующий вес в каждом нейроне для появившегося ненулевого элемента. И таким образом вместо полного перемножения заново всех этих здоровых матриц первого слоя мы сможем теперь обойтись сложением 256 пар "аккумулятор + вес ненулевого элемента в каждом нейроне". Если эта фигура потом снова исчезает с доски, делав ее снова пустой то, соответственно, единичка снова становится нулем и опять вместо полного перемножений 2 самых больших матриц надо будет всего лишь вычесть из 256 значений аккумулятора 256 значений ставшего нулевым веса в каждом нейроне и получить снова нулевой аккумулятор. Вот на этом нехитром принципе и построен апдейт при движении по дереву. Если мы посмотрим что означает любой шахматный ход из корня в глубину в этих терминах, то это будет "снятие с доски" фигуры не поле "откуда" и "появление на доске" фигуры на поле "куда". При взятии - еще убирание с доски побитой фигуры. Рокировка - 2 таких хода (королем и ладьей). Превращение - появление новой фигуры. Как бы все.
НО ЕСТЬ ОДИН ЖИРНЫЙ НЮАНС , КОТОРЫЙ ВЫДЕЛЮ КАПСЛОКОМ : ВСЕ ЭТИ АПДЕЙТЫ АККУМУЛЯТОРА ВОЗМОЖНЫ ТОЛЬКО В ЦЕЛОЧИСЛЕННЫХ ЗНАЧЕНИЯХ. ОКАЗЫВАЕТСЯ FLOAT ПРИ МНОГОКРАТНОМ ОТНИМАНИИ-ДОБАВЛЕНИИ МОЖЕТ ТУПО ОКРУГЛЯТЬ ПРОМЕЖУТОЧНЫЕ РЕЗУЛЬТАЫ И ТЕМ САМЫМ ПОСТОЯННО "ПОДТЕКАТЬ" ТОЧНОСТЬ. ЧЕГО НИКОГДА НЕ СЛУЧАЕТСЯ ПРИ ОТНИМАНИИ-ДОБАВЛЕНИИ ЦЕЛОЧИСЛЕННЫХ ОПЕРАНДОВ.
Поэтому нам необходимо перевести уже обученную будущую сеть (а обучается она всегда в float32) в целочисленные значения и работать уже с ними. Это еще тот гемор и в принципе - краеугольный камень всей технологии. Тут надо и на елку влезть (получить всю нейросеть в целочисленных значениях) и джинсы целыми оставить (в точности не потерять когда значения из нейросети вычислять будем, при этом стараться не получать переполнения по битам на сумматорах , а потом обратно в диапазон [0..1] оценку полученную переводить).
Поэтому я пока в замешательстве как вам это все передать. В отличии от "просто нейросети" отдать вам это на "делайте так" это практически гарантированно обречь вас на недели (а то и месяцы) увлекательного, но по большей части безрезультатного гемора. Даже под присмотром. Там куча нюансов , связанных как с тонкостями обучения нейросети (чтобы веса потом помещались в целочисленное значение) так и с тонкостями представления данных аккумулятора для будущего его использования в движке о которых вы пока не догадываетесь и на которых я в свое время все шишки пособирал. У меня уже на эту тему написанный фреймворк целый как в питоне так и в Делфи, драгоценный и отлаженный. Я подумаю как лучше. Может быть - придется вам его целиком адаптированный и передавать лишь вкратце обьясняя что там и нафига. Либо вообще на нем вашу сеть обучить , оцифровать и с вами только внедрить. Там реально сложно - поверьте.