forked from animatedread/Warrior_EA
Found auditing pointer discipline, per the standing rule that CheckPointer comes before every dereference. THE LEAKS. CNeuronLSTM::feedForward allocated forget_gate, input_gate, output_gate and new_content on the heap and deleted them only on the success path. Eight error returns sit between the first allocation and that delete, and every one of them abandoned whatever had been built so far. calcHidden- Gradients was the same shape with fourteen returns past MemoryGradient. This is the CPU path, which is the only path this machine has - no OpenCL, no DirectML - so it ran on every era of every LSTM and CONVLSTM member. Fixed by construction rather than by adding deletes: none of the five buffers escapes its function, so each is now an automatic object. The return itself destroys them, which means the leak cannot come back the next time someone adds an error path - which is exactly how it got here. CalculateGate had to change shape for that: it now fills a caller-supplied CArrayDouble and answers bool, instead of handing back an object each caller was responsible for deleting on its own error paths and none of them did. It also allocated BEFORE testing `gate`, leaking on that very check, and never tested `sequence` at all before dereferencing it. Both arguments are checked first now. Protected virtual with three call sites, all in this file - no public API moves. THE UNCHECKED DEREFERENCES. The input-gradient loop did four rounds of `temp = SomeGate.At(i); con = temp.getConnections().At(n); value += temp.getGradient() * con.weight` with no check on either pointer, and At() answers NULL for an out-of-range index rather than failing loudly. The four copies are now one AccumulateGateInputGradient() that checks the layer, the neuron and the connection. The line above them read `temp.getConnections()` off whatever the previous loop happened to leave in `temp` - NULL if OutputLayer was empty - and is now checked too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
519 lines
20 KiB
MQL5
519 lines
20 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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();
|
|
//--- AUTOMATIC, not heap. None of these four escape the function, and as heap objects they leaked on
|
|
//--- every one of the eight error returns below - a gate allocated, the next one failing, and three
|
|
//--- CArrayDoubles abandoned per forward pass on the CPU LSTM path this machine actually runs. An
|
|
//--- object on the stack is destroyed by the return itself, so the leak cannot be reintroduced by
|
|
//--- adding an error path later, which is what happened here.
|
|
CArrayDouble forget_gate, input_gate, output_gate, new_content;
|
|
if(!CalculateGate(ForgetGate, Input, forget_gate))
|
|
return false;
|
|
if(!CalculateGate(InputGate, Input, input_gate))
|
|
return false;
|
|
if(!CalculateGate(OutputGate, Input, output_gate))
|
|
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);
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronLSTM::CalculateGate(CLayer *gate, CArrayDouble *sequence, CArrayDouble &result)
|
|
{
|
|
//--- Both arguments checked BEFORE anything is built. The old body allocated first and returned on
|
|
//--- a bad `gate` without freeing, and never checked `sequence` at all before dereferencing it.
|
|
if(CheckPointer(gate) == POINTER_INVALID || CheckPointer(sequence) == POINTER_INVALID)
|
|
return false;
|
|
CNeuronBase *temp;
|
|
CConnection *temp_con;
|
|
result.Clear();
|
|
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)
|
|
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 * (sequence.At(c) == DBL_MAX ? 1 : sequence.At(c));
|
|
}
|
|
val = SigmoidFunction(val);
|
|
temp.setOutputVal(val);
|
|
if(!result.Add(val))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Adds one gate neuron's contribution to input connection `n`'s |
|
|
//| gradient. Four identical accumulations used to sit inline, each |
|
|
//| dereferencing two pointers nothing had checked. |
|
|
//+------------------------------------------------------------------+
|
|
bool CNeuronLSTM::AccumulateGateInputGradient(CLayer *gate, const int i, const int n, double &value)
|
|
{
|
|
if(CheckPointer(gate) == POINTER_INVALID)
|
|
return false;
|
|
CNeuron *neuron = gate.At(i);
|
|
if(CheckPointer(neuron) == POINTER_INVALID)
|
|
return false;
|
|
CConnection *con = neuron.getConnections().At(n);
|
|
if(CheckPointer(con) == POINTER_INVALID)
|
|
return false;
|
|
value += neuron.getGradient() * con.weight;
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
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 = NULL;
|
|
//--- Automatic, for the reason feedForward() gives: fourteen error returns follow, and as a heap
|
|
//--- object this leaked on every one of them.
|
|
CArrayDouble MemoryGradient;
|
|
CNeuron *gate;
|
|
//---
|
|
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(!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. `temp` is whatever the loop above left behind, so an empty
|
|
//--- OutputLayer would reach this line with it still NULL - checked rather than assumed.
|
|
if(CheckPointer(temp) == POINTER_INVALID)
|
|
return false;
|
|
int total_inp = temp.getConnections().Total();
|
|
for(int n = 0; n < total_inp; n++)
|
|
{
|
|
double value = 0;
|
|
for(int i = 0; i < total; i++)
|
|
{
|
|
//--- Four gates, one accumulation each. Every element and every connection is checked: these
|
|
//--- come out of CArrayObj::At(), which answers NULL for an out-of-range index rather than
|
|
//--- failing loudly, and `n` is bounded by the FIRST gate's connection count above.
|
|
if(!AccumulateGateInputGradient(ForgetGate, i, n, value)
|
|
|| !AccumulateGateInputGradient(InputGate, i, n, value)
|
|
|| !AccumulateGateInputGradient(OutputGate, i, n, value)
|
|
|| !AccumulateGateInputGradient(NewContent, i, n, value))
|
|
return false;
|
|
}
|
|
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));
|
|
}
|
|
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 = g_eta * sqrt(1 - pow(AdamBeta2, t)) / (1 - pow(AdamBeta1, 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 ? g_eta * g * (data != DBL_MAX ? data : 1) : 0) + alpha * con.deltaWeight;
|
|
else
|
|
{
|
|
con.mt = AdamBeta1 * con.mt + (1 - AdamBeta1) * g;
|
|
con.vt = AdamBeta2 * con.vt + (1 - AdamBeta2) * 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
|