English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Italiano
Büyüyen Nöral Gaz: MQL5'te Uygulama

Büyüyen Nöral Gaz: MQL5'te Uygulama

MetaTrader 5Örnekler | 15 Aralık 2021, 11:04
89 0
Alexey Subbotin
Alexey Subbotin

Giriş

90'larda yapay nöral ağları inceleyen araştırmacılar, bu hesaplama mekanizmalarından özelliği, ağ katmanlarının sabit bir topolojisinin olmaması olan yeni bir sınıf geliştirmenin gerekli olduğu sonucuna vardılar. Bu, öznitelik uzayındaki yapay nöronların sayısının ve düzeninin önceden belirlenmediği, ancak bu tür modellerin öğrenme sürecinde giriş verilerinin özelliklerine göre bağımsız olarak ayarlanarak hesaplandığı anlamına gelir.

Bu tür fikirlerin ortaya çıkmasının nedeni, konuşma ve görüntü tanıma, soyut formasyonların sınıflandırılması ve tanınması gibi giriş parametrelerinin geciktirilmiş sıkıştırılması ve vektör nicemlemesi ile ilgili bir dizi gerçekçi problemdi.

O zamandan beri özdüzenleyici haritalar ve Hebbian öğrenimi zaten biliniyordu (özellikle, ağın topolojisini üreten, yani bir katman "çerçevesi" oluşturarak nöronlar arasında bir dizi bağlantı oluşturan algoritmalar) ve "hafif" rekabete dayalı öğrenme yaklaşımları işe yaramıştı (bu tür prosedürlerde yalnızca "kazanan" nöronun değil, aynı zamanda "komşularının" da ağırlık uyarlaması gerçekleşir), mantıksal adım, bu yöntemleri birleştirmeye yönelikti; bu, şu anda popüler bir algoritma olan "Büyüyen nöral gazın"(,GNG) yaratıcısı olan Alman bilim insanı Bernd Fritzke tarafından 1995'te gerçekleştirildi.

Yöntemin oldukça başarılı olduğu kanıtlandı, böylece bir dizi değişiklik ortaya çıktı; bunlardan biri de güdümlü öğrenme için uyarlamaydı (Güdümlü-GNG). Yazar tarafından belirtildiği gibi, S-GNG, sınıflandırılması zor olan girdi alanı alanlarında topolojiyi optimize etme yeteneği nedeniyle, verilerin sınıflandırılmasında, örneğin bir radyal bazlı işlevler ağına göre önemli ölçüde daha fazla verimlilik gösterdi. Şüphesiz ki, GNG "K-araçlar" kümelemesinden üstündür.

2001'de Fritzke'nin Alman menkul kıymetler borsasında (Deutsche Bӧrse) bir iş teklifi aldıktan sonra Ruhr Üniversitesi'nde (Bochum, Almanya) bilim insanı olarak kariyerine son vermesi dikkate değerdir. Bu gerçek, algoritmasını bu makaleyi yazmak için temel dayanak olarak seçmesinin başka bir nedeniydi.

1. Büyüyen Nöral Gaz

Dolayısıyla, GNG, giriş verilerinin uyarlanabilir kümelemesini uygulamaya, yani alanı yalnızca kümelere bölmeye değil, aynı zamanda gerekli sayıları verilerin özelliklerine göre belirlemeye de imkan tanıyan bir algoritmadır.

Yalnızca iki nöronla başlayan algoritma, rekabete dayalı Hebbian öğrenme yaklaşımından yararlanarak girdi vektörlerinin dağılımına en iyi karşılık gelen nöronlar arasında bir dizi bağlantı oluştururken, bunların sayısını sürekli olarak değiştirir (çoğunlukla artırır). Her nöron, "yerel hatayı" biriktiren bir dahili değişken içerir. Düğümler arasındaki bağlantılar, "yaş" olarak adlandırılan bir değişkenle karakterize edilir.

GNG sözde kodu şöyle görünür:

  1. Başlatma: Girdi vektörlerinin dağılımının izin verdiği ağırlık vektörleri ve yerel hataların sıfır değerleri ile iki düğüm oluşturun, yaşını 0 olarak ayarlayarak düğümleri bağlayın.
  2. Bir nöral ağa bir vektör girin .
  3. öğesine en yakın iki nöron olan ve nöronlarını bulun; yani ağırlık vektörü ve olan düğümler minimum ve tüm düğümler arasındaki ikinci minimum mesafe değeri olacak şekildedir.
  4. Kazanan nöronun yerel hatasını, buna ve vektörleri arasındaki mesafenin karesini ekleyerek güncelleyin.


  5. Kazanan nöronu ve tüm topolojik komşularını (yani, kazananla bağlantısı olan tüm nöronları) girdi vektörü yönünde, tam olandan ve paylarına eşit mesafelerle kaydırın.


  6. Kazanandan giden tüm bağlantıların yaşını 1 artırın.
  7. En iyi iki nöron ve bağlıysa, bunların bağlantı yaşlarını sıfıra ayarlayın. Aksi takdirde, aralarında bir bağlantı oluşturun.
  8. yaşından büyük bağlantıları kaldırın. Bu, daha fazla yayılan kenarlara sahip olmayan nöronlarla sonuçlanırsa bu nöronları da kaldırın.
  9. Geçerli yineleme sayısı değerinin katsayısıysa ve ağın sınır boyutuna ulaşmadıysa aşağıdaki gibi yeni bir nöron ekleyin:

    • En büyük yerel hataya sahip bir nöron belirleyin.
    • Nöronun komşuları arasından maksimum hata ile belirleyin.
    • ve arasında "ortada" bir düğümü oluşturun.

    • ila arasındaki kenarı ila , ila arasındaki kenarla değiştirin.
    • ila nöronlarının hatalarını azaltın, nöronunun hata değerini ayarlayın.

  10. Tüm nöronların hatalarını fraksiyonu kadar azaltın.

  11. Durdurma kriteri henüz karşılanmadıysa adım 2 ile devam edin.

Büyüyen nöral gazın girdi alanının özelliklerine nasıl uyum sağladığını düşünelim.

Öncelikli olarak, adım 4'te kazananın hata değişkenindeki artışa dikkat edin. Bu prosedür, en sık kazanan düğümlerin, yani en fazla sayıda giriş sinyalinin belirdiği komşulardakilerin en büyük hataya sahip olduğu gerçeğine yol açar ve bu nedenle bu alanlar, yeni düğümler ekleyerek "sıkıştırma" için ana adaylardır.

Adım 5'te düğümlerin girdi vektörü yönünde kayması, kazananın pozisyonunu kendi komşularında bulunan giriş sinyalleri arasında "ortalamaya" çalıştığı anlamına gelir. Bu durumda, en iyi nöron küçük komşularını sinyal yönünde "çeker" (kural olarak seçilir).

İşlemi adım 6-8'de nöronlar arasındaki kenarlarla açıklıyorum. Eski bağlantıların eskimesi ve kaldırılmasının anlamı, ağın topolojisinin Delaunay üçgenlemesi olarak adlandırılan üçgenlemeye maksimum derecede yakın olması gerektiğidir; yani diğerleri arasında, üçgenlemedeki üçgenlerin tüm açılarının minimum açısının maksimize edildiği ("sıska" üçgenlerden kaçınarak) nöronların bir üçgenlemesi (üçgenlere altbölme).

Basitçe söylemek gerekirse, Delaunay üçgenlemesi, katmanın topolojikleştirilmesi anlamında maksimum entropide en "güzel"e karşılık gelir. Topolojik yapının ayrı bir birim olarak gerekli olmadığı, ancak adım 8'de yerleştirildiğinde yeni düğümlerin konumunu belirlemek için kullanıldığında, her zaman bir kenarın ortasında yer aldıkları belirtilmelidir.

Adım p, katmandaki tüm nöronların hata değişkenlerinin bir düzeltmesidir. Bu, ağın eski girdi vektörlerini "unutmasını" ve yenilerine daha iyi yanıt vermesini sağlamak içindir. Böylece, nöral ağların zamana bağlı, yani giriş sinyallerinin yavaşça sürüklenen dağılımları için uyarlanması için büyüyen nöral gazı kullanma olanağı elde ederiz. Ancak bu, ona girdilerin özelliklerindeki hızlı değişiklikleri izleme yeteneği sağlamaz (algoritmanın dezavantajlarının tartışıldığı bölümde daha fazla ayrıntı bulabilirsiniz).

Belki de, durdurma kriterini ayrı ayrı göz önünde bulundurmalıyız. Algoritma, analiz sistemi geliştiricilerinin hayal gücüne bırakıyor. Olası seçenekler şunlardır: Test kümesinde ağın verimliliğini kontrol etmek, nöronların ortalama hatasının dinamiklerini analiz etmek, ağın karmaşıklığını kısıtlamak vb.

Bilgilendirme amacıyla en kolay seçenekle çalışacağız; zira bu makalede hedeflenen şey yalnızca algoritmanın kendisini göstermek değil, aynı zamanda MQL5 aracılığıyla uygulama olanaklarını da göstermektir; girdilerimiz bitene kadar katmanı öğrenmeye devam edeceğiz (doğal olarak sayıları önceden tanımlanmıştır).

2. Verileri Düzenleme Yöntemini Seçme

Algoritmayı programlarken, "kümeler" olarak adlandırılanları depolama ihtiyacıyla açık bir şekilde ilgilenmemiz gerekecek. İki kümemiz olacak; bir dizi nöron ve bunların arasında bir dizi kenar. Program süresince her iki yapı da gelişecek olsa da (ve hem öğe eklemeyi hem de çıkarmayı planlıyoruz), bunun için mekanizmalar da sağlamalıyız.

Elbette ki, nesnelerin dinamik dizilerini kullanmayı deneyebiliriz, ancak çok sayıda veri kopyalama-taşıma işlemi gerçekleştirmemiz gerekir; bu da programı yavaşlatır. Belirtilen özellikleri içeren soyutlamalarla çalışmak için daha uygun bir seçenek, program grafikleri ve bunların en basit sürümü olan bağlantılı bir listedir.

Okuyucularımıza bağlantılı listenin çalışma prensibini hatırlatacağım (Şek. 1). Temel sınıfın nesneleri, üyelerden biri ile aynı nesneye bir işaretçi içerir; bu da bellekteki nesnelerin fiziksel sırasına bakılmaksızın, onları doğrusal yapılarda birleştirmeye olanak tanır. Ayrıca, listede gezinme, düğüm ekleme, yerleştirme ve silme, arama, karşılaştırma ve sıralama ve gerekirse diğer prosedürleri içeren "taşıma" sınıfı vardır.


Şekil 1. Doğrusal bağlantılı listelerin düzenlenmesinin şematik gösterimi

MetaQuotes Software Corp. uzmanları, CObject sınıfının nesnelerinin bağlantılı listelerini standart bir kitaplıkta zaten uyguladı. İlgili program kodu, MetaTrader 5'in standart teslimat paketinin MQL5\Include\Arrays klasöründe bulunan List.mqh üst bilgi dosyasında yer alır.

Tekerleği yeniden icat etmeyeceğiz ve veri yapılarımızın temeli olarak CObject ve CList sınıflarını alarak MetaQuotes'taki saygın programcıların niteliklerine güveneceğiz. Burada nesne yönelimli yaklaşımın sütunlarından birini kullanacağız – kalıtım mekanizması.

3. Modeli Programlama

Önce, "yapay nöron" kavramının yazılım biçimini tanıyalım.

OOP uygulamaları geliştirirken etik kurallarından biri, programlamaya her zaman en yaygın veri yapılarıyla başlamaktır. Yalnızca kendiniz için yazıyor olsanız dahi, özellikle de kodların diğer programcılar tarafından kullanılabileceği varsayılıyorsa, gelecekte geliştiricilerin program mantığının geliştirilmesi ve değiştirilmesi için farklı fikirlere sahip olabileceği gerçeğini unutmamalısınız; değişikliklerin hangi yerde yapılacağını önceden bilemezsiniz.

OOP prensibi, diğer geliştiricilerin sınıflarınızı incelemek zorunda kalmayacakları, bunun yerine veri yapılarını hiyerarşinin doğru yerinde bulunan verilerden devralabilmeleri gerektiğine işaret eder. Bu nedenle, ilk yazılı sınıf mümkün olduğunca soyut olmalı ve "günahlı dünyaya" daha yakınken, daha düşük düzeylerde ayrıntılar eklenmelidir.

Sorunumuza uygulandığında, bu, tüm yapay nöronlar gibi belirli sayıda sinapsa (girdi ağırlıkları) ve çıktı değerine sahip olacak CCustomNeuron sınıfı ("bir tür nöron") tanımıyla bir program yazmaya başladığımız anlamına gelir. Başlatabilecek (ağırlıklara değer atayacak), çıktısındaki sinyalin değerini hesaplayabilecek ve hatta ağırlıklarını belirli bir değere göre uyarlayabilecektir.

Daha fazla soyutlamayı güçlükle elde ederiz (sınıfımızı maksimum genelleştirilmiş bir CObject sınıfından devraldığımız gerçeğini göz önünde bulundurursak) - Tüm nöronlar belirtilen eylemleri gerçekleştirebilmelidir.

Verileri tanımlamak için Neurons.mqh üst bilgi dosyası oluşturarak bunu Include\GNG klasörüne yerleştirin.

//+------------------------------------------------------------------+
//| 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;
  }

Sınıfta tanımlanan işlevler çok basittir; bu nedenle bunların ayrıntılı açıklamalarını buraya eklemeye gerek yok. ProcessVector(double &in[]) (burada çıktı değeri normal bir algılayıcı gibi hesaplanır) giriş verisi işleme işlevini sanal değiştirici ile tanımladığımızı unutmayın.

Bu, yöntemin türetilmiş sınıflar tarafından yeniden tanımlanması halinde, çalışma zamanında dinamik olarak gerçek nesne sınıfına bağlı olarak uygun prosedürün seçileceği anlamına gelir; bu, kullanıcı etkileşimi açısından dahil olmak üzere esnekliğini artırır ve programlama için işçilik maliyetlerini azaltır.

Görünüşte nöronları bağlantılı bir listede düzenlemek için hiçbir şey yapmamış olmamıza rağmen, aslında bu, yeni sınıfın CObject sınıfından devraldığını belirttiğimiz anda zaten oldu. Dolayısıyla şimdi, sınıfımızın özel üyeleri şu şekildedir: m_first_node, m_curr_node ve m_last_node; bunlar, listenin sırasıyla ilk, mevcut ve son öğesinde "CObject'te işaretçi" türü ve noktası'dır. Ayrıca listede gezinmek için gereken tüm işlevlere sahibiz.

Şimdi, CGNGNeuron sınıfını tanımlayarak GNG katmanının bir nöronunun diğer benzerlerinden farklılıklarını özetleme zamanı:

//+------------------------------------------------------------------+
//| 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]);
     }
  }

Yani, gördüğünüz üzere, bu farklılıklar alanların varlığındadır:

  • hata – Girdi vektöründen nöron ağırlıkları vektörüne olan mevcut mesafenin karesi,
  • E – Yerel hatayı ve benzersiz bir kimliği biriktiren bir değişken,
  • uid – Bu, nöronları bağlantılarla çiftler halinde birleştirmemizi sağlamak için gereklidir (CList sınıfında bulunan basit indeksleme yeterli değildir; zira nöronları ekleyip silmemiz gerekecektir; bu da numaralandırmada karışıklığa yol açacaktır).

ProcessVector(...) işlevi değişti – Şu anda hata alanının değerini hesaplıyor.

U alanını dikkate almayın; anlamı daha sonra "Algoritma değişikliği" bölümünde açıklanacaktır.

Bir sonraki adım, iki nöron arasındaki bağlantıyı temsil eden bir sınıf yazmaktır.

//+------------------------------------------------------------------+
//| 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;
  }

Burada herhangi bir zorluk yoktur – Bir kenar iki uç içerir (uid1 ve uid2 tanımlayıcıları tarafından belirtilen nöronlar) ve yaş başlangıçta sıfıra eşittir.

Şimdi, GNG algoritmasını uygulamak için gerekli olasılıkları içerecek olan bağlantılı listelerin "taşıma" sınıfları ile çalışacağız.

İlk olarak, CList sınıfından bir nöron listesi sınıfı devralın:

//+------------------------------------------------------------------+
//| 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;
  }

Oluşturucu sınıfında, bir yalancı rastgele sayı oluşturucusu başlatılır: Bu, benzersiz tanımlayıcılar listesinin öğelerini atamak için kullanılacaktır.

Sınıf yöntemlerinin anlamını açıklığa kavuşturalım:

  • Append() yöntemi, CList sınıfının işlevselliğine bir ektir. Çağırırken, listenin sonuna bir düğüm eklenir veya öğe boşsa ilk düğüm oluşturulur.
  • Init(double &v1[],double &v2[]) işlevi, görünümünü GNG algoritmasından alır. Ağ büyümesinin iki nöronla başladığını unutmayın; bu nedenle bu işaret bizim için en uygun olacaktır. İşlev gövdesinde, m_curr_node, m_first_node, m_last_node kimliklerini kullanırken, bu sınıfın işlevselliğini kullanmak istiyorsak, açık bir şekilde CGNGNeuron* türüne dönüştürmek gerekir. (belirtilen değişkenler CList'ten devralınmıştır; bu nedenle nominal olarak CObject sınıfını gösterirler).
  • Find(int uid) işlevi, adından geldiği gibi, kimliğine göre bir nöron arar ve bulunan öğede bir işaretçi döndürür veya bulamazsa NULL değerini döndürür.
  • FindWinners(CGNGNeuron *&Winner,CGNGNeuron *&SecondWinner) – Ayrıca algoritmanın bir parçasıdır. Nöronlar listesinde bir kazanan aramamız gerekecek ve onun yanındakini girdi vektörüne yakınlık açısından aramamız gerekecek; bu işlevi bunun için kullanıyoruz. Parametrelerin bu işleve referansa göre iletildiğini unutmayın; böylece, oraya döndürülen değerler yazabiliriz, (*& "bir işaretçiye referans" anlamına gelir; bu doğru bir sözdizimidir, tersi &* ise, yasaklanan "bir referansta işaretçi" anlamına gelir: Bu durumda, derleyici bir hata üretecektir).

Bir sonraki sınıf, nöronlar arasındaki bağlantıları içeren bir listedir.

//+------------------------------------------------------------------+
//| 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);
  }

Sınıfın tanımlanmış yöntemleri:

  • Append(). Bu yöntemin uygulanması, dönüş türü dışında önceki sınıfta açıklanana benzer (ne yazık ki, MQL5'te herhangi bir sınıf şablonu yoktur; bu nedenle bunları her seferinde yazmamız gerekir).
  • Init(int uid1,int uid2) – GNG algoritması, başlangıcında bu işlevde gerçekleştirilen bir bağlantının başlatılmasını gerektirir.
  • Find(int uid1,int uid2) işlevi açıktır.
  • FindFirstConnection(int uid) ve FindNextConnection(int uid) yöntemleri arasındaki fark, ilkinin listenin başından başlayarak bir komşuyla bağlantı aramasıyken, ikincisinin, mevcut olanın (m_curr_node) yanındaki düğümle başlamasıdır.

Veri yapılarının açıklamasını ele aldık. Şimdi, kendi algoritmamızı programlamaya başlamanın zamanı.

4. Algoritmanın Sınıfı

Yeni bir GNG.mqh üst bilgi dosyası oluşturun, bunu Include\GNG klasörüne yerleştirin.

//+------------------------------------------------------------------+
//|                                                          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 sınıfı iki önemli alan içerir; nöronların bağlantılı listelerindeki işaretçiler Neurons ve bunlar arasındaki bağlantılar Connections. Bunlar, nöral ağımızın yapısının fiziksel ortamı olacaklar. Kalan alanlar, dışarıdan tanımlanan algoritmanın parametreleridir.

Yardımcı sınıf yöntemlerinden, harici parametreleri algoritmanın bir örneğine ileten ve veri yapılarını ve StoppingCriterion() durdurma kriterini başlatan Init(...) öğesini seçerdim. , daha önce kararlaştırdığımız gibi, her zaman false değerini döndüren hiçbir şey yapmaz.

Belirtilen veri vektörünü işleyen algoritmanın ana işlevi olan ProcessVector(…) işlevi, herhangi bir incelik içermez: Verileri ve bunlarla çalışma yöntemlerini, organize ettik; algoritma söz konusu olduğunda, yalnızca mekanik olarak tüm adımlarından geçmemiz gerekiyor. Koddaki konumları uygun açıklamalarla belirtilir.

5. Çalışmada Kullanma

Algoritmanın çalışma şeklini MetaTrader 5 terminalinin gerçek verileri üzerinde gösterelim.

Burada GNG'ye dayalı çalışan bir Expert Advisor oluşturmayı hedeflemiyoruz (bu bir makale için biraz fazla olurdu), yalnızca "canlı" sunum olarak adlandırılan büyüyen nöral gazın nasıl çalıştığını görmek istiyoruz.

Verileri güzel bir şekilde işlemek için fiyat ekseni boyunca 0-100 aralığında ölçeklendirilmiş boş bir pencere oluşturun. Bu amaç doğrultusunda, "boş" bir gösterge olan Dummy.mq5 göstergesini kullanıyoruz (bunun, başka bir işlevi yoktur):

//+------------------------------------------------------------------+
//|                                                        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'da GNG.mq5 adlı bir script dosyası oluşturun; bu, ağı Dummy göstergesinin penceresinde görüntüleyecektir.

Harici parametreler - Öğrenme için veri vektörlerinin sayısı ve algoritmanın parametreleri:

//--- 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;

Genel değişkenleri bildirin:

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

OnStart() işlevini yazmaya başlayın. İlk olarak gerekli pencereyi bulalım:

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

Giriş verileri için RSI göstergesinin değerlerini kullanırız. Değerleri 0 ila 100 arasında normalize edildiği için bu, elverişlidir; dolayısıyla ön işleme yapmamıza gerek kalmaz.

Nöral ağın bir girdi vektörü için, çiftin (input_dimension=2) mevcut ve önceki çubukta iki RSI değerinden oluştuğunu varsayıyoruz (bunun bilimsel adı "bir zaman serisinin iki boyutlu bir öznitelik uzayına daldırılmasıdır"). Düz bir grafikte iki boyutlu vektörleri görüntülemek daha kolaydır.

Bu nedenle, önce algoritma nesnesinin bir örneğini başlatmak ve oluşturmak için verileri hazırlayın:

//--- 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];
     }

Şimdi algoritmayı başlatın:

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

Dikdörtgen bir kutu ve bilgi etiketleri çizin (algoritmanın kaç yinelemesinin işlendiğini ve ağda kaç nöronun "büyüdüğünü" görsel olarak görmek için):

//-- 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");

Ana döngüde, algoritmanın girişi için bir vektör hazırlayın, bunu grafikte mavi bir nokta olarak gösterin:

//--- 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));

Vektörü algoritmaya iletin (Yalnızca bir işlev - Nesne yönelimli yaklaşımın avantajı budur!):

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

Grafikten eski nöronları çıkarın ve yenilerini (kırmızı daireler) ve bağlantıları (sarı noktalı çizgiler) çizin, kazananı ve ikinci en iyi nöronu Lime Yeşili ve Yeşil rengiyle vurgulayın:

      //--- 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);
  }

Kodu derleyin, Dummy göstergesini başlatın ve ardından aynı grafik üzerinde GNG script dosyasını çalıştırın. Grafikte, aşağıdaki gibi bir resim görünmelidir:


Gördüğünüz gibi, algoritma gerçekten işe yarıyor: Izgara, mavi noktaların durma yoğunluğuna göre alanlarını kaplamaya çalışan yeni gelen verilere yavaş yavaş uyum sağlıyor.

Video, öğrenme sürecinin yalnızca en başlangıcını gösterir (yalnızca 1000 yineleme, GNG'nin öğrenilmesi için gerekli vektörlerin gerçek sayısı on binlerce olabilir); ancak bu bize zaten süreç hakkında oldukça iyi bir anlayış sağlıyor.

6. Bilinen Sorunlar

Daha önce de belirtildiği gibi, GNG'nin temel sorunu, özellikleri hızla değişen durağan olmayan serileri izleyememesidir. Giriş sinyallerinin bu tür "sıçrayan" dağılımları, halihazırda belirli bir topolojik yapı kazanmış olan GNG katmanının nöronlarının büyük bir kısmının kendilerini aniden işin dışında bulmasına yol açabilir.

Ayrıca, giriş sinyalleri bulundukları bölgeye düşmediği için bu nöronlar arasındaki bağlantıların yaşı artmaz; dolayısıyla ağın sinyalin eski özelliklerini "hatırlayan" "ölü" kısmı, yararlı işler yapmaz, yalnızca bilgi işlem kaynaklarını tüketir (bkz. Şek. 2).

Yavaşça sürüklenen dağılımlar durumunda, bu olumsuz etki gözlenmez: Sürüklenme hızının, ağırlıkların uyarlanmasında nöronların "hareket hızı" ile karşılaştırılabilir olması halinde, GNG bu değişiklikleri izleyebilir.

Şekil 2. Büyüyen nöral gazın "sıçrayan" dağılıma tepkisi

Algoritmanın girişinde çok yüksek bir yeni nöron ekleme sıklığı (λ parametresi) verilirse ağda aktif olmayan ayrı (ölü) düğümler de görünebilir.

Çok düşük değeri, ağın, yinelenme olasılığı çok az olan giriş sinyallerinin istatistiksel olarak anlamsız dağılım emisyonlarını izlemeye başlamasına neden olur. Bu yere bir GNG-nöronu yerleştirilirse bundan sonra uzun bir süre hareketsiz kalacağı neredeyse kesindir.

Ayrıca, deneysel araştırmalarla gösterildiği gibi, düşük yerleştirme değeri, öğrenme sürecinin başlangıcında ortalama ağ hatasındaki hızlı düşüşe katkıda bulunsa da, öğrenme sonucunda bu göstergenin en kötü değerlerini verir: Bu tip bir ağ, verileri daha kabataslak bir şekilde toplar.

7. Algoritmayı Değiştirme

"Sıçrayan" dağılım sorunu, algoritma belirli bir şekilde değiştirilerek çözülebilir. Yaygın olarak kabul edilen değişiklik, nöronların faydasının sözde faktörünü tanıtan değişikliktir (Fayda faktörü veya GNG-U ile GNG). Bu durumda sözde koddaki değişiklikler minimum düzeyde olup aşağıdaki gibidir:

  • Her nörona "fayda faktörü" olarak adlandırılan bir değişken (CGNGNeuron sınıfının alanlarını içeren listedeki bu değişken U) uygunluğa ayarlanır,
  • Adım 4'te, nöron kazananının ağırlıklarını uyarladıktan sonra, fayda faktörünü ikinci en iyi nöronun hatası ile kazanan arasındaki farka eşit miktarda değiştiririz:



    Fiziksel olarak, bu katkı, içinde kazanan olmasaydı toplam ağ hatasının değişeceği miktardır (bu durumda, ikinci en iyi kazanan, kazanan olurdu); yani aslında nöronun genel hatayı azaltmak için kullanışlılığını karakterize eder.

  • Nöronlar adım 8'de farklı bir prensibe göre kaldırılır: Yalnızca minimum fayda değerine sahip bir düğüm kaldırılır ve yalnızca katmandaki maksimum hata değeri fayda faktörünü fazla kez aşarsa:


  • Adım 9'da yeni bir düğüm eklenirken, fayda faktörü, komşu nöronların faydaları arasındaki aritmetik ortalama olarak hesaplanır:


  • Adım 10'da, tüm nöronların fayda faktörü, hata değişkenleriyle aynı şekilde ve aynı sırada azaltılır:


Buradaki sabiti, durağansızlığı izleme yeteneği için kritik öneme sahiptir: Çok büyük değeri, yalnızca gerçekten "küçük fayda"nın değil, aynı zamanda diğer oldukça kullanışlı nöronların da kaldırılmasına yol açar; çok küçük bir değer, nadir kaldırmalara ve sonuç olarak, azaltılmış uyarlama oranına neden olur.

GNG.mqh dosyasında GNG-U algoritması, CGNGAlgorithm sınıfından türetilen bir sınıf olarak tanımlanır. Okuyucular, değişiklikleri bağımsız olarak izleyebilir ve algoritmayı kullanmayı deneyebilir.

Sonuç

Bir nöral ağ oluşturarak, MQL5 dilinde yerleşik nesne yönelimli programlamanın temel özelliklerini inceledik. Bu tür fırsatların yokluğunda (bunun için geliştiricilere minnettarım), otomatik alım satım için karmaşık programlar yazmanın çok daha karmaşık olacağı oldukça açık bir gerçek gibi görünüyor.

Analiz edilen algoritmalara gelince, doğal olarak geliştirilebilecekleri belirtilmelidir. Özellikle, yükseltme için ilk aday, harici parametrelerin sayısıdır. Bunlar oldukça fazladır ve bu, bu parametrelerin dahili değişkenler haline geleceği ve giriş verilerinin özelliklerine ve algoritmanın durumuna göre seçileceği bu tür değişikliklerin olabileceği anlamına gelir.

Makalenin yazarı, nöroinformatik okuyan ve bunu, alım satım işlemleri sırasında kullanan herkese iyi şanslar diler!

MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/163

Ekli dosyalar |
gng_en.zip (8.86 KB)
MQL5 Sihirbazı: Programlamadan Expert Advisor'lar Oluşturma MQL5 Sihirbazı: Programlamadan Expert Advisor'lar Oluşturma
Programlama için zaman kaybetmeden bir alım satım stratejisi denemek ister misiniz? MQL5 Sihirbazı'nda alım satım sinyallerinin türünü seçebilir, takip eden pozisyonlar ve para yönetimi modülleri ekleyebilirsiniz; böylelikle işiniz biter! Kendi modül uygulamalarınızı oluşturun veya İşler hizmeti aracılığıyla talimat verin ve yeni modüllerinizi mevcut olanlarla birleştirin.
"Yeni Çubuk" Olay İşleyicisi "Yeni Çubuk" Olay İşleyicisi
MQL5 programlama dili, sorunları yepyeni bir düzeyde çözme kapasitesine sahiptir. Halihazırda bu tür çözümlere sahip olan görevler dahi, nesne yönelimli programlama sayesinde bir üst düzeye çıkabiliyor. Bu makalede, oldukça güçlü ve çok yönlü bir araca dönüştürülmüş bir grafikteki yeni çubuğu kontrol etmenin özellikle basit bir örneğini ele alıyoruz. Peki bu araç ne? Bu makalede bunu öğreneceğiz.
Bulanık Mantık Kullanarak Gösterge Oluşturmanın Basit Örneği Bulanık Mantık Kullanarak Gösterge Oluşturmanın Basit Örneği
Makale, finansal piyasalar analizi için bulanık mantık kavramının pratik uygulamasına ayrılmıştır. Zarflar göstergesine dayalı iki bulanık kural temeline dayanan sinyal üreten gösterge örneğini öneriyoruz. Geliştirilen gösterge birkaç gösterge arabelleği kullanır: Hesaplamalar için 7 arabellek, grafikler için 5 arabellek ve 2 renk arabelleği.
Simulink: Expert Advisor'ların Geliştiricileri için Bir Kılavuz Simulink: Expert Advisor'ların Geliştiricileri için Bir Kılavuz
Profesyonel bir programcı değilim. Ve bu nedenle, alım satım sistemi geliştirme üzerinde çalışırken "basitten karmaşığa gitmek" prensibi benim için birincil öneme sahiptir. Benim için basit olan tam olarak nedir? Her şeyden önce, sistemi oluşturma sürecinin ve çalışmasının mantığının görselleştirilmesidir. Ayrıca, minimum elle yazılmış koddur. Bu makalemde, bir Matlab paketine dayalı alım satım sistemini oluşturup test etmeye çalışacak ve ardından MetaTrader 5 için bir Expert Advisor yazacağım. MetaTrader 5'in geçmiş verileri, test süreci için kullanılacaktır.