English 日本語
preview
Wechselseitige Information als Kriterium für die schrittweise Auswahl von Merkmalen

Wechselseitige Information als Kriterium für die schrittweise Auswahl von Merkmalen

MetaTrader 5Statistik und Analyse | 2 April 2025, 07:54
67 0
Francis Dube
Francis Dube

Einführung

Die wechselseitige Information ist ein wertvolles Instrument zur Ermittlung effektiver Prädiktoren, insbesondere bei komplexen, nichtlinearen Beziehungen. Sie kann Abhängigkeiten aufdecken, die anderen Methoden möglicherweise entgehen, sodass sie sich besonders für Modelle eignet, die solche Feinheiten ausnutzen können. Dieser Artikel untersucht die Anwendung der wechselseitigen Information bei der Merkmalsauswahl und konzentriert sich auf den Algorithmus, den Hanchuan Peng, Fuhui Long und Chris Ding in ihrem Forschungspapier mit dem Titel „Feature Selection Based on Mutual Information: Criteria of Max-Dependency, Max-Relevance, and Min-Redundancy“ vorgeschlagen haben.

Wir werden zunächst die Schätzung der wechselseitigen Information für kontinuierliche Variablen erörtern und uns dann mit dem Prozess der Merkmalsauswahl selbst befassen. Schließlich wird die Wirksamkeit des Algorithmus anhand von Beispielen mit synthetischen und realen Datensätzen veranschaulicht.


Schätzung der wechselseitigen Information von kontinuierlichen Variablen

Die wechselseitige (mutual) Information, bezeichnet als I(X;Y), quantifiziert die gemeinsame Information zwischen zwei Zufallsvariablen, X und Y. Für diskrete Variablen ist ihre Berechnung relativ einfach und beinhaltet die Summierung über gemeinsame und marginale Wahrscheinlichkeiten.

Diskrete wechselseitige Information

Kontinuierliche Variablen stellen jedoch aufgrund ihrer unendlichen Bandbreite an möglichen Werten eine große Herausforderung dar. Ein gängiger Ansatz zur Lösung dieses Problems ist die Diskretisierung der kontinuierlichen Variablen in Bins und die anschließende Anwendung der Formel für diskrete wechselseitige Information. Leider ist die Genauigkeit dieser Methode sehr empfindlich gegenüber der Bin-Breite oder der Anzahl der gewählten Bins, was zu erheblichen Schwankungen in der geschätzten wechselseitigen Information führen kann.

Kontinuierliche wechselseitige Information

Um die wechselseitige Information für kontinuierliche Variablen zu berechnen, müssen wir von diskreten Summierungen zu kontinuierlichen Integralen übergehen. Dies erfordert die Kenntnis der gemeinsamen und marginalen Wahrscheinlichkeitsdichtefunktionen (PDFs), die aufgrund begrenzter Daten oft nicht verfügbar sind. Die Fenstertechnik von Parzen bietet eine Lösung, indem sie die PDFs direkt aus den Daten approximiert. Bei dieser Methode wird eine Fensterfunktion über die Daten gelegt und eine gewichtete Summe der Datenpunkte innerhalb des Fensters errechnet. Die jedem Datenpunkt zugewiesene Gewichtung nimmt mit seinem Abstand zum Mittelpunkt des Fensters ab. Indem wir dieses Fenster über den Datenraum bewegen und die gewichtete Summe an jedem Punkt berechnen, können wir eine Schätzung der Wahrscheinlichkeitsdichtefunktion erstellen.

Die Dichte-Annäherung nach Parzen

Diese Technik ist besonders nützlich in Szenarien, in denen die zugrunde liegende Verteilung unbekannt oder komplex ist. Durch Anpassung der Fensterform und -größe können wir die Glätte und Genauigkeit der Dichteschätzung anpassen. Es ist jedoch zu beachten, dass die Wahl der Fensterparameter die Qualität der Schätzung erheblich beeinflussen kann. Die Effektivität der Parzen-Dichte-Schätzung hängt von der Auswahl einer geeigneten Gewichtungsfunktion ab, die idealerweise zu Eins integriert werden sollte und einen schnellen Zerfall aufweist, wenn sich das Argument von Null entfernt. Eine beliebte Wahl für diese Gewichtungsfunktion ist die Gaußsche Funktion, die durch einen Skalenparameter, sigma, gekennzeichnet ist.

Gaußsche Gewichtungsfunktion

Die Bestimmung des optimalen Wertes für sigma ist jedoch eine schwierige Aufgabe, die oft zu einer erheblichen Variabilität der geschätzten Dichte führt. Diese Empfindlichkeit gegenüber der Wahl von sigma ist ein großer Nachteil der Parzen-Fenstermethode, der sie für Aufgaben wie die Bewertung von Prädiktorenkandidaten, bei denen eine genaue Dichteschätzung Voraussetzung ist, nicht gerade ideal macht.

Eine Alternative zur Parzen-Fenstermethode zur Schätzung der wechselseitigen Information zwischen kontinuierlichen Variablen ist die adaptive Partitionierung. Die adaptive Partitionierung bietet eine erhebliche Verbesserung gegenüber dem naiven Ansatz des festen Binnings. Durch die iterative Unterteilung von Regionen mit hohem Informationsgehalt konzentriert die adaptive Partitionierung die Rechenressourcen effektiv auf die wichtigsten Bereiche des Datenraums. Dieser gezielte Ansatz führt zu genaueren Schätzungen der wechselseitigen Informationen, insbesondere bei komplexen, ungleichmäßigen Verteilungen. Eine wesentliche Stärke der adaptiven Partitionierung liegt in ihrer Fähigkeit, sich an die zugrunde liegende Datenstruktur anzupassen. Der rekursive Partitionierungsprozess stellt sicher, dass Regionen mit signifikanten Abhängigkeiten weiter unterteilt werden, während Regionen mit minimalen Informationen intakt bleiben. Mit diesem dynamischen Ansatz werden die üblichen Fallstricke einer festen Einteilung vermieden, bei der die Daten entweder übermäßig geglättet oder übermäßig angepasst werden können.

Darüber hinaus werden bei der adaptiven Partitionierung statistische Tests zur Steuerung des Partitionierungsprozesses eingesetzt. Durch die Bewertung der statistischen Signifikanz potenzieller Unterteilungen kann die adaptive Partitionierung zwischen echten Informationen und zufälligem Rauschen unterscheiden. Dies hilft, eine Überanpassung zu vermeiden, die andernfalls zu überhöhten Schätzungen der wechselseitigen Informationen führen könnte. Das Hauptproblem der naiven Methode besteht darin, dass sie Bereichen des bivariaten Bereichs mit wenigen oder gar keinen Fällen übermäßig viel Aufmerksamkeit schenkt, während dichte Regionen, in denen die meisten Informationen liegen, möglicherweise vernachlässigt werden.  

Adaptive Partitionierung

Die adaptive Partitionierung beginnt mit einer grobkörnigen Partitionierung des Datenraums. Für jede Partition wird die wechselseitige Information zwischen den Variablen berechnet. Übersteigt dieser Wert einen vordefinierten Schwellenwert, wird die Partition weiter in kleinere Unterpartitionen unterteilt. Dieser rekursive Partitionierungsprozess wird fortgesetzt, bis ein Stoppkriterium erfüllt ist. Wenn der Partitionierungsprozess zu früh beendet wird, können die resultierenden Partitionen zu grob sein, was zu einem Detailverlust und einer Verzerrung der geschätzten wechselseitigen Information nach unten führt. Wird der Partitionierungsprozess hingegen zu lange fortgesetzt, können die Partitionen zu feinkörnig werden, was zu einer Überanpassung und einer überhöhten Schätzung der wechselseitigen Informationen aufgrund von Rauschen führt.

Um festzustellen, ob eine Partition weiter unterteilt werden sollte, wird ein Chi-Quadrat-Test durchgeführt. Mit diesem Test wird die Unabhängigkeit zwischen den beiden Variablen innerhalb der Partition bewertet. Die Partition wird in vier Unterpartitionen unterteilt, wodurch eine 2x2-Kontingenztabelle entsteht. Es wird die Anzahl der Datenpunkte gezählt, die in jede der vier Unterteilungen fallen. Unter der Nullhypothese der Unabhängigkeit zwischen den beiden Variablen wird die erwartete Anzahl der Datenpunkte in jeder Unterteilung auf der Grundlage der Randsummen berechnet. Die berechnete Chi-Quadrat-Statistik wird dann mit einem kritischen Wert aus der Chi-Quadrat-Verteilung mit 1 Freiheitsgrad verglichen. Wenn der berechnete Wert den kritischen Wert überschreitet, wird die Nullhypothese der Unabhängigkeit verworfen, was auf eine signifikante Beziehung zwischen den beiden Variablen innerhalb der Partition hinweist.

Wenn der Chi-Quadrat-Test signifikant ist, wird die Partition weiter in kleinere Partitionen unterteilt. Dieser Prozess wird rekursiv fortgesetzt, bis die Partitionen hinreichend homogen sind oder das Abbruchkriterium erfüllt ist. Wenn der Chi-Quadrat-Test nicht signifikant ist, wird die Partition als homogen angesehen, und es ist keine weitere Unterteilung erforderlich.

Um die Möglichkeit einer komplexeren Beziehung innerhalb einer Partition zu berücksichtigen, die bei einer einfachen 2x2-Aufteilung übersehen werden könnte, enthält der Algorithmus eine zusätzliche Prüfung. Wenn der 2x2-Chi-Quadrat-Test keine signifikante Beziehung aufzeigt, die Partition aber relativ groß bleibt, wird eine verfeinerte 4x4-Aufteilung vorgenommen. Ein Chi-Quadrat-Test wird dann auf diese 4x4-Partition angewandt, um das Vorhandensein einer nicht zufälligen Verteilung festzustellen. Wenn auch der 4x4-Test keine signifikante Beziehung aufdeckt, wird die Partition als homogen betrachtet und ihr Beitrag zur gesamten wechselseitigen Information berechnet. Eine weitere Untergliederung ist in diesem Fall nicht erforderlich.

Wenn entweder der 2x2 oder der 4x4 Chi-Quadrat-Test eine ungleichmäßige Verteilung innerhalb einer Partition anzeigt, wird die Partition in vier kleinere Unterpartitionen unterteilt. Diese Unterpartitionen werden unterschiedlich behandelt, indem ihre Größe überprüft wird. Ist eine Teilpartition zu klein, wird sie als endgültig betrachtet. Ihr Beitrag zur gesamten wechselseitigen Information wird berechnet, und die Partition wird für die weitere Unterteilung nicht mehr berücksichtigt. Andernfalls wird ein Teilbereich, der ausreichend groß ist, als Kandidat für eine künftige Unterteilung gekennzeichnet.

Die Standard-Chi-Quadrat-Teststatistik ist in der folgenden Gleichung dargestellt.

2 mal 2 Chi-Quadrat-Teststatistik


Adaptive Partitionierung in MQL5

Die Klasse Capm in mutualinfo.mqh implementiert den adaptiven Partitionierungsalgorithmus zur Schätzung der wechselseitigen Information für kontinuierliche Variablen. Um den rekursiven Partitionierungsprozess zu verwalten, wird die nutzerdefinierte Struktur IntStack verwendet. Diese Struktur enthält Informationen über die Partitionen und alle darin enthaltenen Unterpartitionen. Die Struktur besteht aus sechs Mitgliedern, die im Folgenden aufgeführt sind:

  • Xstart und Xstop: Diese definieren den Bereich der Indizes für eine Variable innerhalb der aktuellen eingeschlossenen Partition.
  • Ystart und Ystop: Diese definieren den Bereich der Indizes für eine zweite Variable innerhalb der aktuellen eingeschlossenen Partition.
  • DataStart und DataStop: Diese geben die Anfangs- und Endindizes der Datenpunkte innerhalb einer umschließenden Partition an und markieren die Unterpartitionen.
//+------------------------------------------------------------------+
//| int type stack structure                                         |
//+------------------------------------------------------------------+
struct IntStack
  {
   int Xstart ;     // X value (rank) at which this rectangle starts
   int Xstop ;      // And stops
   int Ystart ;     // Ditto for Y
   int Ystop ;
   int DataStart ;  // Starting index into indices for the cases in this
   int DataStop ;   // rectangle, and the (inclusive) ending index
  };

Der Capm-Konstruktor benötigt drei Argumente:

  • no_split: Ein boolesches Flag, das festlegt, wie Werte zu behandeln sind, die ungefähr gleich sind. Wenn diese Option auf „true“ gesetzt ist, stellt der Algorithmus sicher, dass solche Werte während der Partitionierung gruppiert werden, sodass sie nicht in verschiedene Partitionen aufgeteilt werden können.
  • dep_vals: Ein Vektor, der die Werte einer der zu untersuchenden Variablen enthält. Diese Variable ist in der Regel diejenige, deren wechselseitige Information im Vergleich zu zahlreichen anderen Variablen berechnet wird.
  • chi_kritischer_Wert: Ein Schwellenwert für die bei der Partitionierung verwendeten Chi-Quadrat-Tests. Dieser Wert bestimmt das Signifikanzniveau für die Tests und beeinflusst die Tiefe der Partitionierung. Werte zwischen 4,0 und 8,0 sind im Allgemeinen geeignet, wobei 6,0 eine gängige Wahl mit universellem Nutzen ist.
//+------------------------------------------------------------------+
//|  constructor                                                     |
//+------------------------------------------------------------------+
Capm::Capm(bool no_split,vector &dep_vals, double chi_critical_value = 6.0)
  {
   m_no_split = no_split;
   m_size = int(dep_vals.Size()) ;
   m_chi_crit = chi_critical_value;

   if(ArrayResize(m_indices,m_size)<0 || !m_tempvals.Resize(m_size) || ArrayResize(m_ranks,m_size)<0
      || (m_no_split && ArrayResize(m_tied_ranks,m_size)<0))
     {
      Print(__FUNCTION__, " Arrayresize error ", GetLastError());
      return;
     }

   for(int i=0 ; i<m_size ; i++)
     {
      m_tempvals[i] = dep_vals[i] ;
      m_indices[i] = i ;
     }

   qsortdsi(0, m_size-1, m_tempvals, m_indices) ;

   for(int i=0 ; i<m_size ; i++)
     {
      m_ranks[m_indices[i]] = i ;
      if(! m_no_split)
         continue ;
      if(i < m_size-1  &&
         m_tempvals[i+1] - m_tempvals[i] < 1.e-12 * (1.0+fabs(m_tempvals[i])+fabs(m_tempvals[i+1])))
         m_tied_ranks[i] = 1 ;
      else
         m_tied_ranks[i] = 0 ;
     }
  }

Der Konstruktor der Klasse Capm beginnt mit dem Setzen des no_split-Flags, das festlegt, ob Bindungen in den Daten berücksichtigt werden sollen, und des chi_critical_value, der den Schwellenwert für den Chi-Quadrat-Test festlegt, der zur Ermittlung signifikanter Beziehungen zwischen Variablen verwendet wird. Als Nächstes weist der Konstruktor Speicher für Arrays zu, um die Daten, Ränge und gebundenen Werte zu speichern. Sie kopiert die Variablenwerte in einen temporären Vektor und sortiert sie. Die ursprünglichen Indizes der Datenpunkte bleiben in dem Array m_indices erhalten. Schließlich weist der Konstruktor den sortierten Datenpunkten Ränge zu und identifiziert alle gebundenen Werte, indem er sie im Array m_tied_ranks markiert, wenn das Flag no_split gesetzt ist.

//+------------------------------------------------------------------+
//| Mutual information using the Adaptive partitioning method        |
//+------------------------------------------------------------------+
class Capm
  {

public:
                     Capm(bool no_split,vector &dep_vals, double chi_critical_value = 6.0) ;
                    ~Capm(void) ;
   double            fit(vector &raw) ;

private:
   bool               m_no_split;  // respect ties
   int m_size ;                        // Number of cases
   int m_ranks[] ;                     // 'Dependent' variable ranks
   int m_tied_ranks[] ;                // tied[i] != 0 if case with rank i == case with rank i+1
   double m_chi_crit ;                 // Chi-square test criterion
   int               m_indices[];      // indices
   vector            m_tempvals;       // temp values
  } ;

Die Methode fit() in der Capm-Klasse dient der Schätzung der wechselseitigen Information zwischen einer (dem Konstruktor übergebenen) Variablen und einer anderen, als Argument übergebenen Variablen. Sie nimmt einen einzelnen Vektor als Eingabe, der die Werte einer zweiten Variablen darstellt. Dieser Vektor sollte die gleiche Dimension haben wie der dem Konstruktor übergebene Vektor. Durch den mehrfachen Aufruf der fit()-Methode mit verschiedenen Vektoren kann die wechselseitige Information zwischen der einen Variablen und den verschiedenen Variablen effizient berechnet werden.

//+------------------------------------------------------------------+
//|  mutual information for continuous variables                     |
//+------------------------------------------------------------------+
double Capm::fit(vector &raw)
  {
   int i, k, ix, iy, nstack, splittable ;
   int current_indices[], x[], fullXstart, fullXstop, fullYstart, fullYstop, ipos ;
   int trialXstart[4], trialXstop[4], trialYstart[4], trialYstop[4] ;
   int ipx, ipy, xcut[4], ycut[4], iSubRec, x_tied[], ioff ;
   int X_AllTied, Y_AllTied ;
   int centerX, centerY, currentDataStart, currentDataStop ;
   int actual[4], actual44[16] ;
   vector expected(16), xfrac(4), yfrac(4) ;
   double diff, testval;
   double px, py, pxy, MI ;

   IntStack stack[];

   if(ArrayResize(stack,1,256)<0)
     {
      Print(__FUNCTION__," arrayresize error ", GetLastError());
      return EMPTY_VALUE;
     }

   if(ArrayResize(current_indices,m_size)<0 || ArrayResize(x,m_size)<0
      || (m_no_split && ArrayResize(x_tied,m_size)<0))
     {
      Print(__FUNCTION__, " Arrayresize error ", GetLastError());
      return EMPTY_VALUE;
     }

   for(i=0 ; i<m_size ; i++)
     {
      m_tempvals[i] = raw[i] ;
      m_indices[i] = i ;
     }

   if(!qsortdsi(0, m_size-1, m_tempvals, m_indices))
     {
      Print(__FUNCTION__, " Arrayresize error ", GetLastError());
      return EMPTY_VALUE;
     }

   for(i=0 ; i<m_size ; i++)
     {
      x[m_indices[i]] = i ;
      if(! m_no_split)
         continue ;
      if(i < m_size-1  &&
         m_tempvals[i+1] - m_tempvals[i] < 1.e-12 * (1.0+fabs(m_tempvals[i])+fabs(m_tempvals[i+1])))
         x_tied[i] = 1 ;
      else
         x_tied[i] = 0 ;
     }

   for(i=0 ; i<m_size ; i++)
     {
      m_indices[i] = i ;
     }

   stack[0].Xstart = 0 ;
   stack[0].Xstop = m_size-1 ;
   stack[0].Ystart = 0 ;
   stack[0].Ystop = m_size-1 ;
   stack[0].DataStart = 0 ;
   stack[0].DataStop = m_size-1 ;
   nstack = 1 ;

   MI = 0.0 ;
   while(nstack > 0)
     {

      --nstack ;
      fullXstart = stack[nstack].Xstart ;
      fullXstop = stack[nstack].Xstop ;
      fullYstart = stack[nstack].Ystart ;
      fullYstop = stack[nstack].Ystop ;
      currentDataStart = stack[nstack].DataStart ;
      currentDataStop = stack[nstack].DataStop ;


      centerX = (fullXstart + fullXstop) / 2 ;
      X_AllTied = (x_tied.Size())  && (x_tied[centerX] != 0) ;
      if(X_AllTied)
        {
         for(ioff=1 ; centerX-ioff >= fullXstart ; ioff++)
           {
            if(! x_tied[centerX-ioff])
              {
               X_AllTied = 0 ;
               centerX -= ioff ;
               break ;
              }
            if(centerX + ioff == fullXstop)
               break ;
            if(! x_tied[centerX+ioff])
              {
               X_AllTied = 0 ;
               centerX += ioff ;
               break ;
              }
           }
        }

      centerY = (fullYstart + fullYstop) / 2 ;
      Y_AllTied = (m_tied_ranks.Size())  && (m_tied_ranks[centerY] != 0) ;
      if(Y_AllTied)
        {
         for(ioff=1 ; centerY-ioff >= fullYstart ; ioff++)
           {
            if(! m_tied_ranks[centerY-ioff])
              {
               Y_AllTied = 0 ;
               centerY -= ioff ;
               break ;
              }
            if(centerY + ioff == fullYstop)
               break ;
            if(! m_tied_ranks[centerY+ioff])
              {
               Y_AllTied = 0 ;
               centerY += ioff ;
               break ;
              }
           }
        }

      if(X_AllTied  ||  Y_AllTied)
         splittable = 0 ;
      else
        {
         trialXstart[0] = trialXstart[1] = fullXstart ;
         trialXstop[0] = trialXstop[1] = centerX ;
         trialXstart[2] = trialXstart[3] = centerX+1 ;
         trialXstop[2] = trialXstop[3] = fullXstop ;
         trialYstart[0] = trialYstart[2] = fullYstart ;
         trialYstop[0] = trialYstop[2] = centerY ;
         trialYstart[1] = trialYstart[3] = centerY+1 ;
         trialYstop[1] = trialYstop[3] = fullYstop ;

         for(i=0 ; i<4 ; i++)
            expected[i] = (currentDataStop - currentDataStart + 1) *
                          (trialXstop[i]-trialXstart[i]+1.0) / (fullXstop-fullXstart+1.0) *
                          (trialYstop[i]-trialYstart[i]+1.0) / (fullYstop-fullYstart+1.0) ;

         actual[0] = actual[1] = actual[2] = actual[3] = 0 ;
         for(i=currentDataStart ; i<=currentDataStop ; i++)
           {
            k = m_indices[i] ;
            if(x[k] <= centerX)
              {
               if(m_ranks[k] <= centerY)
                  ++actual[0] ;
               else
                  ++actual[1] ;
              }
            else
              {
               if(m_ranks[k] <= centerY)
                  ++actual[2] ;
               else
                  ++actual[3] ;
              }
           }

         testval = 0.0 ;
         for(i=0 ; i<4 ; i++)
           {
            diff = fabs(actual[i] - expected[i]) - 0.5 ;
            testval += diff * diff / expected[i] ;
           }

         splittable = (testval > m_chi_crit)  ?  1 : 0 ;

Die Methode beginnt mit der Initialisierung eines Stapels zur Verwaltung des Partitionierungsprozesses. Der gesamte Datensatz wird zunächst als Root-Partition auf den Stack geschoben. Anschließend holt der Algorithmus iterativ eine Partition vom Stapel und bewertet ihre Homogenität. Ein Chi-Quadrat-Test wird angewendet, um die statistische Signifikanz möglicher Beziehungen zwischen den Variablen innerhalb der Partition zu bewerten. Weist der Test auf eine signifikante Beziehung hin, wird die Partition in vier Unterpartitionen aufgeteilt, und jede Unterpartition wird zur weiteren Prüfung auf den Stapel zurückgeschoben. Dieser Prozess wird so lange fortgesetzt, bis alle Partitionen das Abbruchkriterium erfüllen oder homogen werden.

if(! splittable && fullXstop-fullXstart > 30 && fullYstop-fullYstart > 30)
           {
            ipx = fullXstart - 1 ;
            ipy = fullYstart - 1 ;
            for(i=0 ; i<4 ; i++)
              {
               xcut[i] = (fullXstop - fullXstart + 1) * (i+1) / 4 + fullXstart - 1 ;
               xfrac[i] = (xcut[i] - ipx) / (fullXstop - fullXstart + 1.0) ;
               ipx = xcut[i] ;
               ycut[i] = (fullYstop - fullYstart + 1) * (i+1) / 4 + fullYstart - 1 ;
               yfrac[i] = (ycut[i] - ipy) / (fullYstop - fullYstart + 1.0) ;
               ipy = ycut[i] ;
              }
            for(ix=0 ; ix<4 ; ix++)
              {
               for(iy=0 ; iy<4 ; iy++)
                 {
                  expected[ix*4+iy] = xfrac[ix] * yfrac[iy] *
                                      (currentDataStop - currentDataStart + 1) ;
                  actual44[ix*4+iy] = 0 ;
                 }
              }
            for(i=currentDataStart ; i<=currentDataStop ; i++)
              {
               k = m_indices[i] ;
               for(ix=0 ; ix<3 ; ix++)
                 {
                  if(x[k] <= xcut[ix])
                     break ;
                 }
               for(iy=0 ; iy<3 ; iy++)
                 {
                  if(m_ranks[k] <= ycut[iy])
                     break ;
                 }
               ++actual44[ix*4+iy] ;
              }
            testval = 0.0 ;
            for(ix=0 ; ix<4 ; ix++)
              {
               for(iy=0 ; iy<4 ; iy++)
                 {
                  diff = fabs(actual44[ix*4+iy] - expected[ix*4+iy]) - 0.5 ;
                  testval += diff * diff / expected[ix*4+iy] ;
                 }
              }
            splittable = (testval > 3 * m_chi_crit)  ?  1 : 0 ;
           }
        }

      if(splittable)
        {

         for(i=currentDataStart ; i<=currentDataStop ; i++)
            current_indices[i] = m_indices[i] ;

         ipos = currentDataStart ;
         for(iSubRec=0 ; iSubRec<4 ; iSubRec++)
           {

            if(actual[iSubRec] >= 3)
              {
               stack[nstack].Xstart = trialXstart[iSubRec] ;
               stack[nstack].Xstop = trialXstop[iSubRec] ;
               stack[nstack].Ystart = trialYstart[iSubRec] ;
               stack[nstack].Ystop = trialYstop[iSubRec] ;
               stack[nstack].DataStart = ipos ;
               stack[nstack].DataStop = ipos + actual[iSubRec] - 1 ;
               ++nstack ;
               if(ArrayResize(stack,nstack+1,256)<0)
                 {
                   Print(__FUNCTION__," arrayresize error ", GetLastError());
                   return EMPTY_VALUE;
                 }

               if(iSubRec == 0)
                 {
                  for(i=currentDataStart ; i<=currentDataStop ; i++)
                    {
                     k = current_indices[i] ;
                     if(x[k] <= centerX  &&  m_ranks[k] <= centerY)
                        m_indices[ipos++] = current_indices[i] ;
                    }
                 }
               else
                  if(iSubRec == 1)
                    {
                     for(i=currentDataStart ; i<=currentDataStop ; i++)
                       {
                        k = current_indices[i] ;
                        if(x[k] <= centerX  &&  m_ranks[k] > centerY)
                           m_indices[ipos++] = current_indices[i] ;
                       }
                    }
                  else
                     if(iSubRec == 2)
                       {
                        for(i=currentDataStart ; i<=currentDataStop ; i++)
                          {
                           k = current_indices[i] ;
                           if(x[k] > centerX  &&  m_ranks[k] <= centerY)
                              m_indices[ipos++] = current_indices[i] ;
                          }
                       }
                     else
                       {
                        for(i=currentDataStart ; i<=currentDataStop ; i++)
                          {
                           k = current_indices[i] ;
                           if(x[k] > centerX  &&  m_ranks[k] > centerY)
                              m_indices[ipos++] = current_indices[i] ;
                          }
                       }
              }

            else
              {
               if(actual[iSubRec] > 0)
                 {
                  px = (trialXstop[iSubRec] - trialXstart[iSubRec] + 1.0) / m_size ;
                  py = (trialYstop[iSubRec] - trialYstart[iSubRec] + 1.0) / m_size ;
                  pxy = (double) actual[iSubRec] / m_size ;
                  MI += pxy * log(pxy / (px * py)) ;
                 }
              }
           }
        }
      else
        {
         px = (fullXstop - fullXstart + 1.0) / m_size ;
         py = (fullYstop - fullYstart + 1.0) / m_size ;
         pxy = (currentDataStop - currentDataStart + 1.0) / m_size ;
         MI += pxy * log(pxy / (px * py)) ;
        }
     }

   return MI;

Für Partitionen, die entweder aufgrund eines nicht signifikanten Chi-Quadrat-Tests oder einer minimalen Anzahl von Datenpunkten als homogen gelten, wird die wechselseitige Information berechnet. Diese Berechnung beinhaltet die Schätzung der gemeinsamen und marginalen Wahrscheinlichkeitsverteilungen innerhalb der Partition und die Anwendung der nachstehenden Formel.

Beitrag zur wechselseitigen Information (Mutual Information Contribution)

Der Prozess wird rekursiv fortgesetzt, wobei die Partitionen so lange aufgeteilt und ausgewertet werden, bis keine signifikanten Aufteilungen mehr möglich sind. Die endgültige Schätzung der wechselseitigen Information wird durch die Summierung der Beiträge aller Endpartitionen erhalten, was eine umfassende Schätzung auf der Grundlage der Datenstruktur ergibt. Wenn die Methode auf einen Fehler stößt, gibt sie das Äquivalent zur eingebauten EMPTY_VALUE-Konstante von MQL5 zurück. In einem späteren Abschnitt werden wir die Klasse Capm für die Implementierung eines schrittweisen Algorithmus zur Merkmalsauswahl verwenden.


Auswahl von Prädiktoren unter Verwendung wechselseitiger Informationen

Bei der Anwendung der wechselseitigen Information auf die Auswahl von Merkmalen besteht unser Ziel darin, aus einer größeren Menge von Prädiktoren eine Teilmenge von Prädiktoren zu ermitteln, die die gemeinsame Abhängigkeit mit einer Zielvariablen maximiert. Diese gemeinsame Abhängigkeit ist eine Verallgemeinerung der wechselseitigen Information, bei der eine der Größen eine Sammlung von Variablen und nicht eine einzelne Variable ist. Die Berechnung der gemeinsamen Abhängigkeit für größere Teilmengen von Prädiktoren wird aufgrund des Fluchs der Dimensionen rechnerisch unpraktikabel. Dies ist darauf zurückzuführen, dass die erforderliche mehrdimensionale Partitionierung der Daten zu extrem spärlichen Zellen führt, insbesondere wenn die Anzahl der Dimensionen (Prädiktoren) zunimmt.

Darüber hinaus kann die schiere Anzahl der möglichen Merkmalskombinationen selbst für mäßig große Merkmalsmengen überwältigend sein. Diese kombinatorische Explosion macht es unpraktisch, alle möglichen Teilmengen erschöpfend zu durchsuchen, was den Einsatz effizienter Suchstrategien erforderlich macht.

Ein gängiger Ansatz für die Merkmalsauswahl ist die schrittweise Vorwärtsauswahl. Diese Methode beginnt mit einem leeren Modell und fügt iterativ das Merkmal hinzu, das die Leistung des Modells, gemessen an einem ausgewählten Kriterium, am stärksten verbessert. Dieser Ansatz ist zwar effizient, hat aber den Nachteil, dass er zu gierig ist. Dabei können optimale Merkmalskombinationen übersehen werden, z. B. Fälle, in denen zwei Merkmale in Kombination eine deutlich bessere Vorhersagekraft haben als eines der beiden Merkmale allein. Dies liegt daran, dass sich der Algorithmus bei jedem Schritt auf inkrementelle Verbesserungen konzentriert und dabei möglicherweise synergistische Beziehungen zwischen den Merkmalen übersieht.

Es gibt zwar auch andere Verfahren wie Methoden höherer Ordnung und die rückwärts gerichtete Auswahl, aber die schrittweise Vorwärtsauswahl ist aufgrund ihrer Recheneffizienz nach wie vor die praktischste Wahl, insbesondere bei großen Datensätzen und begrenzten Rechenressourcen. Darüber hinaus zeigt die schrittweise Vorwärtsselektion eine überraschende Eigenschaft, wenn sie auf den spezifischen Kontext der gemeinsamen Abhängigkeit angewendet wird: Sie kann die optimale Merkmalsuntermenge effektiv annähern, obwohl sie das gemeinsame Abhängigkeitsmaß nicht explizit optimiert.

Peng, Long und Ding schlugen einen Algorithmus zur Auswahl von Merkmalen vor, der auf wechselseitiger Information basiert und als MRMR-Kriterium (Maximum Relevance, Minimum Redundancy) bekannt ist. Dieser Algorithmus nähert sich effizient der optimalen Merkmalsuntermenge an, ohne explizit die gemeinsame Abhängigkeit zu berechnen, was rechnerisch nicht machbar ist. Das MRMR-Kriterium basiert auf dem Konzept der Relevanz und Redundanz. Die Relevanz einer Merkmalsgruppe S für die Zielvariable Y ist definiert als die durchschnittliche wechselseitige Information zwischen Y und jedem Merkmal in S.

Relevanz

Dabei ist ∣S∣ die Anzahl der Merkmale in S und I(Xi,Y) ist die wechselseitige Information zwischen den Merkmalen Xi und Y.

Die Maximierung der Relevanz ist zwar intuitiv, kann aber aufgrund von Redundanz zu einer suboptimalen Merkmalsuntermenge führen. Wenn wir die Merkmale einfach auf der Grundlage ihrer individuellen Relevanz für die Zielvariable auswählen, erhalten wir möglicherweise eine Reihe hoch korrelierter Merkmale, die kaum zusätzliche Informationen liefern. Um dieses Problem zu lösen, müssen wir nicht nur die Relevanz eines Merkmals, sondern auch seine Redundanz mit den bereits ausgewählten Merkmalen berücksichtigen.

Die Redundanz eines Prädiktorenkandidaten Xi bezüglich einer bereits ausgewählten Merkmalsgruppe S ist definiert als die durchschnittliche wechselseitige Information zwischen Xi und jedes Merkmal in S:

Redundanz

Dabei ist I(Xi,Xj) die wechselseitige Information zwischen dem Kandidatenmerkmal Xi und dem bereits in S enthaltenen Merkmal Xj.

Es ist wichtig zu beachten, dass der mathematische Ausdruck für Redundanz identisch mit dem Ausdruck für Relevanz ist. Der einzige Unterschied liegt in der Interpretation. Wenn wir die wechselseitige Information zwischen einem Kandidatenmerkmal und der Zielvariablen berechnen, bezeichnen wir dies als Relevanz. Bei der Berechnung der wechselseitigen Information zwischen einem Kandidatenmerkmal und einem Merkmal, das bereits in der Auswahlmenge enthalten ist, sprechen wir jedoch von Redundanz.

Im Wesentlichen geht es bei beiden Konzepten darum, die Menge an Informationen zu messen, die zwischen zwei Variablen ausgetauscht werden, aber der Kontext bestimmt, ob sie als relevant oder redundant betrachtet werden. Bei der Relevanz geht es um die Beziehung zwischen dem Merkmal und der Zielvariablen, während wir uns bei der Redundanz darauf konzentrieren, wie groß die Überschneidung zwischen dem Kandidatenmerkmal und den bereits ausgewählten Merkmalen besteht.

Zusammenfassend lässt sich sagen, dass der MRMR-Algorithmus wie folgt funktioniert:

  1. Das Merkmal mit der höchsten wechselseitigen Information mit der Zielvariablen Y wird als erstes Merkmal ausgewählt.
  2. In jedem weiteren Schritt wählt der Algorithmus das Merkmal aus, das das folgende Kriterium maximiert.

MRMR-Kriterium

Dieses Kriterium gleicht die Relevanz des Merkmals für die Zielvariable Y, d. h. I(X_i, Y), mit seiner Redundanz gegenüber dem bereits ausgewählten Merkmal ab. Der bemerkenswerte Aspekt des MRMR-Algorithmus ist, dass er sich der optimalen Merkmalsuntermenge effektiv annähert, auch wenn die direkte Optimierung der gemeinsamen Abhängigkeit rechnerisch nicht machbar ist. Dieses Ergebnis ist zwar nicht unmittelbar einleuchtend, wird aber in der Originalarbeit mathematisch nachgewiesen.

Um die statistische Signifikanz der ausgewählten Merkmale zu bewerten, können zwei Monte-Carlo-Permutationstests (MCP) durchgeführt werden:

  • Die Bedeutung der einzelnen Merkmale: Bei diesem Test wird die Signifikanz jedes einzelnen Merkmals bewertet, indem seine Relevanz für die Zielvariable mit einer durch Permutation erhaltenen Nullverteilung verglichen wird. Indem wir die Merkmalswerte vertauschen, erstellen wir eine Nullverteilung, die davon ausgeht, dass das Merkmal keine Beziehung zum Ziel hat. Wenn die beobachtete Relevanz signifikant höher ist als die permutierten Werte, können wir daraus schließen, dass das Merkmal wahrscheinlich wirklich informativ ist.
  • Die Signifikanz des Gesamtmodells: Mit diesem Test wird die Signifikanz des gesamten ausgewählten Merkmalsatzes bewertet, indem die kumulative Summe der Relevanz der einzelnen Merkmale mit einer durch Permutation erhaltenen Nullverteilung verglichen wird. Indem wir die Werte der Zielvariablen vertauschen, erstellen wir eine Nullverteilung, die davon ausgeht, dass die Merkmale keine Beziehung zum Ziel haben. Wenn die beobachtete kumulative Relevanz signifikant höher ist als die permutierten Werte, können wir daraus schließen, dass der ausgewählte Merkmalsatz wahrscheinlich die Zielvariable vorhersagen kann.


MQL5-Implementierung der schrittweisen Auswahl auf der Grundlage der wechselseitigen Information

Die Klasse Cmrmr in mutualinfo.mqh implementiert den MRMR-Algorithmus und übernimmt in ihrem Konstruktor die folgenden Parameter:
  • num_reps: Die Anzahl der Replikationen für die Monte-Carlo-Permutationstests. Wird hier ein Wert kleiner oder gleich 1 eingestellt, werden die Tests deaktiviert.
  • max_preds: Die maximale Anzahl der zu wählenden Merkmale.
  • chisquare_thresh: Die Chi-Quadrat-Testschwelle für die adaptive Partitionierungsmethode, die zur Schätzung der wechselseitigen Information verwendet wird.
  • m_verbose: Ein boolesches Flag, mit dem gesteuert wird, ob während der Ausführung des Algorithmus eine detaillierte Ausgabe angezeigt werden soll.
//+-----------------------------------------------------------------------+
//|Relevance minus redundancy for building an optimal subset of predictors|
//+-----------------------------------------------------------------------+
class Cmrmr
  {
private:
   int               m_max_preds;
   int               m_reps;
   bool              m_verbose;
   int               m_samples;
   int               m_vars;
   matrix            m_preds;
   vector            m_target;
   vector            m_crits;
   double            m_chisquare_thresh;
   ulong             m_selected_vars[];
   matrix            m_critical_values;
   vector            mutualinfo(int which_size,int &which[],vector &targs);
public:
                     Cmrmr(int num_reps,int max_preds, double chisquare_thresh,bool verbose);
                    ~Cmrmr(void);
   bool              StepWise(matrix &predictors, vector &targets);
   matrix            GetCriticalValues(void);
   bool              GetSelectedVars(ulong &output[]);
  };

Um den Prozess der Merkmalsauswahl mit Hilfe der Klasse Cmrmr zu starten, müssen wir ein Objekt dieser Klasse instanziieren und dann ihre Methode StepWise() aufrufen. Die Methode StepWise() benötigt zwei primäre Eingaben:

  • Eine Matrix von Kandidaten-Prädiktorvariablen: Diese Matrix sollte die Daten für alle potenziellen Merkmale enthalten, die ein Praktiker in Betracht ziehen möchte.
  • Ein Vektor der Zielvariablen: Dieser Vektor sollte die Werte einer Zielvariablen enthalten.

Die Methode gibt einen booleschen Wert zurück, der den Erfolg oder Misserfolg des Merkmalsauswahlprozesses anzeigt. Die Methode StepWise() in der Klasse Cmrmr initialisiert die erforderlichen Datenstrukturen und fährt dann mit der MCP-Testschleife fort. Wenn keine MCP-Tests angefordert werden („num_reps“ ist kleiner oder gleich 1), wird die Schleife nur einmal ausgeführt. Innerhalb der Schleife berechnet die Methode die wechelseitige Information zwischen jedem Prädiktorkandidaten und der Zielvariablen unter Verwendung einer Instanz der Klasse Capm.

//+------------------------------------------------------------------+
//|  Stepwise feature selection based on mutual information          |
//+------------------------------------------------------------------+
bool Cmrmr::StepWise(matrix &predictors,vector &targets)
  {
   if(m_selected_vars.Size())
      ArrayFree(m_selected_vars);

   m_preds = predictors;
   m_samples = int(m_preds.Rows());
   m_vars = int(m_preds.Cols());
   m_max_preds = m_max_preds>=m_vars?m_vars:m_max_preds;
   m_target = targets;
   
   
   int i, j, k, ivar, irep;
   int index[], stepwise_mcpt_count[], solo_mcpt_count[], stepwise_ivar[], which_preds[],original_stepwise_ivar[] ;

   int nkept,best_ivar;
   vector casework(m_samples), sorted(m_samples), mutual(m_vars);
   vector crit(m_vars), relevance(m_vars), original_relevance(m_vars), current_crits(m_vars), sorted_crits(m_vars);
   double best_crit, dtemp, group_pvalue,solo_pvalue;
   vector stepwise_crit(m_vars), original_stepwise_crit(m_vars);
   double sum_relevance;
   vector original_sum_relevance(m_vars), sum_redundancy(m_vars);
   vector target = m_target;

   best_crit = -DBL_MAX;
   best_ivar = -1;
   nkept = m_max_preds;

   if(ArrayResize(index,m_vars)<0 || ArrayResize(stepwise_mcpt_count,m_vars)<0 ||
      ArrayResize(solo_mcpt_count,m_vars)<0 || ArrayResize(which_preds,m_vars)<0 || ArrayResize(stepwise_ivar,m_vars)<0 ||
      ArrayResize(original_stepwise_ivar,m_vars)<0)
     {
      Print(__FUNCTION__," array resize error ", GetLastError());
      return false;
     }

   int unif_error;
   for(irep=0 ; irep<m_reps ; irep++)
     {
      if(irep)
        {
         i = m_samples ;
         while(i > 1)
           {
            j = (int)(MathRandomUniform(0.0,1.0,unif_error) * i);
            if(unif_error)
              {
               Print(__FUNCTION__," Mathrandomuniform error ", GetLastError());
               return false;
              }
            if(j >= i)
               j = i - 1 ;
            dtemp = target[--i] ;
            target[i] = target[j] ;
            target[j] = dtemp ;
           }
        }

      for(i=0 ; i<m_vars ; i++)
         which_preds[i] = i ;

      crit = mutualinfo(m_vars,which_preds,target);

      for(ivar=0 ; ivar<m_vars ; ivar++)
        {
         relevance[ivar] = crit[ivar] ;
         if(ivar == 0  ||  crit[ivar] > best_crit)
           {
            best_crit = crit[ivar] ;
            best_ivar = ivar ;
           }
        }

      stepwise_crit[0] = best_crit ;
      stepwise_ivar[0] = best_ivar ;
      sum_relevance = best_crit ;
      if(irep == 0)
        {

         original_stepwise_crit[0] = best_crit ;
         original_stepwise_ivar[0] = best_ivar ;
         original_sum_relevance[0] = sum_relevance ;
         stepwise_mcpt_count[0] = 1 ;

         for(ivar=0 ; ivar<m_vars ; ivar++)
           {
            index[ivar] = ivar ;
            original_relevance[ivar] = sorted_crits[ivar] = current_crits[ivar] = crit[ivar] ;
            solo_mcpt_count[ivar] = 1 ;
           }

         if(!qsortdsi(0, m_vars-1, sorted_crits, index))
           {
            Print(__FUNCTION__, " failed qsort ");
            return false;
           }

         if(m_verbose)
            Print("       Variable         MI") ;

         for(i=m_vars-1 ; i>=0 ; i--)
           {
            k = index[i] ;

            if(m_verbose)
               PrintFormat("%15s %12.4lf",string(k), current_crits[k]) ;
           }
        }

      else
        {
         if(sum_relevance >= original_sum_relevance[0])
            ++stepwise_mcpt_count[0] ;
         for(ivar=0 ; ivar<m_vars ; ivar++)
           {
            if(relevance[ivar] >= original_relevance[ivar])
               ++solo_mcpt_count[ivar] ;
           }
        }

      for(i=0 ; i<m_vars ; i++)
         sum_redundancy[i] = 0.0 ;

      for(nkept=1 ; nkept<m_max_preds ; nkept++)
        {

         if(irep == 0 && m_verbose)
           {
            Print("Predictors so far   Relevance   Redundancy   Criterion") ;
            for(i=0 ; i<nkept ; i++)
              {
               k = stepwise_ivar[i] ;
               PrintFormat("%15s %12.4lf %12.4lf %12.4lf",string(k), relevance[k], relevance[k] - stepwise_crit[i], stepwise_crit[i]) ;
              }
           }

         k = 0 ;
         for(i=0 ; i<m_vars ; i++)
           {
            for(j=0 ; j<nkept ; j++)
              {
               if(stepwise_ivar[j] == i)
                  break ;
              }
            if(j == nkept)
               which_preds[k++] = i ;
           }
         if(k != (m_vars - nkept))
           {
            Print(__FUNCTION__, " failed assertion ", __LINE__);
            return false;
           }

         k = stepwise_ivar[nkept-1] ;

         casework = m_preds.Col(k);

         crit = mutualinfo(m_vars-nkept,which_preds,casework);

         for(i=0 ; i<(m_vars-nkept) ; i++)
           {
            k = which_preds[i] ;
            sum_redundancy[k] += crit[i] ;
            index[i] = k ;
            sorted_crits[i] = current_crits[i] = ((relevance[k] - sum_redundancy[k]) / double(nkept)) ;
            if(i == 0  ||  current_crits[i] > best_crit)
              {
               best_crit = current_crits[i] ;
               best_ivar = k ;
              }
           }

         stepwise_crit[nkept] = best_crit ;
         stepwise_ivar[nkept] = best_ivar ;
         sum_relevance += relevance[best_ivar] ;

         if(irep == 0)
           {
            original_stepwise_crit[nkept] = best_crit ;
            original_stepwise_ivar[nkept] = best_ivar ;
            original_sum_relevance[nkept] = sum_relevance ;
            stepwise_mcpt_count[nkept] = 1 ;

            if(!qsortdsi(0, m_vars-nkept-1, sorted_crits, index))
              {
               Print(__FUNCTION__, " failed qsort ");
               return false;
              }

            if(m_verbose)
              {
               Print("Additional candidates, in order of decreasing relevance minus redundancy") ;
               Print("       Variable     Relevance   Redundancy   Criterion") ;

               for(i=m_vars-nkept-1 ; i>=0 ; i--)
                 {
                  k = index[i] ;
                  PrintFormat("%15s %12.4lf %12.4lf %12.4lf",
                              string(k), relevance[k], sum_redundancy[k] / nkept,
                              relevance[k] - sum_redundancy[k] / nkept) ;
                 }
              }
           }

         else
           {
            if(sum_relevance >= original_sum_relevance[nkept])
               ++stepwise_mcpt_count[nkept] ;
           }

        }

     }
//---
   m_critical_values = matrix::Zeros(nkept,m_reps>1?5:3);
//---
   if(ArrayResize(m_selected_vars,nkept)<0)
     {
      Print(__FUNCTION__, " failed array resize ", GetLastError());
      return false;
     }
//---
   if(m_verbose)
     {
      if(m_reps > 1)
         Print("Final predictors ||  Relevance ||  Redundancy || Criterion  ||  Solo pval || Group pval") ;
      else
         Print("Final predictors  ||  Relevance ||  Redundancy ||  Criterion") ;
     }
//---
   for(i=0 ; i<nkept ; i++)
     {
      k = original_stepwise_ivar[i] ;
      m_selected_vars[i] = ulong(k);

      m_critical_values[i][0] = original_relevance[k];
      m_critical_values[i][1] = original_relevance[k] - original_stepwise_crit[i];
      m_critical_values[i][2] = original_stepwise_crit[i];

      if(m_critical_values.Cols()>3)
        {
         group_pvalue = (double) stepwise_mcpt_count[i] / (double) m_reps;
         solo_pvalue = (double) solo_mcpt_count[k] / (double) m_reps;
         m_critical_values[i][3] = solo_pvalue;
         m_critical_values[i][4] = group_pvalue;
        }

      if(m_verbose)
        {
         if(m_reps > 1)
            PrintFormat("%15s %12.4lf %12.4lf %12.4lf    %8.3lf    %8.3lf",string(k), m_critical_values[i][0], m_critical_values[i][1], m_critical_values[i][2],solo_pvalue,group_pvalue) ;
         else
            PrintFormat("%15s %12.4lf %12.4lf %12.4lf",string(k), m_critical_values[i][0], m_critical_values[i][1], m_critical_values[i][2]);
        }
     }

   return true;

  }

Die Methode mutualinfo(), eine private Methode innerhalb der Klasse Cmrmr, ist für die Schätzung der wechelseitigen Information zwischen einem bestimmten Prädiktor und der Zielvariablen unter Verwendung der adaptiven Partitionierungsmethode zuständig. Sie gibt einen Vektor der Näherungen der wechelseitigen Information für die ausgewählten Prädiktoren zurück, die als Spaltenindizes in mutualinfo() angegeben sind.

//+------------------------------------------------------------------+
//| continuous mutual infomation                                     |
//+------------------------------------------------------------------+
vector Cmrmr::mutualinfo(int which_size,int &which[],vector &targs)
  {
   vector res = vector::Zeros(m_vars);

   res.Fill(-DBL_MAX);

   vector vars;

   Capm mia(true,targs,m_chisquare_thresh);

   for(int i = 0; i<which_size; i++)
     {
      vars = m_preds.Col(which[i]);
      res[i] = mia.fit(vars);
     }

   return res;

  }

Die Methode StepWise() speichert die berechneten Werte der wechelseitigen Information im Vektor für die „Relevanz“, da diese Werte in nachfolgenden Iterationen zur Berechnung des Redundanzterms benötigt werden. Das Merkmal mit der höchsten wechselseitigen Information wird als erstes Merkmal ausgewählt, und seine Informationen werden in den Variablen stepwise_crit und stepwise_ivar gespeichert. Zusätzlich wird die Variable sum_relevance initialisiert, um die kumulative Relevanz der ausgewählten Merkmale zu erfassen, die später für den MCP-Test verwendet wird.

Bei der ersten, unveränderten Replikation speichert der Algorithmus die ursprünglichen Werte der Relevanz und des Kriteriums für das erste ausgewählte Merkmal. Außerdem werden Zähler für den Signifikanztest des Gesamtmodells und die Signifikanztests der einzelnen Merkmale initialisiert. Zusätzlich wird eine Tabelle ausgegeben, die die wechelseitige Information jedes Kandidatenmerkmals mit der Zielvariablen anzeigt, um Einblicke in die anfängliche Merkmalseinstufung zu geben. Diese Tabelle hilft bei der Visualisierung des anfänglichen Auswahlprozesses und dient als Referenz für den Vergleich mit den permutierten Werten während der Signifikanztestphase.

Befindet man sich nicht in der ersten, nicht permutierten Replikation, führt der Algorithmus zwei MCP-Tests durch, um die statistische Signifikanz des vom MRMR-Algorithmus ausgewählten Merkmalssatzes zu bewerten. Um die sich entwickelnde Redundanz effizient zu verwalten, wenn neue Merkmale zur Auswahlmenge hinzugefügt werden, verwendet der Algorithmus das Array „sum_redundancy“. Das Array wird mit Nullen initialisiert. Dieses Array wird verwendet, um die kumulative Redundanz für jedes Kandidatenmerkmal im Verhältnis zu den bereits ausgewählten Merkmalen zu speichern.

Da zu Beginn keine Merkmale ausgewählt sind, werden alle Werte in diesem Array auf Null gesetzt. Wenn ein neues Merkmal zur Auswahlmenge hinzugefügt wird, muss seine Redundanz mit jedem der verbleibenden Kandidatenmerkmale aktualisiert werden. Die Redundanz eines Merkmals im Hinblick auf die wachsende Auswahlmenge ist definiert als die durchschnittliche wechelseitige Information zwischen dem Kandidatenmerkmal und den bereits ausgewählten Merkmalen. Die wechselseitige Information zwischen dem neuen Merkmal und jedem der verbleibenden Kandidatenmerkmale wird errechnet. Damit wird quantifiziert, wie viel Information das neue Merkmal mit jedem der ausgewählten Merkmale gemeinsam hat. Der Redundanzwert für jedes verbleibende Merkmal wird aktualisiert, indem der Wert der wechelseitigen Information zwischen dem neu hinzugefügten Merkmal und dem vorhandenen Merkmal addiert wird.

Nach der Aktualisierung der Redundanzwerte berechnet der Algorithmus das MRMR-Kriterium (maximale Relevanz, minimale Redundanz) für jedes der verbleibenden Kandidatenmerkmale. Das MRMR-Kriterium wird berechnet, indem die durchschnittliche Redundanz eines jeden Merkmals von seiner Relevanz abgezogen wird. Nachdem das MRMR-Kriterium für alle verbleibenden Kandidatenmerkmale berechnet wurde, wird das Merkmal mit dem höchsten MRMR-Wert ausgewählt. Der Algorithmus wiederholt diesen Prozess iterativ, aktualisiert das Array „sum_redundancy“ und berechnet das MRMR-Kriterium jedes Mal neu, wenn ein neues Merkmal hinzugefügt wird. Wenn weitere Merkmale ausgewählt werden, wird die Redundanz der verbleibenden Merkmale mit der ausgewählten Menge kontinuierlich aktualisiert, um sicherzustellen, dass der Merkmalsauswahlprozess die sich entwickelnden Beziehungen zwischen den Merkmalen berücksichtigt.

Handelt es sich um die erste, nicht permutierte Replikation, bleiben die ursprünglichen Werte dieser Größen für den späteren Vergleich bei den Permutationstests erhalten. Andernfalls werden die Zähler für die Permutationstests „group“ und „solo“ hochgezählt. Nach Abschluss der gewünschten Anzahl von Iterationen zeigt der Algorithmus eine Tabelle an, in der die ausgewählten Merkmale, ihre Relevanz und ihre p-Werte aus den Permutationstests zusammengefasst sind. Nachdem „StepWise()“ aufgerufen und erfolgreich ausgeführt wurde, kann „GetCriticalValues()“ aufgerufen werden, um eine Matrix der Wertetabelle zu erhalten, die im ausführlichen Modus angezeigt wird. Mit „GetSelectedVars()“ werden die Indizes der ausgewählten Variablen (Spalten) abgerufen, die der Eigenschaft „m_max_preds“ der Klasse entsprechen.

//+------------------------------------------------------------------+
//| get the matrix of decision variables                             |
//| matrix rows arranged in terms of descending relevance of each    |
//| candidate predictor. Most relevant predictors critical values are|
//| in the first row, etc.                                           |
//| Column 0 is the calculated relevance                             |
//| Column 1 is the calculated redundancy                            |
//| Column 2 is the calculated Stepwise Mutual information           |
//| Column 3 and 4 will exist only if MCP test is specified          |
//| Column 3 is the Solo probability value                           |
//| Column 4 are the Group probability values                        |
//+------------------------------------------------------------------+
matrix Cmrmr::GetCriticalValues(void)
{
  return m_critical_values;
}
//+--------------------------------------------------------------------------+
//|get the indices of the most relevant predictors as determined by algorithm|
//+--------------------------------------------------------------------------+
bool Cmrmr::GetSelectedVars(ulong &output[])
{
 return (ArrayCopy(output,m_selected_vars)>0);
}

Im nächsten Abschnitt werden wir sehen, wie die Klasse Cmrmr anhand synthetischer und realer Datensätze angewendet wird.


Beispiele für Relevanz minus Redundanz

Um die Anwendung des MRMR-Algorithmus effektiv zu veranschaulichen, werden wir ihn zunächst auf einen synthetischen Datensatz anwenden. Der Datensatz besteht aus 100 Stichproben und 10 potenziellen Vorhersagevariablen (Merkmalen). Die Zielvariable Y wird durch Summierung der ersten vier Spalten der Matrix gebildet, sodass die Beziehung zwischen dem Ziel und den Prädiktoren für diese Spalten deterministisch ist. Die Spalten 4 bis 7 liefern jedoch keine wirklichen Informationen über das Ziel und dienen als irrelevante Merkmale. Die Variablen in den Spalten 8 und 9 sind Kombinationen von Variablen mit den Spaltenindizes {1,3} bzw. {0,2}.

//---
   MathSrand(125);

   matrix rdata(100,10);

   rdata.Random(0.0,1.0);

   vector dep = rdata.Col(0) + rdata.Col(1) + rdata.Col(2) + rdata.Col(3);

   vector sum02 = rdata.Col(0) + rdata.Col(2);

   vector sum13 = rdata.Col(1) + rdata.Col(3);

   if(!rdata.Col(sum13,8) || !rdata.Col(sum02,9))
     {
      Print(" Failed column insertion ", GetLastError());
      return;
     }

Ziel ist es, mit Hilfe des MRMR-Algorithmus die Spalten mit den wichtigsten Prädiktoren zu identifizieren. Dieses Experiment ist im Skript RelevanceMinusRedundancy.mq5 implementiert, das die Anpassung verschiedener Hyperparameter ermöglicht, darunter die Anzahl der Monte-Carlo-Permutationen und die maximale Anzahl der auszuwählenden Merkmale. Das Skript ist so konfiguriert, dass es im ausführlichen Modus läuft und während der Feature-Auswahl detaillierte Ausgaben liefert.

//inputs
input int NumReplications = 100;
input int MaxPredictors = 10;
input double ChiSquareThreshold = 6.0;

Die anfängliche Ausgabe des Skripts RelevanceMinusRedundancy.mq5 sind die einzelnen Werte der wechelseitigen Information zwischen jedem Kandidatenprädiktor und der Zielvariablen. Angeordnet in absteigender Reihenfolge der wechselseitigen Informationen, was eine einfache Identifizierung der wichtigsten Merkmale ermöglicht.

Variable         MI
      9       0.2675
      8       0.1696
      0       0.1662
      3       0.1336
      2       0.0823
      1       0.0645
      6       0.0000
      7       0.0000
      4       0.0000
      5       0.0000

Wie erwartet ist das erste vom MRMR-Algorithmus ausgewählte Merkmal dasjenige mit der höchsten individuellen wechselseitigen Information mit der Zielvariablen. In diesem Fall handelt es sich um die Spalte mit dem Index 9.

Predictors so far   Relevance   Redundancy   Criterion
            9       0.2675       0.0000       0.2675

Das zweite ausgewählte Merkmal ist die Variable im Spaltenindex 8. Die Ergebnisse der Analyse zeigen, dass diese Variable von hoher Relevanz ist und auch nur eine minimale Redundanz mit der ersten ausgewählten Variable aufweist.

Predictors so far   Relevance   Redundancy   Criterion
            9       0.2675       0.0000       0.2675
            8       0.1696       0.0000       0.1696

Das dritte ausgewählte Merkmal zeigt, wie Redundanz den Auswahlprozess beeinflusst. Obwohl die Merkmale in den Spalten 0, 1, 2 und 3 in direktem Zusammenhang mit der Zielvariablen stehen, verringert ihre hohe Redundanz mit den bereits ausgewählten Merkmalen (Spalten 8 und 9) ihr MRMR-Kriterium insgesamt. Daher wählt der Algorithmus weniger direkt verwandte Merkmale aus, die eine geringere Redundanz mit dem vorhandenen Satz aufweisen. Erst nach der Auswahl einiger nicht verwandter Merkmale beginnt der Algorithmus, die Merkmale in den Spalten 0, 1, 2 und 3 zu priorisieren, da ihre Redundanz mit der ausgewählten Menge abnimmt.

Additional candidates, in order of decreasing relevance minus redundancy
Variable     Relevance   Redundancy   Criterion
    5       0.0000       0.0000       0.0000
    4       0.0000       0.0000       0.0000
    7       0.0000       0.0000       0.0000
    6       0.0000       0.0000       0.0000
    1       0.0645       0.0600       0.0044
    0       0.1662       0.1351       0.0311
    2       0.0823       0.1426      -0.0603
    3       0.1336       0.1781      -0.0445

Während die irrelevanten Variablen in diesem synthetischen Beispiel keine Relevanz hatten, ist es wichtig zu beachten, dass in realen Szenarien irrelevante Merkmale immer noch ein gewisses Maß an Beziehung mit der Zielvariablen aufweisen können, was zu einer Redundanz ungleich Null führt. In solchen Fällen sorgt der MRMR-Algorithmus für ein effektives Gleichgewicht zwischen Relevanz und Redundanz, indem er den Merkmalen den Vorrang gibt, die einzigartige Informationen liefern.

Final predictors ||  Relevance ||  Redundancy || Criterion  || Solo pval|| Group pval
9                     0.2675        0.0000       0.2675       0.010       0.010
8                     0.1696        0.0000       0.1696       0.010       0.010
4                     0.0000        0.0000       0.0000       1.000       0.010
5                     0.0000        0.0000       0.0000       1.000       0.010
6                     0.0000        0.0000       0.0000       1.000       0.010
7                     0.0000        0.0000       0.0000       1.000       0.010
1                     0.0645        0.0738      -0.0093       0.010       0.010
0                     0.1662        0.1811      -0.0149       0.010       0.010
2                     0.0823        0.1077      -0.0254       0.010       0.010
3                     0.1336        0.1584      -0.0247       0.010       0.010

Die aus den Monte-Carlo-Permutationstests gewonnenen p-Werte geben wertvolle Hinweise auf die statistische Bedeutung der ausgewählten Merkmale. Niedrigere p-Werte sind ein stärkerer Hinweis darauf, dass das Merkmal wirklich informativ ist und nicht auf Zufall beruht. Zur Bestimmung der statistischen Signifikanz wird in der Regel ein p-Wert von 0,05 verwendet.

Es folgt eine Demonstration des Algorithmus anhand eines realistischeren Datensatzes. Das Skript StepWiseFeatureSelectionByMutualInformation.mq5 bietet eine praktische Anwendung des MRMR-Algorithmus auf Finanzdaten. Es basiert auf der Prämisse, die zukünftigen Renditen eines bestimmten Symbols anhand einer Reihe von technischen Indikatoren vorherzusagen. Das Skript ermittelt vier Indikatoren: Money Flow Index (MFI), Moving Average (MA), Bears Power, and Bulls Power, für eine Reihe von Rückblickszeiträumen. Diese Indikatoren dienen als Kandidatenmerkmale für den Merkmalsauswahlprozess. Durch die Anwendung des MRMR-Algorithmus zielt das Skript darauf ab, die informativste Kombination von Indikatoren zu ermitteln, die eine effektive Vorhersage künftiger Erträge ermöglicht. Dies beinhaltet die Auswahl von Merkmalen, die für die Zielvariable (künftige Renditen) relevant sind, während die Redundanz mit anderen ausgewählten Merkmalen minimiert wird.

//+------------------------------------------------------------------+
//|                  StepWiseFeatureSelectionByMutualInformation.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
#resource "\\Indicators\\LogReturns.ex5"
#include<mutualinfo.mqh>
#include<ErrorDescription.mqh>
//+------------------------------------------------------------------+
//|indicator type                                                    |
//+------------------------------------------------------------------+
enum SELECT_INDICATOR
  {
   MFI=0,//MFI
   MA,//MA
   BEARS,//BEARS
   BULLS//BULLS
  };
//--- input parameters
input int NumReplications = 100;
input int MaxPredictors = 10;
input double ChiSquareThreshold = 6.0;
input bool VerboseOutPut = false;
input uint     period_inc=2;//lookback increment
input uint     max_lookback=6;
input ENUM_MA_METHOD         AppliedMA = MODE_SMA;
input ENUM_APPLIED_PRICE     AppliedPrice = PRICE_CLOSE;
input datetime SampleStartDate=D'2019.12.31';
input datetime SampleStopDate=D'2022.12.31';
input string   SetSymbol="BTCUSD";
input ENUM_TIMEFRAMES SetTF = PERIOD_D1;
//----
string predictor_names[];                      // variable names
int size_sample,                      //training set size
    size_observations,                //size of of both training and testing sets combined
    maxperiod,                        //maximum lookback
    indicator_handle=INVALID_HANDLE;  //long moving average indicator handle
//---
vector indicator[];                   //indicator indicator values;
//---
matrix feature_matrix;          //full matrix of features;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---get relative shift of sample set
   int samplestart,samplestop,num_features;
   samplestart=iBarShift(SetSymbol!=""?SetSymbol:NULL,SetTF,SampleStartDate);
   samplestop=iBarShift(SetSymbol!=""?SetSymbol:NULL,SetTF,SampleStopDate);
   num_features = int((max_lookback/period_inc)*4);
//---check for errors from ibarshift calls
   if(samplestart<0 || samplestop<0)
     {
      Print(ErrorDescription(GetLastError()));
      return;
     }
//---set the size of the sample sets
   size_observations=(samplestart - samplestop) + 1 ;
   maxperiod=int(max_lookback);
//---check for input errors
   if(size_observations<=0 || maxperiod<=0)
     {
      Print("Invalid inputs ");
      return;
     }
//---allocate memory
   if(ArrayResize(indicator,num_features+1)<0)
     {
      Print(ErrorDescription(GetLastError()));
      return;
     }
//----get the full collection of indicator values
   int period_len;
   int k=0;
//---
   for(SELECT_INDICATOR select_indicator = 0; select_indicator<4; select_indicator++)
     {
      for(int iperiod=0; iperiod<int((indicator.Size()-1)/4); iperiod++)
        {
         period_len=int((iperiod+1) * period_inc);
         int try
               =10;
         predictor_names.Push(EnumToString(select_indicator)+"_"+string(period_len));
         while(try)
           {
            switch(select_indicator)
              {
               case MFI:
                  indicator_handle=iMFI(SetSymbol!=""?SetSymbol:NULL,SetTF,period_len,VOLUME_TICK);
                  break;
               case MA:
                  indicator_handle=iMA(SetSymbol!=""?SetSymbol:NULL,SetTF,period_len,0,AppliedMA,AppliedPrice);
                  break;
               case BEARS:
                  indicator_handle=iBearsPower(SetSymbol!=""?SetSymbol:NULL,SetTF,period_len);
                  break;
               case BULLS:
                  indicator_handle=iBullsPower(SetSymbol!=""?SetSymbol:NULL,SetTF,period_len);
                  break;
              }

            if(indicator_handle==INVALID_HANDLE)
               try
                  --;
            else
               break;
           }

         if(indicator_handle==INVALID_HANDLE)
           {
            Print("Invalid indicator handle ",EnumToString(select_indicator)," ", GetLastError());
            return;
           }

         Comment("copying data to buffer for indicator ",period_len);
         try
               = 0;
         while(!indicator[k].CopyIndicatorBuffer(indicator_handle,0,samplestop,size_observations) && try
                  <10)
              {
               try
                  ++;
               Sleep(5000);
              }

         if(try
               <10)
               ++k;
         else
           {
            Print("error copying to indicator buffers ",GetLastError());
            Comment("");
            return;
           }

         if(indicator_handle!=INVALID_HANDLE && IndicatorRelease(indicator_handle))
            indicator_handle=INVALID_HANDLE;
        }
     }

//---resize matrix
   if(!feature_matrix.Resize(size_observations,indicator.Size()-1))
     {
      Print(__LINE__);
      Print(ErrorDescription(GetLastError()));
      Comment("");
      return;
     }
//---copy collected data to matrix
   for(ulong i = 0; i<feature_matrix.Cols(); i++)
      if(!feature_matrix.Col(indicator[i],i))
        {
         Print(__LINE__);
         Print(ErrorDescription(GetLastError()));
         Comment("");
         return;
        }
//---
   int try
         = 10;
   while(try
            >-1 && indicator_handle == INVALID_HANDLE)
        {
         indicator_handle=iCustom(SetSymbol!=""?SetSymbol:NULL,SetTF,"\\Indicators\\LogReturns",0,1,1);
         try
            --;
        }
//---
   if(try
         <0)
        {
         Print("Could not initialize returns indicator ");
         Print(ErrorDescription(GetLastError()));
         Comment("");
         return;
        }
   else
     {
      try
            = 10;
     }
//---
   while(try
            >-1 && !indicator[indicator.Size()-1].CopyIndicatorBuffer(indicator_handle,0,samplestop-1,size_observations))
        {
         Sleep(2000);
         try
            --;
        }
//---
   if(try
         <0)
        {
         Print("Could not collect returns indicator info ");
         Print(ErrorDescription(GetLastError()));
         Comment("");
         return;
        }
   else
     {
      IndicatorRelease(indicator_handle);
      Comment("");
     }
//--- display the dataset
   string console;
   for(uint i = 0; i<predictor_names.Size(); i++)
      console+=StringFormat(" %12s ",predictor_names[i]);
   console+=" NextBar Returns (Target) ";
   Print(console);
   for(ulong i = 0; i<feature_matrix.Rows(); i++)
     {
      console = "";
      for(ulong j = 0; j<feature_matrix.Cols(); j++)
         console += StringFormat(" %12.6lf ",feature_matrix[i][j]);
      console+=StringFormat(" %12.6lf ",indicator[indicator.Size()-1][i]);
      Print(console);
     }
//---
   Cmrmr mstep(NumReplications,MaxPredictors,ChiSquareThreshold,VerboseOutPut);
//---
   if(!mstep.StepWise(feature_matrix,indicator[indicator.Size()-1]))
      return;
//---
   Print(" Final predictor labels ");
   ulong variables[];
   if(mstep.GetSelectedVars(variables))
     {
      for(uint i = 0; i<variables.Size(); i++)
         Print(predictor_names[variables[i]]);
     }
   return;
  }
//+------------------------------------------------------------------+
Nachfolgend sehen Sie einen Ausschnitt aus dem Datensatz der Indikatoren, die vom MetaTrader 5-Terminal gesammelt wurden, zusammen mit der Zielvariablen in der letzten Spalte. Insgesamt gibt es 12 mögliche Prädiktoren (Indikatoren).
MFI_2         MFI_4         MFI_6          MA_2          MA_4          MA_6       BEARS_2       BEARS_4       BEARS_6       BULLS_2       BULLS_4       BULLS_6  NextBar Returns(Target)
0.000000     53.151442     46.921608   7213.654000   7279.552750   7260.007333    -76.371267   -101.867797   -107.113146     87.265733     61.769203     56.523854     -0.003564
0.000000     26.316595     34.451328   7180.962000   7244.558000   7255.420333    -41.795089    -70.889078    -83.462247     42.344911     13.250922      0.677753     -0.032447
0.000000      0.000000     36.720977   7053.740000   7133.697000   7204.281833   -127.731696   -210.813447   -251.244462    153.948304     70.866553     30.435538      0.054562

Im Folgenden werden die Ergebnisse der Prädiktoranalyse unter Verwendung der Standardparameter des Skripts dargestellt. Zunächst sehen wir uns die Tabelle mit den Werten der wechselseitigen Information an. Hier sehen wir, dass einige Indikatoren keine wechselseitige Information mit dem Ziel haben. Der Indikator Gleitender Durchschnitt mit einer Periodenlänge von 6 hat die höchste wechselseitige Information in Bezug auf das Ziel.
PS          Variable         MI
ND                 5       0.0308
LG                 4       0.0293
MJ                 3       0.0279
GM                 6       0.0227
JP                 9       0.0182
IS                 1       0.0165
MF                 8       0.0135
JI                 7       0.0126
HL                 0       0.0000
JO                 2       0.0000
GS                10       0.0000
HD                11       0.0000

Natürlich ist MA_6 die erste Auswahl, die der Algorithmus trifft. Da die erste Auswahl getroffen wurde, wird es interessant sein zu sehen, ob es andere Kandidaten gibt, die Informationen mit diesem Prädiktor teilen (MA_6). Dies lässt sich aus den nachstehend aufgeführten Redundanzwerten ableiten.

ME   Predictors so far   Relevance   Redundancy   Criterion
GH                 5       0.0308       0.0000       0.0308
II   Additional candidates, in order of decreasing relevance minus redundancy
FE          Variable     Relevance   Redundancy   Criterion
LE                 0       0.0000       0.0095      -0.0095
ED                 1       0.0165       0.0869      -0.0704
FD                 2       0.0000       0.1149      -0.1149
PE                11       0.0000       0.2768      -0.2768
ID                 8       0.0135       0.3251      -0.3116
NS                 7       0.0126       0.3492      -0.3366
CR                10       0.0000       0.3484      -0.3484
ES                 6       0.0227       0.4030      -0.3802
IS                 9       0.0182       0.4285      -0.4103
CR                 3       0.0279       2.5363      -2.5084
DR                 4       0.0293       3.0096      -2.9803
MFI_2 wird als Nächstes für den Algorithmus ausgewählt, da er die geringste Redundanz unter allen verbleibenden Kandidatenprädiktoren aufweist, obwohl er für die Zielvariable keine Relevanz hat. Man beachte auch, dass die Prädiktoren in den Spaltenindizes 3 und 4 (MA_2 bzw. MA_4) die höchste Redundanz mit MA_6 aufweisen. Dies ist sinnvoll, da es sich um denselben Indikator handelt, der mit kürzeren Fensterlängen berechnet wird. Nachfolgend ist der Prädiktorensatz nach zwei weiteren Auswahlen dargestellt, die beide ebenfalls keine Relevanz für die Zielvariable haben. Bemerkenswert ist der beste Prädiktor in der Gruppe der nicht ausgewählten Kandidaten. Der Algorithmus beginnt damit, relevante Prädiktoren in Betracht zu ziehen, da die letzten beiden Auswahlen einen verwässernden Effekt auf die ausgewählten Prädiktoren als Gruppe hatten.
PQ   Predictors so far   Relevance   Redundancy   Criterion
FL                 5       0.0308       0.0000       0.0308
JK                 0       0.0000       0.0095      -0.0095
DK                 2       0.0000       0.1796      -0.1796
EE  Additional candidates, in order of decreasing relevance minus redundancy
JH         Variable     Relevance   Redundancy   Criterion
CH                6       0.0227       0.1469      -0.1241
PH                9       0.0182       0.1465      -0.1283
GI               10       0.0000       0.2024      -0.2024
PG                7       0.0126       0.2133      -0.2007
PF               11       0.0000       0.2308      -0.2308
GG                8       0.0135       0.2664      -0.2529
QG                1       0.0165       0.4679      -0.4514
KF                3       0.0279       0.8840      -0.8561
LF                4       0.0293       1.0372      -1.0079
Wir schließen unseren Kommentar zu den Ergebnissen ab, indem wir den endgültigen Satz der vom Algorithmus ausgewählten Prädiktoren anzeigen.
OQ  Final predictors ||  Relevance ||  Redundancy || Criterion  ||  Solo pval || Group pval
HN                5       0.0308       0.0000       0.0308       0.010       0.010
DJ                0       0.0000       0.0095      -0.0095       1.000       0.010
JG                2       0.0000       0.1796      -0.1796       1.000       0.010
HD                6       0.0227       0.1620      -0.1393       0.010       0.010
FQ                9       0.0182       0.2023      -0.1842       0.010       0.010
DL               11       0.0000       0.2798      -0.2798       1.000       0.010
KJ                1       0.0165       0.2932      -0.2767       0.010       0.010
OG                7       0.0126       0.3298      -0.3172       0.010       0.010
OE               10       0.0000       0.4151      -0.4151       1.000       0.010
JP                8       0.0135       0.4585      -0.4450       0.010       0.010
RN   Final predictor labels
NO  MA_6
DE  MFI_2
NN  MFI_6
KP  BEARS_2
DJ  BULLS_2
FL  BULLS_6
PQ  MFI_4
MK  BEARS_4
FM  BULLS_4
OF  BEARS_6


Schlussfolgerung

In diesem Artikel haben wir die Anwendung der wechselseitigen Information bei der Merkmalsauswahl untersucht und uns dabei auf den von Peng, Long und Ding vorgeschlagenen MRMR-Algorithmus (Max-Dependency, Max-Relevance, Min-Redundancy) konzentriert. Wir haben zunächst die Schätzung der wechselseitigen Information für kontinuierliche Variablen erörtert und ihre Fähigkeit hervorgehoben, komplexe, nicht lineare Abhängigkeiten zwischen Merkmalen und der Zielvariablen zu erfassen. Durch unsere Untersuchung des MRMR-Algorithmus konnten wir zeigen, wie er die konkurrierenden Ziele der Maximierung der Relevanz für die Zielvariable bei gleichzeitiger Minimierung der Redundanz mit bereits ausgewählten Merkmalen effektiv ausgleicht.

Durch das iterative Hinzufügen von Merkmalen auf der Grundlage ihrer MRMR-Werte und die Bewertung ihrer statistischen Signifikanz durch Monte-Carlo-Permutationstests stellt der Algorithmus sicher, dass der ausgewählte Merkmalssatz die informativsten und eindeutigsten Prädiktoren für das Modell liefert. Die synthetischen und realen Beispiele veranschaulichen den praktischen Nutzen des MRMR-Algorithmus in realen Datenszenarien, in denen irrelevante Merkmale und Multikollinearität die Merkmalsauswahl oft erschweren. Die Fähigkeit des MRMR-Algorithmus, relevante Merkmale zu priorisieren und redundante Merkmale zu vermeiden, stellt sicher, dass er ein wertvolles Werkzeug im Merkmalsauswahlprozess bleibt, insbesondere in Fällen, in denen die Beziehungen zwischen Variablen kompliziert und nichtlinear sind.

Der gesamte Code, auf den in dem Artikel verwiesen wird, ist unten angefügt. In der folgenden Tabelle sind alle Quellcodedateien beschrieben, die dem Artikel beigefügt sind.

Dateiname
Beschreibung
MQL5/include/np.mqh
Eine Header-Datei mit Matrix- und Vektor-Utility-Funktionen.
MQL5/include/mutualinfo.mqh
Eine Header-Datei, die die Definition der Klasse Capm enthält, die die adaptive Partitionierungsmethode zur Schätzung der wechselseitigen Information von kontinuierlichen Variablen implementiert. Und die Definition der Klasse Cmrmr, die eine schrittweise Merkmalsauswahl auf der Grundlage wechselseitiger Informationen implementiert.
MQL5/scripts/RelevanceMinusRedundancy.mq5
Ein Skript, das die Verwendung der Klasse Cmrmr anhand synthetischer Daten demonstriert.
MQL5/scripts/StepWiseFeatureSelectionByMutualInformation.mq5
Ein weiteres Skript, das die Anwendung der Klasse Cmrmr auf einen realistischeren Datensatz demonstriert.

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/16416

Handelseinblicke durch Volumen: Mehr als OHLC-Charts Handelseinblicke durch Volumen: Mehr als OHLC-Charts
Ein algorithmisches Handelssystem, das die Volumenanalyse mit Techniken des maschinellen Lernens, insbesondere neuronalen LSTM-Netzen, kombiniert. Im Gegensatz zu traditionellen Handelsansätzen, die sich in erster Linie auf Preisbewegungen konzentrieren, legt dieses System den Schwerpunkt auf Volumenmuster und deren Ableitungen, um Marktbewegungen vorherzusagen. Die Methodik umfasst drei Hauptkomponenten: Analyse der Volumenderivate (erste und zweite Ableitung), LSTM-Vorhersagen für Volumenmuster und traditionelle technische Indikatoren.
Handel mit dem MQL5 Wirtschaftskalender (Teil 3): Hinzufügen de Filter für Währung, Bedeutung und Zeit Handel mit dem MQL5 Wirtschaftskalender (Teil 3): Hinzufügen de Filter für Währung, Bedeutung und Zeit
In diesem Artikel implementieren wir Filter in das MQL5-Wirtschaftskalender-Dashboard, um die Anzeige von Nachrichtenereignissen nach Währung, Bedeutung und Zeit zu verfeinern. Wir erstellen zunächst Filterkriterien für jede Kategorie und integrieren diese dann in das Dashboard, um nur relevante Ereignisse anzuzeigen. Schließlich stellen wir sicher, dass jeder Filter dynamisch aktualisiert wird, um Händlern gezielte wirtschaftliche Erkenntnisse in Echtzeit zu liefern.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 47): Verstärkungslernen mit Temporaler Differenz MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 47): Verstärkungslernen mit Temporaler Differenz
Temporal Difference ist ein weiterer Algorithmus des Reinforcement Learning, der Q-Werte auf der Grundlage der Differenz zwischen vorhergesagten und tatsächlichen Belohnungen während des Agententrainings aktualisiert. Sie befasst sich speziell mit der Aktualisierung von Q-Werten, ohne sich um die Verknüpfung von Zustand und Aktion zu kümmern. Daher wollen wir sehen, wie wir dies, wie in früheren Artikeln, in einem mit einem Assistenten zusammengestellten Expert Advisor anwenden können.
Erstellen von einem Trading Administrator Panel in MQL5 (Teil VI): Das Panel zur Handelsverwaltung (II) Erstellen von einem Trading Administrator Panel in MQL5 (Teil VI): Das Panel zur Handelsverwaltung (II)
In diesem Artikel erweitern wir das Trade Management Panel unseres multifunktionalen Admin Panels. Wir führen eine leistungsstarke Hilfsfunktion ein, die den Code vereinfacht und die Lesbarkeit, Wartbarkeit und Effizienz verbessert. Wir zeigen Ihnen auch, wie Sie zusätzliche Schaltflächen nahtlos integrieren und die Nutzeroberfläche erweitern können, um ein breiteres Spektrum von Handelsaufgaben zu bewältigen. Ob es um die Verwaltung von Positionen, die Anpassung von Aufträgen oder die Vereinfachung von Nutzerinteraktionen geht, dieser Leitfaden hilft Ihnen bei der Entwicklung eines robusten, nutzerfreundlichen Trade Management Panels.