//+------------------------------------------------------------------+ //| NeuronBase.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| Подключаем библиотеки | //+------------------------------------------------------------------+ #include #include "bufferdouble.mqh" #include "layerdescription.mqh" #include "activation.mqh" //+------------------------------------------------------------------+ //| Class CNeuronBase | //| Назначение: Базовый класс полносвязного нейронного слоя | //+------------------------------------------------------------------+ class CNeuronBase : public CObject { protected: bool m_bTrain; CMyOpenCL *m_cOpenCL; CActivation *m_cActivation; ENUM_OPTIMIZATION m_eOptimization; CBufferDouble *m_cOutputs; CBufferDouble *m_cWeights; CBufferDouble *m_cDeltaWeights; CBufferDouble *m_cGradients; CBufferDouble *m_cMomenum[2]; //--- virtual bool SGDUpdate(int batch_size, double learningRate, double &Lambda[]); virtual bool MomentumUpdate(int batch_size, double learningRate, double &Beta[], double &Lambda[]); virtual bool AdaGradUpdate(int batch_size, double learningRate, double &Lambda[]); virtual bool RMSPropUpdate(int batch_size, double learningRate, double &Beta[], double &Lambda[]); virtual bool AdaDeltaUpdate(int batch_size, double &Beta[], double &Lambda[]); virtual bool AdamUpdate(int batch_size, double learningRate, double &Beta[], double &Lambda[]); public: CNeuronBase(void); ~CNeuronBase(void); //--- virtual bool Init(CLayerDescription *description); virtual bool SetOpenCL(CMyOpenCL *opencl); virtual bool FeedForward(CNeuronBase *prevLayer); virtual bool CalcOutputGradient(CBufferDouble *target); virtual bool CalcHiddenGradient(CNeuronBase *prevLayer); virtual bool CalcDeltaWeights(CNeuronBase *prevLayer); virtual bool UpdateWeights(int batch_size, double learningRate, double &Beta[], double &Lambda[]); //--- virtual void TrainMode(bool flag) { m_bTrain = flag; } virtual bool TrainMode(void) const { return m_bTrain; } //--- CBufferDouble *GetOutputs(void) const { return(m_cOutputs); } CBufferDouble *GetGradients(void) const { return(m_cGradients); } CBufferDouble *GetWeights(void) const { return(m_cWeights); } CBufferDouble *GetDeltaWeights(void) const { return(m_cDeltaWeights);} //--- methods for working with files virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); //--- method of identifying the object virtual int Type(void) const { return(defNeuronBase); } }; //+------------------------------------------------------------------+ //| Конструктор класса | //+------------------------------------------------------------------+ CNeuronBase::CNeuronBase(void) : m_eOptimization(Adam) { m_cOpenCL = NULL; m_cActivation = new CActivation(); m_cOutputs = new CBufferDouble(); m_cWeights = new CBufferDouble(); m_cDeltaWeights = new CBufferDouble(); m_cGradients = new CBufferDouble(); m_cMomenum[0] = new CBufferDouble(); m_cMomenum[1] = new CBufferDouble(); } //+------------------------------------------------------------------+ //| Деструктор класса | //+------------------------------------------------------------------+ CNeuronBase::~CNeuronBase(void) { if(m_cActivation) delete m_cActivation; if(m_cOutputs) delete m_cOutputs; if(m_cWeights) delete m_cWeights; if(m_cDeltaWeights) delete m_cDeltaWeights; if(m_cGradients) delete m_cGradients; if(m_cMomenum[0]) delete m_cMomenum[0]; if(m_cMomenum[1]) delete m_cMomenum[1]; } //+------------------------------------------------------------------+ //| Метод распределения указателя на объект работы с контекстом | //| OpenCL | //+------------------------------------------------------------------+ bool CNeuronBase::SetOpenCL(CMyOpenCL *opencl) { if(!opencl) { if(m_cOutputs) m_cOutputs.BufferFree(); if(m_cGradients) m_cGradients.BufferFree(); if(m_cWeights) m_cWeights.BufferFree(); if(m_cDeltaWeights) m_cDeltaWeights.BufferFree(); for(int i = 0; i < 2; i++) { if(m_cMomenum[i]) m_cMomenum[i].BufferFree(); } } if(m_cOpenCL != opencl) { if(m_cOpenCL) delete m_cOpenCL; m_cOpenCL = opencl; if(m_cOutputs) m_cOutputs.BufferCreate(opencl); if(m_cGradients) m_cGradients.BufferCreate(opencl); if(m_cWeights) m_cWeights.BufferCreate(opencl); if(m_cDeltaWeights) m_cDeltaWeights.BufferCreate(opencl); for(int i = 0; i < 2; i++) { if(m_cMomenum[i]) m_cMomenum[i].BufferCreate(opencl); } if(m_cActivation) m_cActivation.SetOpenCL(m_cOpenCL); } //--- return(!!m_cOpenCL); } //+------------------------------------------------------------------+ //| Метод инициализации класса | //+------------------------------------------------------------------+ bool CNeuronBase::Init(CLayerDescription *desc) { //--- Блок контроля исходных данных if(!desc || desc.type != Type() || desc.count <= 0) return false; //--- Создание буфера результатов if(!m_cOutputs) if(!(m_cOutputs = new CBufferDouble())) return false; if(!m_cOutputs.BufferInit(1, desc.count, 0)) return false; //--- Создание буфера градиентов ошибюки if(!m_cGradients) if(!(m_cGradients = new CBufferDouble())) return false; if(!m_cGradients.BufferInit(1, desc.count, 0)) return false; //--- Удаление не используемых объектов для слоя исходных данных if(desc.window <= 0) { if(m_cActivation) delete m_cActivation; if(m_cWeights) delete m_cWeights; if(m_cDeltaWeights) delete m_cDeltaWeights; if(m_cMomenum[0]) delete m_cMomenum[0]; if(m_cMomenum[1]) delete m_cMomenum[1]; if(m_cOpenCL) if(!m_cOutputs.BufferCreate(m_cOpenCL)) return false; m_eOptimization = desc.optimization; return true; } //--- Инициализация объекта функции активации 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.count, desc.window + 1, 0)) 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.count, 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.count, 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.count, desc.window + 1, 0)) return false; } break; default: return false; break; } //--- Сохранение метода оптимизации параметров m_eOptimization = desc.optimization; return true; } //+------------------------------------------------------------------+ //| Метод прямого прохода | //+------------------------------------------------------------------+ bool CNeuronBase::FeedForward(CNeuronBase *prevLayer) { //--- Блок контролей if(!prevLayer || !m_cOutputs || !m_cWeights || !prevLayer.GetOutputs() || !m_cActivation) return false; CBufferDouble *input_data = prevLayer.GetOutputs(); //--- Разветвление алгоритма в зависимости от устройста выполнениия операций if(!m_cOpenCL) { uint input_total = input_data.Total(); uint output_total = m_cOutputs.Total(); if(m_cWeights.Total() < (input_total + 1)*output_total) return false; //--- MATRIX m = input_data.m_mMatrix; m.Resize(m.Rows(), m.Cols() + 1); VECTOR v; v.Init(m.Rows()); v.Fill(1); if(!m.Col(v, input_data.m_mMatrix.Cols())) return false; m_cOutputs.m_mMatrix = m.MatMul(m_cWeights.m_mMatrix.Transpose()); return 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_PerceptronFeedForward, def_pff_inputs, input_data.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_PerceptronFeedForward, def_pff_weights, m_cWeights.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_PerceptronFeedForward, def_pff_sums, m_cActivation.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_PerceptronFeedForward, def_pff_outputs, m_cOutputs.GetIndex())) return false; if(!m_cOpenCL.SetArgument(def_k_PerceptronFeedForward, def_pff_inputs_total, input_data.Total())) return false; double params[]; ENUM_ACTIVATION function = m_cActivation.GetFunction(params); if(!m_cOpenCL.SetArgument(def_k_PerceptronFeedForward, def_pff_activation, (int)function)) return false; if(!m_cOpenCL.SetArgument(def_k_PerceptronFeedForward, def_pff_act_param_a, params[0])) return false; if(!m_cOpenCL.SetArgument(def_k_PerceptronFeedForward, def_pff_act_param_b, params[1])) return false; //--- Постановка кернела в очередь выполнения uint off_set[] = {0}; uint NDRange[] = {m_cOutputs.Total()}; if(!m_cOpenCL.Execute(def_k_PerceptronFeedForward, 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; } //--- return false; } //+------------------------------------------------------------------+ //| Метод расчёта градиента ошибки слоя результатов | //+------------------------------------------------------------------+ bool CNeuronBase::CalcOutputGradient(CBufferDouble *target) { //--- Блок контролей if(!target || !m_cOutputs || !m_cGradients || target.Total() < m_cOutputs.Total() || m_cGradients.Total() < m_cOutputs.Total()) return false; //--- Разветвление алгоритма в зависимости от устройста выполнениия операций if(!m_cOpenCL) { m_cGradients.m_mMatrix = target.m_mMatrix - m_cOutputs.m_mMatrix; } else { //--- Создание буферов данных if(target.GetIndex() < 0) return false; if(m_cOutputs.GetIndex() < 0) return false; if(m_cGradients.GetIndex() < 0) return false; //--- Передача аргументов кернелу if(!m_cOpenCL.SetArgumentBuffer(def_k_CalcOutputGradient, def_outgr_target, target.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_CalcOutputGradient, def_outgr_outputs, m_cOutputs.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_CalcOutputGradient, def_outgr_gradients, m_cGradients.GetIndex())) return false; if(!m_cOpenCL.SetArgument(def_k_CalcOutputGradient, def_outgr_outputs_total, m_cOutputs.Total())) return false; //--- Постановка кернела в лчередь выполнения uint NDRange[] = { (m_cOutputs.Total() + 3) / 4 }; uint off_set[] = {0}; if(!m_cOpenCL.Execute(def_k_CalcOutputGradient, 1, off_set, NDRange)) return false; //--- Получение результатов работы кернела if(!m_cGradients.BufferRead()) return false; } //--- return true; } //+------------------------------------------------------------------+ //| Метод распределения градиента через скрытый слой | //+------------------------------------------------------------------+ bool CNeuronBase::CalcHiddenGradient(CNeuronBase *prevLayer) { //--- Корректировка входящего градиента на производную функции активации if(!m_cActivation.Derivative(m_cOutputs, m_cGradients)) return false; if(!prevLayer) return false; //--- Проверка буферов предыдущего слоя CBufferDouble *input_data = prevLayer.GetOutputs(); CBufferDouble *input_gradient = prevLayer.GetGradients(); if(!input_data || !input_gradient || input_data.Total() > input_gradient.Total()) return false; //--- Проверка соответствия размера буфера исходных данных и матрицы весов uint input_total = input_data.Total(); if(!m_cWeights || m_cWeights.m_mMatrix.Cols() != (input_total + 1)) return false; //--- Разветвление алгоритма в зависимости от устройста выполнениия операций if(!m_cOpenCL) { MATRIX grad = m_cGradients.m_mMatrix.MatMul(m_cWeights.m_mMatrix); grad.Resize(input_data.m_mMatrix.Rows(), input_data.m_mMatrix.Cols()); input_gradient.m_mMatrix = grad; } 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_CalcHiddenGradient, def_hidgr_gradient_inputs, input_gradient.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_CalcHiddenGradient, def_hidgr_weights, m_cWeights.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_CalcHiddenGradient, def_hidgr_gradients, m_cGradients.GetIndex())) return false; if(!m_cOpenCL.SetArgument(def_k_CalcHiddenGradient, def_hidgr_outputs_total, m_cGradients.Total())) return false; //--- Постановка кернела в очередь выполнения uint NDRange[] = {input_total}; uint off_set[] = {0}; if(!m_cOpenCL.Execute(def_k_CalcHiddenGradient, 1, off_set, NDRange)) return false; //--- Получение результатов работы кернела if(!input_gradient.BufferRead()) return false; } //--- return true; } //+------------------------------------------------------------------+ //| Метод распределение градиента ошибки до матрицы весов | //+------------------------------------------------------------------+ bool CNeuronBase::CalcDeltaWeights(CNeuronBase *prevLayer) { //--- Блок контролей if(!prevLayer || !m_cDeltaWeights || !m_cGradients) return false; CBufferDouble *Inputs = prevLayer.GetOutputs(); if(!Inputs) return false; //--- Разветвление алгоритма в зависимости от устройста выполнениия операций if(!m_cOpenCL) { MATRIX m = Inputs.m_mMatrix; m.Resize(m.Rows(), m.Cols() + 1); VECTOR v; v.Init(m.Rows()); v.Fill(1); if(!m.Col(v, Inputs.m_mMatrix.Cols())) return false; m = m_cGradients.m_mMatrix.Transpose().MatMul(m); m_cDeltaWeights.m_mMatrix += m; } else { //--- Создание буферов данных if(m_cGradients.GetIndex() < 0) return false; if(m_cDeltaWeights.GetIndex() < 0) return false; if(Inputs.GetIndex() < 0) return false; //--- Передача аргументов кернелу if(!m_cOpenCL.SetArgumentBuffer(def_k_CalcDeltaWeights, def_delt_delta_weights, m_cDeltaWeights.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_CalcDeltaWeights, def_delt_inputs, Inputs.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_CalcDeltaWeights, def_delt_gradients, m_cGradients.GetIndex())) return false; //--- Постановка кернела в очередь выполнения uint NDRange[] = {m_cGradients.Total(), Inputs.Total()}; uint off_set[] = {0, 0}; if(!m_cOpenCL.Execute(def_k_CalcDeltaWeights, 2, off_set, NDRange)) return false; //--- Получение результатов работы кернела if(!m_cDeltaWeights.BufferRead()) return false; } //--- return true; } //+------------------------------------------------------------------+ //| Метод обновления матрицы весов | //+------------------------------------------------------------------+ bool CNeuronBase::UpdateWeights(int batch_size, double learningRate, double &Beta[], double &Lambda[]) { //--- Блок контролей if(!m_cDeltaWeights || !m_cWeights || m_cWeights.Total() < m_cDeltaWeights.Total() || batch_size <= 0) return false; //--- Разветвление алгоритма в зависимости от используемой функции активации bool result = false; switch(m_eOptimization) { case None: result = true; break; case SGD: result = SGDUpdate(batch_size, learningRate, Lambda); break; case MOMENTUM: result = MomentumUpdate(batch_size, learningRate, Beta, Lambda); break; case AdaGrad: result = AdaGradUpdate(batch_size, learningRate, Lambda); break; case RMSProp: result = RMSPropUpdate(batch_size, learningRate, Beta, Lambda); break; case AdaDelta: result = AdaDeltaUpdate(batch_size, Beta, Lambda); break; case Adam: result = AdamUpdate(batch_size, learningRate, Beta, Lambda); break; } //--- return result; } //+------------------------------------------------------------------+ //| Обновление матрицы весов стохастическим градиентным спуском | //+------------------------------------------------------------------+ bool CNeuronBase::SGDUpdate(int batch_size, double learningRate, double &Lambda[]) { //--- Разветвление алгоритма в зависимости от устройста выполнениия операций if(!m_cOpenCL) { double lr = learningRate / ((double)batch_size); m_cWeights.m_mMatrix -= Lambda[0] + Lambda[1] * m_cWeights.m_mMatrix; m_cWeights.m_mMatrix += m_cDeltaWeights.m_mMatrix * lr; m_cDeltaWeights.m_mMatrix.Fill(0); } else { //--- Создание буферов данных if(m_cWeights.GetIndex() < 0) return false; if(m_cDeltaWeights.GetIndex() < 0) return false; //--- Передача аргументов кернелу if(!m_cOpenCL.SetArgumentBuffer(def_k_SGDUpdate, def_sgd_delta_weights, m_cDeltaWeights.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_SGDUpdate, def_sgd_weights, m_cWeights.GetIndex())) return false; if(!m_cOpenCL.SetArgument(def_k_SGDUpdate, def_sgd_total, m_cWeights.Total())) return false; if(!m_cOpenCL.SetArgument(def_k_SGDUpdate, def_sgd_batch_size, batch_size)) return false; if(!m_cOpenCL.SetArgument(def_k_SGDUpdate, def_sgd_learningRate, learningRate)) return false; if(!m_cOpenCL.SetArgument(def_k_SGDUpdate, def_sgd_Lambda1, Lambda[0])) return false; if(!m_cOpenCL.SetArgument(def_k_SGDUpdate, def_sgd_Lambda2, Lambda[1])) return false; //--- Постановка кернела в очередь выполнения int NDRange[] = { (int)((m_cOutputs.Total() + 3) / 4) }; int off_set[] = {0}; if(!m_cOpenCL.Execute(def_k_SGDUpdate, 1, off_set, NDRange)) return false; //--- Получение результатов работы кернела if(!m_cWeights.BufferRead()) return false; } return true; } //+------------------------------------------------------------------+ //| Обновление матрицы весов методом моментов | //+------------------------------------------------------------------+ bool CNeuronBase::MomentumUpdate(int batch_size, double learningRate, double &Beta[], double &Lambda[]) { if(Beta[0] == 0) return SGDUpdate(batch_size, learningRate, Lambda); //--- Блок контролей if(!m_cMomenum[0]) return false; if(m_cMomenum[0].Total() < m_cWeights.Total()) return false; //--- Разветвление алгоритма в зависимости от устройста выполнениия операций if(!m_cOpenCL) { double lr = learningRate / ((double)batch_size); m_cWeights.m_mMatrix -= Lambda[0] + m_cWeights.m_mMatrix * Lambda[1]; m_cMomenum[0].m_mMatrix = m_cDeltaWeights.m_mMatrix * lr + m_cMomenum[0].m_mMatrix * Beta[0] ; m_cWeights.m_mMatrix += m_cMomenum[0].m_mMatrix; } else { //--- Создание буферов данных if(m_cWeights.GetIndex() < 0) return false; if(m_cDeltaWeights.GetIndex() < 0) return false; if(m_cMomenum[0].GetIndex() < 0) return false; //--- Передача аргументов кернелу if(!m_cOpenCL.SetArgumentBuffer(def_k_MomentumUpdate, def_moment_delta_weights, m_cDeltaWeights.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_MomentumUpdate, def_moment_weights, m_cWeights.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_MomentumUpdate, def_moment_momentum, m_cMomenum[0].GetIndex())) return false; if(!m_cOpenCL.SetArgument(def_k_MomentumUpdate, def_moment_total, m_cWeights.Total())) return false; if(!m_cOpenCL.SetArgument(def_k_MomentumUpdate, def_moment_batch_size, batch_size)) return false; if(!m_cOpenCL.SetArgument(def_k_MomentumUpdate, def_moment_learningRate, learningRate)) return false; if(!m_cOpenCL.SetArgument(def_k_MomentumUpdate, def_moment_Lambda1, Lambda[0])) return false; if(!m_cOpenCL.SetArgument(def_k_MomentumUpdate, def_moment_Lambda2, Lambda[1])) return false; if(!m_cOpenCL.SetArgument(def_k_MomentumUpdate, def_moment_beta, Beta[0])) return false; //--- Постановка кернела в очередь выполнения int NDRange[] = { (int)((m_cOutputs.Total() + 3) / 4) }; int off_set[] = {0}; if(!m_cOpenCL.Execute(def_k_MomentumUpdate, 1, off_set, NDRange)) return false; //--- Получение результатов работы кернела if(!m_cWeights.BufferRead()) return false; } return true; } //+------------------------------------------------------------------+ //| Обновление матрицы весов методом AdaGrad | //+------------------------------------------------------------------+ bool CNeuronBase::AdaGradUpdate(int batch_size, double learningRate, double &Lambda[]) { //--- Блок контролей if(!m_cMomenum[0]) return false; if(m_cMomenum[0].Total() < m_cWeights.Total()) return false; //--- Разветвление алгоритма в зависимости от устройста выполнениия операций if(!m_cOpenCL) { double lr = learningRate; m_cWeights.m_mMatrix -= Lambda[0] + Lambda[1] * m_cWeights.m_mMatrix; MATRIX delta = m_cDeltaWeights.m_mMatrix / ((double)batch_size); MATRIX G = m_cMomenum[0].m_mMatrix = m_cMomenum[0].m_mMatrix + delta.Power(2); for(ulong r = 0; r < G.Rows(); r++) for(ulong c = 0; c < G.Cols(); c++) G[r, c] = (G[r, c] > 0 ? lr / MathSqrt(G[r, c]) : 0); m_cWeights.m_mMatrix += G * delta; } else { //--- Создание буферов данных if(m_cWeights.GetIndex() < 0) return false; if(m_cDeltaWeights.GetIndex() < 0) return false; if(m_cMomenum[0].GetIndex() < 0) return false; //--- Передача аргументов кернелу if(!m_cOpenCL.SetArgumentBuffer(def_k_AdaGradUpdate, def_adagrad_delta_weights, m_cDeltaWeights.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_AdaGradUpdate, def_adagrad_weights, m_cWeights.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_AdaGradUpdate, def_adagrad_momentum, m_cMomenum[0].GetIndex())) return false; if(!m_cOpenCL.SetArgument(def_k_AdaGradUpdate, def_adagrad_total, m_cWeights.Total())) return false; if(!m_cOpenCL.SetArgument(def_k_AdaGradUpdate, def_adagrad_batch_size, batch_size)) return false; if(!m_cOpenCL.SetArgument(def_k_AdaGradUpdate, def_adagrad_learningRate, learningRate)) return false; if(!m_cOpenCL.SetArgument(def_k_AdaGradUpdate, def_adagrad_Lambda1, Lambda[0])) return false; if(!m_cOpenCL.SetArgument(def_k_AdaGradUpdate, def_adagrad_Lambda2, Lambda[1])) return false; //--- Постановка кернела в очередь выполнения int NDRange[] = { (int)((m_cOutputs.Total() + 3) / 4) }; int off_set[] = {0}; if(!m_cOpenCL.Execute(def_k_AdaGradUpdate, 1, off_set, NDRange)) return false; //--- Получение результатов работы кернела if(!m_cWeights.BufferRead()) return false; } return true; } //+------------------------------------------------------------------+ //| Обновление матрицы весов методом RMSProp | //+------------------------------------------------------------------+ bool CNeuronBase::RMSPropUpdate(int batch_size, double learningRate, double &Beta[], double &Lambda[]) { //--- Блок контролей if(!m_cMomenum[0]) return false; if(m_cMomenum[0].Total() < m_cWeights.Total()) return false; //--- Разветвление алгоритма в зависимости от устройста выполнениия операций if(!m_cOpenCL) { double lr = learningRate; m_cWeights.m_mMatrix -= Lambda[0] + Lambda[1] * m_cWeights.m_mMatrix; MATRIX delta = m_cDeltaWeights.m_mMatrix / ((double)batch_size); MATRIX G = m_cMomenum[0].m_mMatrix = Beta[0] * m_cMomenum[0].m_mMatrix + (1 - Beta[0]) * delta.Power(2); for(ulong r = 0; r < G.Rows(); r++) for(ulong c = 0; c < G.Cols(); c++) G[r, c] = (G[r, c] > 0 ? lr / MathSqrt(G[r, c]) : 0); m_cWeights.m_mMatrix += G * delta; } else { //--- Создание буферов данных if(m_cWeights.GetIndex() < 0) return false; if(m_cDeltaWeights.GetIndex() < 0) return false; if(m_cMomenum[0].GetIndex() < 0) return false; //--- Передача аргументов кернелу if(!m_cOpenCL.SetArgumentBuffer(def_k_RMSPropUpdate, def_rms_delta_weights, m_cDeltaWeights.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_RMSPropUpdate, def_rms_weights, m_cWeights.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_RMSPropUpdate, def_rms_momentum, m_cMomenum[0].GetIndex())) return false; if(!m_cOpenCL.SetArgument(def_k_RMSPropUpdate, def_rms_total, m_cWeights.Total())) return false; if(!m_cOpenCL.SetArgument(def_k_RMSPropUpdate, def_rms_batch_size, batch_size)) return false; if(!m_cOpenCL.SetArgument(def_k_RMSPropUpdate, def_rms_learningRate, learningRate)) return false; if(!m_cOpenCL.SetArgument(def_k_RMSPropUpdate, def_rms_Lambda1, Lambda[0])) return false; if(!m_cOpenCL.SetArgument(def_k_RMSPropUpdate, def_rms_Lambda2, Lambda[1])) return false; if(!m_cOpenCL.SetArgument(def_k_RMSPropUpdate, def_rms_beta, Beta[0])) return false; //--- Постановка кернела в очередь выполнения int NDRange[] = { (int)((m_cOutputs.Total() + 3) / 4) }; int off_set[] = {0}; if(!m_cOpenCL.Execute(def_k_RMSPropUpdate, 1, off_set, NDRange)) return false; //--- Получение результатов работы кернела if(!m_cWeights.BufferRead()) return false; } //--- return true; } //+------------------------------------------------------------------+ //| Обновление матрицы весов методом AdaDelta | //+------------------------------------------------------------------+ bool CNeuronBase::AdaDeltaUpdate(int batch_size, double &Beta[], double &Lambda[]) { //--- Блок контролей for(int i = 0; i < 2; i++) { if(!m_cMomenum[i]) return false; if(m_cMomenum[i].Total() < m_cWeights.Total()) return false; } //--- Разветвление алгоритма в зависимости от устройста выполнениия операций if(CheckPointer(m_cOpenCL) == POINTER_INVALID) { MATRIX delta = m_cDeltaWeights.m_mMatrix / ((double)batch_size); MATRIX W = m_cMomenum[0].m_mMatrix = Beta[0] * m_cMomenum[0].m_mMatrix + (1 - Beta[0]) * m_cWeights.m_mMatrix.Power(2); m_cMomenum[1].m_mMatrix = Beta[1] * m_cMomenum[1].m_mMatrix + (1 - Beta[1]) * delta.Power(2); m_cWeights.m_mMatrix -= Lambda[0] + Lambda[1] * m_cWeights.m_mMatrix; for(ulong r = 0; r < W.Rows(); r++) for(ulong c = 0; c < W.Cols(); c++) W[r, c] = (m_cMomenum[1].m_mMatrix[r, c] > 0 ? MathSqrt(W[r, c]) / MathSqrt(m_cMomenum[1].m_mMatrix[r, c]) : 0); m_cWeights.m_mMatrix += W * delta; } else { //--- Создание буферов данных if(m_cWeights.GetIndex() < 0) return false; if(m_cDeltaWeights.GetIndex() < 0) return false; if(m_cMomenum[0].GetIndex() < 0) return false; if(m_cMomenum[1].GetIndex() < 0) return false; //--- Передача аргументов кернелу if(!m_cOpenCL.SetArgumentBuffer(def_k_AdaDeltaUpdate, def_adadelt_delta_weights, m_cDeltaWeights.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_AdaDeltaUpdate, def_adadelt_weights, m_cWeights.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_AdaDeltaUpdate, def_adadelt_momentumW, m_cMomenum[0].GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_AdaDeltaUpdate, def_adadelt_momentumG, m_cMomenum[1].GetIndex())) return false; if(!m_cOpenCL.SetArgument(def_k_AdaDeltaUpdate, def_adadelt_total, m_cWeights.Total())) return false; if(!m_cOpenCL.SetArgument(def_k_AdaDeltaUpdate, def_adadelt_batch_size, batch_size)) return false; if(!m_cOpenCL.SetArgument(def_k_AdaDeltaUpdate, def_adadelt_Lambda1, Lambda[0])) return false; if(!m_cOpenCL.SetArgument(def_k_AdaDeltaUpdate, def_adadelt_Lambda2, Lambda[1])) return false; if(!m_cOpenCL.SetArgument(def_k_AdaDeltaUpdate, def_adadelt_beta1, Beta[0])) return false; if(!m_cOpenCL.SetArgument(def_k_AdaDeltaUpdate, def_adadelt_beta2, Beta[1])) return false; //--- Постановка кернела в очередь выполнения int NDRange[] = { (int)((m_cOutputs.Total() + 3) / 4) }; int off_set[] = {0}; if(!m_cOpenCL.Execute(def_k_AdaDeltaUpdate, 1, off_set, NDRange)) return false; //--- Получение результатов работы кернела if(!m_cWeights.BufferRead()) return false; } //--- return true; } //+------------------------------------------------------------------+ //| Обновление матрицы весов методом Adam | //+------------------------------------------------------------------+ bool CNeuronBase::AdamUpdate(int batch_size, double learningRate, double &Beta[], double &Lambda[]) { //--- Блок контролей for(int i = 0; i < 2; i++) { if(!m_cMomenum[i]) return false; if(m_cMomenum[i].m_mMatrix.Rows() != m_cWeights.m_mMatrix.Rows() || m_cMomenum[i].m_mMatrix.Cols() != m_cWeights.m_mMatrix.Cols()) return false; } //--- Разветвление алгоритма в зависимости от устройста выполнениия операций if(CheckPointer(m_cOpenCL) == POINTER_INVALID) { MATRIX delta = m_cDeltaWeights.m_mMatrix / ((double)batch_size); MATRIX M = m_cMomenum[0].m_mMatrix = Beta[0] * m_cMomenum[0].m_mMatrix + (1 - Beta[0]) * delta; MATRIX V = m_cMomenum[1].m_mMatrix = Beta[1] * m_cMomenum[1].m_mMatrix + (1 - Beta[1]) * (delta * delta); M /= (1 - Beta[0]); V /= (1 - Beta[1]); m_cWeights.m_mMatrix -= Lambda[0] + Lambda[1] * m_cWeights.m_mMatrix; for(ulong r = 0; r < V.Rows(); r++) for(ulong c = 0; c < V.Cols(); c++) V[r, c] = (V[r, c] > 0 ? learningRate / MathSqrt(V[r, c]) : 0); m_cWeights.m_mMatrix += M * V; } else { //--- Создание буферов данных if(m_cWeights.GetIndex() < 0) return false; if(m_cDeltaWeights.GetIndex() < 0) return false; if(m_cMomenum[0].GetIndex() < 0) return false; if(m_cMomenum[1].GetIndex() < 0) return false; //--- Передача аргументов кернелу if(!m_cOpenCL.SetArgumentBuffer(def_k_AdamUpdate, def_adam_delta_weights, m_cDeltaWeights.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_AdamUpdate, def_adam_weights, m_cWeights.GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_AdamUpdate, def_adam_momentumM, m_cMomenum[0].GetIndex())) return false; if(!m_cOpenCL.SetArgumentBuffer(def_k_AdamUpdate, def_adam_momentumV, m_cMomenum[1].GetIndex())) return false; if(!m_cOpenCL.SetArgument(def_k_AdamUpdate, def_adam_total, m_cWeights.Total())) return false; if(!m_cOpenCL.SetArgument(def_k_AdamUpdate, def_adam_batch_size, batch_size)) return false; if(!m_cOpenCL.SetArgument(def_k_AdamUpdate, def_adam_Lambda1, Lambda[0])) return false; if(!m_cOpenCL.SetArgument(def_k_AdamUpdate, def_adam_Lambda2, Lambda[1])) return false; if(!m_cOpenCL.SetArgument(def_k_AdamUpdate, def_adam_beta1, Beta[0])) return false; if(!m_cOpenCL.SetArgument(def_k_AdamUpdate, def_adam_beta2, Beta[1])) return false; if(!m_cOpenCL.SetArgument(def_k_AdamUpdate, def_adam_learningRate, learningRate)) return false; //--- Постановка кернела в очередь выполнения int NDRange[] = { (int)((m_cOutputs.Total() + 3) / 4) }; int off_set[] = {0}; if(!m_cOpenCL.Execute(def_k_AdamUpdate, 1, off_set, NDRange)) return false; //--- Получение результатов работы кернела if(!m_cWeights.BufferRead()) return false; } //--- return true; } //+------------------------------------------------------------------+ //| Метод сохранения элементов класса в файл | //+------------------------------------------------------------------+ bool CNeuronBase::Save(const int file_handle) { //--- Блок контролей if(file_handle == INVALID_HANDLE) return false; //--- Запись данных буфера резльтатов if(!m_cOutputs) return false; if(FileWriteInteger(file_handle, Type()) <= 0 || FileWriteInteger(file_handle, m_cOutputs.Total()) <= 0) return false; //--- Проверка и запись флага слоя исходных данных if(!m_cActivation || !m_cWeights) { if(FileWriteInteger(file_handle, 1) <= 0) return false; return true; } if(FileWriteInteger(file_handle, 0) <= 0) return false; int momentums = 0; switch(m_eOptimization) { case SGD: momentums = 0; break; case MOMENTUM: case AdaGrad: case RMSProp: momentums = 1; break; case AdaDelta: case Adam: momentums = 2; break; default: return false; break; } for(int i = 0; i < momentums; i++) if(!m_cMomenum[i]) return false; //--- Сохранение матрицы весовых коэффициентов, моментов и функции активации if(FileWriteInteger(file_handle, (int)m_eOptimization) <= 0 || FileWriteInteger(file_handle, momentums) <= 0) return false; if(!m_cWeights.Save(file_handle) || !m_cActivation.Save(file_handle)) return false; for(int i = 0; i < momentums; i++) if(!m_cMomenum[i].Save(file_handle)) return false; //--- return true; } //+------------------------------------------------------------------+ //| Метод восстановлкения состояния класса из данных в файле | //+------------------------------------------------------------------+ bool CNeuronBase::Load(const int file_handle) { //--- Блок контролей if(file_handle == INVALID_HANDLE) return false; //--- Загрузка буфера результатов if(!m_cOutputs) if(!(m_cOutputs = new CBufferDouble())) return false; int outputs = FileReadInteger(file_handle); if(!m_cOutputs.BufferInit(1, outputs, 0)) return false; //--- Создание буфера градиентов ошибки if(!m_cGradients) if(!(m_cGradients = new CBufferDouble())) return false; if(!m_cGradients.BufferInit(1, outputs, 0)) return false; //--- Проверка флага слоя исходных данных int input_layer = FileReadInteger(file_handle); if(input_layer == 1) { if(m_cActivation) delete m_cActivation; if(m_cWeights) delete m_cWeights; if(m_cDeltaWeights) delete m_cDeltaWeights; if(m_cMomenum[0]) delete m_cMomenum[0]; if(m_cMomenum[1]) delete m_cMomenum[1]; if(m_cOpenCL) if(!m_cOutputs.BufferCreate(m_cOpenCL)) return false; m_eOptimization = None; return true; } //--- Создание объектов перед загрузкой данных if(!m_cActivation) if(!(m_cActivation = new CActivation())) return false; if(!m_cWeights) if(!(m_cWeights = new CBufferDouble())) return false; m_eOptimization = (ENUM_OPTIMIZATION)FileReadInteger(file_handle); int momentums = FileReadInteger(file_handle); //--- Загрузка данный из файла if(!m_cWeights.Load(file_handle) || !m_cActivation.Load(file_handle)) return false; for(int i = 0; i < momentums; i++) { if(!m_cMomenum[i]) if(!(m_cMomenum[i] = new CBufferDouble())) return false; if(!m_cMomenum[i].Load(file_handle)) return false; } //--- Инициализация оставшихся буферов if(!m_cDeltaWeights) if(!(m_cDeltaWeights = new CBufferDouble())) return false; //if(!m_cDeltaWeights.BufferInit(m_cWeights.m_mMatrix.Rows(),m_cWeights.m_mMatrix.Cols(), 0)) // return false; //--- Передача указателя на объекто OpenCL в объект функции активации m_cActivation.SetOpenCL(m_cOpenCL); //--- return true; } //+------------------------------------------------------------------+