Warrior_EA/AI/Impl/NeuronBase.mqh

243 lines
8.1 KiB
MQL5
Raw Permalink Normal View History

//+------------------------------------------------------------------+
//| NeuronBase.mqh |
//| |
//| CNeuronBase - the pure-MQL5 dense neuron: init, forward, |
//| gradients, activation, persistence. |
//| |
//| 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_NEURONBASE_MQH
#define WARRIOR_AI_IMPL_NEURONBASE_MQH
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
refactor(stdlib): adopt Math\Stat for the deploy gate's normal tail; retire the b1/b2/lr/momentum macros The gate's NormalUpperTail was a hand-rolled Abramowitz & Stegun 26.2.17 approximation. Its own comment gave the reason - "drags a chain of headers behind it" - and that turned out to be one file: Math\Stat\Normal.mqh includes only Math.mqh, which includes nothing. Swapped for Cody's rational approximation in the library (~18 significant digits vs |error| < 7.5e-8). No past verdict changes: at the z the gate operates on, the difference is orders of magnitude below DEPLOY_FAMILY_WISE_ALPHA. Adopting it needed the four bare macros in AI\Network.mqh gone first. "#define b1 AdamBeta1" collides with an identifier in Math.mqh, so the include would have macro-expanded the library's own local and failed to compile - the same landmine that made the original author rename the approximation's coefficients to ntB1..ntB5 rather than use the reference's b1..b5. lr, b2 and momentum are the same class of hazard: single-token global macros in a 52k-line codebase. All four now resolve to the input names they always aliased, which is a pure textual identity - verified zero bare occurrences remain. Also: - SelectionSort over the buffered signals was O(n^2) with an O(n^2) count of StructToTime calls, because the comparison rebuilt both datetimes from the six int date fields every time. Now materialises the keys once and does an insertion sort; ArraySort cannot permute a struct array. IsEarlier goes with it, MakeDateTime becomes SignalTime. - Seven FileOpen sites lacked FILE_SHARE_READ|FILE_SHARE_WRITE, including AtomicWriteBegin, which stages every model save. All 43 sites now carry them - an exclusive open fails outright when another process holds the path, which here has meant a silently skipped save. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:31:36 -04:00
double CNeuronBase::alpha = SgdMomentum;
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CNeuronBase::CNeuronBase(void) :
outputVal(1),
gradient(0),
activation(TANH),
t(1),
optimization(SGD)
{
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CNeuronBase::~CNeuronBase(void)
{
if(CheckPointer(Connections) != POINTER_INVALID)
delete Connections;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CNeuronBase::Init(uint numOutputs, uint myIndex, ENUM_OPTIMIZATION optimization_type, double weighScale = -1.0)
{
if(CheckPointer(Connections) == POINTER_INVALID)
{
Connections = new CArrayCon();
if(CheckPointer(Connections) == POINTER_INVALID)
return false;
}
//---
if(!Connections.Reserve(fmax(numOutputs, 1)))
{
Print(__FUNCTION__ + ": Connections.Reserve failed (allocation failure?) - neuron would silently end up with 0 connections");
return false;
}
for(uint c = 0; c < numOutputs; c++)
{
if(!Connections.CreateElementScaled(c, weighScale))
return false;
Connections.IncreaseTotal();
}
//---
m_myIndex = myIndex;
optimization = optimization_type;
return true;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CNeuronBase::feedForward(CObject *&SourceObject)
{
bool result = false;
//---
if(CheckPointer(SourceObject) == POINTER_INVALID)
return result;
//---
CLayer *temp_l;
CNeuronPool *temp_n;
switch(SourceObject.Type())
{
case defLayer:
temp_l = SourceObject;
result = feedForward(temp_l);
break;
case defNeuronConv:
case defNeuronPool:
case defNeuronLSTM:
temp_n = SourceObject;
result = feedForward(temp_n.getOutputLayer());
break;
}
//---
return result;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CNeuronBase::updateInputWeights(CObject *SourceObject)
{
bool result = false;
//---
if(CheckPointer(SourceObject) == POINTER_INVALID)
return result;
//---
CLayer *temp_l;
CNeuronPool *temp_n;
switch(SourceObject.Type())
{
case defLayer:
temp_l = SourceObject;
result = updateInputWeights(temp_l);
break;
case defNeuronConv:
case defNeuronPool:
case defNeuronLSTM:
temp_n = SourceObject;
temp_l = temp_n.getOutputLayer();
result = updateInputWeights(temp_l);
break;
}
//---
return result;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CNeuronBase::calcHiddenGradients(CObject *&TargetObject)
{
bool result = false;
//---
if(CheckPointer(TargetObject) == POINTER_INVALID)
return result;
//---
CLayer *temp_l;
CNeuronPool *temp_n;
switch(TargetObject.Type())
{
case defLayer:
temp_l = TargetObject;
result = calcHiddenGradients(temp_l);
break;
case defNeuronConv:
case defNeuronPool:
case defNeuronLSTM:
switch(Type())
{
case defNeuron:
temp_n = TargetObject;
result = temp_n.calcInputGradients(GetPointer(this), m_myIndex);
break;
case defNeuronLSTM:
temp_n = TargetObject;
temp_l = getOutputLayer();
if(!temp_n.calcInputGradients(temp_l))
{
result = false;
break;
}
result = calcHiddenGradients(temp_l);
break;
default:
temp_l =getOutputLayer();
temp_n = TargetObject;
result = temp_n.calcInputGradients(temp_l);
break;
}
break;
}
//---
return result;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CNeuronBase::Save(int file_handle)
{
if(file_handle == INVALID_HANDLE)
return false;
if(FileWriteInteger(file_handle, Type()) < INT_VALUE)
return false;
//---
if(FileWriteInteger(file_handle, (int)activation, INT_VALUE) < INT_VALUE)
return false;
//---
if(FileWriteInteger(file_handle, (int)optimization, INT_VALUE) < INT_VALUE)
return false;
//---
if(FileWriteInteger(file_handle, t, INT_VALUE) < INT_VALUE)
return false;
//---
return Connections.Save(file_handle);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
double CNeuronBase::activationFunction(double x)
{
switch(activation)
{
case NONE:
return(x);
break;
case TANH:
return TanhFunction(x);
break;
case SIGMOID:
return SigmoidFunction(x);
break;
case PRELU:
// fixed 0.01 slope, matches CNeuronConv::activationFunction's `param` default -
// this case was previously missing here, so any generic Dense (defNeuron) layer
// given PRELU silently fell through to the `return x` below (pure linear identity,
// no nonlinearity at all) instead of leaky-ReLU.
return(x >= 0 ? x : 0.01 * x);
break;
}
//---
return x;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
double CNeuronBase::activationFunctionDerivative(double x)
{
switch(activation)
{
case NONE:
return(1);
break;
case TANH:
return TanhFunctionDerivative(x);
break;
case SIGMOID:
return SigmoidFunctionDerivative(x);
break;
case PRELU:
// See activationFunction() above - was previously missing here too, defaulting
// to a derivative of 1 (which happens to be right for x>=0, but wrong for x<0,
// where it must be the 0.01 leak slope, not 1).
return(x >= 0 ? 1.0 : 0.01);
break;
}
//---
return 1;
}
#endif