神经网络实践:伪逆(I)·进阶篇
📘

神经网络实践:伪逆(I)·进阶篇

第 2/2 篇

◍ 矩阵乘法与行列式的内核写法

在 MT5 里手写线性代数工具时,矩阵 A 乘矩阵 B 的通用函数先把响应数组 R 的行数锁定为 B_Row,并通过 ArrayResize 按 A_Line * B_Row 铺开内存。若传入的 R 不是动态数组,代码会直接 Print 报错并用 1/MathAbs(0) 主动触发除零崩溃,逼调用方修参数。 [CODE]void Matrix_A_x_B(const double &A[], const uint A_Row, const double &B[], const uint B_Row, double &R[], uint &R_Row) { uint A_Line = (uint)(A.Size() / A_Row); if (!ArrayIsDynamic(R)) { Print("Response array must be of the dynamic type..."); B_Row = (uint)(1 / MathAbs(0)); } ArrayResize(R, A_Line * (R_Row = B_Row)); ZeroMemory(R); for (uint cp = 0, Ai = 0, Bi = 0; cp < R.Size(); cp++, Bi = ((++Bi) == B_Row ? 0 : Bi), Ai += (Bi == 0 ? A_Row : 0)) for (uint c = 0; c < A_Row; c++) R[cp] += (A[Ai + c] * B[Bi + (c * B_Row)]); } [/CODE] 上面这段逐行看:第 4 行用 A.Size()/A_Row 反推 A 的行数;第 9 行 ArrayResize 把 R 拉成 A_Line 乘 B_Row 大小;第 11~14 行用双重循环做点积累加,Bi 在走到 B_Row 时归零并推进 Ai 一行,等价于按列优先读 B。 A 乘自身转置的函数则更省事:R_Row 直接等于 BL(即 A 的列数),ArrayResize 用 MathPow(BL,2) 开平方矩阵,内层循环 R[cp] += A[c+Ai]*A[c+Bi*A_Row] 就是标准 A·Aᵀ 元素。若 A 是 3 行 4 列,转置乘积结果固定是 4×4 共 16 个元素。 行列式函数开头用宏 def_Diagonal 做对角线展开,说明后续走的是按行/列余子式递归或 LU 思路;这类底层例程跑通后,你在 EA 里做协方差矩阵或加权回归会快很多,但外汇与贵金属杠杆高,模型矩阵稳不稳和爆仓距离只差几个 tick,先用历史数据验证数值稳定性再上实盘。

MQL5 / C++
class="type">void Matrix_A_x_B(const class="type">class="kw">double &A[], const class="type">uint A_Row, const class="type">class="kw">double &B[], const class="type">uint B_Row, class="type">class="kw">double &R[], class="type">uint &R_Row)
{
   class="type">uint A_Line = (class="type">uint)(A.Size() / A_Row);
   if (!ArrayIsDynamic(R))
   {
      Print("Response array must be of the dynamic type...");
      B_Row = (class="type">uint)(class="num">1 / MathAbs(class="num">0));
   }
   ArrayResize(R, A_Line * (R_Row = B_Row));
   ZeroMemory(R);
   for (class="type">uint cp = class="num">0, Ai = class="num">0, Bi = class="num">0; cp < R.Size(); cp++, Bi = ((++Bi) == B_Row ? class="num">0 : Bi), Ai += (Bi == class="num">0 ? A_Row : class="num">0))
      for (class="type">uint c = class="num">0; c < A_Row; c++)
         R[cp] += (A[Ai + c] * B[Bi + (c * B_Row)]);
}

class="type">void Matrix_A_x_Transposed(const class="type">class="kw">double &A[], const class="type">uint A_Row, class="type">class="kw">double &R[], class="type">uint &R_Row)
{
   class="type">uint BL = (class="type">uint)(A.Size() / A_Row);
   if (!ArrayIsDynamic(R))
   {
      Print("Response array must be of the dynamic type...");
      BL = (class="type">uint)(class="num">1 / MathAbs(class="num">0));
   }
   ArrayResize(R, (class="type">uint) MathPow(R_Row = (class="type">uint)(A.Size() / A_Row), class="num">2));
   ZeroMemory(R);
   for (class="type">uint cp = class="num">0, Ai = class="num">0, Bi = class="num">0; cp < R.Size(); cp++, Bi = ((++Bi) == BL ? class="num">0 : Bi), Ai += (Bi == class="num">0 ? A_Row : class="num">0))
      for (class="type">uint c = class="num">0; c < A_Row; c++)
         R[cp] += (A[c + Ai] * A[c + (Bi * A_Row)]);
}

class="type">class="kw">double Determinant(const class="type">class="kw">double &A[])
{
class="macro">#define def_Diagonal(a, b)

「行列式递归里的行指针跳跃」

这段逻辑在算一个方阵 A 的行列式展开项,核心不是嵌套 for 本身,而是 cc 这个列索引怎么在内存里跳。 外层循环用 a 决定方向:a 为 0 时 cp 从 0 往上走到 A_Row,a 非 0 时 cp 从 a 往下减到 0,每轮把 Tmp 重置为 1、ct 归零。 内层 do-while 里 ct 从 0 跑到 A_Row,cc 每次加 b(步长),若 cc/A_Row 不等于当前 ct 就 break,否则把 A[cc] 累乘进 Tmp——这一步就是在按展开系数取对应元素。 注意第 11 行:每跑完一行 ct,cc 会整体平移一个 A_Row(方向由 a 定),相当于在二维数组里换行取对角带。外汇与贵金属行情用这类矩阵做协方差滤波时,杠杆敞口方向误设会让 a 取反,行列式符号跟着反,回测曲线可能假性翻转,属高风险操作。 开 MT5 把 A_Row 设成 3、b 设成 1,单步看 cc 从 0→1→2 再跳到 3 的节奏,比读十遍注释直观。

MQL5 / C++
Tmp = class="num">1;
for (class="type">uint cp = a, cc = (a == class="num">0 ? class="num">0 : cp - class="num">1), ct = class="num">0; (a ? cp > class="num">0 : cp < A_Row); cc = (a ? (--cp) - class="num">1 : ++cp), ct = class="num">0, Tmp = class="num">1)
 {
  do {
   for (; (ct < A_Row); cc += b, ct++)
    if ((cc / A_Row) != ct) class="kw">break; else Tmp *= A[cc];
   cc = (a ? cc + A_Row : cc - A_Row);

行列式求解里的方阵校验与递归收口

这段逻辑是通用矩阵行列式函数的收尾部分,核心在方阵判定与低阶快算。A.Size() 拿到元素总数后,用 MathSqrt 开根号再 Floor,得到 A_Row;若 Floor 值与 Ceil 值不等,说明元素数不是完全平方数,矩阵非方阵,直接 Print 报错并故意触发除零崩溃以中止运行。 当 A_Row 等于 2 时走捷径:返回 A[0]*A[3] - A[1]*A[2],即二阶行列式主对角减副对角,避免无谓递归。A_Size 为 1 时直接返回唯一元素 A[0],这是一阶矩阵的定义。 正常 n 阶(n>2)流程里,先调用 def_Diagonal(0, A_Row+1) 与 def_Diagonal(A_Row, A_Row-1) 分别沿两条对角带累加到 Result,最后 return Result。宏 def_Diagonal 在 #undef 后失效,避免污染其他函数作用域。 在 MT5 里把这段贴进自定义指标或脚本,喂一个 3x3 的 double 数组(9 个元素),若日志没报 'The matrix needs to be square',说明方阵校验通过,Result 就是该矩阵行列式值。外汇与贵金属行情矩阵化建模属高风险尝试,参数失配可能导致信号反转概率偏高。

MQL5 / C++
   }while (ct < A_Row);
      Result +=  (Tmp * (a ? -class="num">1 : class="num">1));
     }
                                                                         }

   class="type">uint A_Row, A_Size = A.Size();
   class="type">class="kw">double Result, Tmp;

   if (A_Size == class="num">1)
      class="kw">return A[class="num">0];
   Tmp = MathSqrt(A_Size);
   A_Row = (class="type">uint)MathFloor(Tmp);
   if ((A_Row != (class="type">uint)MathCeil(Tmp)) || (!A_Size))
   {
      Print("The matrix needs to be square");
      A_Row = (class="type">uint)(class="num">1 / MathAbs(class="num">0));
   }
   if (A_Row == class="num">2)
      class="kw">return (A[class="num">0] * A[class="num">3]) - (A[class="num">1] * A[class="num">2]);
   Result = class="num">0;

   def_Diagonal(class="num">0, A_Row + class="num">1);
   def_Diagonal(A_Row, A_Row - class="num">1);

   class="kw">return Result;

 class="macro">#undef def_Diagonal
 }
 class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 伪逆还差一步

这一节原本是作者收尾闲笔,但透露了一个关键技术断点:当前文章给出的伪逆(PInv)计算代码,并不支持自由使用结构体,只能在特定数据建模下跑通。 要在 MT5 里真正把伪逆用起来,下一步必须引入矩阵类型,把数据容器从裸数组升级成可泛化的结构。作者确认会在下一篇用独立应用程序补全这部分,届时能对任意数据建模做必要改动后直接调用。 外汇与贵金属市场高波动,任何矩阵运算策略回测通过不代表实盘盈利,上机前请用 Anexo_01.mq5(0.42 KB)先验证本文附录的限制边界。

常见问题

先单独打印两个输入矩阵和乘积矩阵的维度,再用小尺寸矩阵(如2x2)对照逐元素验算,确认i/j/k三层循环顺序为C[i][j]+=A[i][k]*B[k][j]。
删除第i行第j列生成余子式时,用两个独立游标读原矩阵、写临时矩阵,跳过被删行列,避免原地移动数据导致错位。
可以把代码和测试矩阵贴给小布,它会对照行列式与矩阵乘法内核逐步指出维度校验、递归收口或乘法顺序的问题。
非方阵行列式无定义,入口判断行列数相等可提前返回错误,防止递归里访问越界或算出无意义数值。
还差伴随矩阵转置后除以行列式,并处理行列式近零的奇异情况,用广义逆截断阈值兜底。