English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
성장하는 신경 가스: MQL5 구현

성장하는 신경 가스: MQL5 구현

MetaTrader 5 | 5 7월 2021, 16:53
67 0
Alexey Subbotin
Alexey Subbotin

소개

90 년대에 인공 신경망의 연구원들은 이러한 컴퓨팅 메커니즘의 새로운 등급을 개발해야한다는 결론에 도달했습니다. 이러한 컴퓨팅 메커니즘의 특징은 네트워크 계층의 고정 토폴로지가 없다는 것입니다. 즉, 특징 공간에서 인공 뉴런의 수와 배열이 미리 지정되어 있지 않고 입력 데이터의 특성에 따라 이러한 모델의 학습 과정에서 계산되어 독립적으로 조정됩니다.

이러한 아이디어가 등장한 이유는 음성 및 이미지 인식, 추상 패턴의 분류 및 인식과 같은 입력 매개 변수의 압축 및 벡터 양자화를 방해하는 많은 실제 문제였습니다.

그 당시 자체 구성 맵Hebbian 학습이 이미 알려져 있었기 때문에 (특히, 네트워크 토폴로지 화를 생성하는 알고리즘, 즉 뉴런 간의 연결 집합을 만들어 계층 "프레임 워크"), 그리고 "소프트" 경쟁 학습에 대한 접근 방식이 해결되었습니다 (이러한 절차에서 "승자"뉴런뿐만 아니라 "이웃"의 가중치 적응이 발생 함). 논리적 단계는 이러한 방법을 결합하는 것이 었는데, 1995 년 독일 과학자 Bernd Fritzke가 수행했으며 현재 인기있는 알고리즘 "Growing neural gas"(, GNG)를 만들었습니다.

이 방법은 매우 성공적이어서 일련의 수정이 나타났습니다. 그 중 하나는 지도 학습 (Supervised-GNG)에 대한 적응이었습니다 저자가 언급했듯이 S-GNG는 분류하기 어려운 입력 공간 영역에서 토폴로지를 최적화 할 수 있기 때문에 방사형 기저 함수 네트워크보다 데이터 분류에서 훨씬 더 높은 효율성을 보여주었습니다. 의심할 여지없이 GNG는 "K- 평균" 클러스터링보다 우수합니다.

2001년에 Fritzke는 독일 증권 거래소 (Deutsche Bӧrse)에서 일자리 제안을 받은 후 Ruhr 대학 (독일 보쿰)에서 과학자의 경력을 끝냈다는 사실은 주목할 만 합니다. 글쎄, 이 사실은 그의 알고리즘을 이 기사를 작성하는 기초로 선택한 또 다른 이유였습니다.

1. 성장하는 신경 가스

따라서 GNG는 입력 데이터의 적응 형 클러스터링을 구현할 수 있는 알고리즘입니다. 즉, 공간을 클러스터로 분할할 뿐만 아니라 데이터 특성에 따라 필요한 개수를 결정할 수도 있습니다.

두 개의 뉴런으로 시작하는 알고리즘은 경쟁적인 Hebbian 학습 방식을 사용하여 입력 벡터의 분포에 가장 잘 맞는 뉴런 간의 연결 집합을 생성하면서 지속적으로 뉴런의 수를 변경 (대부분 증가)합니다. 각 뉴런에는 소위 "로컬 오류"를 축적하는 내부 변수가 있습니다. 노드 간의 연결은 "연령"이라는 변수로 특징 지어집니다.

GNG 의사 코드는 다음과 같습니다.

  1. 초기화: 입력 벡터 분포에 의해 허용되는 가중치 벡터와 로컬 오류 값이 0 인 두 개의 노드를 만듭니다. 연령을 0으로 설정하여 노드를 연결합니다.
  2. 신경망에 벡터를 입력합니다.
  3. 두 개의 뉴런 그리고 가장 가까운 노드, 즉 가중치 벡터 그리고 최소 벡터입니다. 또한 모든 노드 간의 거리의 두 번째 최소값입니다.
  4. 벡터 (을)를 사이의 제곱 거리와 다음을 추가하여 승자 뉴런의 로컬 오류를 업데이트합니다.


  5. 공유와 같은 거리 전체 공유에서 입력 벡터 방향으로 승자 뉴런과 모든 토폴로지 이웃 (즉, 승자와 연결된 모든 뉴런)을 이동합니다.


  6. 나가는 모든 연결의 나이를 승자에서 1만큼 늘립니다.
  7. 두 개의 가장 좋은 뉴런 가 연결되어 있으면 연결 나이를 0으로 설정합니다. 그렇지 않으면 그들 사이에 연결을 만듭니다.
  8. 연령이 보다 큰 연결을 제거합니다. 이로 인해 더 이상 발산 가장자리가 없는 뉴런이 생기면 해당 뉴런도 제거합니다.
  9. 현재 반복 횟수가 의 배수이고 네트워크의 제한 크기에 도달하지 않은 경우 다음과 같이 새 뉴런을 삽입합니다.

    • 국소 오차가 가장 큰 뉴런 을 결정합니다.
    • 최대 오차가있는 뉴런 의 이웃 중에서 결정합니다.
    • "중앙에" 노드를 만듭니다 사이 그리고

    • 가장자리를 대체합니다 와/과 사이에 가장자리 사이 , 그리고 .
    • 뉴런 의 오류를 줄이고 뉴런 의 오류 값을 설정합니다.

  10. 모든 뉴런의 오류를 분수만큼 줄입니다.

  11. 중지 기준이 아직 충족되지 않은 경우 2 단계를 계속 진행합니다.

성장하는 신경 가스가 입력 공간의 특성에 어떻게 적응하는지 살펴 보겠습니다.

먼저 4 단계에서 승자의 오차 변수가 증가하는 것에 주의를 기울이십시오. 이 절차는 가장 자주 승리하는 노드, 즉 가장 많은 수의 입력 신호가 나타나는 이웃 노드가 가장 큰 오류를 가지므로 이러한 영역이 새 노드를 추가하여 "압축"할 주요 후보가 된다는 사실로 이어집니다.

5 단계에서 입력 벡터 방향으로 노드를 이동한다는 것은 승자가 이웃에 있는 입력 신호 중 포지션을 "평균화"하려고 한다는 것을 의미합니다. 이 경우 가장 좋은 뉴런은 신호 방향으로 이웃을 조금 "끌어 당깁니다"(일반적으로 가 선택됩니다).

6-8 단계에서 뉴런 사이의 가장자리에 대한 작업을 설명합니다. 노화와 오래된 연결 제거의 의미는 네트워크의 토폴로지가 소위 Delaunay 삼각 분할, 즉 뉴런의 삼각 분할 (삼각형으로 세분화)에 최대한 가까워 야한다는 것입니다. 삼각 분할에서 모든 삼각형 각도의 최소 각도가 최대화됩니다 ("가른"삼각형을 피함).

간단히 말해서 Delaunay 삼각 분할은 레이어의 최대 엔트로피, 위상화라는 의미에서 가장 "아름다운" 부분에 해당합니다. 토폴로지 구조는 별도의 단위가 아니라 8 단계에서 삽입 될 때 새 노드의 포지션을 ​​결정하는 데 사용되는 경우 항상 가장자리의 중간에 위치합니다.

단계 p는 계층에 있는 모든 뉴런의 오류 변수를 수정하는 것입니다. 이것은 네트워크가 이전 입력 벡터를 "잊고" 새로운 벡터에 더 잘 반응하도록 하기 위한 것입니다. 따라서 우리는 시간 의존적, 즉 입력 신호의 천천히 표류하는 분포를 위해 신경망의 적응을 위해 성장하는 신경 가스를 사용할 수 있습니다. 그러나 이것은 입력 특성의 급격한 변화를 추적 할 수있는 능력을 제공하지 않습니다 (알고리즘의 단점이 논의되는 섹션에서 아래의 자세한 내용 참조).

중지 기준을 별도로 고려해야 할 수도 있습니다. 알고리즘은 분석 시스템 개발자의 환상을 위한 자리를 떠납니다. 가능한 옵션은 테스트 세트에서 네트워크의 효율성을 확인하고, 뉴런의 평균 오차의 역학을 분석하고, 네트워크의 복잡성을 제한하는 것입니다.

정보 제공을 위해 가장 쉬운 옵션으로 작업할 것입니다. 이 기사의 목적은 알고리즘 자체뿐만 아니라 MQL5를 통한 구현 가능성을 보여주는 것이기 때문입니다. 입력이 다 떨어질 때까지 레이어 학습을 계속할 것입니다 (당연히 그 수는 미리 정의 됨).

2. 데이터 구성 방법 선택

알고리즘을 프로그래밍할 때 "세트"라고 하는 것을 저장해야 할 필요성을 분명히 처리해야 합니다. 뉴런 세트와 이들 사이의 에지 세트라는 두 세트가 있습니다. 두 구조 모두 프로그램 과정에서 발전 할 것이지만 (그리고 우리는 항목을 추가하고 제거할 계획입니다) 이에 대한 메커니즘도 제공해야 합니다.

물론 객체의 동적 배열을 사용하려고 할 수는 있지만 수많은 데이터 복사 이동 작업을 수행해야하므로 기본적으로 프로그램 속도가 느려집니다. 지정된 속성을 사용하여 추상화 작업에 더 적합한 옵션은 프로그램 그래프와 가장 간단한 버전인 연결 목록입니다.

연결 목록의 작동 원리를 독자들에게 상기시켜드리겠습니다 (그림 1). 기본 클래스의 개체에는 멤버 중 하나와 동일한 개체에 대한 포인터가 포함되어있으므로 메모리에 있는 개체의 물리적 순서에 관계없이 선형 구조로 결합할 수 있습니다. 또한 목록 이동, 노드 추가, 삽입 및 삭제, 검색, 비교 및 ​​정렬, 필요한 경우 기타 절차를 캡슐화하는 "carriage" 클래스가 있습니다.


그림 1. 선형 연결 목록 구성의 도식적 표현

MetaQuotes Software Corp.의 전문가들은 이미 표준 라이브러리에서 CObject 클래스의 개체 연결 목록을 구현했습니다. 해당 프로그램 코드는 MetaTrader 5의 표준 배송 팩의 MQL5\Include\Arrays에 있는 헤더 파일 List.mqh에 있습니다.

우리는 데이터 구조의 기반으로 CObjectCList 클래스를 사용하는 MetaQuotes의 존경받는 프로그래머의 자격을 신뢰하지 않고 바퀴를 재발명하지 않을 것입니다. 여기서는 객체 지향 접근 방식의 핵심 중 하나 인 상속 메커니즘을 사용합니다.

3. 모델 프로그래밍

먼저 "인공 뉴런"개념의 소프트웨어 형식을 정의하겠습니다

OOP 애플리케이션을 개발할 때 에티켓 규칙 중 하나는 항상 가장 일반적인 데이터 구조로 프로그래밍을 시작하는 것입니다. 자신만을 위해 작성하는 경우에도 특히 다른 프로그래머가 코드를 사용할 수 있다고 가정하는 경우에는 향후 개발자가 프로그램 개발 및 수정에 대해 다른 아이디어를 가질 수 있다는 사실을 명심해야 합니다. 논리; 그리고 당신은 수정이 이루어질 장소를 미리 알 수 없습니다.

OOP의 원칙은 다른 개발자가 클래스를 검사 할 필요가 없으며 대신 계층 구조의 올바른 위치에서 사용 가능한 데이터에서 데이터 구조를 상속 할 수 있어야 함을 의미합니다. 따라서 첫 번째로 쓰여진 클래스는 가능한 한 추상적이어야 하며, "죄 많은 지구"에 더 가까워질 때 세부 사항을 더 낮은 수준에 추가해야 합니다.

우리의 문제에 적용될 때 이것은 우리가 CCustomNeuron 클래스 ("어떤 종류의 뉴런")의 정의로 프로그램을 작성하기 시작한다는 것을 의미합니다. 이것은 모든 인공 뉴런과 마찬가지로 특정 수의 시냅스 (입력 가중치)와 출력을 가질 것입니다. 값. 초기화 (값을 가중치에 할당)하고, 출력에서 ​​신호 값을 계산하고, 지정된 값으로 가중치를 조정할 수도 있습니다.

(최대로 일반화 된 CObject에서 클래스를 상속한다는 사실을 고려할 때) 더 많은 추상화를 달성하기는 어렵습니다. 모든 뉴런은 지정된 작업을 수행 할 수 있어야 합니다.

데이터를 설명하기 위해 헤더 파일 Neurons.mqh를 만들고 Include\GNG 폴더에 넣습니다.

//+------------------------------------------------------------------+
//| a base class to introduce object-neurons                |
//+------------------------------------------------------------------+
class CCustomNeuron:public CObject
  {
protected:
   int               m_synapses;
   double            m_weights[];
public:
   double            NET;
                     CCustomNeuron();
                    ~CCustomNeuron(){};
   void              ZeroInit(int synapses);
   int               Synapses();
   void              Init(double &weights[]);
   void              Weights(double &weights[]);
   void              AdaptWeights(double &delta[]);
   virtual void       ProcessVector(double &in[]) {return;}
   virtual int        Type() const          { return(TYPE_CUSTOM_NEURON);}
  };
//+------------------------------------------------------------------+
//| constructor                                                      |
//+------------------------------------------------------------------+
void CCustomNeuron::CCustomNeuron()
  {
   m_synapses=0;
   NET=0;
  }
//+------------------------------------------------------------------+
//| returns the dimension of the input vector of a neuron            |
//| INPUT: no                                                        |
//| OUTPUT: number of "synapses" of the neuron                       |
//+------------------------------------------------------------------+
int CCustomNeuron::Synapses()
  {
   return m_synapses;
  }
//+------------------------------------------------------------------+
//| initializing neuron with a zero vector of weights.               |
//| INPUT: synapses - number of synapses (input weights)             |
//| OUTPUT: no                                                       |
//+------------------------------------------------------------------+
void CCustomNeuron::ZeroInit(int synapses)
  {
   if(synapses<1) return;
   m_synapses=synapses;
   ArrayResize(m_weights,m_synapses);
   ArrayInitialize(m_weights,0);
   NET=0;
  }
//+------------------------------------------------------------------+
//| initializing neuron weights with a set vector.                   |
//| INPUT: weights - data vector                                     |
//| OUTPUT: no                                                       |
//+------------------------------------------------------------------+
void CCustomNeuron::Init(double &weights[])
  {
   if(ArraySize(weights)<1) return;
   m_synapses=ArraySize(weights);
   ArrayResize(m_weights,m_synapses);
   ArrayCopy(m_weights,weights);
   NET=0;
  }
//+------------------------------------------------------------------+
//| obtaining vector of neuron weights.                              |
//| INPUT: no                                                        |
//| OUTPUT: weights - result                                         |                        
//+------------------------------------------------------------------+
void CCustomNeuron::Weights(double &weights[])
  {
   ArrayResize(weights,m_synapses);
   ArrayCopy(weights,m_weights);
  }
//+------------------------------------------------------------------+
//| change weights of the neuron by a specified value                |
//| INPUT: delta - correcting vector                                 |
//| OUTPUT: no                                                       |
//+------------------------------------------------------------------+
void CCustomNeuron::AdaptWeights(double &delta[])
  {
   if(ArraySize(delta)!=m_synapses) return;
   for(int i=0;i<m_synapses;i++) m_weights[i]+=delta[i];
   NET=0;
  }

클래스에 정의된 함수는 매우 간단하므로 여기에 자세한 설명을 포함할 필요가 없습니다. 가상 (virtual) 마디파이어를 사용하여 입력 데이터 처리 ProcessVector(double & in[]) (여기서 출력 값은 일반 퍼셉트론과 같이 계산됨)의 기능을 정의했습니다.

즉, 메소드가 파생 클래스에 의해 재정의되는 경우 런타임에 실제 개체 클래스에 따라 적절한 절차가 선택되므로 사용자 상호 작용의 의미를 포함하여 유연성이 향상되고 프로그래밍 인건비가 절감됩니다.

연결된 목록에서 뉴런을 구성하기 위해 아무 작업도 하지 않은 것 같음에도 불구하고 실제로는 새 클래스가 CObject에서 상속된다는 점을 지적한 순간에 이미 발생했습니다. 이제 우리 클래스의 private 멤버는 m_first_node, m_curr_nodem_last_node입니다. CObject "type 및 point는 각각 목록의 첫 번째, 현재 및 마지막 요소에 있습니다. 또한 목록을 탐색하는데 필요한 모든 기능이 있습니다.

이제 CGNGNeuron 클래스를 정의하여 GNG 레이어의 뉴런과 다른 동료 간의 차이점을 설명할 때입니다.

//+------------------------------------------------------------------+
//| a separate neuron of the GNG network                             |
//+------------------------------------------------------------------+
class CGNGNeuron:public CCustomNeuron
  {
public:
   int               uid;
   double            E;
   double            U;
   double            error;
                    CGNGNeuron();
   virtual void      ProcessVector(double &in[]);
  };
//+------------------------------------------------------------------+
//| constructor                                                      |
//+------------------------------------------------------------------+
CGNGNeuron::CGNGNeuron()
  {
   E=0;
   U=0;
   error=0;
  }
//+------------------------------------------------------------------+
//| calculating "distance" from the neuron to the input vector       |
//| INPUT: in - data vector                                          |
//| OUTPUT: no                                                       |
//| REMARK: the current "distance" is placed in the error variable,  |
//|         "local error" is contained in another variable,          |
//|         which is called E                                        |
//+------------------------------------------------------------------+
void CGNGNeuron::ProcessVector(double &in[])
  {
   if(ArraySize(in)!=m_synapses) return;

   error=0;
   NET=0;
   for(int i=0;i<m_synapses;i++)
     {
      error+=(in[i]-m_weights[i])*(in[i]-m_weights[i]);
     }
  }

따라서 보시다시피 이러한 차이점은 필드가 있다는 것입니다.

  • 오류 – 입력 벡터에서 뉴런 가중치 벡터까지의 현재 거리 제곱
  • E – 로컬 오류와 고유 ID를 누적하는 변수,
  • uid– 연결을 통해 뉴런을 쌍으로 더 조인 할 수 있어야 합니다 (CList 클래스에 존재하는 단순한 인덱싱으로는 충분하지 않습니다. 뉴런을 삭제하면 번호 매기기가 혼동될 수 있음).

ProcessVector(...) 함수가 변경되었습니다. 이제 error 필드의 값을 계산합니다.

지금까지 U 필드에 주의를 기울이지 마십시오. 그 의미는 나중에 "알고리즘 수정"섹션에서 설명합니다.

다음 단계는 두 뉴런 간의 연결을 나타내는 클래스를 작성하는 것입니다.

//+------------------------------------------------------------------+
//| class defining connection (edge) between two neurons             |
//+------------------------------------------------------------------+
class CGNGConnection:public CObject
  {
public:
   int               uid1;
   int               uid2;
   int               age;
                     CGNGConnection();
   virtual int       Type() const          { return(TYPE_GNG_CONNECTION);}
  };
//+------------------------------------------------------------------+
//| constructor                                                      |
//+------------------------------------------------------------------+
CGNGConnection::CGNGConnection()
  {
   age=0;
  }

여기서 어려운 것은 없습니다. 모서리에는 두 개의 끝 (uid1uid2 식별자로 지정된 뉴런)이 있고 초기에는 age가 0입니다.

이제 우리는 GNG 알고리즘을 구현하는데 필요한 가능성을 포함할 연결 목록의 "carriages" 클래스를 사용합니다.

우선 CList에서 뉴런 목록을 상속받습니다.

//+------------------------------------------------------------------+
//| linked list of neurons                                           |
//+------------------------------------------------------------------+
class CGNGNeuronList:public CList
  {
public:
   //--- constructor   
                     CGNGNeuronList() {MathSrand(TimeLocal());}
   CGNGNeuron       *Append();
   void              Init(double &v1[],double &v2[]);
   CGNGNeuron       *Find(int uid);
   void              FindWinners(CGNGNeuron *&Winner,CGNGNeuron *&SecondWinner);
  };
//+------------------------------------------------------------------+
//| adds an "empty" neuron at the end of the list                    |
//| INPUT: no                                                        |
//| OUTPUT: pointer at a new neuron                                  |
//+------------------------------------------------------------------+
CGNGNeuron *CGNGNeuronList::Append()
  {
   if(m_first_node==NULL)
     {
      m_first_node= new CGNGNeuron;
      m_last_node = m_first_node;
     }
   else
     {
      GetLastNode();
      m_last_node=new CGNGNeuron;
      m_curr_node.Next(m_last_node);
      m_last_node.Prev(m_curr_node);
     }
   m_curr_node=m_last_node;
   m_curr_idx=m_data_total++;

   while(true)
     {
      int rnd=MathRand();
      if(!CheckPointer(Find(rnd)))
        {
         ((CGNGNeuron *)m_curr_node).uid=rnd;
         break;
        }
     }
//---
   return(m_curr_node);
  }
//+------------------------------------------------------------------+
//| initializing list by way of creating two neurons set             |
//| by vectors of weights                                            |
//| INPUT: v1,v2 - vectors of weights                                |
//| OUTPUT: no                                                       |
//+------------------------------------------------------------------+
void CGNGNeuronList::Init(double &v1[],double &v2[])
  {
   Clear();
   Append();
   ((CGNGNeuron *)m_curr_node).Init(v1);
   Append();
   ((CGNGNeuron *)m_curr_node).Init(v2);
  }
//+------------------------------------------------------------------+
//| search for a neuron by uid                                       |
//| INPUT: uid - a unique ID of the neuron                           |
//| OUTPUT: pointer at the neuron if successful, otherwise NULL      |
//+------------------------------------------------------------------+
CGNGNeuron *CGNGNeuronList::Find(int uid)
  {
   if(!GetFirstNode()) return(NULL);
   do
     {
      if(((CGNGNeuron *)m_curr_node).uid==uid)
         return(m_curr_node);
     }
   while(CheckPointer(GetNextNode()));
   return(NULL);
  }
//+------------------------------------------------------------------+
//| search for two "best" neurons in terms of minimal current error  |
//| INPUT: no                                                        |
//| OUTPUT: Winner - neuron "closest" to the input vector            |
//|         SecondWinner - second "closest" neuron                   |
//+------------------------------------------------------------------+
void CGNGNeuronList::FindWinners(CGNGNeuron *&Winner,CGNGNeuron *&SecondWinner)
  {
   double err_min=0;
   Winner=NULL;
   if(!CheckPointer(GetFirstNode())) return;
   do
     {
      if(!CheckPointer(Winner) || ((CGNGNeuron *)m_curr_node).error<err_min)
        {
         err_min= ((CGNGNeuron *)m_curr_node).error;
         Winner = m_curr_node;
        }
     }
   while(CheckPointer(GetNextNode()));

   err_min=0;
   SecondWinner=NULL;
   GetFirstNode();
   do
     {
      if(m_curr_node!=Winner)
         if(!CheckPointer(SecondWinner) || ((CGNGNeuron *)m_curr_node).error<err_min)
           {
            err_min=((CGNGNeuron *)m_curr_node).error;
            SecondWinner=m_curr_node;
           }
     }
   while(CheckPointer(GetNextNode()));
   m_curr_node=Winner;
  }

건축 업자 (constructor) 클래스에서 의사 난수 생성기가 초기화되어 목록 고유 식별자의 요소를 할당하는 데 사용됩니다.

클래스 메소드의 의미를 명확히 하겠습니다.

  • Append() 메소드는 CList 클래스의 기능에 추가되었습니다. 이를 호출하면 목록 끝에 노드가 추가되거나 lust가 비어 있으면 첫 번째 노드가 생성됩니다.
  • Init (double &v1[], double &v2[]) 함수는 GNG 알고리즘에서 그 모습을 나타냅니다. 네트워크의 성장은 두 개의 뉴런에서 시작되므로 이 서명이 우리에게 가장 편리할 것입니다. 함수 본문에서 ID m_curr_node, m_first_node, m_last_node를 사용할 때 원하는 경우 CGNGNeuron* 유형으로 명시적으로 변환해야 합니다. 이 클래스의 기능을 사용합니다 (지정된 변수는 CList에서 상속되었으므로 명목상 CObject를 가리킵니다).
  • Find(int uid) 함수는 이름에서 유래되었으므로 ID로 뉴런을 검색하고 발견된 요소에서 포인터를 반환하거나 찾을 수 없는 경우 NULL을 반환합니다.
  • FindWinners(CGNGNeuron *&Winner,CGNGNeuron *& SecondWinner) – 알고리즘의 일부입니다.<부분 5986 ¶> 뉴런 목록에서 승자를 검색해야 하며 입력 벡터에 대한 근접성 측면에서 그 옆에있는 것이 이 함수를 사용하는 것입니다. 매개 변수는 참조에 의해이 함수에 전달되므로 반환된 값을 더 작성할 수 있습니다 (*&는 "포인터에 대한 참조"를 의미합니다. 이것은 올바른 구문입니다. 반대의 &*</ b3>는 금지된 "참조의 포인터"를 의미합니다. 이 경우 컴파일러가 오류를 생성합니다.)

다음 클래스는 뉴런 간의 연결 목록입니다.

//+------------------------------------------------------------------+
//| a linked list of connections between neurons                     |
//+------------------------------------------------------------------+
class CGNGConnectionList:public CList
  {
public:
   CGNGConnection   *Append();
   void              Init(int uid1,int uid2);
   CGNGConnection   *Find(int uid1,int uid2);
   CGNGConnection   *FindFirstConnection(int uid);
   CGNGConnection   *FindNextConnection(int uid);
  };
//+------------------------------------------------------------------+
//| adds an "empty" connection at the end of the list                |
//| INPUT: no                                                        |
//| OUTPUT: pointer at a new binding                                 |
//+------------------------------------------------------------------+
CGNGConnection *CGNGConnectionList::Append()
  {
   if(m_first_node==NULL)
     {
      m_first_node= new CGNGConnection;
      m_last_node = m_first_node;
     }
   else
     {
      GetLastNode();
      m_last_node=new CGNGConnection;
      m_curr_node.Next(m_last_node);
      m_last_node.Prev(m_curr_node);
     }
   m_curr_node=m_last_node;
   m_curr_idx=m_data_total++;
//---
   return(m_curr_node);
  }
//+------------------------------------------------------------------+
//| initialize the list by creating one connection                   |
//| INPUT: uid1,uid2 - IDs of neurons for the connection             |
//| OUTPUT: no                                                       |
//+------------------------------------------------------------------+
void CGNGConnectionList::Init(int uid1,int uid2)
  {
   Append();
   ((CGNGConnection *)m_first_node).uid1 = uid1;
   ((CGNGConnection *)m_first_node).uid2 = uid2;
   m_last_node = m_first_node;
   m_curr_node = m_first_node;
   m_curr_idx=0;
  }
//+------------------------------------------------------------------+
//| check if there is connection between the set neurons             |
//| INPUT: uid1,uid2 - IDs of the neurons                            |
//| OUTPUT: pointer at the connection if there is one, or NULL       |
//+------------------------------------------------------------------+
CGNGConnection *CGNGConnectionList::Find(int uid1,int uid2)
  {
   if(!CheckPointer(GetFirstNode())) return(NULL);
   do
     {
      if((((CGNGConnection *)m_curr_node).uid1==uid1 && ((CGNGConnection *)m_curr_node).uid2==uid2)
         ||(((CGNGConnection *)m_curr_node).uid1==uid2 && ((CGNGConnection *)m_curr_node).uid2==uid1))
         return(m_curr_node);
     }
   while(CheckPointer(GetNextNode()));
   return(NULL);
  }
//+------------------------------------------------------------------+
//| search for the first topological neighbor of the set neuron      |
//| starting with the first element of the list                      |
//| INPUT: uid - ID of the neuron                                    |
//| OUTPUT: pointer at the connection if there is one, or NULL       |
//+------------------------------------------------------------------+
CGNGConnection *CGNGConnectionList::FindFirstConnection(int uid)
  {
   if(!CheckPointer(GetFirstNode())) return(NULL);
   while(true)
     {
      if(((CGNGConnection *)m_curr_node).uid1==uid || ((CGNGConnection *)m_curr_node).uid2==uid) break;
      if(!CheckPointer(GetNextNode())) return(NULL);
     }
   return(m_curr_node);
  }
//+------------------------------------------------------------------+
//| search for the first topological neighbor of the set neuron      |
//| starting with the list element next to the current one           |
//| INPUT: uid - ID of the neuron                                    |
//| OUTPUT: pointer at the connection if there is one, or NULL       |
//+------------------------------------------------------------------+
CGNGConnection   *CGNGConnectionList::FindNextConnection(int uid)
  {
   if(!CheckPointer(GetCurrentNode())) return(NULL);
   while(true)
     {
      if(!CheckPointer(GetNextNode())) return(NULL);
      if(((CGNGConnection *)m_curr_node).uid1==uid || ((CGNGConnection *)m_curr_node).uid2==uid) break;
     }
   return(m_curr_node);
  }

클래스의 정의된 메소드:

  • Append(). 이 메소드의 구현은 리턴 유형을 제외하고 이전 클래스에서 설명한 것과 유사합니다 (안타깝게도 MQL5에는 클래스 템플릿이 없으므로 매번 작성해야 합니다).
  • Init(int uid1,int uid2) – GNG 알고리즘은 처음에 하나의 연결을 초기화해야하며 이 기능에서 수행됩니다.
  • Find(int uid1, int uid2) 함수는 지워졌습니다.
  • FindFirstConnection(int uid) 메소드와 FindNextConnection(int uid) 메소드의 차이점은 첫 번째 메소드가 목록의 시작부터 시작하는 인접 항목과의 연결을 찾고 있다는 것입니다. 두 번째는 현재 (m_curr_node) 옆의 노드로 시작합니다.

여기서 데이터 구조에 대한 설명은 끝났습니다. 자체 알고리즘 프로그래밍을 시작할 때입니다.

4. 알고리즘의 클래스

새 헤더 파일 GNG.mqh를 만들고 Include\GNG 폴더에 저장합니다.

//+------------------------------------------------------------------+
//|                                                          GNG.mqh |
//|                                             Copyright 2010, alsu |
//|                                                 alsufx@gmail.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, alsu"
#property link      "alsufx@gmail.com"

#include "Neurons.mqh"
//+------------------------------------------------------------------+
//| the main class representing the GNG algorithm                    |
//+------------------------------------------------------------------+
class CGNGAlgorithm
  {
public:
   //--- linked lists of object-neurons and connection between them
   CGNGNeuronList   *Neurons;
   CGNGConnectionList *Connections;
   //--- parameters of the algorithm
   int               input_dimension;
   int               iteration_number;
   int               lambda;
   int               age_max;
   double            alpha;
   double            beta;
   double            eps_w;
   double            eps_n;
   int               max_nodes;

                     CGNGAlgorithm();
                    ~CGNGAlgorithm();
   virtual void      Init(int __input_dimension,
                          double &v1[],
                          double &v2[],
                          int __lambda,
                          int __age_max,
                          double __alpha,
                          double __beta,
                          double __eps_w,
                          double __eps_n,
                          int __max_nodes);
   virtual bool      ProcessVector(double &in[],bool train=true);
   virtual bool      StoppingCriterion();
  };
//+------------------------------------------------------------------+
//| constructor                                                      |
//+------------------------------------------------------------------+
CGNGAlgorithm::CGNGAlgorithm(void)
  {
   Neurons=new CGNGNeuronList();
   Connections=new CGNGConnectionList();
   
   Neurons.FreeMode(true);
   Connections.FreeMode(true);
  }
//+------------------------------------------------------------------+
//| destructor                                                       |
//+------------------------------------------------------------------+
CGNGAlgorithm::~CGNGAlgorithm(void)
  {
   delete Neurons;
   delete Connections;
  }
//+------------------------------------------------------------------+
//| initializes the algorithm using two vectors of input data        |
//| INPUT: v1,v2 - input vectors                                     |
//|        __lambda - number of iterations after which a new         |
//|        neuron is inserted                                        |
//|        __age_max - maximum age of connection                     |
//|        __alpha, __beta - used for adapting errors                |
//|        __eps_w, __eps_n - used for adapting weights              |
//|        __max_nodes - limit on the network size                   |
//| OUTPUT: no                                                       |
//+------------------------------------------------------------------+
void CGNGAlgorithm::Init(int __input_dimension,
                         double &v1[],
                         double &v2[],
                         int __lambda,
                         int __age_max,
                         double __alpha,
                         double __beta,
                         double __eps_w,
                         double __eps_n,
                         int __max_nodes)
  {
   iteration_number=0;
   input_dimension=__input_dimension;
   lambda=__lambda;
   age_max=__age_max;
   alpha= __alpha;
   beta = __beta;
   eps_w = __eps_w;
   eps_n = __eps_n;
   max_nodes=__max_nodes;
   Neurons.Init(v1,v2);

   CGNGNeuron *tmp;
   tmp=Neurons.GetFirstNode();
   int uid1=tmp.uid;
   tmp=Neurons.GetLastNode();
   int uid2=tmp.uid;

   Connections.Init(uid1,uid2);
  }
//+------------------------------------------------------------------+
//| the main function of the algorithm                               |
//| INPUT: in - vector of input data                                 |
//|        train - if true, start learning, otherwise                |
//|        only calculate the input values of neurons                |
//| OUTPUT: true, if stop condition is fulfilled, otherwise false    |
//+------------------------------------------------------------------+
bool CGNGAlgorithm::ProcessVector(double &in[],bool train=true)
  {
   if(ArraySize(in)!=input_dimension) return(StoppingCriterion());

   int i;

   CGNGNeuron *tmp=Neurons.GetFirstNode();
   while(CheckPointer(tmp))
     {
      tmp.ProcessVector(in);
      tmp=Neurons.GetNextNode();
     }

   if(!train) return(false);

   iteration_number++;
//--- Find two neurons closest to in[], i.e. the nodes with weight vectors 
//--- Ws and Wt, so that ||Ws-in||^2 is minimal and ||Wt-in||^2 -    
//--- is second minimal value of distance of all the nodes.        
//--- Under ||*|| we mean Euclidean norm                
   CGNGNeuron *Winner,*SecondWinner;
   Neurons.FindWinners(Winner,SecondWinner);

//--- Update the local error of the winner                     
   Winner.E+=Winner.error;

//--- Shift the winner and all its topological neighbors (i.e.
//--- all neurons connected with the winner) in the direction of the input
//--- vector by distances equal to fractions eps_w and eps_n of the full.    
   double delta[],weights[];

   Winner.Weights(weights);
   ArrayResize(delta,input_dimension);

   for(i=0;i<input_dimension;i++) delta[i]=eps_w*(in[i]-weights[i]);
   Winner.AdaptWeights(delta);

//--- Increment the age of all connections emanating from the winner by 1. 
   CGNGConnection *tmpc=Connections.FindFirstConnection(Winner.uid);
   while(CheckPointer(tmpc))
     {
      if(tmpc.uid1==Winner.uid) tmp = Neurons.Find(tmpc.uid2);
      if(tmpc.uid2==Winner.uid) tmp = Neurons.Find(tmpc.uid1);

      tmp.Weights(weights);
      for(i=0;i<input_dimension;i++) delta[i]=eps_n*(in[i]-weights[i]);
      tmp.AdaptWeights(delta);

      tmpc.age++;

      tmpc=Connections.FindNextConnection(Winner.uid);
     }

//--- If two best neurons are connected, reset the age of the connection.    
//--- Otherwise create a connection between them.                     
   tmpc=Connections.Find(Winner.uid,SecondWinner.uid);
   if(tmpc) tmpc.age=0;
   else
     {
      Connections.Append();
      tmpc=Connections.GetLastNode();
      tmpc.uid1 = Winner.uid;
      tmpc.uid2 = SecondWinner.uid;
      tmpc.age=0;
     }

//--- Delete all the connections with an age larger than age_max.       
//--- If this results in neurons having no connections with other    
//--- nodes, remove those neurons.                                     
   tmpc=Connections.GetFirstNode();
   while(CheckPointer(tmpc))
     {
      if(tmpc.age>age_max)
        {
         Connections.DeleteCurrent();
         tmpc=Connections.GetCurrentNode();
        }
      else tmpc=Connections.GetNextNode();
     }

   tmp=Neurons.GetFirstNode();
   while(CheckPointer(tmp))
     {
      if(!Connections.FindFirstConnection(tmp.uid))
        {
         Neurons.DeleteCurrent();
         tmp=Neurons.GetCurrentNode();
        }
      else tmp=Neurons.GetNextNode();
     }

//--- If the number of the current iteration is multiple of lambda, and the network   
//--- hasn't been reached yet, create a new neuron r according to the following rules  
   CGNGNeuron *u,*v;
   if(iteration_number%lambda==0 && Neurons.Total()<max_nodes)
     {
      //--- 1.Find neuron u with the maximum local error.               
      tmp=Neurons.GetFirstNode();
      u=tmp;
      while(CheckPointer(tmp=Neurons.GetNextNode()))
        {
         if(tmp.E>u.E)
            u=tmp;
        }

      //--- 2.determin among the neighbors of u the node u with the maximum local error. 
      tmpc=Connections.FindFirstConnection(u.uid);
      if(tmpc.uid1==u.uid) v=Neurons.Find(tmpc.uid2);
      else v=Neurons.Find(tmpc.uid1);
      while(CheckPointer(tmpc=Connections.FindNextConnection(u.uid)))
        {
         if(tmpc.uid1==u.uid) tmp=Neurons.Find(tmpc.uid2);
         else tmp=Neurons.Find(tmpc.uid1);
         if(tmp.E>v.E)
            v=tmp;
        }

      //--- 3.Create a node r "in the middle" between u and v.                      
      double wr[],wu[],wv[];

      u.Weights(wu);
      v.Weights(wv);
      ArrayResize(wr,input_dimension);
      for(i=0;i<input_dimension;i++) wr[i]=(wu[i]+wv[i])/2;

      CGNGNeuron *r=Neurons.Append();
      r.Init(wr);
      //--- 4.Replace the connection between u and v by a connection between u and r, v and r       
      tmpc=Connections.Append();
      tmpc.uid1=u.uid;
      tmpc.uid2=r.uid;

      tmpc=Connections.Append();
      tmpc.uid1=v.uid;
      tmpc.uid2=r.uid;

      Connections.Find(u.uid,v.uid);
      Connections.DeleteCurrent();

      //--- 5.Decrease the errors of neurons u and v, set the value of the error of  
      //---   neuron r the same as of u.                                 

      u.E*=alpha;
      v.E*=alpha;
      r.E = u.E;
     }

//--- Decrease the errors of all neurons by the fraction beta                     
   tmp=Neurons.GetFirstNode();
   while(CheckPointer(tmp))
     {
      tmp.E*=(1-beta);
      tmp=Neurons.GetNextNode();
     }

//--- Check the stopping criterion                                      
   return(StoppingCriterion());
  }
//+------------------------------------------------------------------+
//| Stopping criterion. In this version of file makes no             |
//| actions, always returns false.                                   |
//| INPUT: no                                                        |
//| OUTPUT: true, if the criterion is fulfilled, otherwise false     |
//+------------------------------------------------------------------+
bool CGNGAlgorithm::StoppingCriterion()
  {
   return(false);
  }

CGNGAlgorithm 클래스에는 두 개의 중요한 필드가 있습니다. 뉴런 뉴런의 연결 목록에있는 포인터와 연결 사이의 연결입니다. 그것들은 우리 신경망 구조의 물리적 매체가 될 것입니다. 나머지 필드는 외부에서 정의된 알고리즘의 매개 변수입니다.

보조 클래스 메소드 중에서 외부 매개 변수를 알고리즘 인스턴스에 전달하고 데이터 구조와 중지 기준을 초기화하는 Init(...)을 골라냅니다. StoppingCriterion() 이전에 동의했듯이 항상 false를 반환하는 작업을 수행하지 않습니다.

지정된 데이터 벡터를 처리하는 알고리즘의 주요 기능인 ProcessVector(…) 함수는 미묘한 부분을 포함하지 않습니다. 데이터 및 작업 방법을 구성하여 알고리즘에 관해서는 모든 단계를 기계적으로 진행하기만 하면 됩니다. 코드에서 해당 위치는 적절한 주석으로 표시됩니다.

5. 직장에서 사용

MetaTrader 5 단말기의 실제 데이터에 대한 알고리즘 작업을 시연해보겠습니다.

여기서 우리는 GNG를 기반으로 작동하는 Expert Advisor를 만드는 것을 목표로 하지 않습니다. (이것은 한 기사에 대해 약간입니다), 성장하는 신경 가스가 어떻게 작동하는지, "라이브" 프리젠테이션이라고하는 것만 보고 싶습니다.

데이터를 아름답게 렌더링하려면 0-100 범위의 가격 축을 따라 크기가 조정된 빈 창을 만듭니다. 이를 위해 "빈" 인디케이터 Dummy.mq5를 사용합니다 (다른 기능이 없음).

//+------------------------------------------------------------------+
//|                                                        Dummy.mq5 |
//|                                             Copyright 2010, alsu |
//|                                                 alsufx@gmail.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, alsu"
#property link      "alsufx@gmail.com"
#property version   "1.00"
#property indicator_separate_window
#property indicator_minimum 0
#property indicator_maximum 100
#property indicator_buffers 1
#property indicator_plots   1
//--- plot Label1
#property indicator_type1   DRAW_LINE
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- indicator buffers
double         DummyBuffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,DummyBuffer,INDICATOR_DATA);
   IndicatorSetString(INDICATOR_SHORTNAME,"GNG_dummy");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
  {
//--- an empty buffer
   ArrayInitialize(DummyBuffer,EMPTY_VALUE);

//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

MetaEditor에서 GNG.mq5라는 스크립트를 생성합니다. Dummy 인디케이터 창에 네트워크가 표시됩니다.

외부 매개 변수 - 학습을 위한 데이터 벡터 수와 알고리즘 매개 변수:

//--- the number of input vectors used for learning
input int      samples=1000;

//--- parameters of the algorithm
input int lambda=20;
input int age_max=15;
input double alpha=0.5;
input double beta=0.0005;
input double eps_w=0.05;
input double eps_n=0.0006;
input int max_nodes=100;

전역 변수 선언:

//---global variables
CGNGAlgorithm *GNGAlgorithm;
int window;
int rsi_handle;
int input_dimension;
int _samples;
double RSI_buffer[];
datetime time[];

OnStart() 함수 작성을 시작합니다. 먼저 필요한 창을 찾아보겠습니다.

void OnStart()
  {
   int i,j;
   int window=ChartWindowFind(0,"GNG_dummy");

입력 데이터의 경우 RSI 인디케이터의 값을 사용합니다. 값이 0에서 100까지의 범위에서 정규화되므로 전처리를 수행할 필요가 없기 때문에 편리합니다.

신경망의 입력 벡터의 경우 현재 및 이전 바에서 두 개의 RSI 값으로 구성된 쌍 (input_dimension = 2)을 가정합니다 (학명은 "2 차원 특성 공간에 시계열 침지"임). 평면 차트에 2 차원 벡터를 표시하는 것이 더 쉽습니다.

따라서 먼저 알고리즘 객체의 인스턴스를 초기화하고 생성할 데이터를 준비합니다.

//--- to have CopyBuffer() work correctly, the number of the vectors 
//--- must be within the number of bars with a reserve left for the vector length 
   _samples=samples+input_dimension+10;
   if(_samples>Bars(_Symbol,_Period)) _samples=Bars(_Symbol,_Period);

//--- receive input data for the algorithm
   rsi_handle=iRSI(NULL,0,8,PRICE_CLOSE);
   CopyBuffer(rsi_handle,0,1,_samples,RSI_buffer);

//--- return the user-defined value
   _samples=_samples-input_dimension-10;

//--- remember open time of the first 100 bars
   CopyTime(_Symbol,_Period,0,100,time);

//--- create an instance of the algorithm and set the size of input data
   GNGAlgorithm=new CGNGAlgorithm;
   input_dimension=2;

//--- data vectors
   double v[],v1[],v2[];
   ArrayResize(v,input_dimension);
   ArrayResize(v1,input_dimension);
   ArrayResize(v2,input_dimension);

   for(i=0;i<input_dimension;i++)
     {
      v1[i] = RSI_buffer[i];
      v2[i] = RSI_buffer[i+3];
     }

이제 알고리즘을 초기화합니다.

//--- initialization
   GNGAlgorithm.Init(input_dimension,v1,v2,lambda,age_max,alpha,beta,eps_w,eps_n,max_nodes);

사각형 상자와 정보 레이블을 그립니다: (알고리즘의 반복 횟수와 네트워크에서 "성장"한 뉴런 수를 시각적으로 확인).

//-- draw a rectangular box and information labels
   ObjectCreate(0,"GNG_rect",OBJ_RECTANGLE,window,time[0],0,time[99],100);
   ObjectSetInteger(0,"GNG_rect",OBJPROP_BACK,true);
   ObjectSetInteger(0,"GNG_rect",OBJPROP_COLOR,DarkGray);
   ObjectSetInteger(0,"GNG_rect",OBJPROP_BGCOLOR,DarkGray);

   ObjectCreate(0,"Label_samples",OBJ_LABEL,window,0,0);
   ObjectSetInteger(0,"Label_samples",OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER);
   ObjectSetInteger(0,"Label_samples",OBJPROP_CORNER,CORNER_RIGHT_UPPER);
   ObjectSetInteger(0,"Label_samples",OBJPROP_XDISTANCE,10);
   ObjectSetInteger(0,"Label_samples",OBJPROP_YDISTANCE,10);
   ObjectSetInteger(0,"Label_samples",OBJPROP_COLOR,Red);
   ObjectSetString(0,"Label_samples",OBJPROP_TEXT,"Total samples: 2");

   ObjectCreate(0,"Label_neurons",OBJ_LABEL,window,0,0);
   ObjectSetInteger(0,"Label_neurons",OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER);
   ObjectSetInteger(0,"Label_neurons",OBJPROP_CORNER,CORNER_RIGHT_UPPER);
   ObjectSetInteger(0,"Label_neurons",OBJPROP_XDISTANCE,10);
   ObjectSetInteger(0,"Label_neurons",OBJPROP_YDISTANCE,25);
   ObjectSetInteger(0,"Label_neurons",OBJPROP_COLOR,Red);
   ObjectSetString(0,"Label_neurons",OBJPROP_TEXT,"Total neurons: 2");

메인 루프에서 알고리즘 입력을 위한 벡터를 준비하여 차트에 파란색 점으로 표시합니다.

//--- start the main loop of the algorithm with i=2 because 2 were used already
   for(i=2;i<_samples;i++)
     {
      //--- fill out the data vector (for clarity, get samples separated
      //--- by 3 bars - they are less correlated)
      for(j=0;j<input_dimension;j++)
         v[j]=RSI_buffer[i+j*3];

      //--- show the vector on the chart
      ObjectCreate(0,"Sample_"+i,OBJ_ARROW,window,time[v[0]],v[1]);
      ObjectSetInteger(0,"Sample_"+i,OBJPROP_ARROWCODE,158);
      ObjectSetInteger(0,"Sample_"+i,OBJPROP_COLOR,Blue);
      ObjectSetInteger(0,"Sample_"+i,OBJPROP_BACK,true);

      //--- change the information label
      ObjectSetString(0,"Label_samples",OBJPROP_TEXT,"Total samples: "+string(i+1));

벡터를 알고리즘에 전달합니다 (단 하나의 함수 - 객체 지향 접근 방식의 장점입니다!):

//--- pass the input vector to the algorithm for calculation
      GNGAlgorithm.ProcessVector(v);

차트에서 오래된 뉴런을 제거하고 새 뉴런 (빨간색 원)과 연결 (노란색 점선)을 그리고 승자 및 두 번째로 좋은 뉴런을 라임색과 녹색으로 강조 표시합니다.

      //--- we need to remove old neurons an connections from the chart to draw new ones then
      for(j=ObjectsTotal(0)-1;j>=0;j--)
        {
         string name=ObjectName(0,j);
         if(StringFind(name,"Neuron_")>=0)
           {
            ObjectDelete(0,name);
           }
         else if(StringFind(name,"Connection_")>=0)
           {
            ObjectDelete(0,name);
           }
        }
      double weights[];
      CGNGNeuron *tmp,*W1,*W2;
      CGNGConnection *tmpc;

      GNGAlgorithm.Neurons.FindWinners(W1,W2);

      //--- drawing the neurons
      tmp=GNGAlgorithm.Neurons.GetFirstNode();
      while(CheckPointer(tmp))
        {
         tmp.Weights(weights);

         ObjectCreate(0,"Neuron_"+tmp.uid,OBJ_ARROW,window,time[weights[0]],weights[1]);
         ObjectSetInteger(0,"Neuron_"+tmp.uid,OBJPROP_ARROWCODE,159);

         //--- the winner is colored Lime, second best - Green, others - Red
         if(tmp==W1) ObjectSetInteger(0,"Neuron_"+tmp.uid,OBJPROP_COLOR,Lime);
         else if(tmp==W2) ObjectSetInteger(0,"Neuron_"+tmp.uid,OBJPROP_COLOR,Green);
         else ObjectSetInteger(0,"Neuron_"+tmp.uid,OBJPROP_COLOR,Red);

         ObjectSetInteger(0,"Neuron_"+tmp.uid,OBJPROP_BACK,false);

         tmp=GNGAlgorithm.Neurons.GetNextNode();
        }
      ObjectSetString(0,"Label_neurons",OBJPROP_TEXT,"Total neurons: "+string(GNGAlgorithm.Neurons.Total()));

      //--- drawing connections
      tmpc=GNGAlgorithm.Connections.GetFirstNode();
      while(CheckPointer(tmpc))
        {
         int x1,x2,y1,y2;

         tmp=GNGAlgorithm.Neurons.Find(tmpc.uid1);
         tmp.Weights(weights);
         x1=weights[0];y1=weights[1];

         tmp=GNGAlgorithm.Neurons.Find(tmpc.uid2);
         tmp.Weights(weights);
         x2=weights[0];y2=weights[1];

         ObjectCreate(0,"Connection_"+tmpc.uid1+"_"+tmpc.uid2,OBJ_TREND,window,time[x1],y1,time[x2],y2);
         ObjectSetInteger(0,"Connection_"+tmpc.uid1+"_"+tmpc.uid2,OBJPROP_WIDTH,1);
         ObjectSetInteger(0,"Connection_"+tmpc.uid1+"_"+tmpc.uid2,OBJPROP_STYLE,STYLE_DOT);
         ObjectSetInteger(0,"Connection_"+tmpc.uid1+"_"+tmpc.uid2,OBJPROP_COLOR,Yellow);
         ObjectSetInteger(0,"Connection_"+tmpc.uid1+"_"+tmpc.uid2,OBJPROP_BACK,false);

         tmpc=GNGAlgorithm.Connections.GetNextNode();
        }

      ChartRedraw();
     }
     
     //--- delete the instance of the algorithm from the memory
     delete GNGAlgorithm;
     
     //--- a pause before clearing the chart
     while(!IsStopped());
     
     //--- remove all the drawings from the chart
     ObjectsDeleteAll(0,window);
  }

코드를 컴파일하고 더미 인디케이터를 시작한 다음 동일한 차트에서 GNG 스크립트를 실행합니다. 다음과 같은 그림이 차트에 나타나야 합니다.


알고리즘은 실제로 작동합니다. 그리드는 파란색 점의 스탠드 밀도에 따라 공간을 덮으려는 새로운 수신 데이터에 점차적으로 적응합니다.

비디오는 학습 과정의 시작 부분만을 보여줍니다 (1000 번의 반복에 불과한 반면 GNG 학습에 필요한 벡터의 실제 수는 최대 수만 개가 될 수 있습니다). 그러나 이것은 이미 우리에게 프로세스에 대한 상당한 이해를 제공합니다.

6. 알려진 문제점

이미 언급했듯이 GNG의 주요 문제는 빠르게 변화하는 특성을 가진 비정상 시리즈를 추적할 수 없다는 것입니다. 입력 신호의 이러한 "점프" 분포는 이미 특정 토폴로지 구조를 얻은 GNG 계층의 많은 뉴런이 갑자기 사업을 중단하게 만들 수 있습니다.

더욱이 입력 신호가 해당 포지션의 영역에 속하지 않기 때문에 이러한 뉴런 간의 연결 수명이 증가하지 않으므로 신호의 오래된 특성을 "기억" 하는 네트워크의 "죽은" 부분은 유용한 작업을 수행하지는 않지만 컴퓨팅 리소스만 소비합니다 (참조. 그림. 2).

느리게 표류하는 분포의 경우이 역효과는 관찰되지 않습니다. 표류 속도가 가중치 적응에서 뉴런의 "이동 속도"와 비슷하면 GNG는 이러한 변화를 추적할 수 있습니다.

그림 2. "점프" 분포에 대한 성장하는 신경 가스의 반응

알고리즘 입력에 새로운 뉴런 (파라미터 λ)의 삽입 빈도가 매우 높으면 별도의 비활성 (죽은) 노드가 네트워크에 나타날 수도 있습니다.

값이 너무 낮으면 네트워크가 재발 확률이 매우 작은 입력 신호 분포의 통계적으로 미미한 방출을 추적하기 시작합니다. 이 자리에 GNG 뉴런을 삽입하면 거의 확실하게 오랜 시간 동안 비활성 상태로 유지됩니다.

또한 경험적 연구에서 알 수 있듯이, 낮은 삽입 값은 학습 프로세스 시작시 평균 네트워크 오류의 급격한 감소에 기여하지만 훈련의 결과로 이 인디케이터의 최악의 값을 제공합니다. 네트워크는 데이터를 더 조잡하게 클러스터합니다.

7. 알고리즘 수정

"점프" 분포 문제는 특정 방식으로 알고리즘을 수정하여 해결할 수 있습니다. 널리 받아 들여지는 수정은 소위 뉴런의 유용성 요인 (유용성 요인을 가진 GNG 또는 GNG-U)을 도입하는 수정입니다. 이 경우 의사 코드의 변경 사항은 최소한이며 다음과 같습니다.

  • 각 뉴런 에 "유틸리티 인자" (CGNGNeuron 클래스의 필드 목록에있는 해당 변수 U)라는 변수가 적합성으로 설정됩니다.
  • 4 단계에서 뉴런 승자의 가중치를 조정한 후 두 번째로 좋은 뉴런의 실수와 승자의 차이와 동일한 양으로 유틸리티 계수를 변경합니다.



    물리적으로 이 첨가물은 승자가 없는 경우 전체 네트워크 오류가 변경되었을 양입니다 (두 번째로 좋은 승자가 승자가 됩니다). 즉, 실제로 전체 오류를 줄이기 위한 뉴런의 유용성을 특성화합니다.

  • 뉴런은 8 단계에서 다른 원칙으로 제거됩니다. 최소 효용 값이 있는 노드만 제거되고 계층의 최대 오류 값이 효용 계수를 배 이상 초과하는 경우에만 제거됩니다.


  • 9 단계에서 새 노드를 추가 할 때 유틸리티 계수는 인접 뉴런의 유틸리티 간의 산술 평균으로 계산됩니다.


  • 10 단계에서 모든 뉴런의 유틸리티 계수는 오류 변수와 동일한 방식 및 동일한 순서로 감소합니다.


여기서 상수는 비정상성을 추적하는 능력에 매우 중요합니다. 값이 너무 크면 실제로 "유용성이 거의 없음" 뿐만 아니라 다른 매우 유용한 뉴런도 제거됩니다. 값이 너무 작으면 제거가 거의 발생하지 않아 적응 속도가 감소합니다.

GNG.mqh 파일에서 GNG-U 알고리즘은 CGNGAlgorithm에서 파생된 클래스로 설명됩니다. 독자는 변경 사항을 독립적으로 추적하고 알고리즘을 사용해 볼 수 있습니다.

결론

신경망을 생성하여 MQL5 언어로 구축된 객체 지향 프로그래밍의 주요 기능을 살펴 보았습니다. 그러한 기회가 없다면 (개발자에게 감사드립니다) 자동화된 거래를위한 복잡한 프로그램을 작성하는 것이 훨씬 더 복잡 할 것입니다.

분석된 알고리즘은 자연스럽게 개선 될 수 있다는 점에 유의해야 합니다. 특히 업그레이드의 첫 번째 후보는 외부 매개 변수의 수입니다. 그것들은 상당히 많으며, 이는 이러한 매개 변수가 내부 변수가 되고 입력 데이터의 특성과 알고리즘의 상태에 따라 선택되는 수정이 있을 수 있음을 의미합니다.

이 기사의 저자는 신경 정보학을 연구하고 거래에 사용하는 모든 사람에게 행운을 빕니다!

MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/163

MQL5 마법사: 프로그래밍 없이 Expert Advisor 만들기 MQL5 마법사: 프로그래밍 없이 Expert Advisor 만들기
프로그래밍에 시간을 낭비하지 않고 거래 전략을 시도하고 싶습니까? MQL5 마법사에서 거래 신호 유형을 선택하고 추적 포지션 및 자금 관리 모듈을 추가하면 작업이 완료됩니다! 고유한 모듈 구현을 생성하거나 작업 서비스를 통해 주문하고 새 모듈을 기존 모듈과 결합합니다.
"New Bar" 이벤트 핸들러 "New Bar" 이벤트 핸들러
MQL5 프로그래밍 언어는 새로운 수준에서 문제를 해결할 수 있습니다. 객체 지향 프로그래밍 덕분에 이미 그러한 솔루션이있는 작업조차도 더 높은 수준으로 올라갈 수 있습니다. 이 기사에서는 차트에서 새로운 바를 확인하는 매우 간단한 예를 살펴 보겠습니다. 이는 다소 강력하고 다양한 도구로 변환되었습니다. 어떤 도구? 이 기사에서 알아보십시오.
퍼지 로직 (Fuzzy Logic)을 이용한 인디케이터 생성의 간단한 예 퍼지 로직 (Fuzzy Logic)을 이용한 인디케이터 생성의 간단한 예
이 글은 금융 시장 분석을위한 fuzzy logic 개념의 실제 적용에 전념합니다. Envelopes 인디케이터를 기반으로 두 가지 퍼지 규칙을 기반으로 신호를 생성하는 인디케이터의 예를 제안합니다. 개발된 인디케이터는 여러 인디케이터 버퍼를 사용함: 계산용 버퍼 7 개, 차트 디스플레이 용 버퍼 5 개, 색상 버퍼 2 개.
Simulink: Expert Advisor 개발자를 위한 가이드 Simulink: Expert Advisor 개발자를 위한 가이드
저는 전문 프로그래머가 아닙니다. 따라서 거래 시스템 개발 작업을 할 때 "단순한 것에서 복잡한 것으로가는 것"의 원칙이 가장 중요합니다. 나에게 정확히 무엇이 간단합니까? 우선 그것은 시스템을 만드는 과정과 그 작업의 논리를 시각화하는 것입니다. 또한 최소한의 수기 코드입니다. 이 기사에서는 Matlab 패키지를 기반으로 거래 시스템을 만들고 테스트한 다음 MetaTrader 5에 대한 Expert Advisor를 작성하려고 합니다. MetaTrader 5의 과거 데이터는 테스트 프로세스에 사용됩니다.