//+------------------------------------------------------------------+ //| NeuronLSTM.mqh | //| | //| CNeuronLSTM - the pure-MQL5 LSTM cell (gate layers, BPTT, | //| 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_NEURONLSTM_MQH #define WARRIOR_AI_IMPL_NEURONLSTM_MQH //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CNeuronLSTM::CNeuronLSTM(void) { ForgetGate = new CLayer(); InputGate = new CLayer(); OutputGate = new CLayer(); NewContent = new CLayer(); Memory = new CArrayDouble(); PrevMemory = new CArrayDouble(); Input = new CArrayDouble(); InputGradient = new CArrayDouble(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CNeuronLSTM::~CNeuronLSTM(void) { if(CheckPointer(ForgetGate) != POINTER_INVALID) delete ForgetGate; if(CheckPointer(InputGate) != POINTER_INVALID) delete InputGate; if(CheckPointer(OutputGate) != POINTER_INVALID) delete OutputGate; if(CheckPointer(NewContent) != POINTER_INVALID) delete NewContent; if(CheckPointer(Memory) != POINTER_INVALID) delete Memory; if(CheckPointer(PrevMemory) != POINTER_INVALID) delete PrevMemory; if(CheckPointer(Input) != POINTER_INVALID) delete Input; if(CheckPointer(InputGradient) != POINTER_INVALID) delete InputGradient; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CNeuronLSTM::Init(uint numOutputs, uint myIndex, int window, int step, int units_count, ENUM_OPTIMIZATION optimization_type) { if(units_count <= 0) return false; //--- Init Layers if(!CNeuronPool::Init(numOutputs, myIndex, window, step, units_count, optimization_type)) return false; if(!InitLayer(ForgetGate, units_count, window + units_count, optimization_type)) return false; if(!InitLayer(InputGate, units_count, window + units_count, optimization_type)) return false; if(!InitLayer(OutputGate, units_count, window + units_count, optimization_type)) return false; if(!InitLayer(NewContent, units_count, window + units_count, optimization_type)) return false; if(!Memory.Reserve(units_count)) return false; if(!PrevMemory.Reserve(units_count)) return false; CNeuron *temp; for(int i = 0; i < units_count; i++) { if(!Memory.Add(0)) return false; if(!PrevMemory.Add(0)) return false; temp = OutputLayer.At(i); temp.setOutputVal(0); } //--- return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CNeuronLSTM::InitLayer(CLayer *layer, int numUnits, int numOutputs, ENUM_OPTIMIZATION optimization_type) { if(CheckPointer(layer) == POINTER_INVALID) { layer = new CLayer(numOutputs); if(CheckPointer(layer) == POINTER_INVALID) return false; } else layer.Clear(); if(!layer.Reserve(numUnits)) return false; //--- //--- LeCun-uniform init, matching CNeuronLSTMOCL::Init's gate weighScale rationale - fan-in is //--- hidden units + input width (window+units_count, i.e. this InitLayer() call's own numOutputs //--- param, since each gate neuron's Connections array IS its fan-in weight vector here, not a //--- fan-out to a next layer). double gateScale = 1.0 / MathSqrt((double)numOutputs + 1.0); CNeuron *temp; for(int i = 0; i < numUnits; i++) { temp = new CNeuron(); if(CheckPointer(temp) == POINTER_INVALID) return false; if(!temp.Init(numOutputs + 1, i, optimization_type, gateScale)) return false; if(!layer.Add(temp)) return false; } //--- return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CNeuronLSTM::feedForward(CLayer *prevLayer) { if(CheckPointer(prevLayer) == POINTER_INVALID || prevLayer.Total() <= 0) return false; CNeuronBase *temp; CConnection *temp_con; if(CheckPointer(Input) == POINTER_INVALID) { Input = new CArrayDouble(); if(CheckPointer(Input) == POINTER_INVALID) return false; } else Input.Clear(); //--- Concatenate input sequence int total = prevLayer.Total(); if(!Input.Reserve(total + OutputLayer.Total())) return false; for(int i = 0; i < total; i++) { temp = prevLayer.At(i); if(CheckPointer(temp) == POINTER_INVALID || !Input.Add(temp.getOutputVal())) return false; } total = OutputLayer.Total(); for(int i = 0; i < total; i++) { temp = OutputLayer.At(i); if(CheckPointer(temp) == POINTER_INVALID || !Input.Add(temp.getOutputVal())) return false; } int total_data = Input.Total(); //--- Calculated forget gate CArrayDouble *forget_gate = CalculateGate(ForgetGate, Input); if(CheckPointer(forget_gate) == POINTER_INVALID) return false; //--- Calculated input gate CArrayDouble *input_gate = CalculateGate(InputGate, Input); if(CheckPointer(input_gate) == POINTER_INVALID) return false; //--- Calculated output gate CArrayDouble *output_gate = CalculateGate(OutputGate, Input); if(CheckPointer(output_gate) == POINTER_INVALID) return false; //--- Calculated new content CArrayDouble *new_content = new CArrayDouble(); if(CheckPointer(new_content) == POINTER_INVALID) return false; total = NewContent.Total(); for(int i = 0; i < total; i++) { temp = NewContent.At(i); if(CheckPointer(temp) == POINTER_INVALID) return false; double val = 0; for(int c = 0; c < total_data; c++) { temp_con = temp.Connections.At(c); if(CheckPointer(temp_con) == POINTER_INVALID) return false; val += temp_con.weight * Input.At(c); } val = TanhFunction(val); temp.setOutputVal(val); if(!new_content.Add(val)) return false; } //--- Calculated output sequences for(int i = 0; i < total; i++) { if(PrevMemory.Total() <= i) PrevMemory.Add(Memory.At(i)); else PrevMemory.Update(i, Memory.At(i)); double value = Memory.At(i) * forget_gate.At(i) + new_content.At(i) * input_gate.At(i); if(!Memory.Update(i, value)) return false; temp = OutputLayer.At(i); value = TanhFunction(value) * output_gate.At(i); temp.setOutputVal(value); } //--- delete forget_gate; delete input_gate; delete new_content; delete output_gate; //--- return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CArrayDouble *CNeuronLSTM::CalculateGate(CLayer *gate, CArrayDouble *sequence) { CNeuronBase *temp; CConnection *temp_con; CArrayDouble *result = new CArrayDouble(); if(CheckPointer(gate) == POINTER_INVALID) return NULL; int total = gate.Total(); int total_data = sequence.Total(); for(int i = 0; i < total; i++) { temp = gate.At(i); if(CheckPointer(temp) == POINTER_INVALID) { delete result; return NULL; } double val = 0; for(int c = 0; c < total_data; c++) { temp_con = temp.Connections.At(c); if(CheckPointer(temp_con) == POINTER_INVALID) { delete result; return NULL; } val += temp_con.weight * (sequence.At(c) == DBL_MAX ? 1 : sequence.At(c)); } val = SigmoidFunction(val); temp.setOutputVal(val); if(!result.Add(val)) { delete result; return NULL; } } //--- return result; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CNeuronLSTM::calcHiddenGradients(CLayer *&nextLayer) { if(CheckPointer(InputGradient) == POINTER_INVALID) { InputGradient = new CArrayDouble(); if(CheckPointer(InputGradient) == POINTER_INVALID) return false; } else InputGradient.Clear(); //--- int total = OutputLayer.Total(); CNeuron *temp; CArrayDouble *MemoryGradient = new CArrayDouble(); CNeuron *gate; CConnection *con; //--- if(nextLayer != OutputLayer) for(int i = 0; i < total; i++) { temp = OutputLayer.At(i); if(CheckPointer(temp) == POINTER_INVALID) return false; temp.setGradient(temp.sumDOW(nextLayer)); } //--- Calculated memory and output gate gradients if(CheckPointer(MemoryGradient) == POINTER_INVALID) return false; if(!MemoryGradient.Reserve(total)) return false; for(int i = 0; i < total; i++) { temp = OutputLayer.At(i); gate = OutputGate.At(i); if(CheckPointer(gate) == POINTER_INVALID) return false; double value = temp.getGradient() * gate.getOutputVal(); value = TanhFunctionDerivative(Memory.At(i)) * value; if(i >= MemoryGradient.Total()) { if(!MemoryGradient.Add(value)) return false; } else { value = MemoryGradient.At(i) + value; if(!MemoryGradient.Update(i, value)) return false; } gate.setGradient(gate.getOutputVal() != 0 && temp.getGradient() != 0 ? temp.getGradient()*temp.getOutputVal()*SigmoidFunctionDerivative(gate.getOutputVal()) / gate.getOutputVal() : 0); //--- Calcculated gates and new content gradients gate = ForgetGate.At(i); if(CheckPointer(gate) == POINTER_INVALID) return false; gate.setGradient(gate.getOutputVal() != 0 && value != 0 ? value * SigmoidFunctionDerivative(gate.getOutputVal()) : 0); gate = InputGate.At(i); temp = NewContent.At(i); if(CheckPointer(gate) == POINTER_INVALID) return false; gate.setGradient(gate.getOutputVal() != 0 && value != 0 ? value * temp.getOutputVal()*SigmoidFunctionDerivative(gate.getOutputVal()) : 0); temp.setGradient(temp.getOutputVal() != 0 && value != 0 ? value * gate.getOutputVal()*TanhFunctionDerivative(temp.getOutputVal()) : 0); } //--- Calculated input gradients int total_inp = temp.getConnections().Total(); for(int n = 0; n < total_inp; n++) { double value = 0; for(int i = 0; i < total; i++) { temp = ForgetGate.At(i); con = temp.getConnections().At(n); value += temp.getGradient() * con.weight; //--- temp = InputGate.At(i); con = temp.getConnections().At(n); value += temp.getGradient() * con.weight; //--- temp = OutputGate.At(i); con = temp.getConnections().At(n); value += temp.getGradient() * con.weight; //--- temp = NewContent.At(i); con = temp.getConnections().At(n); value += temp.getGradient() * con.weight; } if(InputGradient.Total() >= n) { if(!InputGradient.Add(value)) return false; } else if(!InputGradient.Update(n, value)) return false; } //--- Calculated gradients for prev. state int shift = total_inp - total; for(int i = 0; i < total; i++) { temp = OutputLayer.At(i); if(CheckPointer(temp) == POINTER_INVALID) return false; temp.setGradient(InputGradient.At(shift + i)); } //--- Calculated memory and output gate gradients for(int i = 0; i < total; i++) { temp = OutputLayer.At(i); gate = OutputGate.At(i); if(CheckPointer(gate) == POINTER_INVALID) return false; double value = temp.getGradient() * gate.getPrevVal(); value = MemoryGradient.At(i) + TanhFunctionDerivative(PrevMemory.At(i)) * value; if(!MemoryGradient.Update(i, value)) return false; gate.setGradient(gate.getGradient() + (gate.getPrevVal() != 0 && temp.getGradient() != 0 ? temp.getGradient()*temp.getPrevVal()*SigmoidFunctionDerivative(gate.getPrevVal()) / gate.getPrevVal() : 0)); //--- Calcculated gates and new content gradients gate = ForgetGate.At(i); if(CheckPointer(gate) == POINTER_INVALID) return false; gate.setGradient(gate.getGradient() + (gate.getPrevVal() != 0 && value != 0 ? value * SigmoidFunctionDerivative(gate.getPrevVal()) : 0)); gate = InputGate.At(i); temp = NewContent.At(i); if(CheckPointer(gate) == POINTER_INVALID) return false; gate.setGradient(gate.getGradient() + (gate.getPrevVal() != 0 && value != 0 ? value * temp.getPrevVal()*SigmoidFunctionDerivative(gate.getPrevVal()) : 0)); temp.setGradient(temp.getGradient() + (temp.getPrevVal() != 0 && value != 0 ? value * gate.getPrevVal()*TanhFunctionDerivative(temp.getPrevVal()) : 0)); } //--- delete MemoryGradient; //--- return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CNeuronLSTM::updateInputWeights(CLayer *prevLayer) { if(CheckPointer(prevLayer) == POINTER_INVALID || CheckPointer(Input) == POINTER_INVALID) return false; //--- if(!updateInputWeights(ForgetGate, Input) || !updateInputWeights(InputGate, Input) || !updateInputWeights(OutputGate, Input) || !updateInputWeights(NewContent, Input)) { return false; } if(optimization == ADAM) t++; //--- return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CNeuronLSTM::updateInputWeights(CLayer *gate, CArrayDouble *input_data) { if(CheckPointer(gate) == POINTER_INVALID || CheckPointer(input_data) == POINTER_INVALID) return false; CNeuronBase *neuron; CConnection *con; int total_n = gate.Total(); int total_data = input_data.Total(); double lt = eta * sqrt(1 - pow(b2, t)) / (1 - pow(b1, t)); for(int n = 0; n < total_n; n++) { neuron = gate.At(n); if(CheckPointer(neuron) == POINTER_INVALID) return false; double g = neuron.getGradient(); double g2 = g * g; for(int i = 0; i < total_data; i++) { con = neuron.getConnections().At(i); if(CheckPointer(con) == POINTER_INVALID) return false; double data = input_data.At(i); if(optimization == SGD) con.weight += con.deltaWeight = (g != 0 && data != 0 ? eta * g * (data != DBL_MAX ? data : 1) : 0) + alpha * con.deltaWeight; else { con.mt = b1 * con.mt + (1 - b1) * g; con.vt = b2 * con.vt + (1 - b2) * g2 + 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 LSTM_UpdateWeightsAdam MAX_WEIGHT clamp. con.weight = MathMax(-MAX_WEIGHT, MathMin(MAX_WEIGHT, con.weight)); } } //--- return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CNeuronLSTM::calcInputGradients(CNeuronBase *prevNeuron, uint index) { if(CheckPointer(prevNeuron) == POINTER_INVALID || CheckPointer(InputGradient) == POINTER_INVALID || InputGradient.Total() <= (int)index) return false; //--- prevNeuron.setGradient(InputGradient.At(index)); //--- return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CNeuronLSTM::calcInputGradients(CLayer *prevLayer) { if(CheckPointer(prevLayer) == POINTER_INVALID) return false; //--- int total = prevLayer.Total(); if(total <= 0) return false; CNeuronBase *neuron; bool result = true; for(int i = 0; (i < total && result); i++) { neuron = prevLayer.At(i); if(CheckPointer(neuron) == POINTER_INVALID) { result = false; break; } result = calcInputGradients(neuron, i); } //--- return result; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CNeuronLSTM::Save(const int file_handle) { if(!CNeuronPool::Save(file_handle)) return false; if(!ForgetGate.Save(file_handle)) return false; if(!InputGate.Save(file_handle)) return false; if(!OutputGate.Save(file_handle)) return false; if(!NewContent.Save(file_handle)) return false; if(!Memory.Save(file_handle)) return false; //--- return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CNeuronLSTM::Load(const int file_handle) { if(!CNeuronPool::Load(file_handle)) return false; if(!ForgetGate.Load(file_handle)) return false; if(!InputGate.Load(file_handle)) return false; if(!OutputGate.Load(file_handle)) return false; if(!NewContent.Load(file_handle)) return false; if(!Memory.Load(file_handle)) return false; //--- return true; } #endif