forked from animatedread/Warrior_EA
148 lines
7.8 KiB
MQL5
148 lines
7.8 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| NeuronCPU.mqh |
| |||
//| |
| |||
//| CNeuron bodies - the plain-CPU (no DLL/OpenCL) dense neuron, |
| |||
//| last-resort fallback tier. |
| |||
//| |
| |||
//| 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_NEURONCPU_MQH
| |||
#define WARRIOR_AI_IMPL_NEURONCPU_MQH
| |||
//+------------------------------------------------------------------+
| |||
//| |
| |||
//+------------------------------------------------------------------+
| |||
bool CNeuron::updateInputWeights(CLayer *prevLayer)
| |||
{
| |||
if(CheckPointer(prevLayer) == POINTER_INVALID)
| |||
return false;
| |||
//---
| |||
double lt = g_eta * sqrt(1 - pow(AdamBeta2, t)) / (1 - pow(AdamBeta1, t));
| |||
int total = prevLayer.Total();
| |||
for(int n = 0; n < total && !IsStopped(); n++)
| |||
{
| |||
CNeuron *neuron = prevLayer.At(n);
| |||
CConnection *con = neuron.Connections.At(m_myIndex);
| |||
if(CheckPointer(con) == POINTER_INVALID)
| |||
continue;
| |||
if(optimization == SGD)
| |||
con.weight += con.deltaWeight = (gradient != 0 ? g_eta * neuron.getOutputVal() * gradient : 0) + (con.deltaWeight != 0 ? alpha*con.deltaWeight : 0);
| |||
else
| |||
{
| |||
//--- Per-WEIGHT gradient (neuron gradient x presynaptic output), matching the SGD branch
| |||
//--- above and every native backend's Adam kernel (`grad = g[i] * inp` in
| |||
//--- CPU_UpdateWeightsAdam).
| |||
double g = gradient * neuron.getOutputVal();
| |||
con.mt = AdamBeta1 * con.mt + (1 - AdamBeta1) * g;
| |||
// Stores sqrt(...) directly into vt (not the raw second-moment estimate) to exactly match
| |||
// every native backend's Adam recursion (AI\Network.cl's UpdateWeightsAdam, WarriorDML.cpp,
| |||
// WarriorCPU.cpp), and squares it back before re-entering that recursion - which IS the
| |||
// textbook raw-variance recursion, just carried in std-dev form so the stored value can be
| |||
// the denominator directly. Until 2026-08-09 all four tiers agreed on a version that fed
| |||
// the stored sqrt back in as if it were the variance; they agreed, and they were all wrong
| |||
// (see Network.cl for the measurement). Keep these four in lockstep either way: a tier that
| |||
// diverges here silently produces different weights from the same data.
| |||
con.vt = sqrt(AdamBeta2 * con.vt * con.vt + (1 - AdamBeta2) * g * g);
| |||
con.deltaWeight = MathMax(-MAX_WEIGHT_DELTA, MathMin(MAX_WEIGHT_DELTA, lt * con.mt / (con.vt > 0 ? con.vt : lt * 10) - lt * WEIGHT_DECAY * con.weight));
| |||
// No sign-agreement gate (removed 2026-07): gating each step on agreement with the CURRENT
| |||
// sample's gradient sign rectified the one-hot softmax-CCE stream - rare large true-class
| |||
// positives (1/3 of samples), frequent small wrong-class negatives (2/3) - into a permanent
| |||
// downward ratchet on every output neuron, sinking all three logits into sigmoid saturation
| |||
// together (the all-Neutral collapse; IS error frozen at sqrt(1/3)=0.58). The stale-step
| |||
// overshoot it guarded against is covered by the MAX_WEIGHT_DELTA clip, AdamW WEIGHT_DECAY
| |||
// and shuffle-interleaved oversampling. Removed from all four backends in sync (WarriorCPU
| |||
// .cpp / WarriorDML.cpp / Network.cl mirror this).
| |||
con.weight += con.deltaWeight;
| |||
}
| |||
// Mirrors AI\Network.cl's MAX_WEIGHT clamp (see that file's Conv/LSTM Adam kernels) - without
| |||
// it a gradient spike (e.g. from class-balance oversampling replaying the same rare-class bar
| |||
// several times in a row - see Train()'s reps loop) can drive a weight to +-Infinity; the next
| |||
// Adam step then divides Infinity by Infinity (mt/vt both Inf) producing NaN, which propagates
| |||
// through every FeedForward sum that touches it and never recovers, since Adam(NaN)=NaN forever
| |||
// after. That silently freezes the whole network's output at NaN - manifesting as every bar
| |||
// classifying to whatever the "can't decide" default is (e.g. all-Neutral, 0 Buy/Sell) with no
| |||
// error ever surfaced, since NaN comparisons are simply always false.
| |||
con.weight = MathMax(-MAX_WEIGHT, MathMin(MAX_WEIGHT, con.weight));
| |||
}
| |||
if(optimization == ADAM)
| |||
t++;
| |||
//---
| |||
return true;
| |||
}
| |||
//+------------------------------------------------------------------+
| |||
//| |
| |||
//+------------------------------------------------------------------+
| |||
double CNeuron::sumDOW(CLayer *&nextLayer)
| |||
{
| |||
double sum = 0.0;
| |||
int total = nextLayer.Total() - 1;
| |||
for(int n = 0; n < total; n++)
| |||
{
| |||
CConnection *con = Connections.At(n);
| |||
if(CheckPointer(con) == POINTER_INVALID)
| |||
continue;
| |||
double weight = con.weight;
| |||
if(weight != 0)
| |||
{
| |||
CNeuron *neuron = nextLayer.At(n);
| |||
sum += weight * neuron.gradient;
| |||
}
| |||
}
| |||
return sum;
| |||
}
| |||
//+------------------------------------------------------------------+
| |||
//| |
| |||
//+------------------------------------------------------------------+
| |||
bool CNeuron::calcHiddenGradients(CLayer *&nextLayer)
| |||
{
| |||
//--- sumDOW * activation derivative, matching CNeuronConv::calcHiddenGradients (AI\Network.mqh)
| |||
//--- and the CalcHiddenGradient kernels in all three native backends.
| |||
gradient = sumDOW(nextLayer) * activationFunctionDerivative(outputVal);
| |||
return true;
| |||
}
| |||
//+------------------------------------------------------------------+
| |||
//| |
| |||
//+------------------------------------------------------------------+
| |||
bool CNeuron::calcOutputGradients(double targetVal)
| |||
{
| |||
//--- Deliberately NOT multiplied by activationFunctionDerivative(outputVal): for TANH that
| |||
//--- factor is (1-out^2), which vanishes as outputVal approaches +-1 - exactly where this neuron
| |||
//--- needs to converge for a +-1 target (e.g. the buy/sell extremes of a single-neuron
| |||
//--- regression head), stalling training right when it matters most.
| |||
double delta = (targetVal > 1 ? 1 : targetVal < -1 ? -1 : targetVal) - outputVal;
| |||
gradient = delta;
| |||
return true;
| |||
}
| |||
//+------------------------------------------------------------------+
| |||
//| |
| |||
//+------------------------------------------------------------------+
| |||
bool CNeuron::feedForward(CLayer *prevLayer)
| |||
{
| |||
if(CheckPointer(prevLayer) == POINTER_INVALID || prevLayer.Type() != defLayer)
| |||
return false;
| |||
//---
| |||
prevVal = outputVal;
| |||
double sum = 0.0;
| |||
int total = prevLayer.Total();
| |||
for(int n = 0; n < total && !IsStopped(); n++)
| |||
{
| |||
CNeuron *temp = prevLayer.At(n);
| |||
double val = temp.getOutputVal();
| |||
if(val != 0)
| |||
{
| |||
CConnection *con = temp.Connections.At(m_myIndex);
| |||
if(CheckPointer(con) == POINTER_INVALID)
| |||
continue;
| |||
sum += val * con.weight;
| |||
}
| |||
}
| |||
outputVal = activationFunction(MathMin(MathMax(sum, -18), 18));
| |||
//---
| |||
return true;
| |||
}
| |||
//+------------------------------------------------------------------+
| |||
//| |
| |||
//+------------------------------------------------------------------+
| |||
#endif // WARRIOR_AI_IMPL_NEURONCPU_MQH
| |||
//+------------------------------------------------------------------+
|