forked from animatedread/Warrior_EA
MQL5's MathRand() is the 15-bit MSVC LCG - 32768 distinct values and the lattice structure that shape of generator has. Two places here actually lean on randomness and both were hurt by it: WEIGHT INIT. Six He/LeCun-uniform sites drew ((MathRand()+1)/32768.0 - 0.5) * 2 * scale, so a first dense layer of ~250k weights had only 32768 possible values and thousands of connections started byte-identical. Breaking that symmetry is the whole job of random init. SHUFFLING. ShuffleRandomIndex() already had to splice TWO MathRand() draws to reach 30 bits, and its own comment documented the residual modulo bias it still carried. HQRndUniformI() is rejection-sampled and exactly uniform, so the splice and the bias note both go. CHighQualityRand is L'Ecuyer's combined multiplicative congruential generator - two differenced streams, 31-bit output, period ~2.3e18 - and it ships with the terminal. AND A BUG THE MIGRATION EXPOSED. The three MathSrand(GetTickCount()) calls sit immediately before "build a fresh topology", once per model. GetTickCount() steps in ~15.6 ms on Windows and an ensemble builds every member inside one OnInit, so members could be handed the SAME seed and draw the SAME weights wherever their shapes coincide - and members that start identical are not an ensemble. WarriorRandSeed() takes a salt (the model id) plus a never-reset call counter, so a collision is impossible rather than merely unlikely, while the tick keeps the run itself genuinely unrepeatable the way those call sites asked for. Seeds are masked positive rather than trusted: HQRndSeed computes s % (M-1) + 1 and MQL5's % keeps the sign, so a negative seed leaves the generator in a state its own assertions reject. GetTickCount() is a uint and goes negative as an int after ~24 days of uptime - a fault that would surface as "training is broken" on a long-running terminal and nowhere else. The indicator tuner's 52 draws move across too: its random search is where sample quality earns its keep. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
111 lines
5 KiB
MQL5
111 lines
5 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| NeuronPrimitives.mqh |
|
|
//| AnimateDread |
|
|
//| https://www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
//| CConnection/CArrayCon - the per-synapse weight (+ Adam moment) |
|
|
//| storage and its owning array, used by the CPU-only CNeuronBase/ |
|
|
//| CNeuron neuron family (AI\NeuronCPU.mqh) as their fallback last- |
|
|
//| resort weight representation (no DLL import, no OpenCL/DirectML).|
|
|
//| Extracted verbatim out of AI\Network.mqh's own god-file (SOLID |
|
|
//| cleanup) - no logic changes, this is the exact original code. |
|
|
//+------------------------------------------------------------------+
|
|
#include <Arrays\ArrayObj.mqh>
|
|
#include "..\System\Random.mqh"
|
|
class CConnection : public CObject
|
|
{
|
|
public:
|
|
double weight;
|
|
double deltaWeight;
|
|
double mt;
|
|
double vt;
|
|
|
|
CConnection(double w) { weight = w; deltaWeight = 0; mt = 0; vt = 0; }
|
|
~CConnection() {};
|
|
//--- methods for working with files
|
|
virtual bool Save(int const file_handle);
|
|
virtual bool Load(int const file_handle);
|
|
virtual int Type(void) const { return defConnect; }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CConnection::Save(int file_handle)
|
|
{
|
|
if(file_handle == INVALID_HANDLE)
|
|
return false;
|
|
//---
|
|
if(FileWriteDouble(file_handle, weight) <= 0)
|
|
return false;
|
|
if(FileWriteDouble(file_handle, deltaWeight) <= 0)
|
|
return false;
|
|
if(FileWriteDouble(file_handle, mt) <= 0)
|
|
return false;
|
|
if(FileWriteDouble(file_handle, vt) <= 0)
|
|
return false;
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CConnection::Load(int file_handle)
|
|
{
|
|
if(file_handle == INVALID_HANDLE)
|
|
return false;
|
|
//---
|
|
weight = FileReadDouble(file_handle);
|
|
deltaWeight = FileReadDouble(file_handle);
|
|
mt = FileReadDouble(file_handle);
|
|
vt = FileReadDouble(file_handle);
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CArrayCon : public CArrayObj
|
|
{
|
|
public:
|
|
CArrayCon(void) {};
|
|
~CArrayCon(void) {};
|
|
//--- Fan-in-scaled element factory. Deliberately NOT named CreateElement: see the override below.
|
|
bool CreateElementScaled(int const index, double weighScale);
|
|
//--- MUST keep CArrayObj::CreateElement's EXACT signature so it really overrides the base virtual -
|
|
//--- CArrayObj::Load() dispatches through it when reading a saved CNeuronBase's connection array. In
|
|
//--- MQL5 an added parameter (even a defaulted one) turns this into a separate hiding method and
|
|
//--- leaves the base's `return(false)` stub in the vtable, which silently breaks every load. Same trap
|
|
//--- as CLayer::CreateElement - see the long note there.
|
|
virtual bool CreateElement(const int index) { return CreateElementScaled(index, -1.0); }
|
|
virtual void IncreaseTotal() { m_data_total++; }
|
|
virtual int Type(void) const { return defArrayConnects; }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CArrayCon::CreateElementScaled(int index, double weighScale)
|
|
{
|
|
if(index < 0 || index >= m_data_max)
|
|
return false;
|
|
//---
|
|
// Fan-in-scaled to match the CNeuronBaseOCL/CNeuronConvOCL/CNeuronLSTMOCL weight-init sites -
|
|
// callers now compute their own He/LeCun-uniform weighScale (same rationale as those) and pass it
|
|
// down through CNeuronBase::Init(), since this per-connection constructor has no visibility into
|
|
// its owning neuron's fan-in on its own. weighScale < 0 (no caller opinion) keeps the old flat
|
|
// draw as a safe default.
|
|
double weigh;
|
|
if(weighScale > 0.0)
|
|
weigh = WarriorRandSymmetric() * weighScale;
|
|
else
|
|
weigh = WarriorRandUniform() - 0.5;
|
|
if(weigh == 0)
|
|
weigh = 0.001;
|
|
m_data[index] = new CConnection(weigh);
|
|
if(CheckPointer(m_data[index]) == POINTER_INVALID)
|
|
return false;
|
|
//---
|
|
return (true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|