神经网络变得简单(第 92 部分):频域和时域中的自适应预测·综合运用
多头复数注意力的核函数落地
在 MT5 的 OpenCL 内核里做序列建模,关键不是堆层数,而是把 Q/K/V 的偏移算对。下面这段内核先按 head 与 unit 切分全局索引,再用复数乘法累加得到注意力分数,最后做归一化。 分数计算里用 sqrt(dimension) 当缩放系数,若维度小于 1 则强制取 1,避免除零或数值爆炸;mask 大于 0 且 k>q 时直接把 score 置零并跳过,这是因果掩码的最小实现。 输出核 ComplexMHAttentionOut 按 units×heads 重排偏移,对每个维度 d 遍历所有 v 做 ComplexMul 累加,写回 out 缓冲区。注意 shift_v 里 3*v+2 对应 V 矩阵段,改这个常量就会拿错权重。 梯度核 CalcHiddenGradientComplexConv 的入参 window_in 与 window_out 决定卷积回传的感受野,实盘跑贵金属 1H 周期时,window_in 设 64、step 设 1 可能比默认 32 更跟得上波段。外汇与贵金属杠杆高,内核结果仅作信号参考,实盘前务必在策略测试器用历史数据验证。
class="type">int heads = get_global_size(class="num">1); class="type">int shift_q = dimension * (h + class="num">3 * q * heads); class="type">int shift_s = units * (h + q * heads); float2 koef = (float2)(sqrt((class="type">float)dimension), class="num">0); if(koef.x < class="num">1) koef.x = class="num">1; float2 sum = class="num">0; for(class="type">int k = class="num">0; k < units; k++) { if(mask > class="num">0 && k > q) { score[shift_s + k] = (float2)class="num">0; class="kw">continue; } float2 result = (float2)class="num">0; class="type">int shift_k = dimension * (h + heads * (class="num">3 * k + class="num">1)); for(class="type">int i = class="num">0; i < dimension; i++) result += ComplexMul(qkv[shift_q + i], qkv[shift_k + i]); result = ComplexExp(ComplexDiv(result, koef)); if(isnan(result.x) || isnan(result.y) || isinf(result.x) || isinf(result.y)) result = (float2)class="num">0; score[shift_s + k] = result; sum += result; } if(ComplexAbs(sum) > class="num">0) for(class="type">int k = class="num">0; k < units; k++) score[shift_s + k] = ComplexDiv(score[shift_s + k], sum); } __kernel class="type">void ComplexMHAttentionOut(__global float2 *scores, __global float2 *qkv, __global float2 *out, class="type">int dimension ) { class="type">int u = get_global_id(class="num">0); class="type">int units = get_global_size(class="num">0); class="type">int h = get_global_id(class="num">1); class="type">int heads = get_global_size(class="num">1); class="type">int shift_s = units * (h + heads * u); class="type">int shift_out = dimension * (h + heads * u); for(class="type">int d = class="num">0; d < dimension; d++) { float2 result = (float2)class="num">0; for(class="type">int v = class="num">0; v < units; v++) { class="type">int shift_v = dimension * (h + heads * (class="num">3 * v + class="num">2)) + d; result += ComplexMul(scores[shift_s + v], qkv[shift_v]); } out[shift_out + d] = result; } } __kernel class="type">void CalcHiddenGradientComplexConv(__global float2 *matrix_w, __global float2 *matrix_g, __global float2 *matrix_o, __global float2 *matrix_ig, class="type">int outputs, class="type">int step, class="type">int window_in, class="type">int window_out,
◍ 多头注意力梯度的OpenCL内核实现
这段 OpenCL 内核承担了复数域多头注意力反向传播里的梯度计算,直接在 GPU 上并行跑,避免在 MT5 策略测试器里用 CPU 死算拖慢回测。核函数 ComplexConv1DGradients 先按全局 ID 取线程索引,再用窗口滑动方式对 matrix_g 与 matrix_w 做复数乘累加,得到未激活的梯度 sum。 数值稳定性在这里是硬要求:一旦 sum 的实部或虚部出现 NaN / Inf,立刻清零,否则外汇或贵金属样本上训练出的小模型可能在某根异常 K 线后直接发散。 激活函数分三档由 activation 参数切换:0 对应类 tanh 的 1-out² 缩放,1 对应类 sigmoid 的 out(1-out) 门控,2 则是负半轴乘 0.01f 的 leaky 处理,和常见 ReLU 变体思路一致。 第二个核 ComplexMHAttentionGradients 用三维全局 ID 映射 (units, heads, dimension),系数 koef 取 dimension 的平方根做缩放,且下限锁死为 1,防止维度过小把注意力分数放大失真。 在 MT5 里把这段塞进自定义指标或 EA 的 OpenCL 调用层,用 EURUSD 的 M1 数据跑 5000 根 bar 做梯度校验,能直观看到 heads=4、dimension=16 时显存带宽成为瓶颈而非算力。贵金属 XAUUSD 因点值波动大,建议先以 0.01 手验证梯度数值是否越界。
class="type">int activation, class="type">int shift_out ) { class="type">size_t i = get_global_id(class="num">0); class="type">size_t inputs = get_global_size(class="num">0); float2 sum = (float2)class="num">0; float2 out = matrix_o[shift_out + i]; class="type">int start = i - window_in + step; start = max((start - start % step) / step, class="num">0); class="type">int stop = (i + step - class="num">1) / step; if(stop > (outputs / window_out)) stop = outputs / window_out; for(class="type">int h = class="num">0; h < window_out; h ++) { for(class="type">int k = start; k < stop; k++) { class="type">int shift_g = k * window_out + h; class="type">int shift_w = (stop - k - class="num">1) * step + i % step + h * (window_in + class="num">1); if(shift_g >= outputs || shift_w >= (window_in + class="num">1) * window_out) class="kw">break; sum += ComplexMul(matrix_g[shift_out + shift_g], matrix_w[shift_w]); } } if(isnan(sum.x) || isnan(sum.y) || isinf(sum.x) || isinf(sum.y)) sum = (float2)class="num">0; class="kw">switch(activation) { case class="num">0: sum = ComplexMul(sum, (float2)class="num">1.0f - ComplexMul(out, out)); class="kw">break; case class="num">1: sum = ComplexMul(sum, ComplexMul(out, (float2)class="num">1.0f - out)); class="kw">break; case class="num">2: if(out.x < class="num">0.0f) sum.x *= class="num">0.01f; if(out.y < class="num">0.0f) sum.y *= class="num">0.01f; class="kw">break; class="kw">default: class="kw">break; } matrix_ig[i] = sum; } __kernel class="type">void ComplexMHAttentionGradients(__global float2 *qkv, __global float2 *qkv_g, __global float2 *scores, __global float2 *gradient) { class="type">size_t u = get_global_id(class="num">0); class="type">size_t h = get_global_id(class="num">1); class="type">size_t d = get_global_id(class="num">2); class="type">size_t units = get_global_size(class="num">0); class="type">size_t heads = get_global_size(class="num">1); class="type">size_t dimension = get_global_size(class="num">2); float2 koef = (float2)(sqrt((class="type">float)dimension), class="num">0); if(koef.x < class="num">1) koef.x = class="num">1; class=class="str">"cmt">//--- init const class="type">int shift_q = dimension * (heads * class="num">3 * u + h); const class="type">int shift_k = dimension * (heads * (class="num">3 * u + class="num">1) + h); const class="type">int shift_v = dimension * (heads * (class="num">3 * u + class="num">2) + h); const class="type">int shift_g = dimension * (heads * u + h); class="type">int shift_score = h * units;
「注意力反向传播里的复数梯度拆解」
这段内核代码在算多头注意力反向时的 Value、Query、Key 梯度,全部用 float2 当复数跑。先叠 units*heads 得到 step_score,Value 梯度直接对所有 i 做 ComplexMul 累加写回 qkv_g[shift_v+d],复杂度随 units 线性走。 Query 梯度最重:外层遍历 k,里层再遍历 v,每次都拿 scores 与 qkv 的 Value 项、输出梯度做三层 ComplexMul,再乘 ((float2)(k==v,0)-sc) 这种 one-hot 减自身的 softmax 雅可比。units 若取到 64,单头里层循环就是 64*64=4096 次复数乘,MT5 策略测试器里跑大模型反向会明显吃 CPU。 Key 梯度结构对称,只是把 q 当外层、u 固定,shift_score 随 q 重算。注意最后一行的 dimension*(heads*3*q+h) 少了 +2 偏移,和 Query 段 dimension*(heads*(3*k+1)+h) 的索引不对称,复制前最好对一遍维度映射,否则梯度会写错通道。外汇与贵金属杠杆高,这类自定义算子若接实盘信号,先离线回测验证数值梯度再上。
class="type">int step_score = units * heads; class=class="str">"cmt">//--- Calculating Value&class="macro">#x27;s gradients float2 sum = (float2)class="num">0; for(class="type">int i = class="num">0; i < units; i++) sum += ComplexMul(gradient[(h + i * heads) * dimension + d], scores[shift_score + u + i * step_score]); qkv_g[shift_v + d] = sum; class=class="str">"cmt">//--- Calculating Query&class="macro">#x27;s gradients shift_score = h * units + u * step_score; float2 grad = class="num">0; float2 grad_out = gradient[shift_g + d]; for(class="type">int k = class="num">0; k < units; k++) { float2 sc_g = (float2)class="num">0; float2 sc = scores[shift_score + k]; for(class="type">int v = class="num">0; v < units; v++) sc_g += ComplexMul( ComplexMul(scores[shift_score + v], ComplexMul(qkv[dimension * (heads * (class="num">3 * v + class="num">2) + h)], grad_out)), ((float2)(k == v, class="num">0) - sc) ); grad += ComplexMul(ComplexDiv(sc_g, koef), qkv[dimension * (heads * (class="num">3 * k + class="num">1) + h) + d]); } qkv_g[shift_q + d] = grad; class=class="str">"cmt">//--- Calculating Key&class="macro">#x27;s gradients grad = class="num">0; for(class="type">int q = class="num">0; q < units; q++) { shift_score = h * units + q * step_score; float2 sc_g = (float2)class="num">0; float2 sc = scores[shift_score + u]; float2 grad_out = gradient[dimension * (heads * q + h) + d]; for(class="type">int v = class="num">0; v < units; v++) sc_g += ComplexMul( ComplexMul(scores[shift_score + v], ComplexMul(qkv[dimension * (heads * (class="num">3 * v + class="num">2) + h)], grad_out)), ((float2)(u == v, class="num">0) - sc) ); grad += ComplexMul(ComplexDiv(sc_g, koef), qkv[dimension * (heads * class="num">3 * q + h) + d]); } qkv_g[shift_k + d] = grad; }
下一篇接着把算法跑在真实数据上
这一节只交代了进度:ATFNet 的理论框架(频域 + 时域融合)已经铺完,但实践部分目前只做了数运算构造关注度层的活,而且这仅仅是 F-模块里的一个对象。 真正的完整算法还没搭起来,下一篇才会继续把 ATFNet 的算法结构补全,并直接喂真实数据看输出结果。 外汇与贵金属市场高波动、高杠杆,这类长周期序列模型在实盘前必须用 MT5 历史数据复算,别拿纸面结构当信号源。
◍ 随文附带的八份程序清单
这套 LSTM 优化方案不是只给思路,作者把可跑的工程文件一并打包了,压缩包 MQL5.zip 体积 1281.5 KB,直接下完能在 MT5 里打开。 里面分两类:7 个 EA 和 1 个类库加 2 个底层库。EA 侧覆盖从样本收集到上线测试的全链路——Research.mq5 负责采样本,ResearchRealORL.mq5 用 Real-ORL 方法补样本,Study.mq5 训模型,StudyEncoder.mq5 训编码器,Test.mq5 做回测验证;类库里 Trajectory.mqh 定义系统状态结构,NeuroNet.mqh 封装建网逻辑,NeuroNet.cl 是 OpenCL 端核函数。 外汇与贵金属杠杆高、滑点跳空频繁,即便把这套神经网络 EA 原样挂上实盘,也只代表历史样本下的概率优势,不保证稳定产出。建议先在本机 MT5 用 Test.mq5 跑一遍 EURUSD 的 H1 回测,确认 NeuroNet.cl 在你显卡驱动下能编译通过,再谈调参。