x_train = x
y_train = y
model = models.Sequential()
model.add(layers.Input(shape=(x_train.shape,))) # Указываем форму входа
model.add(layers.Dense(256, activation='sigmoid'))
model.add(layers.Dense(16, activation='sigmoid'))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='mse',
optimizer='adam',
metrics=['mae'])
model.summary()
model.fit(x_train, y_train, epochs=100)
model.save('/content/drive/MyDrive/Colab Notebooks/model_1.h5')
print('done saving at')
Прим.
У меня не определены layers и models
А если
model.add(Dense.Input(shape=(x_train.shape,))) # Указываем форму входа
то
AttributeError: type object 'Dense' has no attribute 'Input'
Так - правильно. Во всяком случае "правильно" с точки зрения нашей первоначальной гипотезы о том, что первый наш подход к сети будет таким, о котором мы договорились. Будет ли он финальным и "самым правильным" - вопрос второй.
layers лежит в tensorflow.keras
В принципе удобнее вообще сделать такой импорт :
from tensorflow.keras.layers import Input,Dense
и потом уже определить сеть как
Warning: Spoiler![ Click to expand ][ Click to hide ]
import tensorflow as tf
import keras
import numpy as np
from keras.layers import Input,Dense
from keras.models import Sequential
print('tensorflow:' + tf.__version__)
print('keras:' + keras.__version__)
print('numpy:' + np.__version__)
x = np.zeros((223944, 192))
x.dtype='byte'
y = np.zeros(223944)
f = open('/content/drive/MyDrive/Colab Notebooks/training_data.txt', 'r')
i = -1
while True:
lines=f.readline()
if not lines:
f.close()
break
mylist=lines.split()
wk=int(mylist[0])
wq=int(mylist[1])
bk=int(mylist[2])
i = i + 1
x[i][wk] = 1
x[i][wq+64] = 1
x[i][bk+128] = 1
res=int(mylist[3])
# 1-(Rang/100)
# res = 255 - невозможная,
# 254 уже мат на доске ход черных,
# 253 - пат на доске ход черных,
# 252 битая ничья ход черных и они забирают незащищенного ферзя,
# 0 - ничья, остальное - количество полуходов до мата
y[i] = 1 - (res/100)
if (res == 252) or (res == 253):
y[i] = 0
if (res == 254):
y[i] = 1
# архитектуру нейросетки выберем максимально похожую на ту, что используется в движках :
# 3 Dense слоя по формуле 256-16-1. В качестве активационной функции для всех нейронов всех слоев возьмем сигмоид.
x_train = x
y_train = y
model = Sequential()
model.add(Input(x_train[0].shape))
#model.add(Dense(256, activation='sigmoid', input_shape=(x_train[0].shape)))
model.add(Dense(256, activation='sigmoid'))
model.add(Dense(16, activation='sigmoid'))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='mse',
optimizer='adam',
metrics=['mae'])
model.summary()
model.fit(x_train, y_train, epochs=100)
model.save('/content/drive/MyDrive/Colab Notebooks/model_1.h5')
print('done saving at')
Посмотрим, что получится
Ну и сохранить обученную модель.
Как ее использовать в Делфи?
Наверное назрел этот вопрос.
Да - запустите модель на какое-то большое количество эпох, мы с вами увидим после какой уже не нужно "скотину мучать" и где наступает предел обучения. Увидим ее точность (среднеарифметическую по всей эпохе).
Дальше предлагаю следующее : у вас там где-то я видел питоновский шахматный модуль? Можем сделать промежуточную проверку с файлом модели, как переходом из проекта в проект.
1. В новом проекте грузим обученную модель, и питоновский шахматный модуль получает от юзера позицию и генерит из нее все ходы ферзя (снова играет за белых).
2. Все полученные после этого позиции с ходом черных заносим в numpy массив X размером (кол-во ходов ферзя в позиции,192) предварительно создав его нулевым и заполнив единичками как ранее в файле с выборкой
3. Методом keras.model.evaluate(X) получим оценки всех этих ходов и выберем лучший. Так же можно эти оценки полученные от нейросети сравнить с идеальными оценками из эндшпильных таблиц. Чтоб понять налазит ли вообще эта модель на голову и чего от нее на практике ждать.
4. Ну а дальше уже действительно Делфи. Но там, правда, скучно, относительно долго и противно
Ну... смотря для чего лучше. Я не программист - когда-то в институте паскаль учил, вот и пригодилось для хобби. Но когда общался лично с авторами всеразличных движков от Стокфиша и Шреддера до Комодо и Лейлы, то сошлись все же на том, что в конечной реализации с точки зрения силы игры движка разница инструмента непринципиальна. Что лучше знаешь тем и рубишь. Один фиг 95%+ времени движок AVX2 инструкции крутит, а не откомпилированный в языке высокого уровня код.
Ну я как разработчик четко вижу в профайлере какие процедуры и функции занимают больше всего процессорного времени при переборе в моем движке. Это как раз работа с матрицами (нейросетями). И эти процедуры пишутся не в С++ и не в Делфи, а в ассемблере. Они могут писаться прямо вместе с сишным или паскалевским кодом, но к нему отношения не имеют и транслируются в исполняемый код совершенно самостоятельно. Это отдельные, самостоятельные SIMD процедуры которые действительно могут быть написаны и под AVX2 и под SSE и под другие их типы,поддерживаемые конкретным процессором. Их задача матрицы складывать и перемножать. У меня в ранних версиях тоже была поддержка и SSE 4.2 и AVX512. Потом плюнул - оставил только AVX2. Сейчас ноут себе эппловский обновлю - добавлю еще под NEON и хорош.
Ну а холивары это на любителей Ими пока еще ни одного лишнего пункта ЭЛО к силе движка прибавить никому не удавалось.
А неплохо выглядит! И как раз где-то уже в предел возможностей нашей сетки 256-16-1 уперлись. Среднее арифметическое ошибки 0,005 примерно. В районе 0,5% в диапазоне (0-1). Учитывая что в нашей схеме оценки позиции 1-(Rang/100) между соседними рангами 4% разницы (там только четные ранги в черном файле), то может вполне симпатично получиться . Пробуйте смело в питоне. Потом в Делфи перенесем.
Warning: Spoiler![ Click to expand ][ Click to hide ]
import tensorflow as tf
import keras
import numpy as np
from keras.layers import Input,Dense
from keras.models import Sequential
from keras.src.legacy.saving import legacy_h5_format
print('tensorflow:' + tf.__version__)
print('keras:' + keras.__version__)
print('numpy:' + np.__version__)
x = np.zeros((223944, 192))
x.dtype='byte'
f = open('/content/drive/MyDrive/Colab Notebooks/training_data.txt', 'r')
i = -1
while True:
lines=f.readline()
if not lines:
f.close()
break
mylist=lines.split()
wk=int(mylist[0])
wq=int(mylist[1])
bk=int(mylist[2])
i = i + 1
x[i][wk] = 1
x[i][wq+64] = 1
x[i][bk+128] = 1
#test
s = ''
n = 65283 # line n+1=65284
for j in range(0,193):
if (j==64) or (j==128):
#visual separator
s=s+' '
s=s + str(x[n][j])
model = legacy_h5_format.load_model_from_hdf5('/content/drive/MyDrive/Colab Notebooks/model_200.h5', custom_objects={'mse' : 'mse'})
model.summary()
print(s)
print(np.array([x[65283]]))
print(
model.predict(np.array([x[65283]]))
)
Ну как бы позиция 40-го ранга. Это в идеале соответствовало бы 1-(40:100)=0,6. Дало 0,5657. Многовато, но такие ранги как раз менее характерны для этого эедшпиля. Видим научилась сеть ничейные позиции в основном точно предсказывать
Чисто ради спортивного интереса продолжил обучение сети.
Загрузил сохраненную модель и еще 200 эпох
Warning: Spoiler![ Click to expand ][ Click to hide ]
import tensorflow as tf
import keras
import numpy as np
from keras.layers import Input,Dense
from keras.models import Sequential
from keras.src.legacy.saving import legacy_h5_format
print('tensorflow:' + tf.__version__)
print('keras:' + keras.__version__)
print('numpy:' + np.__version__)
x = np.zeros((223944, 192))
x.dtype='byte'
y = np.zeros(223944)
f = open('/content/drive/MyDrive/Colab Notebooks/training_data.txt', 'r')
i = -1
while True:
lines=f.readline()
if not lines:
f.close()
break
mylist=lines.split()
wk=int(mylist[0])
wq=int(mylist[1])
bk=int(mylist[2])
i = i + 1
x[i][wk] = 1
x[i][wq+64] = 1
x[i][bk+128] = 1
res=int(mylist[3])
# 1-(Rang/100)
# res = 255 - невозможная,
# 254 уже мат на доске ход черных,
# 253 - пат на доске ход черных,
# 252 битая ничья ход черных и они забирают незащищенного ферзя,
# 0 - ничья, остальное - количество полуходов до мата
y[i] = 1 - (res/100)
if (res == 252) or (res == 253):
y[i] = 0
if (res == 254):
y[i] = 1
# архитектуру нейросетки выберем максимально похожую на ту, что используется в движках :
# 3 Dense слоя по формуле 256-16-1. В качестве активационной функции для всех нейронов всех слоев возьмем сигмоид.
x_train = x
y_train = y
model = legacy_h5_format.load_model_from_hdf5('/content/drive/MyDrive/Colab Notebooks/model_200.h5', custom_objects={'mse' : 'mse'})
model.compile(loss='mse',
optimizer='adam',
metrics=['mae'])
model.fit(x_train, y_train, epochs=200)
model.summary()
model.save('/content/drive/MyDrive/Colab Notebooks/model_2.h5')
print('done saving at')
Там "больше" далеко не всегда значит "лучше". Есть такое понятие как "переобучение сети". К нам это не относится потому что мы подаем на сетку ВСЕ возможные данные, но в реальной жизни где обучаешь ее лишь по небольшой частице всех возможных вариантов очень важно , чтобы сетка более-менее корректно обобщала бы их и на те данные, которых в обучающей выборке не было. Ей же потом их предсказывать.
Да тут нервов никаких не хватит с этим гуглом . Я поэтому сразу все себе локально ставлю : вечер потратил, среду настроил и она всегда со мной на ноуте или рабочем компе.
"Переобучение" возникает когда на небольшой обучающей выборке сеть жестко настраивается на уже существующие данные. У нее же задача минимизировать лосс-функцию. Она ее и минимизирует. Может оказаться так, что столкнувшись с новыми данными , которых она никогда не видела и ради которых мы ее , собственно, и обучаем она выдаст очень большую ошибку. В идеале сеть после обучения должна видеть "усредненную картинку" , в которую и вписывать новые полученные данные. Для этого существуют разные методы и , может, позже поговорим.
NNUE это интересно, но это еще сложнее. Давайте сначала перенесем сетку в ДЕлфи, настроим как она есть, а уж потом на ней же потренируемся в NNUE играться. Если получится - там по хорошему нашу сетку изначально нужно обучать в специально ограничивающих гиперпараметрах. Чтобы точно получилось.
Ну так-то вроде сеть еще больше минимизировала ошибку. Забавно где у нее предел и как это отражается на "больших" и "самых интересных" рангах? Может оказаться так, что в угоду минимизации ошибки в ничейных позициях она "загрубляет" выигранные.