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

 

新文章 利用 MQL5 矩阵的反向传播神经网络已发布:

本文讲述在 MQL5 中利用矩阵来应用反向传播算法的理论和实践。 它还提供了现成的类,以及脚本、指标和智能交易系统的示例。

正如我们将在下面看到的,MQL5 提供了大量的内置激活函数。 函数的选择应基于特定问题(回归、分类)。 通常,可以选择几个函数,然后经由验正找到最优的一个。

流行的激活函数

流行的激活函数

激活函数可以具有不同的数值范围、有限或无限。 特别是,sigmoid(3) 将数据映射到范围 [0,+1],这对于分类问题更好;而双曲正切将数据映射到范围 [-1,+1],假设范围,推测这对于回归和预测问题更佳。

作者:Stanislav Korotky

 
感谢作者详尽的文章和诚实的结论。
前一位神经网络学家在他的 35 篇文章中贴出了圣杯图片,但实际上是一枚硬币的翻转。

我读到过一篇文章,说在西方,有人基于强化学习,成功创建了一个持续盈利的神经网络器。
 

再次感谢 Stanislav!终于有一篇关于使用本机矩阵的神经网络的文章被写出来了....。

顺便说一下。就在今天,我发现文档中有一个关于 ML 的新模块

Документация по MQL5: ONNX модели
Документация по MQL5: ONNX модели
  • www.mql5.com
ONNX модели - Справочник MQL5 - Справочник по языку алгоритмического/автоматического трейдинга для MetaTrader 5
 
Ivan Butko #:
感谢作者详尽的文章和诚实的结论。
前一位神经网络学家在他的 35 篇文章中贴出了圣杯图片,但实际上是在抛硬币。

"那些我忘了的东西,你还不知道"--作者就是这样回应这种批评的。

 
Rashid Umarov #:

"那些我已经忘记的事情,你甚至还不知道",作者可能会这样回应这样的批评。

有两个方面很重要:

1.执行,技术部分,普遍性,即使是成熟的程序员也会称赞。在这里,我确实不能、不会也没有试图批评代码。我说的是其他方面:

2.测试。作者指出,他的所有模型都能正常工作。事实上,当您按下测试器中的 "Start"(开始)按钮时,会随机出现一张未来 2 周的图片,在 10 次按下中有 5 次--梅花,5 次--盈利。当然,如果你把这样的东西放在真实的 - 你的车厂 50/50 的可行性。事实并非如此。在我询问这是什么原因以及如何解决时,作者说应该是这样的,这是模型的原理。
两个月后,我试图启动这些网络,但它们无法在 NVIDIA 上运行,多亏Aleksey Vyazmikin 重写了作者的代码,一切才得以在 3080 上运行。最后 - 无法运行的模型。这让人倍感不快。

因此,诉诸权威是不恰当的,我并没有批评他的专业精神。

但如果我冒犯/冒犯了您,我向您道歉,我并没有这样的目的。
 
matrix temp;
if(!outputs[n].Derivative(temp, of))

在反向传播中,导数函数期望接收x,而不是x 的 激活(除非他们最近对其适用的激活函数进行了更改)。

下面是一个例子:

#property version   "1.00"

int OnInit()
  {
//---
  double values[];
  matrix vValues;
  int total=20;
  double start=-1.0;
  double step=0.1;
  ArrayResize(values,total,0);
  vValues.Init(20,1);
  for(int i=0;i<total;i++){
  values[i]=sigmoid(start);
  vValues[i][0]=start;
  start+=step;
  }
  matrix activations;
  vValues.Activation(activations,AF_SIGMOID);
  //打印 sigmoid
  for(int i=0;i<total;i++){
  Print("sigmoidDV("+values[i]+")sigmoidVV("+activations[i][0]+")");
  }
  //衍生产品
  matrix derivatives;
  activations.Derivative(derivatives,AF_SIGMOID);
  for(int i=0;i<total;i++){
  values[i]=sigmoid_derivative(values[i]);
  Print("dDV("+values[i]+")dVV("+derivatives[i][0]+")");
  }
//---
   return(INIT_SUCCEEDED);
  }

double sigmoid(double of){
return((1.0/(1.0+MathExp((-1.0*of)))));
}

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

还有一些激活函数可以在激活和求导过程中输入更多信息,例如 elu

Derivative(output,AF_ELU,alpha);
Activation(output,AF_ELU,alpha);
 
Lorentzos Roussos #:

在反向传播过程中,导数函数希望接收x,而不是x 的 激活(除非最近对其适用的激活函数进行了修改)。

我不明白你的意思。文章中有一些公式,它们被准确地转换成了源代码行。输出是在前馈阶段通过激活调用获得的,然后我们在反向传播过程中求导。你可能没注意到,类中输出数组的索引与层权重的索引 是+1偏置的。

 
Stanislav Korotky #:

我不太明白你的意思。文章中有一些公式,它们被准确地转换成了源代码的行数。输出是在前馈阶段通过激活调用获得的,然后我们在反向传播过程中取其导数。你可能没注意到,类中输出数组的索引与层权重的索引 是+1偏置的。

是的,临时 矩阵乘以权重,然后输出[]包含激活值。

在后面的提议中,您需要这些激活值 的导数,而矩阵向量导数函数希望您发送临时

以下是导数的区别

 

请允许我简化一下......:

#property version   "1.00"

int OnInit()
  {
//---
  //假设 x 是上一层的输出 (*) 节点的权重 
  // the value that goes in activation . 
    double x=3;
  //我们根据下面的公式得到 sigmoid 
    double activation_of_x=sigmoid(x);
  //对于导数,我们的做法是 
    double derivative_of_activation_of_x=sigmoid_derivative(activation_of_x);
  //我们用矩阵向量来实现这一点
    vector vX;
    vX.Init(1);
    vX[0]=3;
    //我们为激活创建一个向量
    vector vActivation_of_x;
    vX.Activation(vActivation_of_x,AF_SIGMOID);
    //我们为派生创建一个向量
    vector vDerivative_of_activation_of_x,vDerivative_of_x;
    vActivation_of_x.Derivative(vDerivative_of_activation_of_x,AF_SIGMOID);
    vX.Derivative(vDerivative_of_x,AF_SIGMOID);
    
    Print("NormalActivation("+activation_of_x+")");
    Print("vector Activation("+vActivation_of_x[0]+")");
    Print("NormalDerivative("+derivative_of_activation_of_x+")");
    Print("vector Derivative Of Activation Of X ("+vDerivative_of_activation_of_x[0]+")");
    Print("vector Derivative Of X ("+vDerivative_of_x[0]+")");
    //你正在对 x 的激活进行矢量导数运算,这会返回错误的值
    //向量矩阵希望您发送的是 x 而不是激活 (x)
//---
   return(INIT_SUCCEEDED);
  }

double sigmoid(double of){
return((1.0/(1.0+MathExp((-1.0*of)))));
}

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


 
Lorentzos Roussos #:

请允许我简化一下......:

我明白你的意思了。事实上,sigmoid 导数是通过 "y "来表示的,即通过x 点的 sigmoid 值,也就是 y(x):y'(x) = y(x)*(1-y(x)) 。文章中的代码正是这样实现的。

您的测试脚本计算 "导数 "时输入的是 x,而不是 y,因此数值不同。

 
Stanislav Korotky #:

我明白你的意思了。事实上,sigmoid 导数是通过 "y "来表示的,即通过x 点的 sigmoid 值,也就是 y(x):y'(x) = y(x)*(1-y(x)) 。文章中的代码正是这样实现的。

您的测试脚本计算 "导数 "时输入的是 x,而不是 y,因此数值不同。

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

你忽略了一点,正确的值是以 x 作为输入的值(根据 mq 函数本身,这才是正确的值)。

您没有将 output_of_previous * 权重存储在某个地方(我认为),而这正是推导函数应该发送的值(根据 mq 函数本身,我再次强调这一点)。