- Moves CLayer neuron construction to AI/Impl/Layer.mqh to keep Network.mqh clean - Unifies four previously duplicated architecture initialisation blocks (MLP/CONV/LSTM/HYBRID) into a single shared function - Eliminates risk of behavioural drift where one architecture missed a setter, causing mismatched feature sets or targets
421 lines
16 KiB
MQL5
421 lines
16 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| NeuronConvPool.mqh |
|
|
//| |
|
|
//| CNeuronConv / CNeuronPool - the pure-MQL5 convolution and |
|
|
//| pooling neurons. |
|
|
//| |
|
|
//| Included from AI\Network.mqh AFTER every class declaration - |
|
|
//| bodies only, no declarations. Relocation is behaviour-neutral by |
|
|
//| construction: nothing here is reachable until Network.mqh ends. |
|
|
//+------------------------------------------------------------------+
|
|
#ifndef WARRIOR_AI_IMPL_NEURONCONVPOOL_MQH
|
|
#define WARRIOR_AI_IMPL_NEURONCONVPOOL_MQH
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronConv::feedForward(CLayer *prevLayer)
|
|
{
|
|
bool result = false;
|
|
//---
|
|
if(CheckPointer(prevLayer) == POINTER_INVALID)
|
|
return result;
|
|
//---
|
|
int total = prevLayer.Total() - iWindow + 1;
|
|
CNeuron *temp;
|
|
CConnection *con;
|
|
result = true;
|
|
for(int i = 0; (i < total && result); i += iStep)
|
|
{
|
|
double sum = 0;
|
|
for(int j = 0; (j < iWindow && result); j++)
|
|
{
|
|
temp = prevLayer.At(i + j);
|
|
con = Connections.At(j);
|
|
if(CheckPointer(temp) == POINTER_INVALID || CheckPointer(con) == POINTER_INVALID)
|
|
return false;
|
|
double val = temp.getOutputVal();
|
|
sum += val * con.weight;
|
|
}
|
|
temp = OutputLayer.At(i / iStep);
|
|
if(CheckPointer(temp) == POINTER_INVALID)
|
|
return false;
|
|
temp.setOutputVal(activationFunction(sum));
|
|
}
|
|
//---
|
|
return result;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
double CNeuronConv::activationFunction(double x)
|
|
{
|
|
if(x >= 0)
|
|
return x;
|
|
return param * x;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronConv::calcHiddenGradients(CLayer *&nextLayer)
|
|
{
|
|
if(CheckPointer(nextLayer) == POINTER_INVALID || CheckPointer(OutputLayer) == POINTER_INVALID || OutputLayer.Total() <= 0)
|
|
return false;
|
|
//---
|
|
gradient = 0;
|
|
int total = OutputLayer.Total();
|
|
CNeuron *temp;
|
|
for(int i = 0; i < total; i++)
|
|
{
|
|
temp = OutputLayer.At(i);
|
|
if(CheckPointer(temp) == POINTER_INVALID)
|
|
return false;
|
|
temp.setGradient(temp.sumDOW(nextLayer)*activationFunctionDerivative(temp.getOutputVal()));
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
double CNeuronConv::activationFunctionDerivative(double x)
|
|
{
|
|
if(x >= 0)
|
|
return 1;
|
|
return param;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronConv::updateInputWeights(CLayer *prevLayer)
|
|
{
|
|
if(CheckPointer(prevLayer) == POINTER_INVALID || CheckPointer(OutputLayer) == POINTER_INVALID)
|
|
return false;
|
|
//---
|
|
CConnection *con;
|
|
double lt = eta * sqrt(1 - pow(b2, t)) / (1 - pow(b1, t));
|
|
for(int n = 0; n < iWindow && !IsStopped(); n++)
|
|
{
|
|
con = Connections.At(n);
|
|
if(CheckPointer(con) == POINTER_INVALID)
|
|
continue;
|
|
double delta = 0;
|
|
int total_i = OutputLayer.Total();
|
|
CNeuron *prev, *out;
|
|
for(int i = 0; i < total_i; i++)
|
|
{
|
|
prev = prevLayer.At(n * iStep + i);
|
|
out = OutputLayer.At(total_i - i - 1);
|
|
if(CheckPointer(prev) == POINTER_INVALID || CheckPointer(out) == POINTER_INVALID)
|
|
continue;
|
|
delta += prev.getOutputVal() * out.getGradient();
|
|
}
|
|
if(optimization == SGD)
|
|
con.weight += con.deltaWeight = (delta != 0 ? eta*delta : 0) + (con.deltaWeight != 0 ? alpha*con.deltaWeight : 0);
|
|
else
|
|
{
|
|
con.mt = b1 * con.mt + (1 - b1) * delta;
|
|
con.vt = b2 * con.vt + (1 - b2) * delta * delta + 0.00000001;
|
|
con.deltaWeight = MathMax(-MAX_WEIGHT_DELTA, MathMin(MAX_WEIGHT_DELTA, lt * con.mt / sqrt(con.vt) - lt * WEIGHT_DECAY * con.weight));
|
|
// Sign-agreement gate removed - see CNeuron::updateInputWeights' comment for why.
|
|
con.weight += con.deltaWeight;
|
|
}
|
|
// See CNeuron::updateInputWeights' matching clamp for why this is needed - matches
|
|
// AI\Network.cl's UpdateWeightsConvMomentum/UpdateWeightsConvAdam MAX_WEIGHT clamp.
|
|
con.weight = MathMax(-MAX_WEIGHT, MathMin(MAX_WEIGHT, con.weight));
|
|
}
|
|
if(optimization == ADAM)
|
|
t++;
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronPool::Init(uint numOutputs, uint myIndex, int window, int step, int units_count, ENUM_OPTIMIZATION optimization_type)
|
|
{
|
|
iWindow = window;
|
|
iStep = step;
|
|
//--- LeCun-uniform init, matching CNeuronConvOCL::Init's rationale - fan-in is the conv window size.
|
|
if(!CNeuronBase::Init(window, myIndex, optimization_type, 1.0 / MathSqrt((double)window + 1.0)))
|
|
return false;
|
|
OutputLayer = new CLayer(numOutputs);
|
|
if(CheckPointer(OutputLayer) == POINTER_INVALID)
|
|
return false;
|
|
//--- He-scaled init for the OutputLayer's own dense units - fan-in is this pool/conv unit's own
|
|
//--- sibling count (units_count); no OCL/DLL equivalent exists to mirror since those tiers use flat
|
|
//--- buffers instead of this per-unit object representation, so this follows the same dense rationale
|
|
//--- as CNet::CNet()'s defNeuron case above.
|
|
double outputScale = MathSqrt(2.0 / ((double)units_count + 1.0));
|
|
if(!OutputLayer.Reserve(units_count))
|
|
{
|
|
Print(__FUNCTION__ + ": OutputLayer.Reserve failed (allocation failure?) - neuron would silently end up with 0 outputs");
|
|
return false;
|
|
}
|
|
for(int i = 0; i < units_count; i++)
|
|
{
|
|
if(!OutputLayer.CreateElementScaled(i, outputScale))
|
|
return false;
|
|
OutputLayer.IncreaseTotal();
|
|
}
|
|
//---
|
|
if(Type() == defNeuronPool)
|
|
{
|
|
if(CheckPointer(Connections) != POINTER_INVALID)
|
|
Connections.Clear();
|
|
}
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
CNeuronPool::~CNeuronPool(void)
|
|
{
|
|
delete OutputLayer;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronPool::feedForward(CLayer *prevLayer)
|
|
{
|
|
if(CheckPointer(prevLayer) == POINTER_INVALID)
|
|
return false;
|
|
//---
|
|
int total = prevLayer.Total() - iWindow + 1;
|
|
CNeuron *temp;
|
|
for(int i = 0; i <= total; i += iStep)
|
|
{
|
|
double sum = 0;
|
|
for(int j = 0; j < iWindow; j++)
|
|
{
|
|
temp = prevLayer.At(i + j);
|
|
if(CheckPointer(temp) == POINTER_INVALID)
|
|
continue;
|
|
sum += temp.getOutputVal();
|
|
}
|
|
temp = OutputLayer.At(i / iStep);
|
|
if(CheckPointer(temp) == POINTER_INVALID)
|
|
return false;
|
|
temp.setOutputVal(sum / iWindow);
|
|
}
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronPool::calcHiddenGradients(CLayer *&nextLayer)
|
|
{
|
|
if(CheckPointer(nextLayer) == POINTER_INVALID || CheckPointer(OutputLayer) == POINTER_INVALID || OutputLayer.Total() <= 0)
|
|
return false;
|
|
//---
|
|
gradient = 0;
|
|
int total = OutputLayer.Total();
|
|
CNeuron *temp;
|
|
for(int i = 0; i < total; i++)
|
|
{
|
|
temp = OutputLayer.At(i);
|
|
if(CheckPointer(temp) == POINTER_INVALID)
|
|
return false;
|
|
temp.setGradient(temp.sumDOW(nextLayer));
|
|
}
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronPool::calcInputGradients(CLayer *prevLayer)
|
|
{
|
|
if(CheckPointer(prevLayer) == POINTER_INVALID || CheckPointer(OutputLayer) == POINTER_INVALID || CheckPointer(prevLayer.At(0)) == POINTER_INVALID)
|
|
return false;
|
|
//---
|
|
if(prevLayer.At(0).Type() != defNeuron)
|
|
{
|
|
CNeuronPool *temp = prevLayer.At(m_myIndex);
|
|
if(CheckPointer(temp) == POINTER_INVALID)
|
|
return false;
|
|
prevLayer = temp.getOutputLayer();
|
|
if(CheckPointer(prevLayer) == POINTER_INVALID)
|
|
return false;
|
|
}
|
|
//---
|
|
CNeuronBase *prevNeuron, *outputNeuron;
|
|
int total = prevLayer.Total();
|
|
for(int i = 0; i < total; i++)
|
|
{
|
|
prevNeuron = prevLayer.At(i);
|
|
if(CheckPointer(prevNeuron) == POINTER_INVALID)
|
|
continue;
|
|
double prev_gradient = 0;
|
|
int start = i - iWindow + iStep;
|
|
start = (start - start % iStep) / iStep;
|
|
double stop = (i - i % iStep) / iStep + 1;
|
|
for(int out = (int)fmax(0, start); out < (int)fmin(OutputLayer.Total(), stop); out++)
|
|
{
|
|
outputNeuron = OutputLayer.At(out);
|
|
if(CheckPointer(outputNeuron) == POINTER_INVALID)
|
|
continue;
|
|
prev_gradient += outputNeuron.getGradient() / iWindow;
|
|
}
|
|
prevNeuron.setGradient(prev_gradient);
|
|
}
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronPool::calcInputGradients(CNeuronBase *prevNeuron, uint index)
|
|
{
|
|
if(CheckPointer(prevNeuron) == POINTER_INVALID || CheckPointer(OutputLayer) == POINTER_INVALID)
|
|
return false;
|
|
//---
|
|
if(prevNeuron.Type() != defNeuron)
|
|
{
|
|
CNeuronPool *temp = prevNeuron;
|
|
return calcInputGradients(temp.getOutputLayer());
|
|
}
|
|
//---
|
|
CNeuronBase *outputNeuron;
|
|
double prev_gradient = 0;
|
|
int start = (int)index - iWindow + iStep;
|
|
start = (start - start % iStep) / iStep;
|
|
double stop = (index - index % iStep) / iStep + 1;
|
|
for(int out = (int)fmax(0, start); out < (int)fmin(OutputLayer.Total(), stop); out++)
|
|
{
|
|
outputNeuron = OutputLayer.At(out);
|
|
if(CheckPointer(outputNeuron) == POINTER_INVALID)
|
|
continue;
|
|
prev_gradient += outputNeuron.getGradient() / iWindow;
|
|
}
|
|
prevNeuron.setGradient(prev_gradient);
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronConv::calcInputGradients(CLayer *prevLayer)
|
|
{
|
|
if(CheckPointer(prevLayer) == POINTER_INVALID || CheckPointer(OutputLayer) == POINTER_INVALID)
|
|
return false;
|
|
//---
|
|
if(prevLayer.At(0).Type() != defNeuron)
|
|
{
|
|
CNeuronPool *temp = prevLayer.At(m_myIndex);
|
|
if(CheckPointer(temp) == POINTER_INVALID)
|
|
return false;
|
|
prevLayer = temp.getOutputLayer();
|
|
if(CheckPointer(prevLayer) == POINTER_INVALID)
|
|
return false;
|
|
}
|
|
//---
|
|
CNeuronBase *prevNeuron, *outputNeuron;
|
|
CConnection *con;
|
|
int total = prevLayer.Total();
|
|
for(int i = 0; i < total; i++)
|
|
{
|
|
prevNeuron = prevLayer.At(i);
|
|
if(CheckPointer(prevNeuron) == POINTER_INVALID)
|
|
continue;
|
|
double prev_gradient = 0;
|
|
int start = i - iWindow + iStep;
|
|
start = (start - start % iStep) / iStep;
|
|
double stop = (i - i % iStep) / iStep + 1;
|
|
for(int out = (int)fmax(0, start); out < (int)fmin(OutputLayer.Total(), stop); out++)
|
|
{
|
|
outputNeuron = OutputLayer.At(out);
|
|
int c = ((int)fmin(OutputLayer.Total(), stop) - out - 1) * iStep + i % iStep;
|
|
con = Connections.At(c);
|
|
if(CheckPointer(outputNeuron) == POINTER_INVALID || CheckPointer(con) == POINTER_INVALID)
|
|
continue;
|
|
prev_gradient += outputNeuron.getGradient() * prevNeuron.activationFunctionDerivative(prevNeuron.getOutputVal()) * con.weight;
|
|
}
|
|
prevNeuron.setGradient(prev_gradient);
|
|
}
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronConv::calcInputGradients(CNeuronBase *prevNeuron, uint index)
|
|
{
|
|
if(CheckPointer(prevNeuron) == POINTER_INVALID || CheckPointer(OutputLayer) == POINTER_INVALID)
|
|
return false;
|
|
//---
|
|
if(prevNeuron.Type() != defNeuron)
|
|
{
|
|
CNeuronPool *temp = prevNeuron;
|
|
return calcInputGradients(temp.getOutputLayer());
|
|
}
|
|
//---
|
|
CNeuronBase *outputNeuron;
|
|
CConnection *con;
|
|
double prev_gradient = 0;
|
|
int start = (int)index - iWindow + iStep;
|
|
start = (start - start % iStep) / iStep;
|
|
double stop = (index - index % iStep) / iStep + 1;
|
|
for(int out = (int)fmax(0, start); out < (int)fmin(OutputLayer.Total(), stop); out++)
|
|
{
|
|
outputNeuron = OutputLayer.At(out);
|
|
int c = (int)(((int)fmin(OutputLayer.Total(), stop) - out - 1) * iStep + index % iStep);
|
|
con = Connections.At(c);
|
|
if(CheckPointer(outputNeuron) == POINTER_INVALID || CheckPointer(con) == POINTER_INVALID)
|
|
continue;
|
|
prev_gradient += outputNeuron.getGradient() * activationFunctionDerivative(outputNeuron.getOutputVal()) * con.weight;
|
|
}
|
|
prevNeuron.setGradient(prev_gradient);
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronPool::Save(const int file_handle)
|
|
{
|
|
if(!CNeuronBase::Save(file_handle) || !OutputLayer.Save(file_handle))
|
|
return false;
|
|
if(FileWriteInteger(file_handle, iWindow, INT_VALUE) < INT_VALUE)
|
|
return false;
|
|
if(FileWriteInteger(file_handle, iStep, INT_VALUE) < INT_VALUE)
|
|
return false;
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronPool::Load(const int file_handle)
|
|
{
|
|
if(!CNeuronBase::Load(file_handle) || !OutputLayer.Load(file_handle))
|
|
return false;
|
|
iWindow = FileReadInteger(file_handle, INT_VALUE);
|
|
iStep = FileReadInteger(file_handle, INT_VALUE);
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronConv::Save(const int file_handle)
|
|
{
|
|
if(!CNeuronPool::Save(file_handle))
|
|
return false;
|
|
if(FileWriteDouble(file_handle, param) < 8)
|
|
return false;
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronConv::Load(const int file_handle)
|
|
{
|
|
if(!CNeuronPool::Load(file_handle))
|
|
return false;
|
|
param = FileReadDouble(file_handle);
|
|
//---
|
|
return true;
|
|
}
|
|
#endif
|