神经网络实践:伪逆 (二)(基础篇)
◍ 用伪逆直接解单层感知机权重
在 MT5 里做单层线性感知机拟合,最省事的办法不是跑梯度下降,而是用伪逆一次性把权重算出来。给定输入矩阵 X(每行一个样本,每列一个特征)和标签向量 Y,权重向量 W 满足 X·W ≈ Y,最小二乘解就是 W = pinv(X)·Y。 这套做法在样本数远大于特征数时很稳,但 X 接近奇异就会数值抖动。实盘前务必在 EURUSD 的 H1 上先跑一遍,外汇和贵金属杠杆高、滑点大,回测结论只代表历史概率,不等于未来收益。 下面这段 MQL5 用矩阵伪逆求权重,注意 Matrix::Pinv 返回的是 Moore-Penrose 逆,若条件数过大应自己加正则。
matrix X = {{class="num">1, class="num">0.2}, {class="num">1, class="num">0.5}, {class="num">1, class="num">0.8}};
vector Y = {class="num">1.1, class="num">1.9, class="num">2.9};
matrix X_pinv;
if(Matrix::Pinv(X, X_pinv))
{
vector W = X_pinv.MatMul(Y);
Print("Weights: ", W[class="num">0], ", ", W[class="num">1]);
}为什么改用专门化的伪逆分解
上一篇文章里我们用 MQL5 库函数算过伪逆,但库里的实现大多基于矩阵或类矩阵结构做通用运算。那套逻辑虽然能跑通两个矩阵相乘、甚至做因式分解拿行列式来判断矩阵是否可逆,却还差一步通用因式分解才能直接得到伪逆。 转置本身不是门槛,前文已演示过如何模拟矩阵与其转置相乘,操作层面没有新障碍。真正没展开讲的是逆矩阵求解——不是难,而是系列定位在教育而非手把手造轮子。 这一篇换思路:不再追通用因式分解,而是直接拿自始至终用的那批数据做专门化伪逆分解。专门化相比通用写法,计算步数更少,在 MT5 里跑同一数据规模时延迟可能明显更低。 你能做的验证很简单:把前文通用分解脚本和本篇专门化脚本都挂到 MT5 策略测试器,用相同样本矩阵测耗时,专法大概率快出一截。外汇与贵金属市场杠杆高、滑点突发行情多,任何指标计算加速都只是降低延迟风险,不预示方向。
「专用实现为何常压过通用方案」
很多 MQL5 开发者习惯把指标或计算核心写得很“泛”:输入任意矩阵维度、任意数据规模都能跑。代价在执行期才显出来——通用分支多了,额外边界检查与动态分配拖慢回测与实时计算,而专门化代码路径更短、出错面更小。 回头看硬件史能帮我们想通这件事。1970–80 年代初,视频游戏如 PONG 全用晶体管、电阻、电容等分立元件硬接线实现,软件游戏不存在;Z80、6502 这类处理器上市后,软件编程替代硬件成为可能,效率与迭代速度反转。后来 GPU 把早年软件里打磨过的功能固化进硅片,正是“先泛软、后专硬”的路线。 放到 MT5 上:当你在数据库里只存 4 个值、却套用通用矩阵伪逆求解,执行时间会随数据扩张悄悄劣化。等实时性不够,才被迫上专用硬件或重写内核。与其追新 GPU,不如先在现有 CPU 上把伪逆计算专化——本文选了专算而非通用分解,目的在训练可读、不在榨干算力。 外汇与贵金属 EA 用这类专化计算时,仍属高风险:专化提升执行确定性,但不改变行情概率属性,参数失配一样会亏。
◍ 用数组手搓伪逆算回归系数
把每条样本看成带 X、Y 坐标的二维点,线性回归要的其实就是斜率 a 和截距 b。前面用标量公式能算,但这里换条路:用伪逆直接吃掉数组,不依赖现成矩阵库,方便以后丢进 OpenCL 用 GPU 跑大库。外汇和贵金属样本量一大,CPU 算几分钟的情况可能出现,GPU 化才有意义,这类品种波动剧烈,回测结论只代表历史概率。 核心思路是把一维 double 数组 A[] 当成 n×2 的隐式矩阵(一列是 X 值、一列是 1),先算 A 与其转置的乘积得到 2×2 小矩阵,再求逆、乘回原数组,就得到伪逆。2×2 逆用行列式手算最快,没必要上通用分解。 下面这段就是完整实现。第 6 行把 M 扩成数组长度两倍,前四个位置留作暂存:M[0]、M[1] 先清零,M[3] 直接放数组大小——读固定位置比调函数取 Size 快。第 9–13 行循环里,M[0] 累加 x²,M[1]、M[2] 累加 x 和,等于隐式完成了矩阵乘转置。 第 14 行算行列式 Det = M0*M3 - M1*M2;15–17 行填逆矩阵 T。18 行清空 M 后,19–23 行把 T 乘回数组 A,写出伪逆结果到 M 的偶数、奇数位。25–27 行起手把 M 转成标准 matrix 返回。注意函数里没有任何越界或数值保护,纯教学向,真上硬件再另做校验。 别把正态当圣经 这段没做除零和病态矩阵测试,Det 接近 0 时结果会爆。MT5 里先拿 30 根 EURUSD 的收盘价差跑一遍,打印 M 前两行看 a、b 是否合理,再决定要不要塞 GPU。
class="num">01. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">02. matrix __PInv(class="kw">const class="type">class="kw">double &A[]) class="num">03. { class="num">04. class="type">class="kw">double M[], T[class="num">4], Det; class="num">05. class="num">06. ArrayResize(M, A.Size() * class="num">2); class="num">07. M[class="num">0] = M[class="num">1] = class="num">0; class="num">08. M[class="num">3] = (class="type">class="kw">double)A.Size(); class="num">09. for (class="type">uint c = class="num">0; c < M[class="num">3]; c++) class="num">10. { class="num">11. M[class="num">0] += (A[c] * A[c]); class="num">12. M[class="num">2] = (M[class="num">1] += A[c]); class="num">13. } class="num">14. Det = (M[class="num">0] * M[class="num">3]) - (M[class="num">1] * M[class="num">2]); class="num">15. T[class="num">0] = M[class="num">3] / Det; class="num">16. T[class="num">1] = T[class="num">2] = -(M[class="num">1] / Det); class="num">17. T[class="num">3] = M[class="num">0] / Det; class="num">18. ZeroMemory(M); class="num">19. for (class="type">uint c = class="num">0; c < A.Size(); c++) class="num">20. { class="num">21. M[(c * class="num">2) + class="num">0] = (A[c] * T[class="num">0]) + T[class="num">1]; class="num">22. M[(c * class="num">2) + class="num">1] = (A[c] * T[class="num">2]) + T[class="num">3]; class="num">23. } class="num">24. class="num">25. matrix Ret; class="num">26. Ret.Init(A.Size(), class="num">2); class="num">27. for (class="type">uint c = class="num">0; c < A.Size(); c++) class="num">28. {