2026-08-01 11:27:28 -04:00 | | | //+------------------------------------------------------------------+
|
| | | //| 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;
|
2026-08-20 07:00:09 -04:00 | | | double lt = g_eta * sqrt(1 - pow(AdamBeta2, t)) / (1 - pow(AdamBeta1, t));
|
2026-08-01 11:27:28 -04:00 | | | 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)
|
2026-08-20 07:00:09 -04:00 | | | con.weight += con.deltaWeight = (delta != 0 ? g_eta*delta : 0) + (con.deltaWeight != 0 ? alpha*con.deltaWeight : 0);
|
2026-08-01 11:27:28 -04:00 | | | else
|
| | | {
|
 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 | | | con.mt = AdamBeta1 * con.mt + (1 - AdamBeta1) * delta;
|
| | | con.vt = AdamBeta2 * con.vt + (1 - AdamBeta2) * delta * delta + 0.00000001;
|
2026-08-01 11:27:28 -04:00 | | | 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
|