外汇EA编写教程:Kohonen神经网络在算法事务中的实际应用。第一部分工具

在mql5.com上有一些关于Kohonen神经网络主题的文章,例如使用自组织特征映射(Kohonen图像)和自组织特征映射(Kohonen图像)-重温MetaTrader 5中的主题。他们向读者介绍了构建此类神经网络的一般原则,并运用这些镜像直观地分析公众多金融市场。

然而,事实上,用于算法事务的Kohonen网络仅限于一种方法,即通过EA优化结果构建的拓扑图的直观分析。在这种情况下,一个人的价值判断,或者更准确地说,一个人的视野和从图片中得出合理结论的能力,可能是关键因素。表示数据的辅助网络属性,如螺栓和螺栓。

换句话说,神经网络算法的特点并没有被充分利用,也就是说,它们在使用时不会自动提取知识或支持特别推荐的决策。本文研究了以更正式的方式定义机器人最优参数集的问题。此外,我们将使用Kohonen网络预测财务范围。但是,在继续处理这些存在的问题之前,我们应该修改现有的源代码,修复一些内容,并进行一些改进。

如果您不熟悉术语“网络”、“层”、“神经元”(“节点”)、“链接”、“权重”、“学习率”、“学习范围”等与Kohonen网络相关的概念,则强烈建议您先阅读以上文章。我们应该在这个问题上充实自己,因为重新教这些基本概念会大大延长文章的发表。

修正误差

我们将调用在上面文章的前一节中发表的CSOM和CSONNoCT类,并着重于后者的补充。关键代码片段实际上是相同的,继承了相同的问题。

首先,应该注意的是,由于某种原因,上述类中的神经元已经被索引,也就是说,它们是通过使用像素坐标作为构造器参数来识别和定义的。这不太合乎逻辑,在某些方面使计算和调试变得复杂。尤其是这样,公式设置会影响计算。假设有两个完全相似的网络具有相同的网格大小。他们使用相同的数据集、相同的设置和随机数据生成器的初始化进行学习。然而,结果不同,正是因为一个网络的图像比另一个网络的图像大。这是个错误。

我们将按数字检索神经元:数组m_节点(CSOM类)中的每个神经元,其坐标x和y分别对应于Kohonen网络输出层中的列号和行号。每个神经元将由csomnode::initnode(x,y)初始化,而不是由csomnode::initnode(x1,y1,x2,y2)初始化。当我们视觉化时,我们改变图像的像素大小,神经元的坐标将保持不变。

输入数据在继承的源代码中没有进行常规化。然而,当输入向量的不同分量(特征)具有不同的数值范围时,这一点非常重要。这是EA优化的结果,也是不同指标数据汇总的情况。对于优化结果,我们可以看到总利润数十万,其值较小,如锐度比得分或恢复系数的一位数。

您不应使用这种不同规范模型的数据来教育Kohonen网络,因为网络实际上只考虑较大的数量而忽略较小的数量。您可以在下面的图片中看到这一点,这是通过使用我们已经在本文中逐步学习过的程序获得的,该程序在文章末尾附加。程序允许生成随机输入向量,其中三个分量定义在[01000]、[0,1]和[-1,+1]范围内。特殊输入使用规范化允许启用/禁用常规化。

让我们看看Kohonen网络的最终结构,它位于与向量三维相关的三个平面上。首先,在线学习的结果没有被常规化。

Kohonen 网络学习结果未经输入常规化

无输入常规化的Kohonen网络学习结果

现在,同样的例子被常规化了。

Kohonen 网络输入常规化的学习结果

Kohonen网络输入常规学习结果

神经元权重的自适应程度与颜色梯度成正比。显然,在非常规条件下,网络只学习第一平面上的拓扑划分(分类),而第二和第三分量则充满了小噪声。也就是说,网络的分析能力已经达到了三分之一。启用常规化后,空间对齐在所有三个平面中都可见。

许多传统方法是已知的,但最常用的方法可能是从每个分量中减去整个选择的平均值,然后除以标准差,即sigma或rms。这将设置转换数据的平均值为零,标准偏差为1。

(1)

更新了CSOM类,方法规范化使用了此技术。显然,您应该首先计算输入数据集每个组件的平均值和sigma值,这是在方法init规范化(见下文)中完成的。

用两种运算算法计算平均值和标准差的典型公式:先求平均值,再求西格玛值。

、NBSPSP、NBSPSP、NBSPSP、NBSPSP、NBSPSPSP和NBSPSPSP;(2)

(3)

在源代码中,我们的单次运行算法基于以下公式:

(4)

显然,入口的程序化需要在出口处进行反向操作-反向程序化,即将网络的输出值转换为实际值的范围。这是通过CSOM::Denormalize。

由于归一化值在接近零的情况下对称地减小,所以在开始教育网络之前,我们将改变神经元权值的初始化原理-替换范围[0,1],现在范围[-1,+1 ](参见方法CSONNOT::InitNode)。这将提高在线学习的效率。

另一个有待修改的领域是学习迭代的计数。在源类中,迭代应该被理解为为为每个网络指定一个单独的输入向量。因此,应该根据教育选择的规模来修改迭代次数。回想一下,Kohonen的网络学习和信息融合原理假设每个样本多次分配给网络。例如,如果选择中有100个条目,那么如果迭代次数等于10000,则必须指定平均100个条目。但是,如果选择生成1000个条目,则迭代次数必须更改为100000。一种更为方便和传统的方法是定义所谓的“学习周期”的数量,其中所有样本随机输入网络的每个周期。次数将在参数epochnumber中设置。通过引入它,学习持续时间与数据集的参数大小相分离。

这一点尤为重要,因为集合输入集可以分为两部分:教育选择和所谓的验证选择。后者用于跟踪在线学习的质量。问题是,在教育过程中,网络适应的输入有一个“翻转”:网络开始适应特定样本的特性,从而失去了对未知数据(教育除外)进行归纳和充分处理的能力。归根结底,学习的理念通常是为了能够在未来使用网络检测功能。

在正在研究的程序中,输入参数validation setpercent负责启用验证。默认为0,所有数据用于学习。如果我们指定10,那么只有90%的样本用于学习,其余10%的样本在每次迭代(周期)时计算常规的均方偏差,并且在错误开始增加时停止学习过程。

(5)

常规化包括将均方差除以数据本身的分散度,这会导致指数始终低于1。当每一个向量被单独考虑时,这个均方误差实际上是一个量化误差,因为它是基于它的成分和相关神经元的突触重量之间的差异,并给出了所有神经元中向量的最佳近似值。我们应该记住,这个获胜的神经元在Kohonen网络中被称为BMU或BMN——在CSOM类中,getbest匹配节点方法和类似的技术负责搜索它。

启用验证后,迭代次数将超过参数epochnumber中指定的次数。由于Kohonen网络体系结构的特点,只有当网络在epochnumber期间通过自组织阶段后,才能进行验证。当这个阶段完成后,学习速度和范围会显著降低,从而使初始权重得到微调,然后开始收敛阶段。这里,学习的“早期停止”是使用验证集完成的。

是否使用验证取决于问题的特殊性。此外,可以使用验证集来匹配网络大小。在本文中,我们不打算讨论这个问题。我们只使用众所周知的经验法则将网络规模与教育数据数量联系起来:

n~5*sqrt(m)(6)

其中,n是网络中的神经元数,m是输入向量数。对于具有平方输出层的Kohonen网络,我们得到了大小:

S=sqrt(5*sqrt(m);(7)

s是垂直和水平神经元的数量。我们将这个值引入参数cellsx和cellsy。

最后一个需要在源代码中纠正的问题是处理六边形网格。Kohonen图像是由矩形或六角形单元(神经元)布局构建的,这两种模式最初都是在源代码中实现的。但是,六角形网格仅显示为六角形单元,但计算为矩形网格。为了在这里找到错误的来源,让我们研究下面的示意图。

神经元邻域几何:矩形和六边形网格

神经元的邻域几何:矩形和六边形网格

这里我们展示了两个几何网格的随机神经元围绕的逻辑(在本例中,坐标为3;3)。周围的半径是1。在方格中,神经元有四个直接邻域,而在六角形格中,神经元有六个。交替地移动每个单元的外围半个单元以优化其外观。然而,这并没有改变它们的内部坐标,甚至在法律上,六角形网格周围的神经元也像以前一样出现了——它以粉红色标记。

显然,这是错误的,应该通过包括黄色突出显示的神经元来纠正。

从形式上讲,该算法利用邻域和作为凸-负径向函数,根据单元坐标之间的距离来计算边缘。换句话说,邻域不是神经元的二元属性(相邻与否),而是由高斯公式计算的连续数。

(8)

这里,dj i是神经元j和i之间的距离(序列号是平均数,而不是坐标x和y);sigma是学习过程中邻域的有效宽度或学习半径逐渐减小。在学习之初,用对称的“时钟”覆盖周围的区域将比那些相邻的神经元有更多的空间。

因为这个公式依赖于距离,所以它也会扭曲邻域,因为坐标没有正确修正。因此,以下源代码来自csom::train方法:

      for(int i = 0; i < total_nodes; i++)
      {
         double DistToNodeSqr = (m_som_nodes[winningnode].X() - m_som_nodes[i].X()) * (m_som_nodes[winningnode].X() - m_som_nodes[i].X())
                              + (m_som_nodes[winningnode].Y() - m_som_nodes[i].Y()) * (m_som_nodes[winningnode].Y() - m_som_nodes[i].Y());

增加了以下内容:

      bool odd = ((winningnode % m_ycells) % 2) == 1;
      for(int i = 0; i < total_nodes; i++)
      {
        bool odd_i = ((i % m_ycells) % 2) == 1;
        double shiftx = 0;

        if(m_hexCells && odd != odd_i)
        {
          if(odd && !odd_i)
          {
            shiftx = +0.5;
          }
          else // 反之亦然 (!odd && odd_i)
          {
            shiftx = -0.5;
          }
        }
        double DistToNodeSqr = (m_node[winningnode].GetX() - (m_node[i].GetX() + shiftx)) * (m_node[winningnode].GetX() - (m_node[i].GetX() + shiftx))
                             + (m_node[winningnode].GetY() - m_node[i].GetY()) * (m_node[winningnode].GetY() - m_node[i].GetY());

校正“移位X”的方向取决于两个神经元的偶数或奇数之比,并计算出两个神经元之间的距离。如果神经元在同一排,就不需要纠正它们。如果获胜的神经元是奇数行,那么偶数行似乎将一半的细胞从它向右移动,所以shiftx等于+0.5。如果获胜的神经元在偶数行中,奇数行显示半个单元格向左移动,那么shiftx等于-0.5。

现在,特别重要的是要注意以下原始字符串:

        if(DistToNodeSqr < WS)
        {
          double influence = MathExp(-DistToNodeSqr / (2 * WS));
          m_node[i].AdjustWeights(data, learning_rate, influence);
        }

事实上,这个条件运算符确保了计算中的一些加速,因为它忽略了sigma邻域之外的一个神经元。然而,在学习质量方面,高斯公式是理想的,这种干预是不合理的。如果一个神经元太远应该被忽略,它应该是三西格玛,而不仅仅是一个。对六角形网格的计算进行修正后,相邻行相邻神经元之间的距离等于sqrt(1*1+0.5*0.5)=1.118,即大于1。在附带的源代码中,这个条件运算符被注释。如果确实需要加快计算速度,请使用以下选项:

        if(DistToNodeSqr < 9 * WS)

小心!由于上述细微差异,相邻神经元之间的距离取决于它们的排列(单列距离为1,相邻行距离为1.118)。目前的实施仍不令人满意,建议进一步修改以实现完全各向异性。

可视化

虽然Kohonen网络主要与可视图像相关,但是它们的拓扑结构和学习算法可以在没有任何用户界面的情况下完美工作。特别是,预测或压缩信息的问题不需要任何视觉分析,图像分类可以将结果作为数字传输,即一个类的数量或事件的概率。因此,Kohonen网络的功能可分为两类。在CSOM类中,只保留计算、数据加载和存储以及网络加载和存储。此外,还创建了一个派生的CSomDisplay类,其中放置了所有图形。在我看来,这是一个比第二篇文章中提出的更简单、更合理的层次结构。未来,我们将使用CSOMDisplay来解决选择最佳EA参数的问题,并将使用CSOM进行预测。

应注意的是,网格类型的特性,即无论是矩形还是六角形,都属于基本类,因为它影响距离的计算。除了垂直和水平方向的节点数量以及数据输入空间的维度之外,网格类型也是体系结构的一部分,应该存储在文件中。从文件加载网络时,应从文件中读取所有这些参数,而不是从程序设置中读取。其他设置只影响视觉显示,例如像素图像大小、单元格边界或副标题,将不会保存在网络文件中,并且可以在完成在线教育后重复和随机更改。

应该注意的是,未更新类不提供带有控件的图形用户界面——所有设置都是由MQL程序输入指定的。同时,CSOMSECTION类仍然实现了一些有用的功能。

回想一下,在使用Kohonen网络的前一个例子中,有一个名为Max图片的输入。它仍然存在于新的实现中。它以maxpict的形式传递给csomdisplay::in it方法,并设置在图表的一行中显示的网络图像(平面)数。使用IMAGEW和IMAGEH的总体图像大小处理此参数,我们可以找到一个选项,使所有图像适合屏幕。但是,当有许多图像时,例如,您必须分析许多EA设置,这些设置需要显著减小大小,这是不方便的。在这种情况下,可以将参数maxpictures设置为0以激活新模式。

在此模式下,图形上生成的图像不是与像素坐标对齐的对象obj_位图_标签,而是与时间刻度对齐的对象obj_位图。这样的图像在填满整个图表高度之前可以增大。您可以使用通常的水平滚动条滚动它们,方法是使用鼠标或滚轮或键盘拖动它们。图像的数量不再局限于屏幕大小。但是,您应该确保列的数量足够。

增加图像的大小可以让我们更详细地研究它们,特别是像CSOMDISPLAY这样的细胞,它可以选择性地显示各种信息,例如在相关平面上的突触权重、教育集合向量的点击数、所有点击cel的向量的相关特征值的平均值和分散度。LS默认情况下不会显示此信息,但如果将鼠标光标放在一个或另一个单元格上,则始终会弹出一个提示来显示此信息。当前平面的名称和神经元的坐标也显示在弹出的提示中。

此外,双击任何一个神经元都会突出显示当前图像和所有其他图像中的互补神经元。这使我们能够同时直观地比较所有特征的神经元活动。

最后,应该注意的是,整个图已经移动到标准类CCANVAS。这简化了外部依赖代码,但也有副作用:Y坐标现在是自上而下计算的,而不是像以前那样自下而上计算的。这将导致包含组件名称及其数值范围的图例显示在图像的上方而不是下方。这一变化似乎无关紧要。

改进的

在处理现有问题之前,我们需要对神经网络类做一些改进。除了在具有特定特征的二维空间中表示突触权重的标准图像外,我们还将准备一些用于计算和显示的业务图像,这是Kohonen网络的实际标准。展望未来,我们将说,在应用的实验阶段,我们需要它们中的许多。

让我们定义附加维度的索引。总共有五个。

#define EXTRA_DIMENSIONS 5
#define DIM_HITCOUNT (m_dimension + 0)
#define DIM_UMATRIX  (m_dimension + 1)
#define DIM_NODEMSE  (m_dimension + 2) // 每个节点的量化误差:平均方差(标准差的平方)
#define DIM_CLUSTERS (m_dimension + 3)
#define DIM_OUTPUT   (m_dimension + 4)

U型矩阵

首先,我们将计算统一距离矩阵U矩阵,以评估学习过程中网络中生成的拓扑结构。对于网络中的每个神经元,矩阵包含神经元与其相邻神经元之间的平均距离。因为Kohonen网络在图像的二维空间中显示了特征的多维空间。折叠发生在这个二维空间。也就是说,虽然Kohonen网络保持了初始空间的固有排列,但在整个二维空间中却无法实现,神经元的地理邻近性变得虚幻。精确地使用U矩阵来检测这些区域。其中,神经元重量与相邻神经元重量差异较大的区域显示为“峰值”,非常相似的区域显示为“凹陷”。

若要计算神经元和特征量之间的距离,则有csomnode::calculateDistance方法。we will create a corresponding method for it,which points the pointer to another neuron to replace the vector(array’double’).

double CSOMNode::CalculateDistance(const CSOMNode *other) const
{
  double vector[];
  other.GetCodeVector(vector);
  return CalculateDistance(vector);
}

在这里,getcodevector方法获取另一个神经元的权重数组,并立即以常规方式计算其距离。

为了获得神经元之间的统一距离,需要计算所有相邻神经元的距离并求其平均值。由于相邻神经元的遍历是网络网格的几种操作中的常见任务,因此我们将创建一个用于遍历的基类,然后在其后代中实现一个单独的算法,包括聚合距离。

#define NBH_SQUARE_SIZE    4
#define NBH_HEXAGONAL_SIZE 6

template<typename T>
class Neighbourhood
{
  protected:
    int neighbours[];
    int nbhsize;
    bool hex;
    int m_ycells;

  public:
    Neighbourhood(const bool _hex, const int ysize)
    {
      hex = _hex;
      m_ycells = ysize;

      if(hex)
      {
        nbhsize = NBH_HEXAGONAL_SIZE;
        ArrayResize(neighbours, NBH_HEXAGONAL_SIZE);
        neighbours[0] = -1; // 上 (可视)
        neighbours[1] = +1; // 下 (可视)
        neighbours[2] = -m_ycells; // 左
        neighbours[3] = +m_ycells; // 右
        /* 模板,在下面的循环中动态应用
        // 奇数行
        neighbours[4] = -m_ycells - 1; // 左上
        neighbours[5] = -m_ycells + 1; // 左下
        // 偶数行
        neighbours[4] = +m_ycells - 1; // 右上
        neighbours[5] = +m_ycells + 1; // 右下
        */
      }
      else
      {
        nbhsize = NBH_SQUARE_SIZE;
        ArrayResize(neighbours, NBH_SQUARE_SIZE);
        neighbours[0] = -1; // 上 (可视)
        neighbours[1] = +1; // 下 (可视)
        neighbours[2] = -m_ycells; // 左
        neighbours[3] = +m_ycells; // 右
      }
    
    }
    ~Neighbourhood()
    {
      ArrayResize(neighbours, 0);
    }

    T loop(const int ind, const CSOMNode &p_node[])
    {
      int nodes = ArraySize(p_node);
      int j = ind % m_ycells;
      
      if(hex)
      {
        int oddy = ((j % 2) == 1) ? -1 : +1;
        neighbours[4] = oddy * m_ycells - 1;
        neighbours[5] = oddy * m_ycells + 1;
      }
      
      reset();

      for(int k = 0; k < nbhsize; k++)
      {
        if(ind + neighbours[k] >= 0 && ind + neighbours[k] < nodes)
        {
          // 跳过包边
          if(j == 0) // 顶行
          {
            if(k == 0 || k == 4) continue;
          }
          else if(j == m_ycells - 1) // 底行
          {
            if(k == 1 || k == 5) continue;
          }
          
          iterate(p_node[ind], p_node[ind + neighbours[k]]);
        }
      }
      
      return getResult();
    }
    
    virtual void reset() = 0;
    virtual void iterate(const CSOMNode &node1, const CSOMNode &node2) = 0;
    virtual T getResult() const = 0;
};

根据传递给构造函数的网格类型,邻域NBHSIZE的数量等于4和6。与当前神经元相关联的相邻神经元数目的增量被存储在“邻居”阵列中。例如,在正方形网格中,从上神经元中减去一层,或从下神经元中添加另一层,以获得上相邻神经元。相邻神经元的数目与网格的列高度不同,因此值作为ysize传递给构造函数。

用“环”法进行相邻神经元的实际横断。类邻域不包含任何神经元数组,因此它作为参数传递给方法“loop”。

在循环中,该方法穿过数组“邻居”,并检查相邻神经元的数量是否超过网格,同时考虑到增量。对于所有有效的数字,调用抽象方法“迭代”,它传递当前神经元和其中一个神经元周围的链接。

在循环之前调用抽象方法“reset”,在循环之后调用抽象方法getresult。一组由三种抽象方法组成的方法允许在随后的类中准备和执行相邻神经元的计数,并生成结果。“循环”方法构造的概念对应于已知的OOP设计范式-模板方法。在这里,我们应该区分范例名称中的术语“模板”和模板的语言范例。模板的语言范式也用于类邻域中,因为它是一个模板,也就是说,它是由变量类型t参数化的。“loop”方法本身和方法getresult返回类型t的值。

基于类邻域,我们将编写一个类来计算U矩阵。

class UMatrixNeighbourhood: public Neighbourhood<double>
{
  private:
    int n;
    double d;
    
  public:
    UMatrixNeighbourhood(const bool _hex, const int ysize): Neighbourhood(_hex, ysize)
    {
    }
    
    virtual void reset() override
    {
      n = 0;
      d = 0.0;
    }
    
    virtual void iterate(const CSOMNode &node1, const CSOMNode &node2) override
    {
      d += node1.CalculateDistance(&node2);
      n++;
    }
    
    virtual double getResult() const override
    {
      return d / n;
    }
};

处理类型为双精度。通过它的基类,距离的计算是非常透明的。

我们将用CSOM::计算距离的方法计算整个图像的距离。

void CSOM::CalculateDistances()
{
  UMatrixNeighbourhood umnh(m_hexCells, m_ycells);
  
  for(int i = 0; i < m_xcells * m_ycells; i++)
  {
    double d = umnh.loop(i, m_node);
    
    if(d > m_max[DIM_UMATRIX])
    {
      m_max[DIM_UMATRIX] = d;
    }
    
    m_node[i].SetDistance(d);
  }
}

均匀距离的值存储在神经元的对象中。稍后,当显示所有平面时,我们将能够使用调色板以标准方式定义距离值,并在计算中包含额外的尺寸标注。为了正确缩放调色板,我们在相关数组m_max元素的范围内保留最大距离值(所有实现原则与以前的实现保持不变)。

命中次数和量化误差

下一个额外的维度将收集特定神经元中学习向量点击次数的统计数据。换句话说,它是用来填充神经元的数据的密度。特定神经元的数目越高,统计学中的权重因子越合理。在网络中,神经元可能具有更小或甚至是零的数据覆盖。在多维空间的二维投影中,可能存在着选择网络尺寸或扭曲拓扑结构等问题。通过以下方法计算样本到神经元的命中率:

void CSOMNode::RegisterPatternHit(const double &vector[])
{
  m_hitCount++;
  double e = 0;
  for(int i = 0; i < m_dimension; i++) 
  {
    m_sum[i] += vector[i];
    m_sumP2[i] += vector[i] * vector[i];
    e += (m_weights[i] - vector[i]) * (m_weights[i] - vector[i]);
  }
  m_mse += e / m_dimension;
}

计数本身是在第一个m_hitcount++字符串中执行的,在该字符串中增加了内部计数器。下面将讨论执行其他有用工作的代码的其余部分。

我们将在CSOM类完成学习后调用方法registerpatternhit,在那里我们将创建一个特殊的统计方法来处理每个向量。

double CSOM::AddPatternStats(const double &data[])
{
  static double vector[];
  ArrayCopy(vector, data);
  
  int ind = GetBestMatchingIndex(vector);
  
  m_node[ind].RegisterPatternHit(vector);

  double code[];
  m_node[ind].GetCodeVector(code);
  Denormalize(code);
  
  double mse = 0;
  
  for(int i = 0; i < m_dimension; i++)
  {
    mse += (data[i] - code[i]) * (data[i] - code[i]);
  }
  
  mse /= m_dimension;
  
  return mse;
}

作为一个问题外话,应注意这里使用的方法getBestMatchingIndex,以及getBestMatchingXYZ方法组中的其他一些方法,常规规范了自身内部的输入数据,因此必须传递一个数量的副本。否则,源数据可能会在调用代码中混乱。

该方法除了对HIT进行重新编码外,还计算了当前神经元和传输向量的量化误差。为此,从获胜神经元中调用所谓的编码向量synaptic weight数组,并计算权重与输入向量分量差的平方和。

对于addpatternstatsm,它立即从另一个方法csom::calculateStats调用,后者只为所有输入分配循环。

double CSOM::CalculateStats(const bool complete = true)
{
  double data[];
  ArrayResize(data, m_dimension);
  double trainedMSE = 0.0;
  
  for(int i = complete ? 0 : m_validationOffset; i < m_nSet; i++)
  {
    ArrayCopy(data, m_set, 0, m_dimension * i, m_dimension);
    trainedMSE += AddPatternStats(data, complete);
  }
  
  double nmse = trainedMSE / m_dataMSE;
  if(complete) Print("Overall NMSE=", nmse);

  return nmse;
}

该方法将所有量化误差相加,并与输入数据分散MyDATAMSE进行比较,这是上述验证和学习停止部分中描述的NMSE计算。此方法引用创建CSOM对象时指定的变量MyValueTebug偏移量,其基于输入数据集是否被划分为学习和验证子集。

您猜对了,在列车方法中的每一次调用CalculateStats方法(如果收敛阶段已经开始),我们可以判断整个网络错误是否已经开始由返回值增加,也就是说,它是否达到了停止时间。

利用以下方法预先计算色散MyDATAMSE:

void CSOM::CalculateDataMSE()
{
  double data[];

  m_dataMSE = 0.0;
  
  for(int i = m_validationOffset; i < m_nSet; i++)
  {
    ArrayCopy(data, m_set, 0, m_dimension * i, m_dimension);

    double mse = 0;
    for(int k = 0; k < m_dimension; k++)
    {
      mse += (data[k] - m_mean[k]) * (data[k] - m_mean[k]);
    }
    
    mse /= m_dimension;
    m_dataMSE += mse;
  }
}

我们得到了数据常规化阶段各分量的平均m_平均值。

void CSOM::InitNormalization(const bool normalization = true)
{
  ArrayResize(m_max, m_dimension + EXTRA_DIMENSIONS);
  ArrayResize(m_min, m_dimension + EXTRA_DIMENSIONS);
  ArrayInitialize(m_max, 0);
  ArrayInitialize(m_min, 0);
  ArrayResize(m_mean, m_dimension);
  ArrayResize(m_sigma, m_dimension);

  for(int j = 0; j < m_dimension; j++)
  {
    double maxv = -DBL_MAX;
    double minv = +DBL_MAX;
    
    if(normalization)
    {
      m_mean[j] = 0;
      m_sigma[j] = 0;
    }
    
    for(int i = 0; i < m_nSet; i++)
    {
      double v = m_set[m_dimension * i + j];
      if(v > maxv) maxv = v;
      if(v < minv) minv = v;
      if(normalization)
      {
        m_mean[j] += v;
        m_sigma[j] += v * v;
      }
    }
    
    m_max[j] = maxv;
    m_min[j] = minv;
    
    if(normalization && m_nSet > 0)
    {
      m_mean[j] /= m_nSet;
      m_sigma[j] = MathSqrt(m_sigma[j] / m_nSet - m_mean[j] * m_mean[j]);
    }
    else
    {
      m_mean[j] = 0;
      m_sigma[j] = 1;
    }
  }
}

转到附加平面,需要注意的是,在csomnode::registerpatternhit中计算后,每个神经元可以使用以下方法返回相关的统计数据:

int CSOMNode::GetHitsCount() const
{
  return m_hitCount;
}

double CSOMNode::GetHitsMean(const int plane) const
{
  if(m_hitCount == 0) return 0;
  return m_sum[plane] / m_hitCount;
}

double CSOMNode::GetHitsDeviation(const int plane) const
{
  if(m_hitCount == 0) return 0;
  double z = m_sumP2[plane] / m_hitCount - m_sum[plane] / m_hitCount * m_sum[plane] / m_hitCount;
  if(z < 0) return 0;
  return MathSqrt(z);
}

double CSOMNode::GetMSE() const
{
  if(m_hitCount == 0) return 0;
  return m_mse / m_hitCount;
}

因此,我们将数据填入两个平面——显示神经元输入向量的数量和量化误差。

网络响应

下一个附加平面将生成特定样本的图像和网络响应。应该记住,当向网络输入信号时,除了获胜的神经元外,其他所有神经元的活性都较低。比较主动响应迁移的可能性有助于定义由网络提出的解决方案的稳定性。

网络响应的计算是最简单的。在csomnode类中,我们将编写以下方法:

double CSOMNode::CalculateOutput(const double &vector[])
{
  m_output = CalculateDistance(vector);
  return m_output;
}

我们将为网络类中的每个神经元调用它。

void CSOM::CalculateOutput(const double &vector[], const bool normalize = false)
{
  double temp[];
  ArrayCopy(temp, vector);
  if(normalize) Normalize(temp);
  m_min[DIM_OUTPUT] = DBL_MAX;
  m_max[DIM_OUTPUT] = -DBL_MAX;
  for(int i = 0; i < ArraySize(m_node); i++)
  {
    double x = m_node[i].CalculateOutput(temp);
    if(x < m_min[DIM_OUTPUT]) m_min[DIM_OUTPUT] = x;
    if(x > m_max[DIM_OUTPUT]) m_max[DIM_OUTPUT] = x;
  }
}

如果无法向程序提供测试向量,则默认计算响应,即零向量。

聚类

最后,最后一个平面,可能也是最重要的平面,是簇状图像。在二维图像上安排输入数据只占一半。分析的真正目的是检测函数并将它们分类为易于理解的应用程序术语类别。当特征空间的尺寸相对较小时,我们可以很容易地通过每个平面上的色斑来区分具有所需特征的区域,通常是孤立的。然而,随着输入数据结构的扩展,图像变得更加复杂,并且不再交叉分析几十个具有不同索引的图像,而是更方便地将图像划分为醒目区域。

聚类结果分为两类,即通过具有相似特征的区域标记图像识别聚类中心。然后我们可以把它们看作统计中相关类的最有代表性的样本。到目前为止,我们正在逐步接近选择最佳EA参数的任务。但是,我们应该实现集群。

K均值

有许多聚类方法。对于MQL5,最简单的选项是使用标准库中包含的alglib版本。包括头文件就足够了:

#include <Math/Alglib/dataanalysis.mqh>

然后写一个这样的方法:

void CSOM::Clusterize(const int clusterNumber)
{
  int count = m_xcells * m_ycells;
  CMatrixDouble xy(count, m_dimension);
  int info;
  CMatrixDouble clusters;
  int membership[];
  double weights[];
  
  for(int i = 0; i < count; i++)
  {
    m_node[i].GetCodeVector(weights);
    xy[i] = weights;
  }

  CKMeans::KMeansGenerate(xy, count, m_dimension, clusterNumber, KMEANS_RETRY_NUMBER, info, clusters, membership);
  Print("KMeans result: ", info);
  if(info == 1) // ok
  {
    for(int i = 0; i < m_xcells * m_ycells; i++)
    {
      m_node[i].SetCluster(membership[i]);
    }
    
    ArrayResize(m_clusters, clusterNumber * m_dimension);
    for(int j = 0; j < clusterNumber; j++)
    {
      for(int i = 0; i < m_dimension; i++)
      {
        m_clusters[j * m_dimension + i] = clusters[i][j];
      }
    }
  }
}

它使用k-均值来执行聚类算法。不幸的是,据我所知,它是MQL5的alglib版本中唯一的集群算法,尽管原始库的最新版本提供了其他算法,如内聚层次集群。

不幸的是,K均值算法在某种程度上是最“直线”:其本质是减少对特征空间中给定数量球体中心的搜索,并以最有效的方式覆盖采样点,即从聚类中心到采样点距离平方的最小和。问题在于,由于它们的固定形式,球体在非线性聚合体的可分离性方面具有一些特定的限制。原则上,k-means is a special case of maximizing the expectation of the algorithm.它操作具有不同方向和形状的椭球体,因此它是首选。然而,即使使用了它,也有可能坚持局部最小值,因为这两种算法都使用凸中心和随机排列的聚类中心。缺点还包括必须事先指定集群的数量。

然而,我们研究了如何在alglib中使用k-均值来安排聚类。主操作由ckmeans::kmeansGenerate方法执行。我们向它传递一个数组,其中源数据是一种特殊的基于对象的格式(cmatrix double xy)、向量数(count)、特征空间的维数(m_维数)和所需的clusterNumber,这些都在MQL程序的参数中指定。下一个输入,kmeans_retry_number,是迭代次数,取决于算法,随机选择初始中心以避免局部解。在我们的示例中,它是一个等于10的宏定义。作为函数操作的结果,我们将得到名为“info”的执行代码(不同的值表示成功或错误)。基于聚类对象的数组名为cmatrixdouble,包含聚类坐标,输入数组是成员。

我们将聚类中心存储在数组m_簇中,以在图像上标记它们,并根据与其聚类成员关系相关的颜色为每个神经元着色:

m_node[i].SetCluster(membership[i]);

使用alglib操作时,请记住,它使用自己的随机数生成器来考虑特定静态对象的内部状态。因此,即使标准生成器是通过mathsrand显式初始化的,它的状态也不会被重置。这对EA尤其重要,因为全局对象在设置更改时不会重新生成。因此,如果在OnInit中没有清除cmath::m_状态,alglib可能会发现很难重现计算结果。

针对上述k均值的不足,提出了一种新的聚类方法。另一个选择是显而易见的。

选择

我们把注意力转向Kohonen图像,特别是我们介绍的其他维度。U型矩阵是特别有趣的。平面显示最靠近神经元的区域,也就是说,它们在2D映射拓扑和特征空间方面彼此接近。正如我们所记得的,在U矩阵中有一种“抑郁”神经元。它们是很好的集群候选者。

例如,我们可以通过以下方式将等距图像转换为聚类。

所有神经元的信息被复制到一个数组中,并根据U形距离(csomnode::getDistance())的值进行排序。

对于一个给定的神经元,我们将检查循环中的数组,看看相邻的神经元是否属于一个簇。

  • 如果没有,我们创建一个新的集群并将当前神经元分配给它。请注意,将创建一个集群,从零索引开始,它对应于最“重要”的集群,因为它匹配最小的U型距离,然后按重要性的降序排序。就U型距离而言,每一个连续的聚类将不那么紧凑。
  • 如果相邻神经元中存在聚类标记,我们将从中选择最高的一个,也就是最低的索引标记,并将当前神经元分配给该聚类。

这很简单。我们不应该考虑神经元的填充密度吗?毕竟,U型距离对不同点击的神经元有不同的支持。换言之,如果两个神经元有相同的U形距离,其中一个神经元显示的样本比另一个神经元多,样本量较小。

然后,根据公式csomnode::getDistance()/sqrt(csomnode::getHitsCount())中的数字顺序修改算法中的初始数组排序就足够了。在高密度的情况下,我增加了平方根来平滑效果,而在低密度的情况下,惩罚效果应该更严格。

然而,如果我们使用两个服务平面,分析第三个服务平面可能是合理的,也就是说,哪个量化误差?事实上,特定神经元的量化误差越大,我们对较小的U形距离信息的信任就越少,反之亦然。

如果我们记得发生的量化误差函数:

double CSOMNode::GetMSE() const
{
  if(m_hitCount == 0) return 0;
  return m_mse / m_hitCount;
}

然后我们很容易注意到使用了计数器m_hitcount(仅以分母为单位)。因此,我们可以重写前面的公式,按照csomnode::getDistance()*mathsqrt(csomnode::getmse())对神经元数组进行排序,然后考虑我们在Kohonen网络实现中添加的所有三个附加索引。

我们已经准备好提出最后一种聚类算法的替代形式,但是仍然存在一个小问题。在神经元阵列的循环中,我们应该检查当前神经元附近是否存在相邻的簇。前面,我们实现了模板类邻域的部分查找。现在,我们将创建他们的后代,重点是搜索集群。

class ClusterNeighbourhood: public Neighbourhood<int>
{
  private:
    int cluster;

  public:
    ClusterNeighbourhood(const bool _hex, const int ysize): Neighbourhood(_hex, ysize)
    {
    }
    
    virtual void reset() override
    {
      cluster = -1;
    }
    
    virtual void iterate(const CSOMNode &node1, const CSOMNode &node2) override
    {
      int x = node2.GetCluster();
      if(x > -1)
      {
        if(cluster != -1) cluster = MathMin(cluster, x);
        else cluster = x;
      }
    }
    
    virtual int getResult() const override
    {
      return cluster;
    }
};

此类包含潜在集群的数量(该数字是一个整数,因此我们使用int类型参数化模板)。最初,这个变量在reset方法中被初始化为-1,也就是说,没有集群。然后,当父类从循环方法中调用我们的新实现“迭代”时,我们得到每个相邻神经元的集群数,将其与集群进行比较,并保存最小值。如果找不到群集,getresult方法将返回相同的或-1。

作为改进,我们建议追踪神经元之间的“峰高”,即节点1的值。计算距离(&amp;node2),并且只有当“高度”低于前面的值时,簇数才能从一个神经元“流动”到另一个神经元。源代码中提供了最终的实现版本。

最后,我们可以实现可选的集群。

void CSOM::Clusterize()
{
  double array[][2];
  int n = m_xcells * m_ycells;
  ArrayResize(array, n);
  for(int i = 0; i < n; i++)
  {
    if(m_node[i].GetHitsCount() > 0)
    {
      array[i][0] = m_node[i].GetDistance() * MathSqrt(m_node[i].GetMSE());
    }
    else
    {
      array[i][0] = DBL_MAX;
    }
    array[i][1] = i;
    m_node[i].SetCluster(-1);
  }
  ArraySort(array);
  
  ClusterNeighbourhood clnh(m_hexCells, m_ycells);

  int count = 0; // 聚类数量
  ArrayResize(m_clusters, 0);
  
  for(int i = 0; i < n; i++)
  {
    // 如果已经分配则跳过
    if(m_node[(int)array[i][1]].GetCluster() > -1) continue;
    
    // 检查当前节点是否与任何现有聚类相邻
    int r = clnh.loop((int)array[i][1], m_node);
    if(r > -1) // 邻居已经属于聚类
    {
      m_node[(int)array[i][1]].SetCluster(r);
    }
    else // 我们需要新的聚类
    {
      ArrayResize(m_clusters, (count + 1) * m_dimension);
      
      double vector[];
      m_node[(int)array[i][1]].GetCodeVector(vector);
      ArrayCopy(m_clusters, vector, count * m_dimension, 0, m_dimension);
      
      m_node[(int)array[i][1]].SetCluster(count++);
    }
  }
}

实际上,该算法完全遵循上述口头伪码:我们填充二维数组(从第一维的公式值和第二维的神经元索引中),对循环中的所有神经元进行排序、访问,并分析每个相邻的神经元。

当然,集群的质量应该在实践中进行评估,我假定存在拓扑问题。然而,考虑到大多数经典的聚类方法也存在一些问题,并且该建议相当糟糕,新的解决方案似乎很有吸引力。

在该实现的优点中,我要提到的是,根据其重要性(在上面的k-均值中,聚类是相等的)来排列聚类,它们的形式是随机的,并且不需要预定义数字。应该注意的是,最后一个具有相反的方面,即,簇的数目可能相当大。此外,基于内容相似性和最小误差的聚类只能考虑第一个5-10个聚类,并且在场景后面留下其他聚类。

由于我在任何开放源代码中还没有找到类似的聚类方法,所以我建议将其命名为Korotky聚类,或者基于U-矩阵和量化误差(QE)的更长但合适的短路径聚类。

我应该在前面说。在许多试验中,证明了K均值算法的聚类结果比选择性聚类(至少在优化结果分析中)差。因此,本聚类方法仅在下文中引用和应用。

试验

现在是时候从理论转向实践,测试网络是如何工作的了。我们创建了一个简单、通用的智能交易系统,其中包含用于演示基本功能的选项。我们把它命名为探险家。

我们包括上面提到的类头文件。定义输入。

分组网络结构和数据设置

  • data file name-包含教育或测试数据的文本文件的名称;csom类支持csv格式,但稍后我们将向ea本身添加一个从文件集合读取,因为优化分析其他eas的设置是“危险的”;指定文件中包含的输入,该输入还用于在网络ED后保存网络。ucation,但使用另一个扩展名(见下文);可以指示或省略csv扩展名;此名称可能包含mql5/文件中的文件夹;
  • NetFileName—以自身格式的二进制文件名,扩展名为som;csom类可以在这些文件中保存和读取网络;如果有人需要修改要存储的数据结构,他需要修改文件开头写入的签名中的版本号;如果NetFileName为空,则ea工作于学习模式,and如果指定了网络,则在测试模式下。也就是说,在就绪网络中显示输入;您可以指示或省略SOM扩展;名称可能包含MQL5/文件中的文件夹;
  • 如果datafilename和netfilename都为空,则EA将随机生成演示3D数据集并进行培训;
  • 如果netfilename中的网络名称正确,可以在datafilename中指定不存在的文件名,例如“?”性格。这将导致EA生成测试数据的随机样本,并将定义范围保存在网络文件中。(请注意,这些信息对于受过教育的网络来说是必要的,以便在操作和网络馈送模式下正确地规范未知数据。从另一个定义的范围输入值肯定不会导致错误,但结果将不可靠;例如,如果提取值为负数,或为其提供的事务数为负数,则很难期望网络正常工作。
  • cellsx-网格的水平大小(神经元数)默认为10;
  • Cellsy-网格的垂直大小(神经元数),默认为10;
  • 六角形网格-使用六角形网格的特性,默认为“真”;对于矩形网格,切换为“假”;
  • usenormalization-启用/禁用输入常规化;默认为“真”,建议不要禁用它;
  • epochnumber-学习次数;默认值为100;
  • validation setpercent-验证选择比例,表示为总输入的百分比;默认为0,即禁用验证;如果使用,建议值约为10;
  • cluster number—集群的数目,默认为1,表示我们的自适应集群;0值表示禁用集群;大于0的值表示使用k-means方法开始集群;学习后立即集群;集群保存到网络文件中;

分组可视化

  • imagew-每个图像(平面)的水平大小(像素),默认为500;
  • imageh-每个图像(平面)的垂直大小(像素),默认为500;
  • maxpictures-一行中的图像数;默认值为0,表示使用滚动选项(允许大图像)将图像模式显示为连续行;如果maxpictures大于0,则整个平面集将显示为多行,其中每个图像的maxpictures(用于以较小的比例查看所有图像)N);

    A33

  • 显示边界-启用/禁用在神经元之间绘制边界;默认为“假”;
  • 显示标题-启用/禁用显示神经元特征的文本,默认为“真”;
  • 配色方案-从四种配色方案中选择一种;默认蓝绿色红色(最鲜艳的);
  • 显示进度-在学习期间启用/禁用网络图像的动态更新;每秒执行一次;默认值为“真”;

分组选项

  • 随机种子-用于初始化随机数生成器的整数;默认值为0;
  • saveimages—完成后保存网络映像的选项;学习后和第一次引导后也可以使用;默认值为“false”;

这些只是基本设置。当我们继续解决这个问题时,我们将添加一些其他特殊参数。

请注意!EA将更改当前的图表设置-打开一个新图表专门用于操作此EA。

csomdisplay类对象将在EA中执行所有操作。

CSOMDisplay KohonenMap;

在初始化期间,不要忘记启用鼠标移动事件处理——这个类使用它们来显示弹出提示和滚动。

void OnInit()
{
  ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
  EventSetMillisecondTimer(1);
}

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
  KohonenMap.OnChartEvent(id, lparam, dparam, sparam);
}

神经网络算法(学习或测试)只能在EA中启动一次-通过计时器,然后禁用计时器。

void OnTimer()
{
  EventKillTimer();
  
  MathSrand(RandomSeed);
  
  bool hasOneTestPattern = false;
  
  if(NetFileName != "")
  {
    if(!KohonenMap.Load(NetFileName)) return;
    KohonenMap.DisplayInit(ImageW, ImageH, MaxPictures, ColorScheme, ShowBorders, ShowTitles);

    Comment("Map ", NetFileName, " is loaded; size: ", KohonenMap.GetWidth(), "*", KohonenMap.GetHeight(), "; features: ", KohonenMap.GetFeatureCount());

如果指定了包含网络的文件,我们将加载该文件,并根据视觉设置准备显示该文件。

    if(DataFileName != "")
    {
      if(!KohonenMap.LoadPatterns(DataFileName))
      {
        Print("Data loading error, file: ", DataFileName);

        // 生成随机测试矢量
        int n = KohonenMap.GetFeatureCount();
        double min, max;
        double v[];
        ArrayResize(v, n);
        for(int i = 0; i < n; i++)
        {
          KohonenMap.GetFeatureBounds(i, min, max);
          v[i] = (max - min) * rand() / 32767 + min;
        }
        KohonenMap.AddPattern(v, "RANDOM");
        Print("Random Input:");
        ArrayPrint(v);
        double y[];
        CSOMNode *node = KohonenMap.GetBestMatchingFeatures(v, y);
        Print("Matched Node Output (", node.GetX(), ",", node.GetY(), "); Hits:", node.GetHitsCount(), "; Error:", node.GetMSE(),"; Cluster N", node.GetCluster(), ":");
        ArrayPrint(y);
        KohonenMap.CalculateOutput(v, true);
        hasOneTestPattern = true;
      }
    }

如果指定了包含测试详细信息的文件,我们将尝试加载它。如果不起作用,日志中会显示一条消息,并生成随机测试数据样本。利用getFeatureCount和getFeatureBounds方法定义特征数量(数量的维数)及其允许的范围。然后,通过调用addPattern,sample is added to the working data set name random.

这种方法适用于从不支持的数据源(如数据库)形成教育选择,并直接从指标中填充。原则上,在这种特定的情况下,只需将样本添加到工作集中,以便以后可以在图像上显示它们(如下所示),并且仅调用一次GetBest匹配功能就足以找到网络中最合适的神经元。此方法来自几种可用的getbest匹配xyz方法,这些方法使我们能够获得数组y中获胜神经元特征的相关值。最后,使用calculateOutput,我们在附加平面中显示对测试样本的网络响应。

我们继续关注EA代码。

  }
  else // 未提供网络文件,因此假定进行训练
  {
    if(DataFileName == "")
    {
      // 生成无缩放值的 3D 演示矢量 {[0,+1000], [0,+1], [-1,+1]}
      // 将它们投喂到网络中以便比较有无常规化的结果
      // 注意 标题应该是有效的 BMP 文件名
      string titles[] = {"R1000", "R1", "R2"};
      KohonenMap.AssignFeatureTitles(titles);
      double x[3];
      for(int i = 0; i < 1000; i++)
      {
        x[0] = 1000.0 * rand() / 32767;
        x[1] = 1.0 * rand() / 32767;
        x[2] = -2.0 * rand() / 32767 + 1.0;
        KohonenMap.AddPattern(x, StringFormat("%f %f %f", x[0], x[1], x[2]));
      }
    }

如果没有指定教育网络,我们假设一个学习模型。检查任何输入。如果不是,我们生成三维矢量的随机集合,其中第一分量在[0,+1000 ]的范围内,第二分量在[0,+1 ]的范围内,第三分量在[1,+1 ]的范围内。SudioPosialPosialPosithCopyPosithEngult:AddioTimeCudioType。

    else // 提供了一个数据文件
    {
      if(!KohonenMap.LoadPatterns(DataFileName))
      {
        Print("Data loading error, file: ", DataFileName);
        return;
      }
    }

如果输入来自文件,则加载文件。如果发生错误,则由于没有网络或数据而终止操作。

此外,我们还实施了教育和集群。

    KohonenMap.Init(CellsX, CellsY, ImageW, ImageH, MaxPictures, ColorScheme, HexagonalCell, ShowBorders, ShowTitles);
    
    if(ValidationSetPercent > 0 && ValidationSetPercent < 50)
    {
      KohonenMap.SetValidationSection((int)(KohonenMap.GetDataCount() * (1.0 - ValidationSetPercent / 100.0)));
    }

    KohonenMap.Train(EpochNumber, UseNormalization, ShowProgress);

    if(ClusterNumber > 1)
    {
      KohonenMap.Clusterize(ClusterNumber);
    }
    else
    {
      KohonenMap.Clusterize();
    }
  }

如果没有指定单个测试样本的分析(特别是在学习后立即),那么默认情况下,我们会对零向量形成网络响应。

  if(!hasOneTestPattern)
  {
    double vector[];
    ArrayResize(vector, KohonenMap.GetFeatureCount());
    ArrayInitialize(vector, 0);
    KohonenMap.CalculateOutput(vector);
  }

然后我们在图形资源的内部缓冲区中绘制所有图像-首先,它后面的颜色:

  KohonenMap.Render(); // 将映像绘制到内部 BMP 缓冲区中

然后,标题:

  if(hasOneTestPattern)
    KohonenMap.ShowAllPatterns();
  else
    KohonenMap.ShowAllNodes(); // 在 BMP 缓冲区中绘制细胞内标签

标记聚类:

  if(ClusterNumber != 0)
  {
    KohonenMap.ShowClusters(); // 标记聚类
  }

在图表上显示缓冲区,并选择将图像保存到文件:

  KohonenMap.ShowBMP(SaveImages); // 在图表上将文件显示为位图图像,可选择保存到文件中

这些文件放在与网络文件(如果提供)或包含数据的文件(如果提供)同名的单独文件夹中。如果未指定数据文件,并且网络已根据随机生成的数据完成学习,则使用SOM前缀和当前日期和时间来形成SOM文件的名称和包含图像的文件夹。

最后,将完成的学习网络保存到一个文件中。如果在netfilename中指定了网络名称,则表示ea正在测试模式下工作,因此我们不需要再次保存网络。

  if(NetFileName == "")
  {
    KohonenMap.Save(KohonenMap.GetID());
  }
}

我们将尝试通过生成测试随机数据来启动EA。使用所有默认设置,除了确保所有平面都可以向下进入屏幕捕获图像,imagew=230,imageh=230,maxpictures=3,我们得到以下图片:

Kohonen 网络学习结果未经输入常规化0

样本Kohonen映射的随机三维矢量

在这里,服务数据显示在每个神经元中(您可以通过指向鼠标光标查看详细信息),并标记已发现的集群。

在此过程中,日志中会显示以下信息(集群信息限制为5;您可以在源代码中对其进行更改):

Pass 0 from 1000 0%
Pass 78 from 1000 7%
Pass 157 from 1000 15%
Pass 232 from 1000 23%
Pass 310 from 1000 31%
Pass 389 from 1000 38%
Pass 468 from 1000 46%
Pass 550 from 1000 55%
Pass 631 from 1000 63%
Pass 710 from 1000 71%
Pass 790 from 1000 79%
Pass 870 from 1000 87%
Pass 951 from 1000 95%
Overall NMSE=0.09420336270396877
Training completed at pass 1000, NMSE=0.09420336270396877
Clusters [14]:
"R1000" "R1"    "R2"   
N0
754.83131   0.36778   0.25369
N1
341.39665   0.41402  -0.26702
N2
360.72925   0.86826  -0.69173
N3
798.15569   0.17846  -0.37911
N4
470.30648   0.52326   0.06442
Map file SOM-20181205-134437.som saved

如果我们现在在参数netfilename中指定为网络创建的som-20181205-134437.som文件的名称并输入“?”在参数datafilename中,我们将获得测试运行结果,而不是来自学习集,而是来自随机样本。为了更好地查看图像,我们扩大了它们的大小,并将maxpictures设置为0。nbsp;

Kohonen 网络学习结果未经输入常规化1

Kohonen映射随机三维矢量的前两个分量

Kohonen 网络学习结果未经输入常规化2

Kohonen图像,用于随机三维矢量和计数器的第三部分

Kohonen 网络学习结果未经输入常规化3

U型矩阵与量化误差

Kohonen 网络学习结果未经输入常规化4

聚类和Kohonen网络对测试样本的响应

样品用随机标记。当鼠标光标指向一个点时,会弹出关于神经元的提示。日志中将显示以下内容:

FileOpen error ?.csv : 5004
Data loading error, file: ?
Random Input:
457.17510   0.29727   0.57621
Matched Node Output (8,3); Hits:5; Error:0.05246704285146882; Cluster N0:
497.20453   0.28675   0.53213

因此,操作Kohonen网络的工具已经准备就绪。我们可以解决这个问题。我们将在第二篇文章中讨论这个问题。

结束语

近年来,Kohonen神经网络的开放式实现已经提供给MetaTrader用户。我们已经修复了其中一些错误,并辅以有用的工具,并用一个特殊的示范EA测试了它们的操作。源代码允许您将这些类应用于您自己的任务;我们将进一步查看相关的示例——尚未完成。

本文由MetaQuotes Software Corp.翻译自俄语原文
,网址为https://www.mql5.com/ru/articles/5472。

附加文件下载zip kohonen1mql5.zip(21.05 kb)

 

 


MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!

 

免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经(www.myfxtop.cn)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。

風險提示

MyFxtops邁投所列信息僅供參考,不構成投資建議,也不代表任何形式的推薦或者誘導行為。MyFxtops邁投非外匯經紀商,不接觸妳的任何資金。 MYFXTOPS不保證客戶盈利,不承擔任何責任。從事外彙和差價合約等金融產品的槓桿交易具有高風險,損失有可能超過本金,請量力而行,入市前需充分了解潛在的風險。過去的交易成績並不代表以後的交易成績。依據各地區法律法規,MyFxtops邁投不向中國大陸、美國、加拿大、朝鮮居民提供服務。

邁投公眾號

聯繫我們

客服QQ:981617007
Email: service@myfxtop.com

MyFxtops 邁投