交易中的神经网络:定向扩散模型(DDM)·进阶篇
🧭

交易中的神经网络:定向扩散模型(DDM)·进阶篇

(2/3)· 传统扩散在金融不对称走势上信噪比崩太快,DDM 如何缓解这个坑

案例拆解 第 2/3 篇

很多人在行情里套用通用噪声模型做表征,却没意识到金融数据天生带方向性和各向异性, isotropic 噪声一加进去结构就被冲没了。等发现特征学不出来,往往已经在一堆失效信号上耗掉不少时间。这篇接着基础篇,把 DDM 怎么把方向偏置塞进扩散过程拆开看。

「带噪声的批归一化怎么在 OpenCL 里落地」

这段代码实现的是在 GPU(OpenCL)上跑的批归一化变体:CNeuronBatchNormWithNoise 继承自普通批归一化类,多带了一个 cNoise 噪声缓冲。训练阶段(bTrain=true)才会注入噪声,推理阶段直接走父类的前向逻辑,避免线上信号被随机扰动。 核函数开头先判断 batch<=1 直接 return,因为单样本无法估计均值和方差;随后按 optimization 标志决定每个神经元占用 7 或 9 个 float 参数槽(shift 偏移)。对每个槽先做 NaN/Inf 清洗,把非法值置 0,防止显存里的脏数据污染整个 batch。

均值用递推式更新:mean = (原均值*(batch-1) + 当前输入) / batch;方差同理累加 delta 平方。归一化后 nx = delta/sqrt(variance),再叠一层噪声:noisex = sqrt(alpha)*nx + sqrt(1-alpha)*noise*sign(nx)。alpha 是混合系数,调小它等于放大噪声占比,模型可能更抗过拟合但收敛更慢。

feedForward 里若 iBatchSize<=1 只记录激活类型就返回;否则用 MathRandomNormal(0,1,Neurons(),random) 抽标准正态噪声写进 cNoise。你在 MT5 里改 Neurons() 数量或 alpha 常量,能直接观察策略对噪声灵敏度的变化,外汇与贵金属波动本就高噪,这类层对过拟合有概率上的缓冲作用。

MQL5 / C++
if(batch <= class="num">1)
      class="kw">return;
   class="type">int n = get_global_id(class="num">0);
   class="type">int shift = n * (optimization == class="num">0 ? class="num">7 : class="num">9);
   for(class="type">int i = class="num">0; i < (optimization == class="num">0 ? class="num">7 : class="num">9); i++)
      {
         class="type">float opt = options[shift + i];
         if(isnan(opt) || isinf(opt))
            options[shift + i] = class="num">0;
      }
   class="type">float inp = inputs[n];
   class="type">float mean = (batch > class="num">1 ? (options[shift] * ((class="type">float)batch - class="num">1.0f) + inp) / ((class="type">float)batch) : inp);
   class="type">float delt = inp - mean;
   class="type">float variance = options[shift + class="num">1] * ((class="type">float)batch - class="num">1.0f) + pow(delt, class="num">2);
   if(batch > class="num">0)
      variance /= (class="type">float)batch;
   class="type">float nx = (variance > class="num">0 ? delt / sqrt(variance) : class="num">0);
   class="type">float noisex = sqrt(alpha) * nx + sqrt(class="num">1-alpha) * fabs(noise[n]) * sign(nx);
   class="type">float gamma = options[shift + class="num">3];
   if(gamma == class="num">0 || isinf(gamma) || isnan(gamma))
      {
         options[shift + class="num">3] = class="num">1;
         gamma = class="num">1;
      }
   class="type">float betta = options[shift + class="num">4];
   if(isinf(betta) || isnan(betta))
      {
         options[shift + class="num">4] = class="num">0;
         betta = class="num">0;
      }
class=class="str">"cmt">//---
   options[shift] = mean;
   options[shift + class="num">1] = variance;
   options[shift + class="num">2] = nx;
   output[n] = Activation(gamma * noisex + betta, activation);
   }
class CNeuronBatchNormWithNoise  :  class="kw">public CNeuronBatchNormOCL
   {
class="kw">protected:
   CBufferFloat       cNoise;
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool      feedForward(CNeuronBaseOCL *NeuronOCL);

class="kw">public:
                     CNeuronBatchNormWithNoise(class="type">void) {};
                    ~CNeuronBatchNormWithNoise(class="type">void) {};
   class=class="str">"cmt">//---
   class="kw">virtual class="type">int       Type(class="type">void) const   {  class="kw">return defNeuronBatchNormWithNoise;   }
   };
class="type">bool CNeuronBatchNormWithNoise::feedForward(CNeuronBaseOCL *NeuronOCL)
   {
   if(!bTrain)
      class="kw">return CNeuronBatchNormOCL::feedForward(NeuronOCL);
if(!OpenCL || !NeuronOCL)
   class="kw">return false;
PrevLayer = NeuronOCL;
if(iBatchSize <= class="num">1)
  {
   activation = (ENUM_ACTIVATION)NeuronOCL.Activation();
   class="kw">return true;
  }
class="type">class="kw">double random[];
if(!Math::MathRandomNormal(class="num">0, class="num">1, Neurons(), random))
   class="kw">return false;
if(cNoise.Total() != Neurons() ||
   cNoise.GetOpenCL() != OpenCL)
  {
   cNoise.BufferFree();
   if(!cNoise.AssignArray(random))
      class="kw">return false;

给前向传播叠一层随机噪声

在 MT5 的 OpenCL 管线里给神经网络批量前向传播加噪声,核心是先准备噪声缓冲,再逐个把参数绑进内核。若走 GPU 路径,用 BufferCreate 建噪声缓冲失败就直接返回 false;走 CPU 路径则靠 AssignArray 接随机数组再 BufferWrite 落盘,任一步失败同样退出。 噪声强度由 noise_alpha 控制:用 MathRand() 除以 32767.0 再乘 0.01,得到 0~0.01 的扰动,最终 alpha 落在 0.99~1.0 区间。这个量级在外汇与贵金属模型里只是轻微抖动,但足以让过拟合倾向被压一截——当然只是概率上的缓解,不是免死金牌,杠杆品种的高风险仍在。 绑定参数这段最易出错:def_k_normwithnoise_inputs 到 def_k_normwithnoise_optimization 共 9 个 SetArgument / SetArgumentBuffer 调用,任一返回 false 就 printf 打出内核名、GetLastError() 和 __LINE__ 后退出。最后 Execute 用 global_work_offset={0} 与 global_work_size[0]=Neurons() 拉起内核,批次数被 MathMin(iBatchCount, iBatchSize) 截断。 别把正态当圣经:MathRand 给的是均匀随机,不是高斯;若你的正则化假设需要正态噪声,得自己换 Box-Muller,否则噪声分布偏差可能让训练结果偏得你看不懂。

MQL5 / C++
if(!cNoise.BufferCreate(OpenCL))
   class="kw">return false;
 }
else
  {
   if(!cNoise.AssignArray(random))
     class="kw">return false;
   if(!cNoise.BufferWrite())
     class="kw">return false;
  }
iBatchCount = MathMin(iBatchCount, iBatchSize);
class="type">float noise_alpha = class="type">float(class="num">1.0 - MathRand() / class="num">32767.0 * class="num">0.01);
class="type">uint global_work_offset[class="num">1] = {class="num">0};
class="type">uint global_work_size[class="num">1];
global_work_size[class="num">0] = Neurons();
class="type">int kernel = def_k_BatchFeedForwardAddNoise;
ResetLastError();
if(!OpenCL.SetArgumentBuffer(kernel, def_k_normwithnoise_inputs, NeuronOCL.getOutputIndex()))
  {
   printf("Error of set parameter kernel %s: %d; line %d", OpenCL.GetKernelName(kernel),
                                                                     GetLastError(), __LINE__);
   class="kw">return false;
  }
if(!OpenCL.SetArgumentBuffer(kernel, def_k_normwithnoise_noise, cNoise.GetIndex()))
{
   printf("Error of set parameter kernel %s: %d; line %d", OpenCL.GetKernelName(kernel),
                                                                     GetLastError(), __LINE__);
   class="kw">return false;
  }
if(!OpenCL.SetArgumentBuffer(kernel, def_k_normwithnoise_options, BatchOptions.GetIndex()))
  {
   printf("Error of set parameter kernel %s: %d; line %d", OpenCL.GetKernelName(kernel),
                                                                     GetLastError(), __LINE__);
   class="kw">return false;
  }
if(!OpenCL.SetArgumentBuffer(kernel, def_k_normwithnoise_output, Output.GetIndex()))
  {
   printf("Error of set parameter kernel %s: %d; line %d", OpenCL.GetKernelName(kernel),
                                                                     GetLastError(), __LINE__);
   class="kw">return false;
  }
if(!OpenCL.SetArgument(kernel, def_k_normwithnoise_activation, class="type">int(activation)))
  {
   printf("Error of set parameter kernel %s: %d; line %d", OpenCL.GetKernelName(kernel),
                                                                     GetLastError(), __LINE__);
   class="kw">return false;
  }
if(!OpenCL.SetArgument(kernel, def_k_normwithnoise_alpha, noise_alpha))
  {
   printf("Error of set parameter kernel %s: %d; line %d", OpenCL.GetKernelName(kernel),
                                                                     GetLastError(), __LINE__);
   class="kw">return false;
  }
if(!OpenCL.SetArgument(kernel, def_k_normwithnoise_batch, iBatchCount))
  {
   printf("Error of set parameter kernel %s: %d; line %d", OpenCL.GetKernelName(kernel),
                                                                     GetLastError(), __LINE__);
   class="kw">return false;
  }
if(!OpenCL.SetArgument(kernel, def_k_normwithnoise_optimization, class="type">int(optimization)))
  {
   printf("Error of set parameter kernel %s: %d; line %d", OpenCL.GetKernelName(kernel),
                                                                     GetLastError(), __LINE__);
   class="kw">return false;
  }
class=class="str">"cmt">//---
if(!OpenCL.Execute(kernel, class="num">1, global_work_offset, global_work_size))
  {
   printf("Error of execution kernel %s: %d; line %d", OpenCL.GetKernelName(kernel),

◍ 扩散神经元的类结构与初始化链路

CNeuronDiffusion 继承自 CNeuronUShapeAttention,内部组合了加噪批归一(cAddNoise)、残差连接(cResidual)和可逆归一(cRevIn)三个组件,前向、梯度与权重更新均声明为虚函数以便子类重写。 Init 方法是验证结构拼装是否正确的最快入口:先调基类 CNeuronBaseOCL::Init,传入 window * units_count 作为展开维度;随后 cAddNoise、cAttention[0]、cMergeSplit[0] 依次初始化,任意一步返回 false 都会中断。 当 inside_bloks > 0 时,代码用 new 动态创建下一级 CNeuronDiffusion 临时对象并判空。在 MT5 里把 inside_bloks 从 0 改为 1,可观察递归块是否触发额外显存分配,外汇与贵金属模型训练属高风险,参数误配可能导致 EA 初始化失败。

MQL5 / C++
class CNeuronDiffusion : class="kw">public CNeuronUShapeAttention
  {
class="kw">protected:
   CNeuronBatchNormWithNoise   cAddNoise;
   CNeuronBaseOCL             cResidual;
   CNeuronRevINDenormOCL      cRevIn;
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool      feedForward(CNeuronBaseOCL *NeuronOCL);
   class="kw">virtual class="type">bool      calcInputGradients(CNeuronBaseOCL *prevLayer);
   class="kw">virtual class="type">bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL);
class="kw">public:
                    CNeuronDiffusion(class="type">void) {};
                   ~CNeuronDiffusion(class="type">void) {};
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool      Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                          class="type">uint window, class="type">uint window_key, class="type">uint heads, class="type">uint units_count,
                          class="type">uint layers, class="type">uint inside_bloks,
ENUM_OPTIMIZATION optimization_type, class="type">uint batch);
   class=class="str">"cmt">//---
   class="kw">virtual class="type">int       Type(class="type">void) const   { class="kw">return defNeuronDiffusion; }
   class=class="str">"cmt">//--- methods for working with files
   class="kw">virtual class="type">bool      Save(class="type">int const file_handle);
   class="kw">virtual class="type">bool      Load(class="type">int const file_handle);
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool      WeightsUpdate(CNeuronBaseOCL *source, class="type">float tau);
   class="kw">virtual class="type">void      SetOpenCL(COpenCLMy *obj);
   };
class="type">bool CNeuronDiffusion::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                            class="type">uint window, class="type">uint window_key, class="type">uint heads,
                            class="type">uint units_count, class="type">uint layers, class="type">uint inside_bloks,
ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
  {
  if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * units_count, optimization_type, batch))
     class="kw">return false;
if(!cAddNoise.Init(class="num">0, class="num">0, OpenCL, window * units_count, iBatch, optimization))
   class="kw">return false;
if(!cAttention[class="num">0].Init(class="num">0, class="num">1, OpenCL, window, window_key, heads, units_count, layers, optimization, iBatch))
   class="kw">return false;
if(!cMergeSplit[class="num">0].Init(class="num">0, class="num">2, OpenCL, class="num">2 * window, class="num">2 * window, window, (units_count + class="num">1) / class="num">2, optimization,
iBatch))
   class="kw">return false;
 if(inside_bloks > class="num">0)
   {
    CNeuronDiffusion *temp = new CNeuronDiffusion();
    if(!temp)
      class="kw">return false;

「扩散层的前向链路与梯度回传」

在 CNeuronDiffusion 的初始化尾部,cAttention[1] 用 4 号索引、heads 个头、窗口数折半的 (units_count+1)/2 单元数接管上一层输出,cMergeSplit[1] 则把窗口拼成 2*window 宽度的特征图,这两个 Init 任一失败直接返回 false。 残差分支由 cResidual 以 Neurons() 为输入规模建立,并通过 SetGradient 绑定 cMergeSplit[1] 的梯度指针,true 参数代表接管所有权,这是后续 SumAndNormilize 能就地写回梯度的前提。 feedForward 里数据流向是定死的:噪声层 → 注意力0 → 合并拆分0 → 瓶颈层(cNeck) → 注意力1 → 合并拆分1,最后 SumAndNormilize 拿噪声输出与合并拆分1输出做加和归一,再进 cRevIn 逆向归一化,任一环节返回 false 整链中断。 calcInputGradients 开头把误差标量锁成 1.0f,先让 cRevIn 拿 prevLayer 的输出和这个误差算输出梯度,外汇与贵金属行情下用此类结构做信号生成仍属高风险,过拟合概率不低,建议开 MT5 把 heads 与 window 调小先做单品种回测。

MQL5 / C++
if(!temp.Init(class="num">0, class="num">3, OpenCL, window, window_key, heads, (units_count + class="num">1) / class="num">2, layers, inside_bloks - class="num">1,
optimization, iBatch))
  {
   class="kw">delete temp;
   class="kw">return false;
  }
 cNeck = temp;
 }
else
  {
  CNeuronConvOCL *temp = new CNeuronConvOCL();
  if(!temp)
   class="kw">return false;
  if(!temp.Init(class="num">0, class="num">3, OpenCL, window, window, window, (units_count + class="num">1) / class="num">2, optimization, iBatch))
   {
    class="kw">delete temp;
     class="kw">return false;
   }
  cNeck = temp;
   }
if(!cAttention[class="num">1].Init(class="num">0, class="num">4, OpenCL, window, window_key, heads, (units_count + class="num">1) / class="num">2, layers, optimization,
iBatch))
  class="kw">return false;
if(!cMergeSplit[class="num">1].Init(class="num">0, class="num">5, OpenCL, window, window, class="num">2 * window, (units_count + class="num">1) / class="num">2, optimization, iBatch))
  class="kw">return false;
if(!cResidual.Init(class="num">0, class="num">6, OpenCL, Neurons(), optimization, iBatch))
  class="kw">return false;
if(!cResidual.SetGradient(cMergeSplit[class="num">1].getGradient(), true))
  class="kw">return false;
if(!cRevIn.Init(class="num">0, class="num">7, OpenCL, Neurons(), class="num">0, cAddNoise.AsObject()))
  class="kw">return false;
  if(!SetOutput(cRevIn.getOutput(), true))
     class="kw">return false;
class=class="str">"cmt">//---
   class="kw">return true;
  }
class="type">bool CNeuronDiffusion::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cAddNoise.FeedForward(NeuronOCL))
     class="kw">return false;
if(!cAttention[class="num">0].FeedForward(cAddNoise.AsObject()))
   class="kw">return false;
if(!cMergeSplit[class="num">0].FeedForward(cAttention[class="num">0].AsObject()))
   class="kw">return false;
if(!cNeck.FeedForward(cMergeSplit[class="num">0].AsObject()))
   class="kw">return false;
if(!cAttention[class="num">1].FeedForward(cNeck))
   class="kw">return false;
if(!cMergeSplit[class="num">1].FeedForward(cAttention[class="num">1].AsObject()))
   class="kw">return false;
if(!SumAndNormilize(cAddNoise.getOutput(), cMergeSplit[class="num">1].getOutput(), cResidual.getOutput(),
class="num">1, true, class="num">0, class="num">0, class="num">0, class="num">1))
   class="kw">return false;
   if(!cRevIn.FeedForward(cResidual.AsObject()))
     class="kw">return false;
class=class="str">"cmt">//---
   class="kw">return true;
  }
class="type">bool CNeuronDiffusion::calcInputGradients(CNeuronBaseOCL *prevLayer)
  {
   if(!prevLayer)
     class="kw">return false;
class="type">float error = class="num">1;
if(!cRevIn.calcOutputGradients(prevLayer.getOutput(), error) ||
把各向异性诊断交给小布
小布盯盘的 AIGC 已内置对品种走势方向性与噪声结构的初判,打开对应页就能看到哪些阶段更适合用 DDM 类方法做表征,你只管决策要不要跟。

常见问题

外汇序列常呈现趋势与调整交替的方向性形态,各向同性噪声会快速压低信噪比,令去噪模型抓不到区分性结构,特征表示偏向无用白噪声。
目前小布提供的是基于走势各向异性的初步筛查与提示,完整的 DDM 训练仍需在 MT5 环境自行部署,小布负责替你标出值得深挖的品种段。
它在初始高斯噪声上叠加两个约束将其转换为各向异性噪声,使正向过程按数据本身方向缓慢退化,从而在多信噪比等级保留细粒度特征。
论文实验显示 DDM 提取的中间激活捕获了更关键的语义与拓扑信息,图形分类任务上甚至超过基线监督,说明方向偏置对结构保留更有效。