//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+