Büyüyen Nöral Gaz: MQL5'te Uygulama
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:
- 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.
- Bir nöral ağa bir vektör girin .
- öğ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.
-
Kazanan nöronun yerel hatasını, buna ve vektörleri arasındaki mesafenin karesini ekleyerek güncelleyin.
-
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.
- Kazanandan giden tüm bağlantıların yaşını 1 artırın.
- 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.
- 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.
-
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.
-
Tüm nöronların hatalarını fraksiyonu kadar azaltın.
- 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
- Ücretsiz alım-satım uygulamaları
- İşlem kopyalama için 8.000'den fazla sinyal
- Finansal piyasaları keşfetmek için ekonomik haberler
Gizlilik ve Veri Koruma Politikasını ve MQL5.com Kullanım Şartlarını kabul edersiniz