交易中的神经网络:场景感知物体检测(HyperDet3D)·进阶篇
「Transformer 层里的张量尺寸怎么算」
在 MT5 用 OpenCL 跑多头注意力,第一步是把每层需要的显存张量尺寸先算清楚。下面这段初始化逻辑里,Q 张量大小为 iWindowKey * iHeads * iUnits,KV 张量因为要存 K 和 V 两套,尺寸是 2 * iWindowKey * iHeadsKV * iUnits。 所有窗口、头数、层数参数都先用 fmax(x,1) 兜底,避免传 0 导致后续矩阵维度崩掉。比如 iWindow 和 iUnits 至少取 1,iLayers 也是同理,这是写神经网络容器时容易漏的防御式写法。 注意力内部的权重矩阵也有固定算法:Q 的权重矩阵尺寸是 (iWindow * iHeads) * iWindowKey,KV 权重则是两倍。前馈部分 ff_1 用 4*(iWindow+1)*iWindow,ff_2 用 (4*iWindow+1)*iWindow,这个 4 倍扩展是 Transformer 常规做法,显存占用会随 window 平方级增长,EURUSD 这类品种若把 window 设到 64,单层的 ff_1 就要约 66k 个 float。 循环里每层先 new 两个 CBufferFloat 塞进 QKV_Tensors,BufferInit 填 0、BufferCreate 绑 OpenCL 上下文,任何一步返回 false 就直接退出。想验证的话,把 iWindow=10、iHeads=2、iUnits=8 代进去,num_q 应该是 160,开 MT5 断点看 QKV_Tensors 总大小即可。
class="type">uint window, class="type">uint window_key, class="type">uint heads, class="type">uint heads_kv, class="type">uint units_count, class="type">uint layers, class="type">uint layers_to_one_kv, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) { if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * units_count, optimization_type, batch)) class="kw">return class="kw">false; iWindow = fmax(window, class="num">1); iWindowKey = fmax(window_key, class="num">1); iUnits = fmax(units_count, class="num">1); iHeads = fmax(heads, class="num">1); iLayers = fmax(layers, class="num">1); iHeadsKV = fmax(heads_kv, class="num">1); iLayersToOneKV = fmax(layers_to_one_kv, class="num">1); class="type">uint num_q = iWindowKey * iHeads * iUnits; class=class="str">"cmt">//Size of Q tensor class="type">uint num_kv = class="num">2 * iWindowKey * iHeadsKV * iUnits; class=class="str">"cmt">//Size of KV tensor class="type">uint q_weights = (iWindow * iHeads) * iWindowKey; class=class="str">"cmt">//Size of weights&class="macro">#x27; matrix of Q tenzor class="type">uint kv_weights = class="num">2 * (iWindow * iHeadsKV) * iWindowKey; class=class="str">"cmt">//Size of weights&class="macro">#x27; matrix of KV tenzor class="type">uint scores = iUnits * iUnits * iHeads; class=class="str">"cmt">//Size of Score tensor class="type">uint mh_out = iWindowKey * iHeads * iUnits; class=class="str">"cmt">//Size of multi-heads self-attention class="type">uint out = iWindow * iUnits; class=class="str">"cmt">//Size of out tensore class="type">uint w0 = (iWindowKey * iHeads + class="num">1) * iWindow; class=class="str">"cmt">//Size W0 tensor class="type">uint ff_1 = class="num">4 * (iWindow + class="num">1) * iWindow; class=class="str">"cmt">//Size of weights&class="macro">#x27; matrix class="num">1-st feed forward layer class="type">uint ff_2 = (class="num">4 * iWindow + class="num">1) * iWindow; class=class="str">"cmt">//Size of weights&class="macro">#x27; matrix class="num">2-nd feed forward layer CNeuronBaseOCL *base = NULL; CNeuronSceneSpecific *ss = NULL; for(class="type">uint i = class="num">0; i < iLayers; i++) { CBufferFloat *temp = NULL; for(class="type">int d = class="num">0; d < class="num">2; d++) { class=class="str">"cmt">//--- Initilize Q tensor temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return class="kw">false; if(!temp.BufferInit(num_q, class="num">0)) class="kw">return class="kw">false; if(!temp.BufferCreate(OpenCL)) class="kw">return class="kw">false; if(!QKV_Tensors.Add(temp)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Initilize Q weights temp = new CBufferFloat();
◍ 注意力层里的张量分步建池
这段逻辑出现在多头部注意力模块的初始化循环里,每跑一层就先建 Q 权重缓冲,再按周期建 KV 缓冲与打分缓冲。 先看 Q 侧:new 出 CBufferFloat 后用 CheckPointer 判空,POINTER_INVALID 直接返 false;BufferInit(q_weights,0) 把元素清 0,BufferCreate(OpenCL) 把显存对象建到 OpenCL 上下文,最后塞进 QKV_Weights 数组。任何一步失败都立刻退出,避免半吊子对象留坑。 KV 侧不是每层都建,由 i % iLayersToOneKV == 0 控制节奏。比如 iLayersToOneKV 设 2,就是每两层共享一组 KV 张量与 KV 权重,显存和计算量都能压下来。组内同样三步:判空、BufferInit(num_kv 或 kv_weights,0)、BufferCreate(OpenCL),分别进 KV_Tensors 与 KV_Weights。 打分缓冲 S_Tensors 和后面的注意力输出缓冲,每层必建,结构与前面一致。你在 MT5 里改 iLayersToOneKV 这个值,能直接观察到显存占用的阶跃变化——外汇与贵金属品种波动大、高风险,跑前先用小周期数据验证缓冲尺寸不溢出。
if(CheckPointer(temp) == POINTER_INVALID) class="kw">return class="kw">false; if(!temp.BufferInit(q_weights, class="num">0)) class="kw">return class="kw">false; if(!temp.BufferCreate(OpenCL)) class="kw">return class="kw">false; if(!QKV_Weights.Add(temp)) class="kw">return class="kw">false; if(i % iLayersToOneKV == class="num">0) { class=class="str">"cmt">//--- Initilize KV tensor temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return class="kw">false; if(!temp.BufferInit(num_kv, class="num">0)) class="kw">return class="kw">false; if(!temp.BufferCreate(OpenCL)) class="kw">return class="kw">false; if(!KV_Tensors.Add(temp)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Initilize KV weights temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return class="kw">false; if(!temp.BufferInit(kv_weights, class="num">0)) class="kw">return class="kw">false; if(!temp.BufferCreate(OpenCL)) class="kw">return class="kw">false; if(!KV_Weights.Add(temp)) class="kw">return class="kw">false; } class=class="str">"cmt">//--- Initialize scores temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return class="kw">false; if(!temp.BufferInit(scores, class="num">0)) class="kw">return class="kw">false; if(!temp.BufferCreate(OpenCL)) class="kw">return class="kw">false; if(!S_Tensors.Add(temp)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Initialize multi-heads attention out temp = new CBufferFloat();
Transformer 层里的张量缓冲装配
在 MT5 的 OpenCL 环境里搭 Transformer,每一层的张量缓冲必须逐个 new 出来并挂到对应的容器,否则后续核函数调用会直接崩。下面这段就是注意力输出、前馈 1、前馈 2 三组缓冲的初始化逻辑,任何一步返回 false 都意味着整层构建失败。 注意前馈 1 的缓冲长度是 4 * out,这是 Transformer 里常见的扩展维度(如 512→2048);而最后一层的前馈 2 不再 new 临时缓冲,而是按 d==0 直接塞 Output 或 Gradient,并 continue 跳过常规挂载。
…
if(CheckPointer(temp) == POINTER_INVALID) class="kw">return class="kw">false; if(!temp.BufferInit(mh_out, class="num">0)) class="kw">return class="kw">false; if(!temp.BufferCreate(OpenCL)) class="kw">return class="kw">false; if(!AO_Tensors.Add(temp)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Initialize attention out temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return class="kw">false; if(!temp.BufferInit(out, class="num">0)) class="kw">return class="kw">false; if(!temp.BufferCreate(OpenCL)) class="kw">return class="kw">false; if(!FF_Tensors.Add(temp)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Initialize Feed Forward class="num">1 temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return class="kw">false; if(!temp.BufferInit(class="num">4 * out, class="num">0)) class="kw">return class="kw">false; if(!temp.BufferCreate(OpenCL)) class="kw">return class="kw">false; if(!FF_Tensors.Add(temp)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Initialize Feed Forward class="num">2 if(i == iLayers - class="num">1) { if(!FF_Tensors.Add(d == class="num">0 ? Output : Gradient)) class="kw">return class="kw">false; class="kw">continue; } temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return class="kw">false; if(!temp.BufferInit(out, class="num">0)) class="kw">return class="kw">false; if(!temp.BufferCreate(OpenCL)) class="kw">return class="kw">false; if(!FF_Tensors.Add(temp)) class="kw">return class="kw">false; } if(i % iLayersToOneKV == class="num">0) {
「场景相关与无关层的初始化分叉」
在 MT5 的 OpenCL 神经网络封装里,场景相关(Scene-Specific)和场景无关(Scene-Agnostic)两类子网络的初始化走的是两条不同路径,核心差异在权重入口参数:当走分支一时,场景相关层 Init 拿的是 (q_weights + kv_weights) 合并权重,而分支二只用 q_weights。
class=class="str">"cmt">//--- 初始化场景相关层 ss = new CNeuronSceneSpecific(); if(!ss) class="kw">return class="kw">false; if(!ss.Init((q_weights + kv_weights), cSceneSpecific.Total(), OpenCL, iWindow, iWindowKey, class="num">4, class="num">2, iUnits, class="num">100, class="num">2, class="num">2, optimization, iBatch)) class="kw">return class="kw">false; if(!cSceneSpecific.Add(ss)) class="kw">return class="kw">false;
class=class="str">"cmt">//--- Initilize Scene-Specific layers ss = new CNeuronSceneSpecific(); if(!ss) class="kw">return class="kw">false; if(!ss.Init((q_weights + kv_weights), cSceneSpecific.Total(), OpenCL, iWindow, iWindowKey, class="num">4, class="num">2, iUnits, class="num">100, class="num">2, class="num">2, optimization, iBatch)) class="kw">return class="kw">false; if(!cSceneSpecific.Add(ss)) class="kw">return class="kw">false; base = new CNeuronBaseOCL(); if(!base) class="kw">return class="kw">false; if(!base.Init(class="num">0, cSceneSpecific.Total(), OpenCL, (q_weights + kv_weights), optimization, iBatch)) class="kw">return class="kw">false; base.SetActivationFunction(TANH); if(!cSceneSpecific.Add(base)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Initilize Scene-Agnostic layers base = new CNeuronBaseOCL(); if(!base) class="kw">return class="kw">false; if(!base.Init((q_weights + kv_weights), cSceneAgnostic.Total(), OpenCL, class="num">1, optimization, iBatch)) class="kw">return class="kw">false; temp = base.getOutput(); if(!temp.BufferInit(class="num">1, class="num">1) || !temp.BufferWrite()) class="kw">return class="kw">false; if(!cSceneAgnostic.Add(base)) class="kw">return class="kw">false; base = new CNeuronBaseOCL(); if(!base) class="kw">return class="kw">false; if(!base.Init(class="num">0, cSceneAgnostic.Total(), OpenCL, (q_weights + kv_weights), optimization, iBatch)) class="kw">return class="kw">false; if(!cSceneAgnostic.Add(base)) class="kw">return class="kw">false; } else { class=class="str">"cmt">//--- Initilize Scene-Specific layers ss = new CNeuronSceneSpecific(); if(!ss) class="kw">return class="kw">false; if(!ss.Init(q_weights, cSceneSpecific.Total(), OpenCL, iWindow, iWindowKey, class="num">4, class="num">2, iUnits, class="num">100, class="num">2, class="num">2, optimization, iBatch))
◍ 双塔结构里的权重初始化细节
这段初始化逻辑把网络拆成场景相关(Scene-Specific)与场景无关(Scene-Agnostic)两条塔,各自挂 CNeuronBaseOCL 基元层。第一座塔的基元层输入维度由 cSceneSpecific.Total() 决定,激活函数被钉死在 TANH,第二座塔在 Init 后立刻把输出 buffer 以 1×1 尺寸做 BufferInit 与 BufferWrite,保证 OpenCL 端能直接读张量。 权重0(w0)的初始化用 Xavier 风格的缩放:k = 1/sqrt(iWindow+1),循环里每个权重值 = GenerateWeight()*2k - k,把随机量约束在 [-k, k] 区间。若 iWindow 取 59,k 约为 0.128,单权重的绝对上限就被压到 0.128 附近,避免早期梯度爆炸。 任一指针创建失败、Reserve 失败或 BufferCreate 不通过都会直接 return false,说明这套结构对显存/内存申请零容忍。开 MT5 把 iWindow 从 59 改成 119,能看到 k 降到约 0.091,网络浅层响应幅度倾向更收敛,可据此对比回测稳定性。 别把正态当圣经:这里没用纯高斯,而是均匀边界缩放,复制代码时若手滑换成 rand() 不乘 2k-k,收敛速度可能明显变慢。
class="kw">return class="kw">false; if(!cSceneSpecific.Add(ss)) class="kw">return class="kw">false; base = new CNeuronBaseOCL(); if(!base) class="kw">return class="kw">false; if(!base.Init(class="num">0, cSceneSpecific.Total(), OpenCL, q_weights, optimization, iBatch)) class="kw">return class="kw">false; base.SetActivationFunction(TANH); if(!cSceneSpecific.Add(base)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Initilize Scene-Agnostic layers base = new CNeuronBaseOCL(); if(!base) class="kw">return class="kw">false; if(!base.Init(q_weights, cSceneAgnostic.Total(), OpenCL, class="num">1, optimization, iBatch)) class="kw">return class="kw">false; temp = base.getOutput(); if(!temp.BufferInit(class="num">1, class="num">1) || !temp.BufferWrite()) class="kw">return class="kw">false; if(!cSceneAgnostic.Add(base)) class="kw">return class="kw">false; base = new CNeuronBaseOCL(); if(!base) class="kw">return class="kw">false; if(!base.Init(class="num">0, cSceneAgnostic.Total(), OpenCL, q_weights, optimization, iBatch)) class="kw">return class="kw">false; if(!cSceneAgnostic.Add(base)) class="kw">return class="kw">false; } class=class="str">"cmt">//--- Initilize Weights0 temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return class="kw">false; if(!temp.Reserve(w0)) class="kw">return class="kw">false; class="type">class="kw">float k = (class="type">class="kw">float)(class="num">1 / sqrt(iWindow + class="num">1)); for(class="type">uint w = class="num">0; w < w0; w++) { if(!temp.Add(GenerateWeight() * class="num">2 * k - k)) class="kw">return class="kw">false; } if(!temp.BufferCreate(OpenCL)) class="kw">return class="kw">false; if(!FF_Weights.Add(temp))
前馈权重的初始化与显存落地
这段逻辑紧接前面网络结构定义,负责把两层前馈权重和偏置真正塞进 GPU 缓冲。第一层权重数量为 ff_1,初始化区间由 k 控制,k 在前面通常取 1/sqrt(4*iWindow+1),权重值通过 GenerateWeight()*2*k - k 映射到 [-k, k],避免初始梯度过早饱和。 第二层权重 ff_2 用同样的映射公式,但 k 重新算成 1/sqrt(4*iWindow+1) 的浮点版本,说明两层输入规模不同、缩放因子也分算。每个 temp 缓冲在 Add 完权重后必须调 BufferCreate(OpenCL),否则后面核函数读不到数据,返回 false 即中断整个 Init。 优化器分支在这里显形:循环次数按 optimization 走,SGD 只跑 d=0 一轮,ADAM 则跑 d=0 和 d=1 两轮,分别装动量缓冲。d=0 时缓冲长度取 w0(输入窗口展平维度),d=1 在 ADAM 下也取 w0,SGD 下则取 iWindow;第二块权重缓冲长度对应 ff_1 或 4*iWindow。 在 MT5 里跑这套,若改了 iWindow 从 30 调到 60,ff_1 和 k 都会变,必须重新 Reserve 足够空间,否则 Reserve 失败直接返 false,EA 初始化会静默崩掉。
class="kw">return class="kw">false; class=class="str">"cmt">//--- Initilize FF Weights temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return class="kw">false; if(!temp.Reserve(ff_1)) class="kw">return class="kw">false; for(class="type">uint w = class="num">0; w < ff_1; w++) { if(!temp.Add(GenerateWeight() * class="num">2 * k - k)) class="kw">return class="kw">false; } if(!temp.BufferCreate(OpenCL)) class="kw">return class="kw">false; if(!FF_Weights.Add(temp)) class="kw">return class="kw">false; class=class="str">"cmt">//--- temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return class="kw">false; if(!temp.Reserve(ff_2)) class="kw">return class="kw">false; k = (class="type">class="kw">float)(class="num">1 / sqrt(class="num">4 * iWindow + class="num">1)); for(class="type">uint w = class="num">0; w < ff_2; w++) { if(!temp.Add(GenerateWeight() * class="num">2 * k - k)) class="kw">return class="kw">false; } if(!temp.BufferCreate(OpenCL)) class="kw">return class="kw">false; if(!FF_Weights.Add(temp)) class="kw">return class="kw">false; class=class="str">"cmt">//--- for(class="type">int d = class="num">0; d < (optimization == SGD ? class="num">1 : class="num">2); d++) { temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return class="kw">false; if(!temp.BufferInit((d == class="num">0 || optimization == ADAM ? w0 : iWindow), class="num">0)) class="kw">return class="kw">false; if(!temp.BufferCreate(OpenCL)) class="kw">return class="kw">false; if(!FF_Weights.Add(temp)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Initilize FF Weights temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return class="kw">false; if(!temp.BufferInit((d == class="num">0 || optimization == ADAM ? ff_1 : class="num">4 * iWindow), class="num">0))