//+------------------------------------------------------------------+ //| 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 gradient_values = vector::Zeros(dimension); vector expected_correction = vector::Zeros(dimension); vector expected_direction = vector::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(); } //+------------------------------------------------------------------+