//+------------------------------------------------------------------+ //| 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 //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double CNeuronBase::alpha = momentum; // momentum //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ 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