NeuroNetworksBook/Include/realization/neuronconv.mqh
super.admin 4a9222852c convert
2025-05-30 16:12:34 +02:00

463 lines
39 KiB
MQL5

//+------------------------------------------------------------------+
//| NeuronConv.mqh |
//| Copyright 2021, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link "https://www.mql5.com"
//+------------------------------------------------------------------+
//| Подключаем библиотеки |
//+------------------------------------------------------------------+
#include "neuronproof.mqh"
//+------------------------------------------------------------------+
//| Class CNeuronConv |
//| Назначение: Класс организации свёрточного слоя |
//+------------------------------------------------------------------+
class CNeuronConv : public CNeuronProof
{
protected:
bool m_bTransposedOutput;
public:
CNeuronConv(void) {m_bTransposedOutput = false;};
~CNeuronConv(void) {};
//---
virtual bool Init(CLayerDescription *desc);
virtual bool FeedForward(CNeuronBase *prevLayer);
virtual bool CalcHiddenGradient(CNeuronBase *prevLayer);
virtual bool CalcDeltaWeights(CNeuronBase *prevLayer);
virtual bool UpdateWeights(int batch_size, double learningRate,
double &Beta[], double &Lambda[])
{
return CNeuronBase::UpdateWeights(batch_size,
learningRate,
Beta,
Lambda);
}
//---
virtual CBufferDouble *GetWeights(void) const { return(m_cWeights); }
virtual CBufferDouble *GetDeltaWeights(void) const { return(m_cDeltaWeights);}
void SetTransposedOutput(const bool value) { m_bTransposedOutput = value; }
//--- методы работы с файлами
virtual bool Save(const int file_handle);
virtual bool Load(const int file_handle);
//--- метод идентификации объекта
virtual int Type(void) const { return(defNeuronConv); }
};
//+------------------------------------------------------------------+
//| Метод инициализации класса |
//+------------------------------------------------------------------+
bool CNeuronConv::Init(CLayerDescription *desc)
{
//--- Блок контролей
if(!desc || desc.type != Type() || desc.count <= 0 || desc.window <= 0)
return false;
//--- Сохраняем константы
m_iWindow = desc.window;
m_iStep = desc.step;
m_iWindowOut = desc.window_out;
m_iNeurons = desc.count;
//--- Инициализируем буфер результатов
if(!m_cOutputs)
if(!(m_cOutputs = new CBufferDouble()))
return false;
if(!m_cOutputs.BufferInit(1,m_iNeurons * m_iWindowOut, 0))
return false;
//--- Инициализируем буфер градиентов ошибки
if(!m_cGradients)
if(!(m_cGradients = new CBufferDouble()))
return false;
if(!m_cGradients.BufferInit(1,m_iNeurons * m_iWindowOut, 0))
return false;
//--- Инициализируем класс функции активации
if(!m_cActivation)
if(!(m_cActivation = new CActivation()))
return false;
m_cActivation.SetFunction(desc.activation, m_cOutputs.m_mMatrix, desc.activation_params[0], desc.activation_params[1]);
m_cActivation.SetOpenCL(m_cOpenCL);
//--- Инициализируем буфер матрицы весов
if(!m_cWeights)
if(!(m_cWeights = new CBufferDouble()))
return false;
if(!m_cWeights.BufferInit(desc.window_out, desc.window + 1))
return false;
double weights[];
double sigma = desc.activation == ACT_LReLU ?
2.0 / (double)(MathPow(1 + desc.activation_params[0], 2) * desc.window) :
1.0 / (double)desc.window;
if(!MathRandomNormal(0, MathSqrt(sigma), m_cWeights.Total(), weights))
return false;
for(uint i = 0; i < m_cWeights.Total(); i++)
if(!m_cWeights.m_mMatrix.Flat(i, weights[i]))
return false;
//--- Инициалищируем буфер градиентов на уровне матрицы весов
if(!m_cDeltaWeights)
if(!(m_cDeltaWeights = new CBufferDouble()))
return false;
if(!m_cDeltaWeights.BufferInit(desc.window_out, desc.window + 1, 0))
return false;
//--- Инициализируем буферы моментов
switch(desc.optimization)
{
case None:
case SGD:
for(int i = 0; i < 2; i++)
if(m_cMomenum[i])
delete m_cMomenum[i];
break;
case MOMENTUM:
case AdaGrad:
case RMSProp:
if(!m_cMomenum[0])
if(!(m_cMomenum[0] = new CBufferDouble()))
return false;
if(!m_cMomenum[0].BufferInit(desc.window_out, desc.window + 1, 0))
return false;
if(m_cMomenum[1])
delete m_cMomenum[1];
break;
case AdaDelta:
case Adam:
for(int i = 0; i < 2; i++)
{
if(!m_cMomenum[i])
if(!(m_cMomenum[i] = new CBufferDouble()))
return false;
if(!m_cMomenum[i].BufferInit(desc.window_out, desc.window + 1, 0))
return false;
}
break;
default:
return false;
break;
}
//--- Сохраняем метод оптимизации параметров и флаг транспонирования тензора результатов
m_eOptimization = desc.optimization;
m_bTransposedOutput = (desc.probability != 0);
return true;
}
//+------------------------------------------------------------------+
//| Метод прямого прохода |
//+------------------------------------------------------------------+
bool CNeuronConv::FeedForward(CNeuronBase *prevLayer)
{
//--- Блок контролей
if(!prevLayer || !m_cOutputs || !m_cWeights || !prevLayer.GetOutputs())
return false;
CBufferDouble *input_data = prevLayer.GetOutputs();
//--- Разветвление алгоритма в зависимости от устройста выполнениия операций
if(!m_cOpenCL)
{
ulong batch = input_data.Rows();
MATRIX m;
ulong rows = (input_data.Cols() - m_iWindow) / m_iStep + ((input_data.Cols() - m_iWindow) % m_iStep == 0 ? 1 : 2);
if(m_iWindow == m_iStep)
{
if(!m.Init(1, input_data.Cols()))
return false;
if(!m.Row(input_data.m_mMatrix.Row(0), 0))
return false;
if(!m.Reshape(rows, m_iWindow))
return false;
}
//---
else
{
if(!m.Init(rows, m_iWindow))
return false;
VECTOR v = input_data.m_mMatrix.Row(0);
ulong total = v.Size();
for(ulong r = 0; r < rows; r++)
{
ulong shift = r * m_iStep;
for(ulong c = 0; c < m_iWindow; c++)
{
ulong k = shift + c;
m[r, c] = (k < total ? v[shift + c] : 0);
}
}
}
VECTOR v;
if(!v.Init(m.Rows()))
return false;
v.Fill(1);
if(!m.Resize(m.Rows(),m_iWindow+1) ||
!m.Col(v,m_iWindow))
return false;
m = m.MatMul(m_cWeights.m_mMatrix.Transpose());
if(!m.Reshape(m_cOutputs.Rows(), m_cOutputs.Cols()))
return false;
m_cOutputs.m_mMatrix = m;
if(m_cActivation)
m_cActivation.Activation(m_cOutputs);
}
else
{
//--- Создание буферов данных
if(input_data.GetIndex() < 0)
return false;
if(m_cWeights.GetIndex() < 0)
return false;
if(m_cActivation.GetIndex() < 0)
return false;
if(m_cOutputs.GetIndex() < 0)
return false;
//--- Передача аргументов кернелу
if(!m_cOpenCL.SetArgumentBuffer(def_k_ConvolutionFeedForward, def_cff_inputs, input_data.GetIndex()))
return false;
if(!m_cOpenCL.SetArgumentBuffer(def_k_ConvolutionFeedForward, def_cff_weights, m_cWeights.GetIndex()))
return false;
if(!m_cOpenCL.SetArgumentBuffer(def_k_ConvolutionFeedForward, def_cff_sums, m_cActivation.GetIndex()))
return false;
if(!m_cOpenCL.SetArgumentBuffer(def_k_ConvolutionFeedForward, def_cff_outputs, m_cOutputs.GetIndex()))
return false;
if(!m_cOpenCL.SetArgument(def_k_ConvolutionFeedForward, def_cff_inputs_total, input_data.Total()))
return false;
if(!m_cOpenCL.SetArgument(def_k_ConvolutionFeedForward, def_cff_window, m_iWindow))
return false;
if(!m_cOpenCL.SetArgument(def_k_ConvolutionFeedForward, def_cff_step, m_iStep))
return false;
if(!m_cOpenCL.SetArgument(def_k_ConvolutionFeedForward, def_cff_window_out, m_iWindowOut))
return false;
if(!m_cOpenCL.SetArgument(def_k_ConvolutionFeedForward, def_cff_transposed_out, (int)m_bTransposedOutput))
return false;
double params[];
ENUM_ACTIVATION function = m_cActivation.GetFunction(params);
if(!m_cOpenCL.SetArgument(def_k_ConvolutionFeedForward, def_cff_activation, (int)function))
return false;
if(!m_cOpenCL.SetArgument(def_k_ConvolutionFeedForward, def_cff_act_param_a, params[0]))
return false;
if(!m_cOpenCL.SetArgument(def_k_ConvolutionFeedForward, def_cff_act_param_b, params[1]))
return false;
//--- Постановка кернела в очередь выполнения
int off_set[] = {0};
int NDRange[] = {(int)m_iNeurons};
if(!m_cOpenCL.Execute(def_k_ConvolutionFeedForward, 1, off_set, NDRange))
return false;
//--- Получение результатов работы кернела
if(!m_cOutputs.BufferRead())
return false;
if(function != ACT_SOFTMAX)
return true;
//--- Только для функции активации SoftMax нормализация данных
//--- суммирование значений буфера
double summ = 0;
double array[];
int total = m_cOutputs.GetData(array);
if(total <= 0)
return false;
for(int i = 0; i < total; i++)
summ += array[i];
//--- Передача аргументов кернелу
if(!m_cOpenCL.SetArgumentBuffer(def_k_Normalize, def_norm_inputs, m_cOutputs.GetIndex()))
return false;
if(!m_cOpenCL.SetArgumentBuffer(def_k_Normalize, def_norm_outputs, m_cOutputs.GetIndex()))
return false;
if(!m_cOpenCL.SetArgument(def_k_Normalize, def_norm_inputs_total, total))
return false;
if(!m_cOpenCL.SetArgument(def_k_Normalize, def_norm_const_value, summ))
return false;
//--- Постановка кернела в очередь выполнения
NDRange[0] = (total + 3) / 4;
if(!m_cOpenCL.Execute(def_k_Normalize, 1, off_set, NDRange))
return false;
//--- Получение результатов работы кернела
if(!m_cOutputs.BufferRead())
return false;
}
//---
return true;
}
//+------------------------------------------------------------------+
//| Метод распределения градиента ошибки через скрытый слой |
//+------------------------------------------------------------------+
bool CNeuronConv::CalcHiddenGradient(CNeuronBase *prevLayer)
{
//--- Блок контролей
if(!prevLayer || !prevLayer.GetOutputs() || !prevLayer.GetGradients() || !m_cGradients || !m_cWeights)
return false;
//--- Корректировка градиентов ошибки на производную функции активации
if(m_cActivation)
{
if(!m_cActivation.Derivative(m_cOutputs, m_cGradients))
return false;
}
//--- Разветвление алгоритма в зависимости от устройста выполнениия операций
CBufferDouble *input_data = prevLayer.GetOutputs();
CBufferDouble *input_gradient = prevLayer.GetGradients();
if(!m_cOpenCL)
{
for(uint inp = 0; inp < input_data.Total(); inp++)
{
double value = 0;
uint start = inp + m_iStep - m_iWindow;
start = (int)MathMax((start - start % m_iStep) / m_iStep, 0);
uint stop = MathMin((inp + m_iStep - 1) / m_iStep + 1, m_iNeurons);
for(int h = 0; h < (int)m_iWindowOut; h++)
{
for(uint k = start; k < stop; k++)
{
uint shift_w = (stop - k - 1) * m_iStep + inp % (int)m_iStep + h * (m_iWindow + 1);
uint shift_g = (m_bTransposedOutput ? h + k * (int)m_iWindowOut : h * (int)m_iNeurons + k);
if(shift_g >= m_cGradients.Total() || shift_w >= m_cWeights.Total())
break;
value += m_cGradients.At(shift_g) * m_cWeights.At(shift_w);
}
}
if(!input_gradient.Update(inp, value))
return false;
}
}
else
{
//--- Создание буферов данных
if(m_cWeights.GetIndex() < 0)
return false;
if(input_gradient.GetIndex() < 0)
return false;
if(m_cGradients.GetIndex() < 0)
return false;
//--- Передача аргументов кернелу
if(!m_cOpenCL.SetArgumentBuffer(def_k_ConvolutionHiddenGradients, def_convhgr_gradient_inputs, input_gradient.GetIndex()))
return false;
if(!m_cOpenCL.SetArgumentBuffer(def_k_ConvolutionHiddenGradients, def_convhgr_weights, m_cWeights.GetIndex()))
return false;
if(!m_cOpenCL.SetArgumentBuffer(def_k_ConvolutionHiddenGradients, def_convhgr_gradients, m_cGradients.GetIndex()))
return false;
if(!m_cOpenCL.SetArgument(def_k_ConvolutionHiddenGradients, def_convhgr_outputs_total, m_cGradients.Total()))
return false;
if(!m_cOpenCL.SetArgument(def_k_ConvolutionHiddenGradients, def_convhgr_neurons, m_iNeurons))
return false;
if(!m_cOpenCL.SetArgument(def_k_ConvolutionHiddenGradients, def_convhgr_window, m_iWindow))
return false;
if(!m_cOpenCL.SetArgument(def_k_ConvolutionHiddenGradients, def_convhgr_step, m_iStep))
return false;
if(!m_cOpenCL.SetArgument(def_k_ConvolutionHiddenGradients, def_convhgr_window_out, m_iWindowOut))
return false;
if(!m_cOpenCL.SetArgument(def_k_ConvolutionHiddenGradients, def_convhgr_transposed_out, (int)m_bTransposedOutput))
return false;
//--- Постановка кернела в очередь выполнения
int NDRange[] = {(int)input_data.Total()};
int off_set[] = {0};
if(!m_cOpenCL.Execute(def_k_ConvolutionHiddenGradients, 1, off_set, NDRange))
return false;
//--- Получение результатов работы кернела
if(!input_gradient.BufferRead())
return false;
}
//---
return true;
}
//+------------------------------------------------------------------+
//| Метод распределения градиентов ошибки до матрицы весов |
//+------------------------------------------------------------------+
bool CNeuronConv::CalcDeltaWeights(CNeuronBase *prevLayer)
{
//--- Блок контролей
if(!prevLayer || !prevLayer.GetOutputs() || !m_cGradients || !m_cDeltaWeights)
return false;
//--- Разветвление алгоритма в зависимости от устройста выполнениия операций
CBufferDouble *input_data = prevLayer.GetOutputs();
if(!m_cOpenCL)
{
uint input_total = input_data.Total();
for(int w = 0; w < (int)m_iWindowOut; w++)
{
int shift_delt = w * ((int)m_iWindow + 1);
for(int inp_w = 0; inp_w < (int)m_iWindow; inp_w++)
{
double value = 0;
for(uint n = 0; n < m_iNeurons; n++)
{
uint shift_inp = n * m_iStep + inp_w;
if(shift_inp >= input_total)
break;
uint shift_grad = (m_bTransposedOutput ? w + n * m_iWindowOut : w * m_iNeurons + n);
value += input_data.At(shift_inp) * m_cGradients.At(shift_grad);
}
if(!m_cDeltaWeights.Update(shift_delt + inp_w, value))
return false;
}
double value = 0;
for(int n = 0; n < (int)m_iNeurons; n++)
value += m_cGradients.At(m_bTransposedOutput ? w + n * (int)m_iWindowOut : w * (int)m_iNeurons + n);
if(!m_cDeltaWeights.Update(shift_delt + m_iWindow, value))
return false;
}
}
else
{
//--- Создание буферов данных
if(m_cGradients.GetIndex() < 0)
return false;
if(m_cDeltaWeights.GetIndex() < 0)
return false;
if(input_data.GetIndex() < 0)
return false;
//--- Передача аргументов кернелу
if(!m_cOpenCL.SetArgumentBuffer(def_k_ConvolutionDeltaWeights, def_convdelt_delta_weights, m_cDeltaWeights.GetIndex()))
return false;
if(!m_cOpenCL.SetArgumentBuffer(def_k_ConvolutionDeltaWeights, def_convdelt_inputs, input_data.GetIndex()))
return false;
if(!m_cOpenCL.SetArgumentBuffer(def_k_ConvolutionDeltaWeights, def_convdelt_gradients, m_cGradients.GetIndex()))
return false;
if(!m_cOpenCL.SetArgument(def_k_ConvolutionDeltaWeights, def_convdelt_inputs_total, input_data.Total()))
return false;
if(!m_cOpenCL.SetArgument(def_k_ConvolutionDeltaWeights, def_convdelt_neurons, m_iNeurons))
return false;
if(!m_cOpenCL.SetArgument(def_k_ConvolutionDeltaWeights, def_convdelt_step, m_iStep))
return false;
if(!m_cOpenCL.SetArgument(def_k_ConvolutionDeltaWeights, def_convdelt_transposed_out, (int)m_bTransposedOutput))
return false;
//--- Постановка кернела в очередь выполнения
uint NDRange[] = {m_iWindow + 1, m_iWindowOut};
uint off_set[] = {0, 0};
if(!m_cOpenCL.Execute(def_k_ConvolutionDeltaWeights, 2, off_set, NDRange))
return false;
//--- Получение результатов работы кернела
if(!m_cDeltaWeights.BufferRead())
return false;
}
//---
return true;
}
//+------------------------------------------------------------------+
//| Метод сохранения элементов класса в файл |
//+------------------------------------------------------------------+
bool CNeuronConv::Save(const int file_handle)
{
//--- Вызов метода родительского класса
if(!CNeuronBase::Save(file_handle))
return false;
//--- Сохранение значений констант
if(FileWriteInteger(file_handle, (int)m_iWindow) <= 0)
return false;
if(FileWriteInteger(file_handle, (int)m_iStep) <= 0)
return false;
if(FileWriteInteger(file_handle, (int)m_iWindowOut) <= 0)
return false;
if(FileWriteInteger(file_handle, (int)m_iNeurons) <= 0)
return false;
if(FileWriteInteger(file_handle, (int)m_bTransposedOutput) <= 0)
return false;
//---
return true;
}
//+------------------------------------------------------------------+
//| Метод восстановления класса из файла |
//+------------------------------------------------------------------+
bool CNeuronConv::Load(const int file_handle)
{
//--- Вызов метода родительского класса
if(!CNeuronBase::Load(file_handle))
return false;
//--- Считывание значенийи переменных
m_iWindow = (uint)FileReadInteger(file_handle);
m_iStep = (uint)FileReadInteger(file_handle);
m_iWindowOut = (uint)FileReadInteger(file_handle);
m_iNeurons = (uint)FileReadInteger(file_handle);
m_bTransposedOutput = (bool)FileReadInteger(file_handle);
m_eActivation = ACT_None;
//---
return true;
}
//+------------------------------------------------------------------+