- BufferDouble: replace hardcoded "DirectML/CPU-DLL" with dynamic backend name and add buffer index/element count to all error prints for easier debugging. - NetPersistence: distinguish missing file from transient lock by probing FileIsExist before logging, eliminating false "sharing violation" warnings when no saved model exists on first run.
734 lines
33 KiB
MQL5
734 lines
33 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| NeuronOCLConvPool.mqh |
|
|
//| AnimateDread |
|
|
//| https://www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
//| CNeuronConvOCL/CNeuronPoolOCL - the GPU-accelerated (OpenCL + |
|
|
//| DirectML) convolution and max-pooling layers. Both derive from |
|
|
//| CNeuronBaseOCL (AI\Network.mqh, must already be declared) and use|
|
|
//| CBufferDouble (AI\BufferDouble.mqh). Included from Network.mqh at|
|
|
//| the exact point these classes used to sit (right after |
|
|
//| CNeuronBaseOCL's own declaration, before CNeuronLSTMOCL), so |
|
|
//| ordering matches the original file. Extracted verbatim (SOLID |
|
|
//| cleanup) - no logic changes. |
|
|
//+------------------------------------------------------------------+
|
|
#include "BufferDouble.mqh"
|
|
//| GPU-accelerated convolution layer (OpenCL + DirectML). Ported |
|
|
//| from the NeuroNet_DNG reference library's CNeuronConvOCL/ |
|
|
//| CNeuronProofOCL, adapted to this project's double-precision |
|
|
//| CBufferDouble/COpenCLMy/CDirectMLMy conventions. A single |
|
|
//| (window+1)*window_out weight block is shared across every |
|
|
//| sliding position - unlike CNeuronBaseOCL, where every output has |
|
|
//| its own private weight vector. |
|
|
//+------------------------------------------------------------------+
|
|
class CNeuronConvOCL : public CNeuronBaseOCL
|
|
{
|
|
protected:
|
|
uint iWindow;
|
|
uint iStep;
|
|
uint iWindowOut;
|
|
CBufferDouble *WeightsConv;
|
|
CBufferDouble *DeltaWeightsConv;
|
|
CBufferDouble *FirstMomentumConv;
|
|
CBufferDouble *SecondMomentumConv;
|
|
//---
|
|
virtual bool feedForward(CNeuronBaseOCL *NeuronOCL);
|
|
virtual bool feedForwardCPU(CNeuronBaseOCL *NeuronOCL); // pure-MQL5 mirror of FeedForwardConv
|
|
virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL);
|
|
|
|
public:
|
|
CNeuronConvOCL(void) : iWindow(1), iStep(1), iWindowOut(1)
|
|
{
|
|
WeightsConv = NULL;
|
|
DeltaWeightsConv = NULL;
|
|
FirstMomentumConv = NULL;
|
|
SecondMomentumConv = NULL;
|
|
}
|
|
~CNeuronConvOCL(void);
|
|
virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint window_in, uint step, uint window_out, uint units_count, ENUM_OPTIMIZATION optimization_type);
|
|
virtual bool Init(uint numOutputs, uint myIndex, CDirectMLMy *direct_ml, uint window_in, uint step, uint window_out, uint units_count, ENUM_OPTIMIZATION optimization_type);
|
|
virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL);
|
|
virtual bool Save(int const file_handle);
|
|
virtual bool Load(int const file_handle);
|
|
virtual int Type(void) const { return defNeuronConvOCL; }
|
|
// Shape accessors. A .nnw persists the window/step it was BUILT with, so a model saved by an older
|
|
// build keeps that shape forever on load - these let EnforceTopologyContract() detect a stale
|
|
// receptive field instead of training on silently. See AI\Network.mqh's CNet::FirstConvWindow.
|
|
uint Window(void) const { return iWindow; }
|
|
uint Step(void) const { return iStep; }
|
|
uint WindowOut(void) const { return iWindowOut; }
|
|
// See CNeuronBaseOCL::getWeights/setWeights - same pair, targeting WeightsConv instead of the
|
|
// base class's Weights, for CNet::BlendWeightsFrom()'s EMA shadow-weight deployment.
|
|
virtual int getWeightsConv(double &values[]) { return (CheckPointer(WeightsConv) == POINTER_INVALID ? 0 : WeightsConv.GetData(values)); }
|
|
virtual bool setWeightsConv(double &values[])
|
|
{
|
|
if(CheckPointer(WeightsConv) == POINTER_INVALID)
|
|
return false;
|
|
if(!WeightsConv.AssignArray(values))
|
|
return false;
|
|
return WeightsConv.BufferWrite();
|
|
}
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
CNeuronConvOCL::~CNeuronConvOCL(void)
|
|
{
|
|
if(CheckPointer(WeightsConv) != POINTER_INVALID)
|
|
delete WeightsConv;
|
|
if(CheckPointer(DeltaWeightsConv) != POINTER_INVALID)
|
|
delete DeltaWeightsConv;
|
|
if(CheckPointer(FirstMomentumConv) != POINTER_INVALID)
|
|
delete FirstMomentumConv;
|
|
if(CheckPointer(SecondMomentumConv) != POINTER_INVALID)
|
|
delete SecondMomentumConv;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronConvOCL::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint window_in, uint step, uint window_out, uint units_count, ENUM_OPTIMIZATION optimization_type)
|
|
{
|
|
if(window_out <= 0)
|
|
return false;
|
|
if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, units_count * window_out, optimization_type))
|
|
return false;
|
|
//---
|
|
iWindow = window_in;
|
|
iStep = step;
|
|
iWindowOut = (uint)fmax(window_out, 1);
|
|
//---
|
|
int count = (int)((iWindow + 1) * iWindowOut);
|
|
if(CheckPointer(WeightsConv) == POINTER_INVALID)
|
|
{
|
|
WeightsConv = new CBufferDouble();
|
|
if(CheckPointer(WeightsConv) == POINTER_INVALID)
|
|
return false;
|
|
}
|
|
if(!WeightsConv.Reserve(count))
|
|
return false;
|
|
// Fan-in-scaled (LeCun-uniform) init - see CNeuronBaseOCL::Init's OpenCL overload for the full
|
|
// rationale; fan-in here is the conv window size.
|
|
double weighScale = 1.0 / MathSqrt((double)iWindow + 1.0);
|
|
for(int i = 0; i < count; i++)
|
|
{
|
|
double weigh = ((MathRand() + 1) / 32768.0 - 0.5) * 2.0 * weighScale;
|
|
if(weigh == 0)
|
|
weigh = 0.001;
|
|
if(!WeightsConv.Add(weigh))
|
|
return false;
|
|
}
|
|
if(!WeightsConv.BufferCreate(OpenCL))
|
|
return false;
|
|
//---
|
|
if(optimization == SGD)
|
|
{
|
|
if(CheckPointer(DeltaWeightsConv) == POINTER_INVALID)
|
|
{
|
|
DeltaWeightsConv = new CBufferDouble();
|
|
if(CheckPointer(DeltaWeightsConv) == POINTER_INVALID)
|
|
return false;
|
|
}
|
|
if(!DeltaWeightsConv.BufferInit(count, 0))
|
|
return false;
|
|
if(!DeltaWeightsConv.BufferCreate(OpenCL))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if(CheckPointer(FirstMomentumConv) == POINTER_INVALID)
|
|
{
|
|
FirstMomentumConv = new CBufferDouble();
|
|
if(CheckPointer(FirstMomentumConv) == POINTER_INVALID)
|
|
return false;
|
|
}
|
|
if(!FirstMomentumConv.BufferInit(count, 0))
|
|
return false;
|
|
if(!FirstMomentumConv.BufferCreate(OpenCL))
|
|
return false;
|
|
//---
|
|
if(CheckPointer(SecondMomentumConv) == POINTER_INVALID)
|
|
{
|
|
SecondMomentumConv = new CBufferDouble();
|
|
if(CheckPointer(SecondMomentumConv) == POINTER_INVALID)
|
|
return false;
|
|
}
|
|
if(!SecondMomentumConv.BufferInit(count, 0))
|
|
return false;
|
|
if(!SecondMomentumConv.BufferCreate(OpenCL))
|
|
return false;
|
|
}
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| DirectML/D3D12 tier equivalent of Init(COpenCLMy*) above. |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronConvOCL::Init(uint numOutputs, uint myIndex, CDirectMLMy *direct_ml, uint window_in, uint step, uint window_out, uint units_count, ENUM_OPTIMIZATION optimization_type)
|
|
{
|
|
if(window_out <= 0)
|
|
return false;
|
|
if(!CNeuronBaseOCL::Init(numOutputs, myIndex, direct_ml, units_count * window_out, optimization_type))
|
|
return false;
|
|
//---
|
|
iWindow = window_in;
|
|
iStep = step;
|
|
iWindowOut = (uint)fmax(window_out, 1);
|
|
//---
|
|
int count = (int)((iWindow + 1) * iWindowOut);
|
|
if(CheckPointer(WeightsConv) == POINTER_INVALID)
|
|
{
|
|
WeightsConv = new CBufferDouble();
|
|
if(CheckPointer(WeightsConv) == POINTER_INVALID)
|
|
return false;
|
|
}
|
|
if(!WeightsConv.Reserve(count))
|
|
return false;
|
|
// Fan-in-scaled (LeCun-uniform) init - see the matching OpenCL Init() overload above.
|
|
double weighScale = 1.0 / MathSqrt((double)iWindow + 1.0);
|
|
for(int i = 0; i < count; i++)
|
|
{
|
|
double weigh = ((MathRand() + 1) / 32768.0 - 0.5) * 2.0 * weighScale;
|
|
if(weigh == 0)
|
|
weigh = 0.001;
|
|
if(!WeightsConv.Add(weigh))
|
|
return false;
|
|
}
|
|
if(!WeightsConv.BufferCreate(DirectML))
|
|
return false;
|
|
//---
|
|
if(optimization == SGD)
|
|
{
|
|
if(CheckPointer(DeltaWeightsConv) == POINTER_INVALID)
|
|
{
|
|
DeltaWeightsConv = new CBufferDouble();
|
|
if(CheckPointer(DeltaWeightsConv) == POINTER_INVALID)
|
|
return false;
|
|
}
|
|
if(!DeltaWeightsConv.BufferInit(count, 0))
|
|
return false;
|
|
if(!DeltaWeightsConv.BufferCreate(DirectML))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if(CheckPointer(FirstMomentumConv) == POINTER_INVALID)
|
|
{
|
|
FirstMomentumConv = new CBufferDouble();
|
|
if(CheckPointer(FirstMomentumConv) == POINTER_INVALID)
|
|
return false;
|
|
}
|
|
if(!FirstMomentumConv.BufferInit(count, 0))
|
|
return false;
|
|
if(!FirstMomentumConv.BufferCreate(DirectML))
|
|
return false;
|
|
//---
|
|
if(CheckPointer(SecondMomentumConv) == POINTER_INVALID)
|
|
{
|
|
SecondMomentumConv = new CBufferDouble();
|
|
if(CheckPointer(SecondMomentumConv) == POINTER_INVALID)
|
|
return false;
|
|
}
|
|
if(!SecondMomentumConv.BufferInit(count, 0))
|
|
return false;
|
|
if(!SecondMomentumConv.BufferCreate(DirectML))
|
|
return false;
|
|
}
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronConvOCL::feedForward(CNeuronBaseOCL *NeuronOCL)
|
|
{
|
|
if(CheckPointer(NeuronOCL) == POINTER_INVALID)
|
|
return false;
|
|
int positions = Output.Total() / (int)iWindowOut;
|
|
if(CheckPointer(DirectML) != POINTER_INVALID)
|
|
{
|
|
if(!DirectML.FeedForwardConv(WeightsConv.GetIndex(), NeuronOCL.getOutputIndex(), Output.GetIndex(),
|
|
NeuronOCL.Neurons(), (int)iStep, (int)iWindow, (int)iWindowOut, NativeActivationCode(activation), positions))
|
|
{
|
|
Print(__FUNCTION__ + ": " + DirectML.BackendName() + " FeedForwardConv failed, error " + IntegerToString(DirectML.LastError()));
|
|
return false;
|
|
}
|
|
return Output.BufferRead();
|
|
}
|
|
if(CheckPointer(OpenCL) == POINTER_INVALID)
|
|
return false;
|
|
uint global_work_offset[1] = {0};
|
|
uint global_work_size[1];
|
|
global_work_size[0] = (uint)positions;
|
|
OpenCL.SetArgumentBuffer(def_k_FeedForwardConv, def_k_ffc_matrix_w, WeightsConv.GetIndex());
|
|
OpenCL.SetArgumentBuffer(def_k_FeedForwardConv, def_k_ffc_matrix_i, NeuronOCL.getOutputIndex());
|
|
OpenCL.SetArgumentBuffer(def_k_FeedForwardConv, def_k_ffc_matrix_o, Output.GetIndex());
|
|
OpenCL.SetArgument(def_k_FeedForwardConv, def_k_ffc_inputs, NeuronOCL.Neurons());
|
|
OpenCL.SetArgument(def_k_FeedForwardConv, def_k_ffc_step, (int)iStep);
|
|
OpenCL.SetArgument(def_k_FeedForwardConv, def_k_ffc_window_in, (int)iWindow);
|
|
OpenCL.SetArgument(def_k_FeedForwardConv, def_k_ffc_window_out, (int)iWindowOut);
|
|
OpenCL.SetArgument(def_k_FeedForwardConv, def_k_ffc_activation, NativeActivationCode(activation));
|
|
if(!OpenCL.Execute(def_k_FeedForwardConv, 1, global_work_offset, global_work_size))
|
|
{
|
|
printf("Error of execution kernel FeedForwardConv: %d", GetLastError());
|
|
return false;
|
|
}
|
|
//--- Output stays GPU-resident; see the note in CNeuronBaseOCL::feedForward().
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Pure-MQL5 double-precision mirror of Network.cl's FeedForwardConv |
|
|
//| kernel (host buffers only) - the CPU inference path. Filters live |
|
|
//| in this layer's own WeightsConv, [window_out][window_in+1] with |
|
|
//| the +1 bias last; inputs are the previous layer's Output. |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronConvOCL::feedForwardCPU(CNeuronBaseOCL *NeuronOCL)
|
|
{
|
|
if(CheckPointer(NeuronOCL) == POINTER_INVALID || CheckPointer(Output) == POINTER_INVALID || CheckPointer(WeightsConv) == POINTER_INVALID)
|
|
return false;
|
|
int inputs = NeuronOCL.Neurons();
|
|
int window_in = (int)iWindow;
|
|
int step = (int)iStep;
|
|
int window_out = (int)iWindowOut;
|
|
if(window_out <= 0)
|
|
return false;
|
|
int positions = Output.Total() / window_out;
|
|
int wTotal = WeightsConv.Total();
|
|
for(int i = 0; i < positions; i++)
|
|
{
|
|
int shift_out = window_out * i;
|
|
int shift_in = step * i;
|
|
for(int out = 0; out < window_out; out++)
|
|
{
|
|
int shift = (window_in + 1) * out;
|
|
if(shift + window_in >= wTotal)
|
|
return false;
|
|
int stop = (window_in <= (inputs - shift_in)) ? window_in : (inputs - shift_in);
|
|
double sum = 0.0;
|
|
for(int k = 0; k < stop; k++)
|
|
sum += NeuronOCL.OutputHost(shift_in + k) * WeightsConv.At(shift + k);
|
|
sum += WeightsConv.At(shift + window_in); // bias
|
|
switch(activation)
|
|
{
|
|
case TANH:
|
|
sum = tanh(sum);
|
|
break;
|
|
case SIGMOID:
|
|
sum = 1.0 / (1.0 + exp(-MathMax(-50.0, MathMin(50.0, sum))));
|
|
break;
|
|
case PRELU:
|
|
if(sum < 0.0)
|
|
sum *= 0.01;
|
|
break;
|
|
}
|
|
if(!Output.Update(out + shift_out, sum))
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Writes the gradient into NeuronOCL (the EARLIER/input-side layer) |
|
|
//| - opposite call direction from the dense calcHiddenGradients, but |
|
|
//| matches the reference library's own CNeuronConvOCL convention. |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronConvOCL::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
|
|
{
|
|
if(CheckPointer(NeuronOCL) == POINTER_INVALID)
|
|
return false;
|
|
int outputs = Neurons();
|
|
if(CheckPointer(DirectML) != POINTER_INVALID)
|
|
{
|
|
if(!DirectML.CalcHiddenGradientConv(WeightsConv.GetIndex(), getGradientIndex(), NeuronOCL.getOutputIndex(), NeuronOCL.getGradientIndex(),
|
|
outputs, (int)iStep, (int)iWindow, (int)iWindowOut, (int)NeuronOCL.Activation(), NeuronOCL.Neurons()))
|
|
{
|
|
Print(__FUNCTION__ + ": " + DirectML.BackendName() + " CalcHiddenGradientConv failed, error " + IntegerToString(DirectML.LastError()));
|
|
return false;
|
|
}
|
|
double temp[];
|
|
return NeuronOCL.getGradient(temp) > 0;
|
|
}
|
|
if(CheckPointer(OpenCL) == POINTER_INVALID)
|
|
return false;
|
|
uint global_work_offset[1] = {0};
|
|
uint global_work_size[1];
|
|
global_work_size[0] = NeuronOCL.Neurons();
|
|
OpenCL.SetArgumentBuffer(def_k_CalcHiddenGradientConv, def_k_chgc_matrix_w, WeightsConv.GetIndex());
|
|
OpenCL.SetArgumentBuffer(def_k_CalcHiddenGradientConv, def_k_chgc_matrix_g, getGradientIndex());
|
|
OpenCL.SetArgumentBuffer(def_k_CalcHiddenGradientConv, def_k_chgc_matrix_o, NeuronOCL.getOutputIndex());
|
|
OpenCL.SetArgumentBuffer(def_k_CalcHiddenGradientConv, def_k_chgc_matrix_ig, NeuronOCL.getGradientIndex());
|
|
OpenCL.SetArgument(def_k_CalcHiddenGradientConv, def_k_chgc_outputs, outputs);
|
|
OpenCL.SetArgument(def_k_CalcHiddenGradientConv, def_k_chgc_step, (int)iStep);
|
|
OpenCL.SetArgument(def_k_CalcHiddenGradientConv, def_k_chgc_window_in, (int)iWindow);
|
|
OpenCL.SetArgument(def_k_CalcHiddenGradientConv, def_k_chgc_window_out, (int)iWindowOut);
|
|
OpenCL.SetArgument(def_k_CalcHiddenGradientConv, def_k_chgc_activation, (int)NeuronOCL.Activation());
|
|
if(!OpenCL.Execute(def_k_CalcHiddenGradientConv, 1, global_work_offset, global_work_size))
|
|
{
|
|
printf("Error of execution kernel CalcHiddenGradientConv: %d", GetLastError());
|
|
return false;
|
|
}
|
|
//--- NeuronOCL's Gradient stays GPU-resident; its own calcHiddenGradients/calcInputGradients
|
|
//--- reads it via getGradientIndex(). The old getGradient(temp) call was a discarded-result
|
|
//--- sync (GetData() BufferRead()s internally) with no consumer of the read - pure overhead.
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronConvOCL::updateInputWeights(CNeuronBaseOCL *NeuronOCL)
|
|
{
|
|
if(CheckPointer(NeuronOCL) == POINTER_INVALID)
|
|
return false;
|
|
int inputs = NeuronOCL.Neurons();
|
|
if(CheckPointer(DirectML) != POINTER_INVALID)
|
|
{
|
|
if(optimization == SGD)
|
|
{
|
|
if(!DirectML.UpdateWeightsConvMomentum(WeightsConv.GetIndex(), getGradientIndex(), NeuronOCL.getOutputIndex(), DeltaWeightsConv.GetIndex(),
|
|
inputs, eta, alpha, (int)iWindow, (int)iWindowOut, (int)iStep, 0))
|
|
{
|
|
Print(__FUNCTION__ + ": " + DirectML.BackendName() + " UpdateWeightsConvMomentum failed, error " + IntegerToString(DirectML.LastError()));
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
double lt = eta * sqrt(1 - pow(b2, t)) / (1 - pow(b1, t));
|
|
if(!DirectML.UpdateWeightsConvAdam(WeightsConv.GetIndex(), getGradientIndex(), NeuronOCL.getOutputIndex(),
|
|
FirstMomentumConv.GetIndex(), SecondMomentumConv.GetIndex(), inputs, lt, b1, b2, (int)iWindow, (int)iWindowOut, (int)iStep))
|
|
{
|
|
Print(__FUNCTION__ + ": " + DirectML.BackendName() + " UpdateWeightsConvAdam failed, error " + IntegerToString(DirectML.LastError()));
|
|
return false;
|
|
}
|
|
t++;
|
|
}
|
|
//--- WeightsConv stays DLL-resident; feedForward reads it via GetIndex() (same as the OpenCL
|
|
//--- branch below). Save()/BlendWeightsFrom() BufferRead() on demand.
|
|
return true;
|
|
}
|
|
if(CheckPointer(OpenCL) == POINTER_INVALID)
|
|
return false;
|
|
uint global_work_offset[1] = {0};
|
|
uint global_work_size[1];
|
|
if(optimization == SGD)
|
|
{
|
|
global_work_size[0] = WeightsConv.Total();
|
|
OpenCL.SetArgumentBuffer(def_k_UpdateWeightsConvMomentum, def_k_uwcm_matrix_w, WeightsConv.GetIndex());
|
|
OpenCL.SetArgumentBuffer(def_k_UpdateWeightsConvMomentum, def_k_uwcm_matrix_g, getGradientIndex());
|
|
OpenCL.SetArgumentBuffer(def_k_UpdateWeightsConvMomentum, def_k_uwcm_matrix_i, NeuronOCL.getOutputIndex());
|
|
OpenCL.SetArgumentBuffer(def_k_UpdateWeightsConvMomentum, def_k_uwcm_matrix_dw, DeltaWeightsConv.GetIndex());
|
|
OpenCL.SetArgument(def_k_UpdateWeightsConvMomentum, def_k_uwcm_inputs, inputs);
|
|
OpenCL.SetArgument(def_k_UpdateWeightsConvMomentum, def_k_uwcm_learning_rates, (float)eta);
|
|
OpenCL.SetArgument(def_k_UpdateWeightsConvMomentum, def_k_uwcm_momentum, (float)alpha);
|
|
OpenCL.SetArgument(def_k_UpdateWeightsConvMomentum, def_k_uwcm_window_in, (int)iWindow);
|
|
OpenCL.SetArgument(def_k_UpdateWeightsConvMomentum, def_k_uwcm_window_out, (int)iWindowOut);
|
|
OpenCL.SetArgument(def_k_UpdateWeightsConvMomentum, def_k_uwcm_step, (int)iStep);
|
|
OpenCL.SetArgument(def_k_UpdateWeightsConvMomentum, def_k_uwcm_optimizer, 0);
|
|
ResetLastError();
|
|
if(!OpenCL.Execute(def_k_UpdateWeightsConvMomentum, 1, global_work_offset, global_work_size))
|
|
{
|
|
printf("Error of execution kernel UpdateWeightsConvMomentum: %d", GetLastError());
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
global_work_size[0] = iWindow + 1;
|
|
double lt = eta * sqrt(1 - pow(b2, t)) / (1 - pow(b1, t));
|
|
OpenCL.SetArgumentBuffer(def_k_UpdateWeightsConvAdam, def_k_uwca_matrix_w, WeightsConv.GetIndex());
|
|
OpenCL.SetArgumentBuffer(def_k_UpdateWeightsConvAdam, def_k_uwca_matrix_g, getGradientIndex());
|
|
OpenCL.SetArgumentBuffer(def_k_UpdateWeightsConvAdam, def_k_uwca_matrix_i, NeuronOCL.getOutputIndex());
|
|
OpenCL.SetArgumentBuffer(def_k_UpdateWeightsConvAdam, def_k_uwca_matrix_m, FirstMomentumConv.GetIndex());
|
|
OpenCL.SetArgumentBuffer(def_k_UpdateWeightsConvAdam, def_k_uwca_matrix_v, SecondMomentumConv.GetIndex());
|
|
OpenCL.SetArgument(def_k_UpdateWeightsConvAdam, def_k_uwca_inputs, inputs);
|
|
OpenCL.SetArgument(def_k_UpdateWeightsConvAdam, def_k_uwca_l, (float)lt);
|
|
OpenCL.SetArgument(def_k_UpdateWeightsConvAdam, def_k_uwca_b1, (float)b1);
|
|
OpenCL.SetArgument(def_k_UpdateWeightsConvAdam, def_k_uwca_b2, (float)b2);
|
|
OpenCL.SetArgument(def_k_UpdateWeightsConvAdam, def_k_uwca_window_in, (int)iWindow);
|
|
OpenCL.SetArgument(def_k_UpdateWeightsConvAdam, def_k_uwca_window_out, (int)iWindowOut);
|
|
OpenCL.SetArgument(def_k_UpdateWeightsConvAdam, def_k_uwca_step, (int)iStep);
|
|
ResetLastError();
|
|
if(!OpenCL.Execute(def_k_UpdateWeightsConvAdam, 1, global_work_offset, global_work_size))
|
|
{
|
|
printf("Error of execution kernel UpdateWeightsConvAdam: %d", GetLastError());
|
|
return false;
|
|
}
|
|
t++;
|
|
}
|
|
//--- WeightsConv stays GPU-resident; see the note in CNeuronBaseOCL::updateInputWeights().
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronConvOCL::Save(const int file_handle)
|
|
{
|
|
if(!CNeuronBaseOCL::Save(file_handle))
|
|
return false;
|
|
if(FileWriteInteger(file_handle, (int)iWindow, INT_VALUE) < INT_VALUE)
|
|
return false;
|
|
if(FileWriteInteger(file_handle, (int)iStep, INT_VALUE) < INT_VALUE)
|
|
return false;
|
|
if(FileWriteInteger(file_handle, (int)iWindowOut, INT_VALUE) < INT_VALUE)
|
|
return false;
|
|
if(CheckPointer(WeightsConv) == POINTER_INVALID || !WeightsConv.BufferRead() || !WeightsConv.Save(file_handle))
|
|
return false;
|
|
if(optimization == SGD)
|
|
{
|
|
if(CheckPointer(DeltaWeightsConv) == POINTER_INVALID || !DeltaWeightsConv.BufferRead() || !DeltaWeightsConv.Save(file_handle))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if(CheckPointer(FirstMomentumConv) == POINTER_INVALID || !FirstMomentumConv.BufferRead() || !FirstMomentumConv.Save(file_handle))
|
|
return false;
|
|
if(CheckPointer(SecondMomentumConv) == POINTER_INVALID || !SecondMomentumConv.BufferRead() || !SecondMomentumConv.Save(file_handle))
|
|
return false;
|
|
}
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronConvOCL::Load(const int file_handle)
|
|
{
|
|
if(!CNeuronBaseOCL::Load(file_handle))
|
|
return false;
|
|
iWindow = (uint)FileReadInteger(file_handle, INT_VALUE);
|
|
iStep = (uint)FileReadInteger(file_handle, INT_VALUE);
|
|
iWindowOut = (uint)FileReadInteger(file_handle, INT_VALUE);
|
|
//---
|
|
if(CheckPointer(WeightsConv) == POINTER_INVALID)
|
|
{
|
|
WeightsConv = new CBufferDouble();
|
|
if(CheckPointer(WeightsConv) == POINTER_INVALID)
|
|
return false;
|
|
}
|
|
if(WeightsConv.GetIndex() >= 0)
|
|
WeightsConv.BufferFree();
|
|
if(!WeightsConv.Load(file_handle))
|
|
return false;
|
|
if(!BackendBufferCreate(WeightsConv))
|
|
return false;
|
|
//---
|
|
if(optimization == SGD)
|
|
{
|
|
if(CheckPointer(DeltaWeightsConv) == POINTER_INVALID)
|
|
{
|
|
DeltaWeightsConv = new CBufferDouble();
|
|
if(CheckPointer(DeltaWeightsConv) == POINTER_INVALID)
|
|
return false;
|
|
}
|
|
if(DeltaWeightsConv.GetIndex() >= 0)
|
|
DeltaWeightsConv.BufferFree();
|
|
if(!DeltaWeightsConv.Load(file_handle))
|
|
return false;
|
|
if(!BackendBufferCreate(DeltaWeightsConv))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if(CheckPointer(FirstMomentumConv) == POINTER_INVALID)
|
|
{
|
|
FirstMomentumConv = new CBufferDouble();
|
|
if(CheckPointer(FirstMomentumConv) == POINTER_INVALID)
|
|
return false;
|
|
}
|
|
if(FirstMomentumConv.GetIndex() >= 0)
|
|
FirstMomentumConv.BufferFree();
|
|
if(!FirstMomentumConv.Load(file_handle))
|
|
return false;
|
|
if(!BackendBufferCreate(FirstMomentumConv))
|
|
return false;
|
|
//---
|
|
if(CheckPointer(SecondMomentumConv) == POINTER_INVALID)
|
|
{
|
|
SecondMomentumConv = new CBufferDouble();
|
|
if(CheckPointer(SecondMomentumConv) == POINTER_INVALID)
|
|
return false;
|
|
}
|
|
if(SecondMomentumConv.GetIndex() >= 0)
|
|
SecondMomentumConv.BufferFree();
|
|
if(!SecondMomentumConv.Load(file_handle))
|
|
return false;
|
|
if(!BackendBufferCreate(SecondMomentumConv))
|
|
return false;
|
|
}
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| GPU-accelerated max-pooling layer (OpenCL + DirectML). No weights,|
|
|
//| so no updateInputWeights work - just a sliding max. Ported from |
|
|
//| the NeuroNet_DNG reference's CNeuronProofOCL kernels. |
|
|
//+------------------------------------------------------------------+
|
|
class CNeuronPoolOCL : public CNeuronBaseOCL
|
|
{
|
|
protected:
|
|
uint iWindow;
|
|
uint iStep;
|
|
//---
|
|
virtual bool feedForward(CNeuronBaseOCL *NeuronOCL);
|
|
virtual bool feedForwardCPU(CNeuronBaseOCL *NeuronOCL); // pure-MQL5 mirror of FeedForwardProof
|
|
virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) { return true; }
|
|
|
|
public:
|
|
CNeuronPoolOCL(void) : iWindow(2), iStep(1) {}
|
|
~CNeuronPoolOCL(void) {}
|
|
virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint window, uint step, uint units_count, ENUM_OPTIMIZATION optimization_type);
|
|
virtual bool Init(uint numOutputs, uint myIndex, CDirectMLMy *direct_ml, uint window, uint step, uint units_count, ENUM_OPTIMIZATION optimization_type);
|
|
virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL);
|
|
virtual bool Save(int const file_handle);
|
|
virtual bool Load(int const file_handle);
|
|
virtual int Type(void) const { return defNeuronPoolOCL; }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronPoolOCL::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint window, uint step, uint units_count, ENUM_OPTIMIZATION optimization_type)
|
|
{
|
|
if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, units_count, optimization_type))
|
|
return false;
|
|
iWindow = window;
|
|
iStep = step;
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronPoolOCL::Init(uint numOutputs, uint myIndex, CDirectMLMy *direct_ml, uint window, uint step, uint units_count, ENUM_OPTIMIZATION optimization_type)
|
|
{
|
|
if(!CNeuronBaseOCL::Init(numOutputs, myIndex, direct_ml, units_count, optimization_type))
|
|
return false;
|
|
iWindow = window;
|
|
iStep = step;
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronPoolOCL::feedForward(CNeuronBaseOCL *NeuronOCL)
|
|
{
|
|
if(CheckPointer(NeuronOCL) == POINTER_INVALID)
|
|
return false;
|
|
int outputs = Output.Total();
|
|
if(CheckPointer(DirectML) != POINTER_INVALID)
|
|
{
|
|
if(!DirectML.FeedForwardProof(NeuronOCL.getOutputIndex(), Output.GetIndex(), NeuronOCL.Neurons(), (int)iWindow, (int)iStep, outputs))
|
|
{
|
|
Print(__FUNCTION__ + ": " + DirectML.BackendName() + " FeedForwardProof failed, error " + IntegerToString(DirectML.LastError()));
|
|
return false;
|
|
}
|
|
return Output.BufferRead();
|
|
}
|
|
if(CheckPointer(OpenCL) == POINTER_INVALID)
|
|
return false;
|
|
uint offset1[1] = {0};
|
|
uint size1[1] = {(uint)outputs};
|
|
OpenCL.SetArgumentBuffer(def_k_FeedForwardProof, def_k_ffp_matrix_i, NeuronOCL.getOutputIndex());
|
|
OpenCL.SetArgumentBuffer(def_k_FeedForwardProof, def_k_ffp_matrix_o, Output.GetIndex());
|
|
OpenCL.SetArgument(def_k_FeedForwardProof, def_k_ffp_inputs, NeuronOCL.Neurons());
|
|
OpenCL.SetArgument(def_k_FeedForwardProof, def_k_ffp_window, (int)iWindow);
|
|
OpenCL.SetArgument(def_k_FeedForwardProof, def_k_ffp_step, (int)iStep);
|
|
if(!OpenCL.Execute(def_k_FeedForwardProof, 1, offset1, size1))
|
|
{
|
|
printf("Error of execution kernel FeedForwardProof: %d", GetLastError());
|
|
return false;
|
|
}
|
|
//--- Output stays GPU-resident; see the note in CNeuronBaseOCL::feedForward().
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Pure-MQL5 mirror of Network.cl's FeedForwardProof kernel (sliding |
|
|
//| max-pool over the previous layer's Output). Host buffers only. |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronPoolOCL::feedForwardCPU(CNeuronBaseOCL *NeuronOCL)
|
|
{
|
|
if(CheckPointer(NeuronOCL) == POINTER_INVALID || CheckPointer(Output) == POINTER_INVALID)
|
|
return false;
|
|
int outputs = Output.Total();
|
|
int inputs = NeuronOCL.Neurons();
|
|
int window = (int)iWindow;
|
|
int step = (int)iStep;
|
|
for(int i = 0; i < outputs; i++)
|
|
{
|
|
int pos = i * step;
|
|
if(pos >= inputs)
|
|
return false;
|
|
double result = NeuronOCL.OutputHost(pos);
|
|
for(int k = 1; k < window; k++)
|
|
{
|
|
int shift = k + pos;
|
|
if(shift >= inputs)
|
|
break;
|
|
result = MathMax(result, NeuronOCL.OutputHost(shift));
|
|
}
|
|
if(!Output.Update(i, result))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Inverted-call convention, same as Conv/LSTM's calcInputGradients. |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronPoolOCL::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
|
|
{
|
|
if(CheckPointer(NeuronOCL) == POINTER_INVALID)
|
|
return false;
|
|
int outputs = Neurons();
|
|
int inputs = NeuronOCL.Neurons();
|
|
if(CheckPointer(DirectML) != POINTER_INVALID)
|
|
{
|
|
if(!DirectML.CalcInputGradientProof(NeuronOCL.getOutputIndex(), getGradientIndex(), getOutputIndex(), NeuronOCL.getGradientIndex(),
|
|
outputs, (int)iWindow, (int)iStep, inputs))
|
|
{
|
|
Print(__FUNCTION__ + ": " + DirectML.BackendName() + " CalcInputGradientProof failed, error " + IntegerToString(DirectML.LastError()));
|
|
return false;
|
|
}
|
|
double temp[];
|
|
return NeuronOCL.getGradient(temp) > 0;
|
|
}
|
|
if(CheckPointer(OpenCL) == POINTER_INVALID)
|
|
return false;
|
|
uint offset1[1] = {0};
|
|
uint size1[1] = {(uint)inputs};
|
|
OpenCL.SetArgumentBuffer(def_k_CalcInputGradientProof, def_k_cigp_matrix_i, NeuronOCL.getOutputIndex());
|
|
OpenCL.SetArgumentBuffer(def_k_CalcInputGradientProof, def_k_cigp_matrix_g, getGradientIndex());
|
|
OpenCL.SetArgumentBuffer(def_k_CalcInputGradientProof, def_k_cigp_matrix_o, getOutputIndex());
|
|
OpenCL.SetArgumentBuffer(def_k_CalcInputGradientProof, def_k_cigp_matrix_ig, NeuronOCL.getGradientIndex());
|
|
OpenCL.SetArgument(def_k_CalcInputGradientProof, def_k_cigp_outputs, outputs);
|
|
OpenCL.SetArgument(def_k_CalcInputGradientProof, def_k_cigp_window, (int)iWindow);
|
|
OpenCL.SetArgument(def_k_CalcInputGradientProof, def_k_cigp_step, (int)iStep);
|
|
if(!OpenCL.Execute(def_k_CalcInputGradientProof, 1, offset1, size1))
|
|
{
|
|
printf("Error of execution kernel CalcInputGradientProof: %d", GetLastError());
|
|
return false;
|
|
}
|
|
//--- NeuronOCL's Gradient stays GPU-resident; see the note in
|
|
//--- CNeuronConvOCL::calcInputGradients().
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronPoolOCL::Save(const int file_handle)
|
|
{
|
|
if(!CNeuronBaseOCL::Save(file_handle))
|
|
return false;
|
|
if(FileWriteInteger(file_handle, (int)iWindow, INT_VALUE) < INT_VALUE)
|
|
return false;
|
|
if(FileWriteInteger(file_handle, (int)iStep, INT_VALUE) < INT_VALUE)
|
|
return false;
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronPoolOCL::Load(const int file_handle)
|
|
{
|
|
if(!CNeuronBaseOCL::Load(file_handle))
|
|
return false;
|
|
iWindow = (uint)FileReadInteger(file_handle, INT_VALUE);
|
|
iStep = (uint)FileReadInteger(file_handle, INT_VALUE);
|
|
return true;
|
|
}
|