Warrior_EA/AI/Impl/NeuronConvPool.mqh
AnimateDread d7eea325fb refactor(ai): extract Layer.mqh and deduplicate AI config
- 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
2026-08-01 11:27:28 -04:00

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