NN_in_Trading/Experts/D2Skill/D2SkillItemExperiment.mq5
2026-08-20 22:59:48 +03:00

355 lines
16 KiB
MQL5

//+------------------------------------------------------------------+
//| D2SkillItemExperiment.mq5 |
//| Article 1: synthetic pseudo-residual representation experiment. |
//+------------------------------------------------------------------+
#property strict
#property version "1.00"
#include "..\\NeuroNet_DNG\\NeuroNet.mqh"
#define D2_ITEM_DIM 4
#define D2_ITEM_SAMPLES 32
#define D2_ITEM_EPOCHS 4
#define D2_ITEM_TOLERANCE 1.0e-5
CNet g_net;
CNeuronBaseOCL g_source;
CBufferFloat g_target;
bool g_completed = false;
//+------------------------------------------------------------------+
bool CreateOpenCLHost(void)
{
CArrayObj *description = new CArrayObj();
if(!description)
ReturnFalse;
description.FreeMode(true);
for(int i = 0; i < 2; i++)
{
CLayerDescription *layer = new CLayerDescription();
if(!layer)
{
DeleteObjAndFalse(description);
}
layer.type = defNeuronBaseOCL;
layer.count = D2_ITEM_DIM;
layer.activation = None;
layer.optimization = ADAM;
if(!description.Add(layer))
{
DeleteObj(layer);
DeleteObjAndFalse(description);
}
}
const bool result = g_net.Create(description);
delete description;
return(result && g_net.GetOpenCL() != NULL);
}
//+------------------------------------------------------------------+
float TargetValue(const int sample, const int dimension)
{
//--- Stable direction is +X; bounded noise is deliberately injected.
float value = (dimension == 0 ? 0.80f : 0.0f);
value += 0.05f * (float)MathSin(0.71 * sample + 0.37 * dimension);
//--- Two isolated outliers test robustness of both EMA modes.
if(sample == 9 || sample == 22)
value += (dimension == 0 ? -2.5f : (dimension == 1 ? 2.0f : 0.0f));
return(value);
}
//+------------------------------------------------------------------+
bool RunRepresentation(const ED2SkillRepresentation representation, const float beta,
float &final_skill_loss, float &final_cosine,
float &final_reconstruction_error, float &outlier_response)
{
COpenCLMy *opencl = g_net.GetOpenCL();
CD2SkillItem item;
if(!opencl || !item.Init(0, 300, opencl, D2_ITEM_DIM, ADAM, 1))
ReturnFalse;
item.SetRepresentation(representation);
item.SetEMA(beta, beta, beta);
float latent[D2_ITEM_DIM] = {0.0f, 0.0f, 0.0f, 0.0f};
if(!g_source.getOutput().AssignArray(latent) ||
!g_source.getOutput().BufferWrite() ||
!g_target.BufferInit(D2_ITEM_DIM, 0.0f) ||
!g_target.BufferCreate(opencl))
ReturnFalse;
//--- Forward, loss-gradient and bank update stay on the GPU. No output,
//--- gradient or slot buffer is read during this sample loop.
const float stable[D2_ITEM_DIM] = {0.80f, 0.0f, 0.0f, 0.0f};
final_skill_loss = 0.0f;
float final_base_loss = 0.0f;
final_cosine = 0.0f;
final_reconstruction_error = 0.0f;
outlier_response = 0.0f;
int stabilization_epoch = -1;
for(int epoch = 0; epoch < D2_ITEM_EPOCHS; epoch++)
{
float pseudo_norm_sum = 0.0f;
for(int sample = 0; sample < D2_ITEM_SAMPLES; sample++)
{
const int sample_index = epoch * D2_ITEM_SAMPLES + sample;
float target[D2_ITEM_DIM];
float pseudo_norm = 0.0f;
for(int d = 0; d < D2_ITEM_DIM; d++)
{
target[d] = TargetValue(sample_index, d);
pseudo_norm += target[d] * target[d];
}
pseudo_norm_sum += MathSqrt(pseudo_norm);
if(epoch == 0 && (sample == 0 || sample == 9 || sample == 22))
PrintFormat("D2SkillItemExperiment: pseudo sample=%d target=(%.5f,%.5f,%.5f,%.5f) norm=%.5f outlier=%s",
sample_index, target[0], target[1], target[2], target[3],
MathSqrt(pseudo_norm), (sample == 9 || sample == 22 ? "true" : "false"));
if(!g_target.AssignArray(target) || !g_target.BufferWrite())
ReturnFalse;
float error = 1.0f;
if(!g_source.calcOutputGradients(&g_target, error) ||
!item.UpdateFromGradient(g_source.getGradient()))
ReturnFalse;
}
//----- Host readback is restricted to completed epoch boundaries.
if(!item.feedForward(g_source.AsObject()) || !item.Correction() ||
!item.Correction().BufferRead() || !item.Direction().BufferRead() ||
!item.Scale().BufferRead() || !item.Observations().BufferRead() ||
!item.Mass().BufferRead() || !item.getOutput().BufferRead())
ReturnFalse;
float correction_norm = 0.0f;
float correction_error = 0.0f;
float base_loss = 0.0f;
float skill_loss = 0.0f;
for(int d = 0; d < D2_ITEM_DIM; d++)
{
const float correction = item.Correction()[d];
const float base_delta = latent[d] - stable[d];
const float skill_delta = item.getOutput()[d] - stable[d];
const float reconstruction_delta = correction - stable[d];
correction_norm += correction * correction;
correction_error += reconstruction_delta * reconstruction_delta;
base_loss += 0.5f * base_delta * base_delta;
skill_loss += 0.5f * skill_delta * skill_delta;
}
const float correction_length = MathSqrt(correction_norm);
const float reconstruction_error = MathSqrt(correction_error);
const float cosine = (correction_length > 1.0e-12f ?
item.Correction()[0] / correction_length : 0.0f);
if(stabilization_epoch < 0 && cosine >= 0.90f)
stabilization_epoch = epoch + 1;
outlier_response = MathMax(outlier_response,
MathAbs(item.Correction()[0] - stable[0]));
PrintFormat("D2SkillItemExperiment: epoch=%d mode=%s beta=%.3f pseudo_norm=%.5f direction_cosine=%.5f reconstruction_error=%.5f outlier_response=%.5f observations=%.0f mass=%.5f base_loss=%.6f skill_loss=%.6f",
epoch + 1,
(representation == D2SkillFullResidual ? "full" : "direction-magnitude"),
beta, pseudo_norm_sum / (float)D2_ITEM_SAMPLES, cosine,
reconstruction_error, outlier_response, item.Observations()[0],
item.Mass()[0], base_loss, skill_loss);
final_skill_loss = skill_loss;
final_base_loss = base_loss;
final_cosine = cosine;
final_reconstruction_error = reconstruction_error;
}
PrintFormat("D2SkillItemExperiment: mode=%s beta=%.3f stabilization_epoch=%d final_cosine=%.5f reconstruction_error=%.5f outlier_response=%.5f",
(representation == D2SkillFullResidual ? "full" : "direction-magnitude"),
beta, stabilization_epoch, final_cosine, final_reconstruction_error,
outlier_response);
return(final_skill_loss < final_base_loss && final_cosine > 0.0f &&
final_reconstruction_error >= 0.0f && stabilization_epoch > 0 &&
outlier_response >= 0.0f);
}
//+------------------------------------------------------------------+
//| Create an isolated OpenCL host for one arbitrary-dimension test. |
//+------------------------------------------------------------------+
bool CreateCoverageHost(CNet &net, const uint dimension)
{
CArrayObj *description = new CArrayObj();
if(!description)
ReturnFalse;
description.FreeMode(true);
for(int i = 0; i < 2; i++)
{
CLayerDescription *layer = new CLayerDescription();
if(!layer)
{
DeleteObjAndFalse(description);
}
layer.type = defNeuronBaseOCL;
layer.count = dimension;
layer.activation = None;
layer.optimization = ADAM;
if(!description.Add(layer))
{
DeleteObj(layer);
DeleteObjAndFalse(description);
}
}
const bool result = net.Create(description);
delete description;
return(result && net.GetOpenCL() != NULL);
}
//+------------------------------------------------------------------+
//| Compare one deterministic GPU update with the complete CPU state.|
//+------------------------------------------------------------------+
bool RunArbitraryDimensionCoverage(void)
{
const int dimensions[] = {1, 2, 4, 63, 64, 65, 257, 1025};
const float beta_correction = 0.05f;
const float beta_direction = 0.125f;
const float beta_magnitude = 0.075f;
const double tolerance = D2_ITEM_TOLERANCE;
for(int test = 0; test < ArraySize(dimensions); test++)
{
const int dimension = dimensions[test];
for(int mode = 0; mode < 2; mode++)
{
const ED2SkillRepresentation representation =
(mode == 0 ? D2SkillFullResidual : D2SkillDirectionMagnitude);
const string mode_name =
(representation == D2SkillFullResidual ? "full" : "direction-magnitude");
CNet net;
CBufferFloat gradient;
CD2SkillItem item;
if(!CreateCoverageHost(net, (uint)dimension) ||
!gradient.BufferInit(dimension, 0.0f) ||
!gradient.BufferCreate(net.GetOpenCL()) ||
!item.Init(0, 400 + (uint)(2 * test + mode), net.GetOpenCL(),
(uint)dimension, ADAM, 1))
ReturnFalse;
item.SetRepresentation(representation);
item.SetEMA(beta_correction, beta_direction, beta_magnitude);
vector<float> gradient_values = vector<float>::Zeros(dimension);
vector<float> expected_correction = vector<float>::Zeros(dimension);
vector<float> expected_direction = vector<float>::Zeros(dimension);
float gradient_norm_square = 0.0f;
for(int d = 0; d < dimension; d++)
{
const float sign = (d % 2 == 0 ? 1.0f : -1.0f);
gradient_values[d] = sign * 0.001f * (float)(1 + d % 31);
}
//--- A nonzero tail distinguishes full coverage from workgroup truncation.
gradient_values[dimension - 1] = 0.75f;
for(int d = 0; d < dimension; d++)
gradient_norm_square += gradient_values[d] * gradient_values[d];
const float gradient_norm = (float)MathSqrt(gradient_norm_square);
float expected_scale = 0.0f;
float expected_direction_norm_square = 0.0f;
for(int d = 0; d < dimension; d++)
{
if(representation == D2SkillFullResidual)
expected_correction[d] = beta_correction * gradient_values[d];
else
{
expected_direction[d] = (1.0e-12f < gradient_norm ?
beta_direction * gradient_values[d] / gradient_norm : 0.0f);
expected_direction_norm_square += expected_direction[d] * expected_direction[d];
}
}
if(representation == D2SkillDirectionMagnitude)
{
expected_scale = beta_magnitude * gradient_norm;
const float expected_direction_norm = (float)MathSqrt(expected_direction_norm_square);
for(int d = 0; d < dimension; d++)
expected_correction[d] = (1.0e-12f < expected_direction_norm ?
expected_scale * expected_direction[d] /
expected_direction_norm : 0.0f);
}
if(!gradient.AssignArray(gradient_values) || !gradient.BufferWrite() ||
!item.UpdateFromGradient(GetPointer(gradient)) ||
!item.Correction().BufferRead() || !item.Direction().BufferRead() ||
!item.Scale().BufferRead() || !item.Observations().BufferRead() ||
!item.Mass().BufferRead())
ReturnFalse;
for(int component = 0; component < dimension; component++)
{
if(MathAbs(item.Correction()[component] - expected_correction[component]) > tolerance)
{
PrintFormat("D2SkillItemExperiment: coverage correction failed dimension=%d mode=%s component=%d actual=%.8f expected=%.8f",
dimension, mode_name, component, item.Correction()[component],
expected_correction[component]);
ReturnFalse;
}
if(MathAbs(item.Direction()[component] - expected_direction[component]) > tolerance)
{
PrintFormat("D2SkillItemExperiment: coverage direction failed dimension=%d mode=%s component=%d actual=%.8f expected=%.8f",
dimension, mode_name, component, item.Direction()[component],
expected_direction[component]);
ReturnFalse;
}
}
const float expected_observations = 1.0f;
const float expected_mass = gradient_norm;
if(MathAbs(item.Scale()[0] - expected_scale) > tolerance ||
MathAbs(item.Observations()[0] - expected_observations) > tolerance ||
MathAbs(item.Mass()[0] - expected_mass) > tolerance)
{
PrintFormat("D2SkillItemExperiment: coverage scalar failed dimension=%d mode=%s scale=%.8f/%.8f observations=%.8f/%.8f mass=%.8f/%.8f",
dimension, mode_name, item.Scale()[0], expected_scale,
item.Observations()[0], expected_observations, item.Mass()[0], expected_mass);
ReturnFalse;
}
PrintFormat("D2SkillItemExperiment: coverage dimension=%d mode=%s components=%d tail=%.8f scale=%.8f observations=%.0f mass=%.8f",
dimension, mode_name, dimension, item.Correction()[dimension - 1],
item.Scale()[0], item.Observations()[0], item.Mass()[0]);
}
}
return(true);
}
//+------------------------------------------------------------------+
int OnInit()
{
if(!CreateOpenCLHost() ||
!g_source.Init(0, 100, g_net.GetOpenCL(), D2_ITEM_DIM, ADAM, 1))
return(INIT_FAILED);
float full_skill_loss = 0.0f;
float full_cosine = 0.0f;
float full_reconstruction_error = 0.0f;
float full_outlier_response = 0.0f;
float direction_skill_loss = 0.0f;
float direction_cosine = 0.0f;
float direction_reconstruction_error = 0.0f;
float direction_outlier_response = 0.0f;
float slow_skill_loss = 0.0f;
float slow_cosine = 0.0f;
float slow_reconstruction_error = 0.0f;
float slow_outlier_response = 0.0f;
if(!RunArbitraryDimensionCoverage() ||
!RunRepresentation(D2SkillFullResidual, 0.08f, full_skill_loss, full_cosine,
full_reconstruction_error, full_outlier_response) ||
!RunRepresentation(D2SkillDirectionMagnitude, 0.08f, direction_skill_loss,
direction_cosine, direction_reconstruction_error,
direction_outlier_response) ||
!RunRepresentation(D2SkillDirectionMagnitude, 0.02f, slow_skill_loss, slow_cosine,
slow_reconstruction_error, slow_outlier_response))
{
PrintFormat("D2SkillItemExperiment: FAILED error=%d", GetLastError());
return(INIT_FAILED);
}
const bool direction_advantage =
(direction_skill_loss < full_skill_loss &&
direction_reconstruction_error <= full_reconstruction_error &&
direction_outlier_response <= full_outlier_response);
PrintFormat("D2SkillItemExperiment: representation-summary beta=0.080 direction_advantage=%s default=direction-magnitude full(skill_loss=%.6f cosine=%.5f reconstruction=%.5f outlier=%.5f) direction(skill_loss=%.6f cosine=%.5f reconstruction=%.5f outlier=%.5f)",
(direction_advantage ? "observed" : "not-observed"),
full_skill_loss, full_cosine, full_reconstruction_error,
full_outlier_response, direction_skill_loss, direction_cosine,
direction_reconstruction_error, direction_outlier_response);
Print("D2SkillItemExperiment: PASS");
g_completed = true;
if(!EventSetMillisecondTimer(1))
{
PrintFormat("D2SkillItemExperiment: timer setup failed error=%d", GetLastError());
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
void OnTimer()
{
if(g_completed)
ExpertRemove();
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
EventKillTimer();
g_source.Clear();
}
//+------------------------------------------------------------------+