У меня не получается вашу модель загрузить (скорее всего у вас версия кераса новее - мне приходиться держать старую чтоб CUDA работала). Пришлите мне пожалуйста на почту финальный код в питоне + сам файлик с обучающей выборкой. Я у себя получу ту же структуру и поиграюсь с ней. Что-то мне не очень нравится что она у вас показывает.
На самом деле я подумал, что при загрузке модели я просто продолжил обучение.
Если в какой нибудь другой задачи дать новые данные и продолжить обучение.
Правда я не понял, после загрузки модели alexlaw wrote:
1. Уберите у себя строчку x.dtype='byte'. Это так не работает - вы в уже созданном массиве меняете тип данных и ВСЯ память что раньше была зарезервирована по 8 байт на ячейку (по умолчанию тип float64 вроде) выделяется уже по 1 байту на ячейку типа байт. Ячеек сразу становится больше в 8 раз. Не 192, а 192*8. Так что убирайте : нам и float подойдет - все равно веса будут умножаться тоже в этом формате.
На будущее : тип данных массива numpy задается СРАЗУ при создании его. Типа x = np.zeros((223944, 192),dtype='byte')
2. При определении первого слоя Input лучше сразу имплицитно задавать формат ваших входных данных. Чтоб не было как у нас : ошибка в массиве + определение по типу "посмотришь как в массиве" дает совсем не то что мы хотим.Сеть раздувается до какого-то нереального количества параметров (у вас их 300+тысяч). Вместо строчки model.add(Input(x_train[0].shape)) дайте строчку
model.add(Input(shape=(192,)))
Сетка после всего этого должна иметь 53537 обучаемых параметров (суммари выводит их). Потом мы с вами их все ручками пересчитаем.
Теперь по переносу из питона в Делфи подробнее. Из сохраненной модели нам нужно перенести и смоделировать следующее:
1. Веса и биасы всех нейронов обученной нейросети. Это удобнее делать по слоям. У нас их 3 : 256-16-1. На вход каждого нейрона входного слоя поступает 192 значения. Значит у каждого из нейронов первого слоя 192 входа. В модели это все хранится в виде матриц. Матрица весов нейронов первого слоя (192,256) =49152 значения. Матрица биасов нейронов первого слоя - 256 значений. Для второго слоя матрица весов (256,16)=4096 значения. Матрица биасов второго слоя 16 значений. У последнего нейрона в выходном слое 16 весов и 1 биас. Если мы все эти количества просуммируем 49152+256+4096+16+16+1, то как раз получим все наши 53537 обученных параметра о которых говорит керас. Все их надо забрать у питона и перенести в Делфи и проверить, чтобы по дороге не потерялись. Опять все делаем через файлик.
Вот вам процедурка в питоне, которая опрашивает сохраненную обученную модель (процедура универсальна), выводит послойно информацию о ней и сохраняет все веса и биасы всех слоев послойно в общий файлик. Замените там имя-путь как там вам удобно. В конечной модели веса и биасы все хранятся в формате float32. 4 байта на параметр. Размер файла для нашей сети предсказуемо должен быть 53537*4=214148 байт.
def analyze_model(model):
f=open('net.dat','wb')
kol=len(model.layers)
print('Model has ',kol,' layers.')
for i in range(kol):
if len(model.layers.get_weights()) > 0:
w,b = model.layers.get_weights()
print(i,model.layers.name,w.shape,w.dtype,b.shape,b.dtype)
f.write(w.tobytes())
f.write(b.tobytes())
f.close()
Вам останется только все это прогрузить в Делфи в предварительно созданные массивы тоже типа float32. Ну и сравнить, конечно, желательно.Глазами или еще как.
2. Перенести активационную функцию (sigmoid). Она в каждом нейроне нашей будущей нейросети в Делфи будет вызываться после функции суммирования. Но тут проще : result:=1/(1+exp(-x))
3. Воспроизвести процедуры перемножения матриц по всем 3 слоям, пропустить их результаты через функцию-сигмоиду и полученные результаты, опять же, сравнить с тем что питон дает. Но это чуть позже. Сначала давайте 2 первых пункта.
ДА вроде как вопросов нет - отработала процедурка. Вы проверьте еще раз что выдает результатом сеть в заведомо ничейных позициях если короля белых в центр доски поставить. На всякий.
Думая сейчас предсказывает в пределах mae.
Epoch 150/150
6999/6999 ━━━━━━━━━━━━━━━━━━━━ 20s 3ms/step - loss: 2.7751e-04 - mae: 0.0026
Проверял в разных позициях.
8/8/8/2K5/8/8/k6Q/8 w - - 0 1
Warning: Spoiler![ Click to expand ][ Click to hide ]
Ну что : переносите это в Делфи и проверяйте чтобы веса и биасы для всех нейронов совпали. Так как мы выгружали матрицы побайтно, то и загружать их в Делфи можно точно так же побайтно. Предварительно определив аналогичные по размерности и по типу.
Для начала загрузил данные сети из файла просто в массив Делфи, чтобы проверить правильно или это работает.
Ну и сохранил данные сети тексовой файл, чтобы был с чем сравнивать
Warning: Spoiler![ Click to expand ][ Click to hide ]
import tensorflow as tf
import keras
import sys
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__)
def analyze_model(model):
#f=open('/content/drive/MyDrive/Colab Notebooks/net2.dat','wb')
f1=open('/content/drive/MyDrive/Colab Notebooks/net2.txt','w')
kol=len(model.layers)
print('Model has ',kol,' layers.')
for i in range(kol):
if len(model.layers[i].get_weights()[0]) > 0:
b0 = model.layers[i].get_weights()[0]
b1 = model.layers[i].get_weights()[1]
print('layers',i,' Weights -',model.layers[i].name,b0.shape,b0.dtype,' Bias -',b1.shape,b1.dtype)
#f.write(b0.tobytes())
#f.write(b1.tobytes())
f1.write(str(b0))
f1.write(str(b1))
f1.close()
np.set_printoptions(threshold=sys.maxsize)
model_150 = legacy_h5_format.load_model_from_hdf5('/content/drive/MyDrive/Colab Notebooks/model_150.h5', custom_objects={'mse' : 'mse'})
model_150.summary()
analyze_model(model_150)
print('done saving at')
Первые 256 значений
Warning: Spoiler![ Click to expand ][ Click to hide ]
Warning: Spoiler![ Click to expand ][ Click to hide ]
...
FUNCTION BytesToSingle(CONST B : ARRAY OF BYTE) : Single;
VAR
S : Single ABSOLUTE B;
BEGIN
Result:=S
END;
procedure Tchess.FormCreate(Sender: TObject);
var
Stream: TFileStream;
Value: array of Single;
ByteVals: array[0..3] of Byte;
i,j: Integer;
str:String;
begin
j:=0;
try
Stream := TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'net.dat', fmOpenRead or fmShareDenyWrite);
try
SetLength(Value, Stream.Size div 4);
while Stream.Position < Stream.Size do begin
Stream.ReadBuffer(ByteVals, Length(ByteVals) * SizeOf(Byte));
Value[j]:=BytesToSingle(ByteVals);
Inc(j);
end;
finally
FreeAndNil(Stream);
end;
except
on E: EStreamError do
Application.MessageBox(PChar(Format('При работе с потоком произошла ошибка %s', [E.Message])), 'Ошибка', MB_OK or MB_ICONERROR);
end;
GUI64.chess.Caption:=IntTostr(Length(Value));
str:='';
for i := 0 to 255 do begin
str:=str+' '+FloatToStr(Value[i]);
if (((i+1) mod 4)=0) and (i<>0) then str:=str+#13#10;
end;
WriteToFile('',0);
WriteToFile(str,1);
...
Первые 256 значений Делфи
Warning: Spoiler![ Click to expand ][ Click to hide ]
Похоже . Загрузите последовательно все и проверьте биасы - их проще руками просмотреть. Если все будет ок - реализуйте сигмоиду процедурой и начинаем реализацию процедур прохода "вперед" по нейросети.
Математически у нас идет преобразование в оценку нашей входной матрицы (1,192) с ноликами и единичками (в Делфи нас интересует оценка только по одной позиции за раз).
1 слой - умножение (1,192)*(192,256)=(1,256). В цикле в Делфи перемножайте строчку на каждый столбец и не забудьте добавить биасы для каждого из 256 полученных результатов. Лучше всего - дать сумматору в цикле сразу начальное значения соответствующего биаса. После пропустить все 256 полученных результатов через функцию сигмоиду и получить выходы нейронов первого слоя.
2 слой - умножение (1,256)*(256,16)=(1,16). То же самое. Не забываем снова про 16 биасов и пропустить все в конце через сигмоиду.
Выходной слой - последний нейрон умножение (1,16)*(16,1)=(1). Снова не забываем биас и последнюю сигмоиду. На выходе как раз и будет результат - float32 число.
Останется только выборочно сравнить ответ от одной и той же позиции в питоне и в Делфи. Должно быть очень похоже
Девушка-программер едет в трамвае, читает книгу.
Старушка смотрит на девушку, смотрит на книгу, крестится и в ужасе выбегает на следующей становке.
Девушка читала книгу "Язык Ада".
Я не заморачивался пока что с матрицами, взял готовое решение от Автор: Andrew M. Omutov
Мне интересен сейчас результат работы сети для задачи неприкосновенный король.
Поискал матричный калькулятор (надо же проверять, как работает реализация Автор: Andrew M. Omutov.
Не нашел такой калькулятор, работающий с большими матрицами.
А Copyright:(c) 2011, Michael R. . All rights reserved. - у меня сходу не пошло, поэтому оставил их в покое
Warning: Spoiler![ Click to expand ][ Click to hide ]
1. Сумматор. E = B+x1*w1+x2*w2 + .... xn*wn, где n -количество входов у нейрона,
Xi - информация пришедшая на i-й вход, Wi - вес i-го входа,
B - так называемый биас - тоже вес, но не привязанный ни к одному из входов, а принадлежит всему нейрону.
Активационная функция.
Все что пришло от сумматора (Е) является аргументом этой функции f(E).
Саму архитектуру нейросетки выберем максимально похожую на ту, что используется в движках :
3 Dense слоя по формуле 256-16-1.
В качестве активационной функции для всех нейронов всех слоев возьмем сигмоид.
Она в каждом нейроне нашей будущей нейросети будет вызываться после функции суммирования.
result:=1/(1+exp(-x))
На вход каждого нейрона входного слоя поступает 192 значения.
Матрица весов нейронов первого слоя (192,256) = 49152 значения.
Матрица биасов нейронов первого слоя - 256 значений.
Для второго слоя матрица весов (256,16)=4096 значения.
Матрица биасов второго слоя 16 значений.
У последнего нейрона в выходном слое 16 весов и 1 биас.
Если мы все эти количества просуммируем 49152+256+4096+16+16+1, то как раз получим все наши 53537 обученных параметра
Размер файла для нашей сети предсказуемо должен быть 53537*4=214148 байт.
1 слой - умножение (1,192)*(192,256)=(1,256) и не забудьте добавить биасы для каждого из 256 полученных результатов.
Лучше всего - дать сумматору в цикле сразу начальное значения соответствующего биаса
После пропустить все 256 полученных результатов через функцию сигмоиду и получить выходы нейронов первого слоя.
2 слой - умножение (1,256)*(256,16)=(1,16). То же самое. Не забываем снова про 16 биасов и пропустить все в конце через сигмоиду.
Выходной слой - последний нейрон умножение (1,16)*(16,1)=(1). Снова не забываем биас и последнюю сигмоиду.
На выходе как раз и будет результат - float32 число.
Warning: Spoiler![ Click to expand ][ Click to hide ]
0,314582198858261
Осталось все правильно математически просчитать.
Наверное для задачи о короле, можно просчитать заранее для всех позиций (как в эндшпильной таблице), а не на лету и составить таблицу.
Прим
Кто хочет(может) составить все это в виде формулы?
У вас на входе строка данных длинной 192 значения. В первом слое вы эту строку умножаете поэлементно с каждым из 256 столбцов матрицы весов (складывая результаты). Так же не забываете прибавить соответствующий биас (для первого столба - 1-й биас, для второго столбца - второй и так далее. У вас их ровно 256 по количеству столбцов ). После того как вы обработали все 256 столбцов и получили на выходе 256 результатов вы все эти 256 результатов пропускаете через сигмоиду. Получая таким образом выходы первого слоя. И уже их отправляете на вход второго слоя , умножая (1,256)*(256,16)=(1,16)
Самому с нуля написать мне нужно достаточно много времени, а я бы хотел сосредоточиться на другом.
И скорость для меня сейчас не важна, важна правильность.
Так что там писать-то? Загрузили веса и биасы и создали 3 небольших процедуры по числу слоев. Каждая на 2 вложенных цикла for.
"Я не понял этот момент
Прим
Теперь наверное понял "
Просто представьте что делает нейрон с данными : x1*d1+x2*d2+....Xn*Dn +bias. Вот собственно эту функцию нейрона вы и реализовываете. В цикле попарно перемножая данные (в входной строке) с весами (в столбце весов) для каждого из 256 нейронов входного слоя Все результаты от этих перемножений 192 пар складываете вместе и добавляете к результату биас соответствующего нейрона. 256 нейронов - 256 биасов - 256 столбцов одинаковой длины со строкой данных (192) все один к одному .
Ну вот чтоб на пальцах: Пусть веса первого слоя у вас лежат в
w1 : array [0..255,0..191] of float32;
a биасы в
b1: array[0..255] of float32;
тогда обявляете массив (строку) входных данных как
data: array[0..191] of float32; (там 0 и 1 но чтоб делфи не ругалось типы делаете одинаковыми - не суть )
обьявляете массив результатов (выходы первого слоя):
o1: array[0..255] of float32;
Тогда вся процедура у вас для первого слоя будет что-то типа :
for i:=0 to 255 do
begin
o1{i}:=b1{i}; сразу биасом будущую общую сумму очередного нейрона проинициализировали в начале и вперед :
for j:=0 to 191 do
o1{i}:=o1{i}+data{j}*w1{i,j};
o1{i}:=Sigmoid(o1{i}); и сразу после сумматора активационную функцию нейрона, чтобы не бегать 2 раза
end;
ВСЕ! Скобки {} специально выбрал такие чтобы форум их за теги не принимал и не коверкал
Интересно было попробовать в colab.
Попробовал для входных данных и 1 слоя, чтобы было потом с чем сравнивать.
Warning: Spoiler![ Click to expand ][ Click to hide ]
import numpy as np
import sys
print('numpy:' + np.__version__)
pd = np.zeros((1, 192))
b0 = np.zeros((1, 256))
d0 = np.zeros((192,256))
def sigmoid(x):
return 1 / (1 + np.exp(-x))
f = open('/content/drive/MyDrive/Colab Notebooks/pos_dat.txt', 'r')
i = -1
while True:
lines=f.readline()
mylist=lines.split()
#print(mylist)
if not lines:
f.close()
break
for j in range(0,len(mylist)):
i = i + 1
if (int(mylist[j]) == 1):
pd[0][i] = 1
i = -1
f = open('/content/drive/MyDrive/Colab Notebooks/bias_0.txt', 'r')
while True:
lines=f.readline()
mylist=lines.split()
#print(mylist)
if not lines:
f.close()
break
for j in range(0,len(mylist)):
i = i + 1
b0[0][i] = np.asarray((mylist[j]).replace(',', '.')).astype(np.float32)
i = 0
z = 0
f = open('/content/drive/MyDrive/Colab Notebooks/dense_0.txt', 'r')
while True:
lines=f.readline()
mylist=lines.split()
#print(mylist)
if not lines:
f.close()
break
newarr = np.array_split(mylist, 4)
for n in range(0,4):
#print(newarr[n])
#d0[i][z] = i
d0[i][z] = np.asarray((newarr[n][0]).replace(',', '.')).astype(np.float32)
z = z + 1
if (z==256):
z = 0
i = i + 1
np.set_printoptions(threshold=sys.maxsize)
lay1=np.dot(pd,d0)
print (lay1.shape)
lay1=np.add(lay1,b0)
print (lay1.shape)
layer0 = sigmoid(lay1)
print (layer0.shape)
print (layer0)
print('END')
Результат
Warning: Spoiler![ Click to expand ][ Click to hide ]