文章 "利用 MQL5 矩阵的反向传播神经网络" - 页 2

 
Lorentzos Roussos #:

是的,但激活值是传递给派生函数的,而派生函数期望的是预激活值。我就是这个意思。

我认为你错了。推导是根据我上面给出的公式计算的:y'(x) = y(x)*(1-y(x)),其中 x 是激活前的神经元状态,y(x) 是应用激活函数后的结果。计算推导时不要使用激活前的值,而要使用激活结果 (y)。下面是一个简化的测试:

double derivative(double output)
{
  return output * (1 - output);
}

void OnStart()
{
  vector x = {{0.68}};
  vector y;
  x.Activation(y, AF_SIGMOID);                            // 在 y[0] 中以 "y(x) "形式得到激活/半球结果
  vector d;
  x.Derivative(d, AF_SIGMOID);                            // 在 x 处得到了 sigmoid 的导数
  Print(derivative(x[0]), " ", derivative(y[0]), " ", d); //0.2176 0.2231896389723258 [0.2231896389723258]
}
 
Stanislav Korotky #:

我想你错了。推导是根据我上面给出的公式计算的:y'(x) = y(x)*(1-y(x)) ,其中 x 是激活前的神经元状态,y(x) 是应用激活函数后的结果。计算推导时不要使用激活前的值,而要使用激活结果 (y)。下面是一个简化的测试:

是的,这就是我要说的,正确的导数与根据 x 值调用的导数相匹配。

在后向函数中,你调用的是等价的 y.Derivative(d,AF_SIGMOID)

在文章中的 backprop 中,输出矩阵是 y,我不认为你是在矩阵中存储 x 的等效值,并以此调用导数。

(同样,根据 mq 函数)

--

即使在你的例子中,你也是调用 x 的导数,我打赌你一开始输入的是 y,然后 "哎呀 "了一声。

在俄罗斯论坛上告诉他们吧。如果他们能在文档中加入这一点,将会节省很多人的时间。

谢谢

 
Stanislav Korotky #:

我想你错了。推导是根据我上面给出的公式计算的:y'(x) = y(x)*(1-y(x)) ,其中 x 是激活前的神经元状态,y(x) 是应用激活函数后的结果。计算推导时不要使用激活前的值,而要使用激活结果 (y)。下面是一个简化的测试:

让我来简化一下。

这是你的例子

double derivative(double output)
{
  return output * (1 - output);
}

void OnStart()
{
  vector x = {{0.68}};
  vector y;
  x.Activation(y, AF_SIGMOID);                            // 在 y[0] 中以 "y(x) "形式得到激活/半球结果
  vector d;
  x.Derivative(d, AF_SIGMOID);                            // 在 x 处得到了 sigmoid 的导数
  Print(derivative(x[0]), " ", derivative(y[0]), " ", d); //0.2176 0.2231896389723258 [0.2231896389723258]
}

在你的示例中,你调用x.Derivative 来填充导数向量 d。

你没有调用y.Derivative 来填充导数,为什么?因为它会返回错误的值。(你可能也看到了,所以才使用了x.Derivative)。

y 是什么?

所以当你这样做的时候 :

x.Activation(y, AF_SIGMOID);  

你用x 的激活值填充了y,但你调用的是x 的导数,而不是 y 的 导数(根据 mql5 函数,这是正确的)。

在您的文章中,在前馈过程中,tempx

matrix temp = outputs[i].MatMul(weights[i]);

那是什么矩阵?

temp.Activation(outputs[i + 1], i < n - 1 ? af : of)

输出。 在文章中,y( 您在示例中没有调用导数 输出 矩阵

(我们在上面的代码中看到的是示例中的 x.Activation(y,AF),它将激活值填充到 y 中。)

在您的辅助代码中,您没有调用x.Derivative,因为x(矩阵 temp = outputs[i].MatMul(weights[i]);)

没有存储在任何地方,因此无法调用。您调用的是y.Derivative,它返回的是错误的值

outputs[n].Derivative(temp, of)
outputs[i].Derivative(temp, af)

因为y激活值

这也是 mql5 函数的规定。

因此,在你的示例中,你使用了正确的调用,而在你的文章中,你使用了错误的调用。

谢谢

 

那么,你想要这个

   bool backProp(const matrix &target)
   {
      if(!ready) return false;
   
      if(target.Rows() != outputs[n].Rows() ||
         target.Cols() != outputs[n].Cols())
         return false;
      
      // 输出层
      matrix temp;
      //*if(!outputs[n].Derivative(temp, of))
      //* 返回 false;
      if(!outputs[n - 1].MatMul(weights[n - 1]).Derivative(temp, of))
         return false;
      matrix loss = (outputs[n] - target) * temp; // 每行数据记录
     
      for(int i = n - 1; i >= 0; --i) // 除输出外的每一层
      {
         //*// remove unusable pseudo-errors for neurons, added as constant bias source
         //*// (in all layers except for the last (where it wasn't added))
         //*if(i < n - 1) loss.Resize(loss.Rows(), loss.Cols() - 1);
         #ifdef  BATCH_PROP
         matrix delta = speed[i] * outputs[i].Transpose().MatMul(loss);
         adjustSpeed(speed[i], delta * deltas[i]);
         deltas[i] = delta;
         #else
         matrix delta = speed * outputs[i].Transpose().MatMul(loss);
         #endif
         
         //*if(!outputs[i].Derivative(temp, af))
         //* 返回 false;
         //*loss = loss.MatMul(weights[i].Transpose()) * temp.MatMul(weights[i].Transpose())
         if(i > 0) // 将损失反向传播到前几层
         {
            if(!outputs[i - 1].MatMul(weights[i - 1]).Derivative(temp, af))
               return false;
            matrix mul = loss.MatMul(weights[i].Transpose());
            // 去除神经元无法使用的伪误差,将其添加为恒定偏置源
            mul.Resize(mul.Rows(), mul.Cols() - 1);
            loss = mul * temp;
         }
         
         weights[i] -= delta;
      }
      return true;
   }

我会考虑的

 
Stanislav Korotky #:

所以,你想要这个:

我会考虑的

第一眼看上去还不错,是的。现场计算比存储更快,我想。

👍

 
Lorentzos Roussos #:

乍一看还不错,是的。我认为现场计算比存储更快。

我想我知道最初通过激活函数的输出进行编码的原因了。在我之前使用的所有 NN 库和其他一些库中,导数都是通过输出计算的,因为这样更简单有效(在适应矩阵 API 的过程中,我没有注意到两者的区别)。例如

sigmoid' = sigmoid * (1 - sigmoid)
tanh' = 1 - tanh^2
softsign' = (1 - |softsign|)^2

这样我们就不需要保留激活前的参数(矩阵),也不需要在反向传播阶段重新计算这些参数(就像在修正版中那样)。我不喜欢这两种方法。可以说,"自减 "计算看起来更优雅。因此,我更倾向于找到一些包含所有(或许多)支持的激活函数的自乘法公式的参考资料,然后回到我最初的方法。

有趣的是,并不要求自求导公式严格来自于激活函数--任何具有同等效果的函数都足够了。
 
Stanislav Korotky #:

我想我知道最初通过激活函数的输出来编码的原因了。在我之前使用的所有 NN 库和其他一些库中,导数都是通过输出计算的,因为这样更简单有效(在适应矩阵 API 的过程中,我没有注意到两者的区别)。例如

这样我们就不需要保留激活前的参数(矩阵),也不需要在反向传播阶段重新计算这些参数(就像在修正版中那样)。我不喜欢这两种方法。可以说,"自减 "计算看起来更优雅。因此,我更愿意找到一些包含所有(或许多)支持的激活函数的自乘法公式的参考资料,然后回到我最初的方法。

是的,但 mq 决定这样做,所以它适用于所有激活函数。

简单地说,他们决定让所有函数都接收激活前的值,而不是让.Derivative 函数 "适应 "激活函数(就像你提到的 3 个函数可以接收输出)。这没有问题,问题是文档中没有说明。

任何人的默认假设都是它适应自动对焦。

这对新手(比如我)很不利,因为他们还没开始就 "解决 "了问题。

(基于对象的网络和基于矩阵的网络的比较也将是一篇非常有趣的文章,对许多不精通数学的编码员会有帮助)。

总之,我把它放在了版主为报告文档问题而设置的主题中

(题外话:您可以使用 TanH,我认为它更快也更正确)。

double customTanH(double of){
  double ex=MathExp(2*of);
  return((ex-1.0)/(ex+1.0));
}
有趣的是,并不要求自求导公式严格来自激活函数--任何具有同等效果的函数都足够了。

你是说 "替代"?

比如说,一个节点的输出出现了错误,而你知道输出的 "波动",那么如果你把它 "转移 "到一个更简单的激活函数中并推导出来,它就会起作用?

因此,理论上这就像是对输出进行 "正则化",但不需要做正则化,而只是在激活的导数之前乘以正则化的导数?

例如 :

tanh output -1 -> +1 
sigmoid output 0 -> +1 
tanh to sigmoid output = (tanh_out+1)/2.0
and you just multiply by the derivative of that which is 0.5 ? (without touching the tanh outputs at all)
mql5 documentation errors, defaults or inconsistencies.
mql5 documentation errors, defaults or inconsistencies.
  • 2023.04.07
  • www.mql5.com
Page : https://www.mql5.com/en/docs/globals/globalvariabletime This is inexact, GlobalVariableCheck() does NOT modify the last access time. 2023.04...
 
错误修正
附加的文件:
MatrixNet.mqh  23 kb
 

一位管理员在版主主题中回复了这一问题。您可能会感兴趣

关于交易、自动交易系统和测试交易策略的论坛

mql5 文档错误、默认设置或不一致。

Rashid Umarov, 2023.04.18 11:36

将尽快改进。在一段时间内,您可以使用此包含文件作为参考。

@Stanislav Korotky
 
Stanislav Korotky # : 错误修正

@Stanislav Korotky

非常感谢你将神经网络概念与 MQL 结合起来。对于像我这样的初学者来说,这确实是一个很好的入门工具。感谢 :)

感谢您更新文件并修复了错误。 不过,我建议更换下载区的文件。

幸运的是,我浏览了关于该主题的讨论,发现文件中存在一个错误。现在我试图寻找修复方法,却在这里找到了这个文件链接。

我希望只有在第 490-493 行、第 500 行、第 515-527 行这些地方有错误修复,我可以用 //* 标记。如果还有其他地方,请注明行号或标记 //*BugFix ...

致敬