English Русский 中文 Español 日本語
preview
Schrittweise Merkmalsauswahl in MQL5

Schrittweise Merkmalsauswahl in MQL5

MetaTrader 5Statistik und Analyse |
130 0
Francis Dube
Francis Dube

Einführung

Die traditionelle schrittweise Auswahl von Merkmalen ist eine Technik, die dazu dient, eine optimale Teilmenge von Variablen aus einem größeren Pool von Kandidatenmerkmalen für eine maschinelle Lernaufgabe zu ermitteln. Dieser Prozess beginnt mit der Bewertung jedes einzelnen Kandidatenmerkmals, um die vielversprechendste Variable für die Aufnahme in das endgültige Modell auszuwählen. Anschließend werden weitere Kandidaten auf ihren Beitrag in Kombination mit den bereits ausgewählten getestet, bis ein Zielwert für die Vorhersage- oder Klassifizierungsleistung erreicht ist.

In diesem Artikel untersuchen wir die Grenzen der konventionellen schrittweisen Merkmalsauswahl, wie z. B. ihr Potenzial zur Überanpassung und ihre Schwierigkeiten bei der Erfassung von Wechselwirkungen zwischen Merkmalen. Anschließend stellen wir einen verbesserten Algorithmus vor, der zur Lösung dieser Probleme entwickelt und in MQL5 implementiert wurde und der eine flexible Integration mit verschiedenen überwachten Lernmethoden ermöglicht.

Dieser verbesserte Ansatz wurde von Timothy Masters entwickelt und in seinem Buch „Modern Data Mining Algorithms in C++ and CUDA C“ beschrieben. Abschließend stellen wir die praktische Anwendung des Algorithmus vor, indem wir ihn zur Auswahl optimaler Variablen für eine beispielhafte Regressionsaufgabe verwenden und seine Effektivität veranschaulichen.


Einschränkungen der schrittweisen Merkmalsauswahl

Datensätze, die bei Aufgaben des maschinellen Lernens verwendet werden, sind oft von Natur aus komplex und weisen manchmal nichtlineare Beziehungen, zeitlich veränderliche Muster und zahlreiche miteinander verbundene Faktoren auf. Diese Komplexität stellt eine große Herausforderung für die Merkmalsauswahl dar, da herkömmliche Methoden wie die schrittweise Auswahl diese komplizierte Dynamik nur schwer erfassen können. Eine wesentliche Einschränkung der schrittweisen Auswahl besteht darin, dass sie nicht in der Lage ist, die Auswirkungen verschiedener Merkmalskombinationen zu bewerten. Häufig kann ein Merkmal, das für sich allein oder in Verbindung mit bestimmten anderen Variablen wenig aussagekräftig erscheint, in Kombination mit bestimmten ergänzenden Merkmalen eine hohe Vorhersagekraft haben.

Stellen wir uns die Aufgabe, ein Prognosemodell für Devisenkurse zu erstellen. In diesem Szenario könnten wir verschiedene technische und fundamentale Indikatoren sammeln. Einige Fundamentalindikatoren scheinen für sich genommen oder in Verbindung mit nicht komplementären technischen Daten nur einen geringen Vorhersagewert zu haben. Eine Kombination von Indikatoren, wie z. B. bevorstehende Wahlergebnisse, mit dem aktuellen Markttrend könnte jedoch zu einer zuverlässigeren Vorhersage führen, als wenn man sich allein auf die Markttrends verlässt. Darüber hinaus könnte das Verständnis des allgemeinen Wirtschaftsklimas und die Vorhersage, wie die neue Führung auf die makroökonomischen Bedingungen reagieren könnte, die Vorhersagegenauigkeit des Modells erheblich verbessern. Diese Herausforderungen machen deutlich, dass ein verbessertes schrittweises Auswahlverfahren erforderlich ist, mit dem vielversprechende Merkmalskombinationen getestet werden können, ohne auf eine rechenintensive, erschöpfende Suche zurückgreifen zu müssen.

Eine zweite Herausforderung bei der schrittweisen Auswahl ist die Anfälligkeit für eine Überanpassung aufgrund von Rauschen. Jedes Mal, wenn eine neue Variable hinzugefügt wird, besteht die Gefahr, dass der Algorithmus zufällige Schwankungen mit wertvollen Informationen verwechselt, was dazu führen kann, dass er Muster lernt, die sich nicht verallgemeinern lassen. So kann beispielsweise die Hinzufügung mehrerer technischer Indikatoren die In-Sample-Performance erhöhen, wenn das Modell anfängt, sich an das Marktrauschen statt an aussagekräftige Trends anzupassen, wodurch seine Robustheit bei Out-of-Sample-Daten beeinträchtigt wird. Dieses Problem tritt häufig auf, wenn die Leistung des Modells ausschließlich anhand der Anpassung in der Stichprobe beurteilt wird, was dazu führt, dass mehr Variablen als nötig einbezogen werden und sich die Leistung außerhalb der Stichprobe letztlich verschlechtert.

Die statistische Signifikanz der Merkmalsauswahl wird häufig anhand von p-Werten bewertet. Bei der traditionellen regressionsbasierten, schrittweisen Auswahl bewerten die p-Werte die Wahrscheinlichkeit, dass der Koeffizient eines Merkmals Null ist, was seine Vorhersagekraft widerspiegelt. Konfidenzintervalle geben darüber hinaus einen Bereich für den wahren Populationskoeffizienten an, der die Stichprobenunsicherheit berücksichtigt. Ein niedriger p-Wert deutet auf eine starke Evidenz gegen die Nullhypothese (dass der Koeffizient Null ist) hin, was die Signifikanz des Merkmals unterstützt. Diese p-Werte ergeben sich aus Hypothesentests, bei denen die Nullhypothese einen Nullkoeffizienten und die Alternativhypothese einen Nicht-Nullkoeffizienten annimmt. Um den p-Wert zu erhalten, berechnen wir einen t-Wert auf der Grundlage der Koeffizientenschätzung und ihres Standardfehlers.

Dieser Ansatz ist jedoch spezifisch für Regressionsmodelle. Bei anderen Verfahren des maschinellen Lernens würde das Erreichen vergleichbarer p-Werte einen angepassten Hypothesentest erfordern, was die praktische Anwendung des Algorithmus zusätzlich erschwert. Idealerweise würde eine universellere Technik ein relevantes Maß für die Signifikanz liefern, das einen p-Wert oder eine gleichwertige Metrik erzeugt, die über verschiedene Arten von maschinellen Lernmethoden hinweg konsistent ist.

Grenzen der konventionellen schrittweisen Merkmalsauswahl

In Anbetracht der Einschränkungen der herkömmlichen schrittweisen Merkmalsauswahl bietet der hier vorgestellte modifizierte Algorithmus drei wesentliche Verbesserungen gegenüber der herkömmlichen Methode. Die erste Verbesserung betrifft die Herausforderung, effizient nach vielversprechenden Untergruppen von Merkmalen zu suchen. Durch die Beibehaltung mehrerer Teilmengen mit hohem Potenzial und die Bewertung neuer Kandidaten in Kombination mit diesen Teilmengen erreicht der modifizierte Algorithmus ein Gleichgewicht zwischen umfassender Erkundung und Recheneffizienz. Mit diesem Ansatz können synergetische Beziehungen zwischen den Merkmalen aufgedeckt werden, die bei der herkömmlichen schrittweisen Auswahl möglicherweise übersehen werden.

Um das Risiko einer Überanpassung zu verringern, verwendet der Algorithmus die Leistung der Kreuzvalidierung als Grundlage für die Bewertung von Merkmalssätzen. Diese strenge Bewertungsmethode minimiert das Risiko, zufälliges Rauschen mit aussagekräftigen Vorhersageinformationen zu verwechseln, und bietet einen unkomplizierten Mechanismus, um die Aufnahme zusätzlicher Merkmale automatisch zu stoppen, wenn ein abnehmender Nutzen festgestellt wird.

Außerdem berechnet der Algorithmus für jedes neu hinzugefügte Merkmal zwei Wahrscheinlichkeiten:

  • Wahrscheinlichkeit des zufälligen Auftretens: Diese Wahrscheinlichkeit misst die Wahrscheinlichkeit, dass die beobachtete Leistung des aktuellen Merkmalsatzes dem Zufall zuzuschreiben ist, unter der Annahme, dass alle ausgewählten Merkmale irrelevant sind.
  • Wahrscheinlichkeit einer ungewollten Verbesserung: Mit dieser Wahrscheinlichkeit wird die Chance bewertet, dass der Leistungszuwachs durch das zuletzt hinzugefügte Merkmal eher auf Zufall als auf einen echten Vorhersagewert zurückzuführen ist.

Diese Wahrscheinlichkeiten geben Aufschluss über die statistische Signifikanz des Merkmalsauswahlprozesses und helfen dabei, echte Vorhersagekraft von statistischem Rauschen zu unterscheiden. Diese berechneten Wahrscheinlichkeiten sind modellunabhängig, sodass sich der Algorithmus nahtlos in eine breite Palette von maschinellen Lernmodellen integrieren lässt. In den folgenden Abschnitten werden wir die wichtigsten Teile des Codes untersuchen, die diesen Algorithmus implementieren. Die bereitgestellten Codeabschnitte sind der Klasse CStepwisebase entnommen, die in stepwise.mqh definiert ist. Diese Klasse ist abstrakt, d. h. der Nutzer muss eine abgeleitete Klasse erstellen, die bestimmte virtuelle Mitglieder implementiert, sodass jede Art von Modell integriert werden kann.


Das schrittweise Verfahren

Der vorgeschlagene Algorithmus verbessert die herkömmliche schrittweise Auswahl, indem er mehrere vielversprechende Teilmengen von Merkmalen verfolgt und so eine erschöpfende Suche vermeidet. Dieser Ansatz grenzt die Teilmengen von Merkmalen, die wahrscheinlich die endgültige optimale Menge bilden, effizient ein. Nachfolgend finden Sie einen Überblick über das Verfahren:

  1. Initialisierung: Datenstrukturen, die während des gesamten Prozesses verwendet werden, um Informationen über Merkmalsgruppenversuche zu speichern, werden initialisiert.
  2. Iteratives Hinzufügen von Merkmalen: Führe für jede Iteration mehrere Permutationsreplikationen durch. Mische die Zielvariable (Shuffle Target Variable), um eine neue Permutation zu erstellen. Füge ein neues Merkmal (Add New Feature) zum aktuellen Merkmalssatz hinzu. Bewerte die Leistung (Evaluate Performance) des aktualisierten Merkmalssatzes mit Hilfe einer Kreuzvalidierung. Aktualisiere die leistungsstärksten Sätze (Update Best Sets) der Eigenschaften und die entsprechenden Leistungskennzahlen. 
  3. Beendigungskriterien: Der Algorithmus wird beendet, wenn entweder die maximale Anzahl von Merkmalen (definiert durch max_num_predictors) erreicht ist oder wenn die Leistungsverbesserung unter einen vordefinierten Schwellenwert fällt.

    Erweiterter Algorithmus der schrittweisen Merkmalsauswahl

Die wichtigsten Schritte sind in stepwiseSearch() implementiert, wobei der Prozess durch mehrere Hyperparameter gesteuert wird, die verschiedene Aspekte der Suche kontrollieren. Diese Hyperparameter sind:

  • num_kept: Die Anzahl der Kandidaten-Merkmalsteilmengen mit der besten Leistung auf der Grundlage des Kriteriums, die bei jeder Iteration beibehalten werden sollen. Es wird davon ausgegangen, dass diese Kandidaten die optimale Merkmalsgruppe bilden.
  • num_folds: Die Anzahl der bei der Kreuzvalidierung verwendeten Faltungen (folds). Größere Werte verbessern zwar die Genauigkeit der Leistungsschätzungen, erhöhen aber auch den Rechenaufwand.
  • min_num_predictors: Die minimale Anzahl von Prädiktoren, die der endgültige Merkmalssatz enthalten kann, sodass der Nutzer eine Untergrenze für die Modellkomplexität festlegen kann.
  • max_num_predictors: Die maximale Anzahl der Prädiktoren, die in der endgültigen Merkmalsmenge zulässig sind und eine Obergrenze für die Modellkomplexität darstellen.
  • num_replications: Die Anzahl der Iterationen für den Monte-Carlo-Permutationstest, mit dessen Hilfe die statistische Signifikanz der Merkmalsleistung bewertet wird.
  • verbose_output: Legt fest, ob die Ergebnisse der einzelnen Schritte des Merkmalsauswahlprozesses im Log des MetaTrader 5 für die EAs angezeigt werden.

Diese Parameter bieten Flexibilität und ermöglichen es den Nutzern, den Prozess der Merkmalsauswahl genau auf ihre Bedürfnisse abzustimmen. Sie können durch den Aufruf von setParams() angegeben werden.

//+------------------------------------------------------------------+
//|   set the stepwise selection parameters                          |
//+------------------------------------------------------------------+
void              setParams(ulong num_kept, ulong num_folds, ulong min_num_predictors, ulong max_num_predictors,long num_replications,bool verbose_output)
  {
   m_keptvars = num_kept>0?num_kept:1;
   m_folds = num_folds>0?num_folds:ULONG_MAX;
   m_min_num_preds = min_num_predictors;
   m_max_num_preds = max_num_predictors;
   m_reps = num_replications>0?num_replications:1;
   m_verbose = verbose_output;
   return;
  }

Die Methode stepwiseSearch() beginnt mit der Initialisierung interner Datenstrukturen, um die verschiedenen Versuche mit Merkmalssätzen zu verfolgen. Dann geht es in eine Hauptschleife, die so lange läuft, bis die maximale Anzahl von Prädiktoren erreicht ist. Während jeder Iteration der Schleife wird die Zielvariable für mehrere Wiederholungen zufällig gemischt und eine neue Variable wird dem Modell durch den Aufruf von addFeature() hinzugefügt. Der Algorithmus speichert die leistungsstärksten Modellmerkmale, und wenn das Hinzufügen einer neuen Variable zu einer Leistungsverschlechterung führt, kann er vorzeitig beendet werden. Wenn sich die Leistung weiter verbessert, wird der Prozess fortgesetzt, bis die maximale Anzahl von Prädiktoren erreicht ist. Schließlich werden die Indizes der ausgewählten Variablen in dem Array m_var_indices gespeichert.

//+------------------------------------------------------------------+
//|  coordinates main stepwise search operation                      |
//+------------------------------------------------------------------+
int               stepwiseSearch(void)
  {
   int done = 1;
   int btrials;
   m_trial_length = m_max_num_preds*m_vars*m_keptvars;

   reset();

   if(!m_prior_best_crits.Resize(m_keptvars) || !m_all_best_crits.Resize(m_reps,m_keptvars))
     {
      Print(__FUNCTION__," data structure resizing error ", GetLastError());
      return done;
     }

   if(ArrayResize(m_all_best_trials,int(m_reps*m_max_num_preds*m_keptvars))<0  ||
      ArrayResize(m_already_tried,int(m_trial_length))<0 ||
      ArrayResize(m_trial_vars,int(m_max_num_preds))<0 ||
      ArrayResize(m_prior_best_trials,int(m_max_num_preds*m_keptvars))<0)
     {
      Print(__FUNCTION__,"  error resizing array ", GetLastError());
      return done;
     }


   ArrayInitialize(m_all_best_trials,-1);
   ArrayInitialize(m_already_tried,-1);
   ArrayInitialize(m_trial_vars,-1);
   ArrayInitialize(m_prior_best_trials,-1);

   vector target;
   int n_so_far =0;
   int rval, rem, j,n_this_rep = 0;
   int mcpt_mod_count, mcpt_change_count;
   mcpt_mod_count = mcpt_mod_count = mcpt_change_count = -1;
   double temp,prior_crit;
   double original_crit, original_change, new_crit;
   original_crit = original_change = new_crit = -DBL_MIN;

   if(m_verbose)
     {
      if(m_reps>1)
         Print("Criterion  || New pval  || Diff pval  || Column Indices");
      else
         Print("Criterion  || Column Indices");
     }
   while(true)
     {

      target = m_target;

      for(ulong rep=0; rep<m_reps; rep++)
        {


         if(rep)
           {
            rval = 17*int(rep) + 11;
            unif(rval);
            unif(rval);
            rem = int(m_samples);
            while(rem>1)
              {
               j = (int)(unif(rval))*rem;
               if(j>=rem)
                  j = rem-1;
               temp = target[--rem];
               target[rem] = target[j];
               target[j] = temp;
              }
           }

         btrials = int(rep*m_max_num_preds*m_keptvars);
         if(n_so_far == 0)
            prior_crit = -1.e60;
         else
            prior_crit = m_all_best_crits[rep][0];
         n_this_rep = n_so_far;

         done = addFeature(target,n_this_rep,btrials,rep);

         if(done || n_this_rep!=(n_so_far+1))
           {
            Print(__FUNCTION__, " internal error ");
            return 1;
           }

         if(m_all_best_crits[rep][0]<=prior_crit && n_so_far>=int(m_min_num_preds) && !rep)
           {
            for(ulong i = 0 ; i<m_keptvars; i++)
              {
               m_all_best_crits[rep][i] = m_prior_best_crits[i];
               if(ArrayCopy(m_all_best_trials,m_prior_best_trials,btrials+int(i*n_so_far),int(i*n_so_far),n_so_far)<0)
                 {
                  Print(__FUNCTION__, " ArrayCopy error ", GetLastError());
                  return 1;
                 }
              }
            if(m_verbose)
               Print(__FUNCTION__, " procedure terminated early because adding a new variable caused performance degradation");
            return 0;
           }

         if(prior_crit < 0.0)
            prior_crit = 0.0;

         new_crit = m_all_best_crits[rep][0];

         if(new_crit<0.0)
            new_crit = 0.0;

         if(rep == 0)
           {
            original_crit = new_crit;
            original_change = new_crit - prior_crit;
            mcpt_mod_count = mcpt_change_count = 1;
           }
         else
           {
            if(new_crit >= original_crit)
               ++mcpt_mod_count;
            if(new_crit - prior_crit >= original_change)
               ++mcpt_change_count;
           }

        }

      if(n_so_far == 0)
         mcpt_change_count = mcpt_mod_count;

      if(!m_decision_matrix.Resize(n_so_far+1,m_reps>1?3:1,100) || ArrayResize(m_var_indices,int(m_var_indices.Size())+n_this_rep,100)<0)
        {
         Print(__FUNCTION__, " container resize error ", GetLastError());
         return 1;
        }

      string msg;
      if(m_reps>1)
        {
         double mod_pval = (double) mcpt_mod_count / (double) m_reps;
         double change_pval = (double) mcpt_change_count / (double) m_reps;

         if(m_verbose)
            msg = StringFormat("%10.8lf %10.8lf %10.8lf  ", original_crit,mod_pval,change_pval);

         m_decision_matrix[n_so_far][0] = original_crit;
         m_decision_matrix[n_so_far][1] = mod_pval;
         m_decision_matrix[n_so_far][2] = change_pval;
        }
      else
        {
         msg = m_verbose?StringFormat("%10.8lf", original_crit):NULL;
         m_decision_matrix[n_so_far][0] = original_crit;
        }
      if(m_verbose)
        {
         for(int i = 0; i<n_this_rep; i++)
           {
            msg += StringFormat(" %s",IntegerToString(m_all_best_trials[i]));
           }
        }
      if(ArrayCopy(m_var_indices,m_all_best_trials,(n_so_far*(n_so_far+1))/2,0,n_this_rep)<0)
        {
         Print(__FUNCTION__, " array copy error ", GetLastError());
         return 1;
        }

      if(m_verbose)
         Print(msg);

      ++n_so_far;
      if(n_so_far == int(m_max_num_preds))
        {
         if(m_verbose)
            Print(" Stepwise selection successfully completed ");
         break;
        }
     }

   return 0;

  }


Berechnung des kreuzvalidierten Kriteriums

Wenn man sich bei der Merkmalsauswahl auf einen einzigen Trainingsdatensatz verlässt, führt dies häufig zu suboptimalen Ergebnissen, da die Gefahr einer Überanpassung besteht. Um dieses Risiko zu mindern, wird eine Kreuzvalidierung durchgeführt. Bei der Kreuzvalidierung wird der Datensatz in mehrere Trainings- und Validierungssätze aufgeteilt, um eine umfassendere Bewertung der Modellleistung und der Bedeutung der Merkmale zu ermöglichen. Durch iteratives Trainieren und Testen auf verschiedenen Datenuntergruppen reduziert die Kreuzvalidierung die Überanpassung und verbessert die Generalisierung.

Die Kreuzvalidierung ist zwar sehr effektiv, stellt aber einen Kompromiss zwischen Recheneffizienz und statistischer Zuverlässigkeit dar. Eine größere Anzahl von Faltungen liefert im Allgemeinen eine genauere Schätzung der Modellleistung, erhöht jedoch den Rechenaufwand. Die ideale Anzahl von Faltungen hängt sowohl von den Eigenschaften des Datensatzes als auch von den verfügbaren Rechenressourcen ab. In der Praxis erfordert die Bestimmung der geeigneten Anzahl von Faltungen eine sorgfältige Abwägung der Größe des Datensatzes und des gewünschten statistischen Zuverlässigkeitsgrads. Wenn man die Prinzipien und Grenzen der Kreuzvalidierung versteht, kann man diese Technik anwenden, um die Zuverlässigkeit und Robustheit der Merkmalsauswahl und des Modellbildungsprozesses zu verbessern.

Das Verfahren der Kreuzvalidierung wird durch die Methode crossEval() implementiert.

//+------------------------------------------------------------------+
//|  evaluates a predictor set using cross validation                |
//+------------------------------------------------------------------+
int               crossEval(ulong num_vars,vector &target,double &criterion)
  {
   ulong ifold, n_remaining, test_start, test_stop ;
   double error, evalresult ;

   n_remaining = m_samples;
   test_start = 0;

   error = 0.0;

   for(ifold = 0; ifold<m_folds; ifold++)
     {
      test_stop = test_start + (n_remaining/(m_folds-ifold));
      if(!fitModel(num_vars,test_start,test_stop,target))
        {
         criterion = -DBL_MIN;
         Print(__FUNCTION__, " fit() failed ");
         return 1;
        }
      evalresult = evalModel(num_vars,test_start,test_stop,target);
      if(evalresult==EMPTY_VALUE)
        {
         criterion = -DBL_MIN;
         Print(__FUNCTION__, " evalModel() failed ");
         return 1;
        }
      error+=evalresult;
      n_remaining -= test_stop - test_start;
      test_start = test_stop;
     }

   criterion = 1.0 - error/double(m_samples);
   return 0;
  }

Diese Funktion benötigt als Eingabe die Anzahl der zu bewertenden Prädiktoren, einen Vektor mit gemischten Zielwerten und einen Verweis auf eine Variable, die den Wert eines Kriteriums zurückgibt, das als eine der Entscheidungsvariablen für die Identifizierung der am besten geeigneten Merkmalsuntergruppe verwendet wird. Die Funktion gibt bei erfolgreicher Ausführung 0 zurück. Dabei wird zunächst ein Modell angepasst und dann dessen Leistung über alle Foldings der Beispieldaten hinweg bewertet.

Die Methode fitModel() ist für das Trainieren eines Modells auf einer Untergruppe von Kandidatenmerkmalen zuständig. Sie benötigt die folgenden Eingaben:

  • num_preds: Die Anzahl der Prädiktoren,
  • row_start: Der Index der ersten Stichprobe,
  • row_stop: Der Index eins nach der letzten Probe, die aus dem Trainingssatz ausgeschlossen werden soll, und,
  • targets: Der Vektor der gemischten Zielwerte.

Diese Funktion gibt einen booleschen Wert zurück, der angibt, ob das Modell erfolgreich trainiert wurde.

Die Funktion evalModel() hat die gleichen Eingaben wie fitModel(), aber dieses Mal stellt die angegebene Partition der Zeilen die Stichproben dar, die zur Bewertung des trainierten Modells verwendet werden. Diese Funktion gibt einen Wert zurück, der einen Hinweis auf die Leistung des Modells gibt. Sowohl fitModel() als auch evalModel() sind virtuelle Mitglieder, die in einer abgeleiteten Klasse implementiert werden müssen, um den spezifischen verwendeten Modelltyp zu integrieren.

//+------------------------------------------------------------------+
//| fit a model                                                      |
//+------------------------------------------------------------------+
virtual bool      fitModel(ulong num_preds,ulong row_start,ulong row_stop, vector& targets) { return false;}
//+------------------------------------------------------------------+
//| evaluate a model                                                 |
//+------------------------------------------------------------------+
virtual double    evalModel(ulong num_preds,ulong row_start,ulong row_stop, vector& targets) { return EMPTY_VALUE;}


Die Suche nach dem ersten Merkmal

Die Methode addFirstFeature() wird von addFeature() aufgerufen, um den ersten Prädiktor in einer gegebenen Menge zu identifizieren.

//+------------------------------------------------------------------+
//|     finds the first predictor in the set                         |
//+------------------------------------------------------------------+
int               addFirstFeature(vector &targ, int bindex,ulong rep_index)
  {
   double crit;

   for(ulong i = 0; i<m_keptvars; i++)
     {
      m_all_best_crits[rep_index][i] = -1.e60;
      m_all_best_trials[bindex+i] = -1;
     }

   for(int i = 0; i<int(m_vars); i++)
     {
      m_trial_vars[0] = i;
      if(crossEval(1,targ,crit))
        {
         Print(__FUNCTION__, " xval error ");
         return 1;
        }
      ulong j;
      for(j = 0; j<m_keptvars; j++)
        {
         if(crit > m_all_best_crits[rep_index][j])
            break;
        }
      if(j<m_keptvars)
        {
         for(long k = long(m_keptvars-2); k>=long(j); k--)
           {
            m_all_best_trials[bindex+k+1] = m_all_best_trials[bindex+k];
            m_all_best_crits[rep_index][k+1] = m_all_best_crits[rep_index][k];
           }
         m_all_best_trials[bindex+j] = i;
         m_all_best_crits[rep_index][j] = crit;
        }
     }

   return 0;
  }

Es durchläuft jede Variable und bewertet ihre Leistung mithilfe einer Kreuzvalidierung. Die Leistung wird anhand eines Kriteriums bewertet, das in einer abgeleiteten Klasse definiert ist, die wiederum die Lerntechnik zur Bestimmung des Modells bestimmt.

Während dieser Bewertung führt die Methode eine Liste der leistungsfähigsten Variablen mit den entsprechenden Kriterienwerten. Bei der Bewertung der einzelnen Variablen wird ihre Leistung mit derjenigen der derzeit besten Variablen verglichen. Wenn eine neue Variable eine der bestehenden Spitzenvariablen übertrifft, wird sie in die Liste eingefügt, wodurch die Variablen mit niedrigerer Leistung nach unten verschoben werden.

Dieser Prozess wird fortgesetzt, bis alle Variablen ausgewertet sind und der erste Prädiktor ermittelt ist. Bei nachfolgenden Aufrufen von addFeature() wird eine neue Variable durch den Aufruf von addNewFeature() eingeführt, wodurch ein systematisches Vorgehen beim Hinzufügen von Merkmalen gewährleistet wird.


Erweiterung des Funktionsumfangs

Die Methode addNewFeature() bestimmt den nächsten Prädiktor, der in den Funktionssatz des Modells aufgenommen wird.

//+------------------------------------------------------------------+
//|  looks for next predictor to include                             |
//+------------------------------------------------------------------+
int               addNewFeature(vector& target,int &ind, int bindex,ulong rep_index)
  {
   int i, j, k, ivar, ir ;
   int npred, n_already_tried,nbest, rootvars;
   nbest  = -1;
   double crit;

   ind = npred = ind+1;

   for(i = 0; i<int(m_keptvars); i++)
     {
      m_prior_best_crits[i] = m_all_best_crits[rep_index][i];
      if(ArrayCopy(m_prior_best_trials,m_all_best_trials,int(i*(npred-1)),bindex+int(i*(npred-1)),npred-1)<0)
        {
         Print(__FUNCTION__, " ArrayCopy error ", GetLastError());
         return 1;
        }
      m_all_best_crits[rep_index][i] = -1.e60;
     }
   n_already_tried = 0;
   for(ir = 0; ir<int(m_keptvars); ir++)
     {
      if(m_prior_best_crits[ir] < -1.e59)
         break;
     }
   nbest = ir;

   for(ir = 0; ir<nbest; ir++)
     {
      rootvars = ir*(npred-1);
      for(ivar=0; ivar<int(m_vars); ivar++)
        {
         for(i = 0; i<npred-1; i++)
           {
            if(m_prior_best_trials[rootvars+i] == ivar)
               break;
           }
         if(i < npred-1)
            continue;
         k = 0;

         for(i = 0; i<int(m_vars); i++)
           {
            for(j=0; j<npred-1; j++)
              {
               if(m_prior_best_trials[rootvars+j] == i)
                 {
                  m_trial_vars[k++] = i;
                  break;
                 }
              }
            if(ivar == i)
               m_trial_vars[k++] = i;
           }
         for(i = 0; i<n_already_tried; i++)
           {
            for(j = 0; j<npred; j++)
              {
               if(m_trial_vars[j] != m_already_tried[i*npred+j])
                  break;
              }
            if(j == npred)
               break;
           }
         if(i < n_already_tried)
            continue;

         for(i =0; i<npred; i++)
            m_already_tried[n_already_tried*npred+i]=m_trial_vars[i];
         ++n_already_tried;

         if(crossEval(ulong(npred),target,crit))
           {
            Print(__FUNCTION__, " xval error ");
            return 1;
           }

         for(i = 0; i< int(m_keptvars); i++)
           {
            if(crit > m_all_best_crits[rep_index][i])
               break;
           }

         if(i<int(m_keptvars))
           {
            for(j = int(m_keptvars-2); j>=i; j--)
              {
               m_all_best_crits[rep_index][j+1] = m_all_best_crits[rep_index][j];
               if(ArrayCopy(m_all_best_trials,m_all_best_trials,bindex+int((j+1)*npred),bindex+int(j*npred),npred)<0)
                 {
                  Print(__FUNCTION__, " array copy error ", GetLastError());
                  return 1;
                 }
              }
            m_all_best_crits[rep_index][i] = crit;
            if(ArrayCopy(m_all_best_trials,m_trial_vars,bindex+int(i*npred),0,npred)<0)
              {
               Print(__FUNCTION__, " array copy error ", GetLastError());
               return 1;
              }
           }
        }
     }
   return 0;
  }

Sie baut auf den vorhandenen Prädiktoren auf, indem sie alle möglichen Kombinationen der derzeit besten Prädiktoren mit zusätzlichen Variablen untersucht. Für jede Kombination führt die Methode eine Kreuzvalidierung durch, um ihre Leistung zu bewerten, die anhand eines bestimmten Kriteriums gemessen wird. Nachdem wir die Kernelemente des Codes für die schrittweise Merkmalsauswahl untersucht haben, werden wir nun untersuchen, wie diese Codeabschnitte zu einer vollständigen Implementierung zusammengefügt werden.


Die Klasse CStepwisebase

Die Klasse CStepwisebase dient als grundlegende Implementierung für die schrittweise Auswahl von Merkmalen, um iterativ prädiktive Merkmale aus einer Gruppe von Kandidaten auszuwählen. Bei diesem Auswahlverfahren werden systematisch Variablen auf der Grundlage ihres Beitrags zur Leistung eines Vorhersagemodells hinzugefügt. Als abstrakte Klasse ermöglicht sie die Integration jeder Art von Lerntechnik und des entsprechenden Kriteriums innerhalb einer abgeleiteten Klasse durch Implementierung der Methoden fitModel() und evalModel(). Ein Beispiel für diese Umsetzung wird im nächsten Abschnitt gegeben.

Die Methode stepwiseSelection() fungiert als der wichtigste öffentlich zugängliche Einstiegspunkt, der den Prozess durch die Vorbereitung der Daten und die Ausführung von stepwiseSearch() einleitet.

//+------------------------------------------------------------------+
//|  main method to execute stepwise feature selection               |
//+------------------------------------------------------------------+
bool              stepwiseSelection(matrix& predictors, vector& targets)
  {

   if(targets.Size()!=predictors.Rows() || !targets.Size())
     {
      Print(__FUNCTION__, " invalid inputs ");
      return false;
     }

   m_data = stdmat(predictors);

   m_target = (targets-targets.Mean())/(targets.Std()+1e-10);

   m_vars = predictors.Cols();
   m_samples = predictors.Rows();

   if(m_keptvars>m_vars || m_keptvars<1)
      m_keptvars = m_vars;

   if(m_folds<1)
      m_folds = 1;

   if(m_folds>m_samples)
      m_folds = m_samples;

   if(m_min_num_preds==0 || m_min_num_preds>m_vars || m_min_num_preds>m_max_num_preds)
      m_min_num_preds = m_vars;

   if(m_max_num_preds==0 || m_max_num_preds>m_vars || m_max_num_preds<m_min_num_preds)
      m_max_num_preds = m_vars;

   if(m_reps<1)
      m_reps = 1;

   if(m_verbose)
     {
      Print(" STEPWISE SELECTION PARAMETERS ");
      Print(" Number of retained candidate variables for each iteration ", m_keptvars);
      Print(" Number of folds in cross validation procedure ", m_folds);
      Print(" Final minimum number of selected predictors ", m_min_num_preds);
      Print(" Final maximum number of selected predictors ", m_max_num_preds);
      Print(" Number of replications for MCP test ", m_reps);
     }

   return (stepwiseSearch()==0);
  }

Um die Parameter für die Merkmalsauswahl im Vorfeld zu konfigurieren, steht die Methode setParams() zur Verfügung. Das Ergebnis der Merkmalsauswahl kann im MetaTrader 5 im Log der Experten angezeigt werden, indem der letzte Parameter von setParams() auf true gesetzt wird. Außerdem können die Ergebnisse des Auswahlverfahrens mit den folgenden Methoden abgerufen werden:

  •  Der Aufruf von getCriticalVals() liefert eine Matrix mit einer unterschiedlichen Anzahl von Spalten, je nachdem, ob ein Monte-Carlo-Permutationstest (MCP) als Teil der schrittweisen Suche angegeben wurde. Dies wird erreicht, indem num_replications in setParams() auf einen beliebigen Wert größer als 1 gesetzt wird. In diesem Fall gibt getCriticalVals() eine Matrix mit drei Spalten zurück, wobei jede Zeile Werte enthält, die das Hinzufügen eines zusätzlichen Merkmals quantifizieren. Die Anzahl der Zeilen in dieser Matrix entspricht der Anzahl der Iterationen durch die äußerste Schleife des schrittweisen Algorithmus. Die drei Werte in jeder Zeile bestehen aus dem Kriterium, einem p-Wert, der die Wahrscheinlichkeit angibt, dass die beobachtete Leistung dem Zufall zuzuschreiben ist (unter der Annahme, dass die ausgewählten Merkmale irrelevant sind), und einem weiteren p-Wert, der die Wahrscheinlichkeit widerspiegelt, dass die Leistungsverbesserung, die sich aus der Hinzufügung der Merkmale in der Iteration ergibt, auf den Zufall zurückzuführen ist.
    //+------------------------------------------------------------------+
    //| get all crit values and corresponding p-values                   |
    //+------------------------------------------------------------------+
    matrix            getCriticalVals(void)
      {
       return m_decision_matrix;
      }
  • Wenn kein MCP-Test angegeben ist, gibt die Funktion eine einzelne Spalte zurück, die die jeweiligen Kriterienwerte für jede Iteration des Algorithmuslaufs enthält. Die Anzahl der gefundenen besten Merkmalsuntergruppen kann mit getNumFeatureSets() abgefragt werden, während die Merkmalsgruppen selbst durch den Aufruf von getFeatureSetAt() abgerufen werden können, der einen der gewünschten Iteration entsprechenden Index und einen Verweis auf ein Array akzeptiert, in das die Indizes der Spalten, die die Merkmalsuntergruppe darstellen, kopiert werden.
    //+------------------------------------------------------------------+
    //|  get the selected features at a specific iteration of the process|
    //|  iterations denoted as zero based indices                        |
    //|  eg, first selected feature located at iteration index 0         |
    //+------------------------------------------------------------------+
    bool              getFeatureSetAt(ulong iteration_index,ulong &selectedvars[])
      {
       if(ArrayCopy(selectedvars,m_var_indices,0,int((iteration_index*(iteration_index+1))/2),int(iteration_index+1))>0)
          return true;
       else
         {
          Print(__FUNCTION__, " ArrayCopy error ", GetLastError());
          return false;
         }
      }
    //+---------------------------------------------------------------------+
    //| get maximum number of iterations cycled through in selection process|
    //+---------------------------------------------------------------------+
    ulong             getNumFeatureSets(void)
      {
       return m_decision_matrix.Rows();
      }

Damit ist unsere Diskussion über die Basisklasse abgeschlossen. Im nächsten Abschnitt werden wir untersuchen, wie dieser Code durch die Integration eines Modells und die Durchführung von Tests angewendet werden kann.


Ein Beispiel unter Verwendung eines linear-quadratischen Regressionsmodells

Zur Veranschaulichung des verbesserten schrittweisen Auswahlalgorithmus wird seine Anwendung bei der Auswertung eines Datensatzes von Kandidatenprädiktoren für die Anpassung eines linear-quadratischen Regressionsmodells demonstriert. Diese Implementierung ist in dem MetaTrader 5-Skript StepWiseFeatureSelection_Demo.mq5 enthalten. In diesem Skript wird der Prozess der Auswahl der prädiktivsten Merkmale durch die erweiterte schrittweise Auswahlmethode beschrieben und gezeigt, wie der Algorithmus die optimalen Prädiktoren für das linear-quadratische Regressionsmodell ermittelt. Die praktische Demonstration umfasst die Einrichtung des Datensatzes, die Konfiguration der Modellparameter und die Ausführung des Merkmalsauswahlprozesses in der MetaTrader 5-Umgebung.

Die linear-quadratische Regression ist eine Erweiterung der linearen Regression, die sowohl lineare als auch quadratische (quadratische) Terme der Prädiktorvariable(n) einbezieht. Dieser Ansatz ermöglicht es dem Modell, nicht-lineare Beziehungen zu erfassen, indem es eine Kurve an die Daten anpasst anstatt einer geraden Linie. Die allgemeine Form einer linear-quadratischen Regressionsgleichung für eine einzelne Prädiktorvariable X ist durch die folgende allgemeine Formel gegeben.

Linear-quadratische Regressionsformel

wobei:

  • y ist die abhängige Variable,
  • x ist die unabhängige Variable,
  • β0, β1​und β2 sind zu schätzende Koeffizienten,
  • x-Quadrat ist der quadratische Term, der es dem Modell ermöglicht, die Krümmung zu berücksichtigen,
  • ϵ ist der Fehlerterm.

In dieser Gleichung modelliert der Term βx die lineare Beziehung, während β(x-squared) die Krümmung erfasst, wodurch das Modell für Daten nützlich ist, die einen parabolischen oder gekrümmten Trend aufweisen. Der Koeffizient β0 stellt den Achsenabschnitt dar, β1 steuert die Steigung des linearen Anteils, und β2​ beeinflusst die Krümmung und bestimmt, ob sich die Parabel nach oben (β2>0) oder nach unten (β2<0) öffnet.

In der Praxis wird die linear-quadratische Regression in Situationen angewandt, in denen die Beziehung zwischen den abhängigen und unabhängigen Variablen nicht streng linear ist. Die Implementierung der linear-quadratischen Regression in unserem Skript wird durch die Nutzung der Klasse der gewöhnlichen kleinsten Quadrate (OLS) ermöglicht, die in OLS.mqh definiert ist.

Als Nächstes werden wir erläutern, wie dieses Regressionsmodell in den schrittweisen Auswahlprozess integriert wird, sodass die optimalen Prädiktoren aus unserem Datensatz ermittelt werden können.

Das Skript StepWiseFeatureSelection_Demo.mq5 implementiert den erweiterten schrittweisen Feature-Selection-Algorithmus, der für linear-quadratische Regressionsmodelle zugeschnitten ist. Es folgt eine Aufschlüsselung der Komponenten und Funktionen des Skripts. Die Klasse CStepwise erbt von CStepwisebase und bietet eine konkrete Implementierung der in der Basisklasse definierten abstrakten Methoden. Dieses Design ermöglicht die Integration einer spezifischen Modelllogik für linear-quadratische Regression. Die Kernlogik zur Anpassung und Auswertung des linear-quadratischen Modells ist in der Klasse CStepwise gekapselt. Dies beinhaltet die notwendigen Anpassungen, um sowohl lineare als auch quadratische Terme der Prädiktorvariablen zu berücksichtigen.

Die Methode fitModel() ist für die Konstruktion der Entwurfsmatrix auf der Grundlage der Trainingsmuster verantwortlich. Sie schließt den angegebenen Datenbereich für die Validierung während der Kreuzvalidierung aus. Es wird eine Instanz der OLS-Klasse erstellt (bezeichnet als m_ols), die die Regressionsoperationen der gewöhnlichen kleinsten Quadrate verwaltet.

//+------------------------------------------------------------------+
//|    fit the OLS model                                             |
//+------------------------------------------------------------------+
virtual bool      fitModel(ulong num_preds,ulong row_start,ulong row_stop,vector& targets)
  {
   int k1,k2;

   ulong nvars = num_preds + num_preds * (num_preds+1)/2;
   ulong ntrain = m_samples - (row_stop - row_start);

   vector dependent(ntrain);
   matrix independent(ntrain,nvars+1);
   ntrain = 0;

   for(ulong sample=0; sample<m_samples; sample++)
     {
      if(sample>=row_start && sample<row_stop)
         continue;
      k1=0;
      k2=1;
      for(ulong var=0; var<nvars; var++)
        {
         if(var<num_preds)
            independent[ntrain][var]=m_data[sample][m_trial_vars[var]];
         else
           {
            if(var<(num_preds*2))
              {
               independent[ntrain][var]=pow(m_data[sample][m_trial_vars[var-num_preds]],2.0);
              }
            else
              {
               independent[ntrain][var]=m_data[sample][m_trial_vars[k1]]*m_data[sample][m_trial_vars[k2]];
               ++k2;
               if(ulong(k2)==num_preds)
                 {
                  ++k1;
                  k2 = k1 + 1;
                 }
              }
           }
        }
      independent[ntrain][nvars] = 1.0;
      dependent[ntrain++] = targets[sample];
     }

   return m_ols.Fit(dependent,independent);
  }

In evalModel() bewertet die Methode die Leistung des trainierten linear-quadratischen Modells unter Verwendung der Daten, die während der Trainingsphase ausgelassen wurden. Hier wird das Kriterium für die Bewertung der Modellleistung angegeben, das in diesem Fall die Summe der quadrierten Fehler (SSE) ist. Dieses Kriterium misst, wie gut die Vorhersagen des Modells mit den tatsächlichen Ergebnissen übereinstimmen.

//+------------------------------------------------------------------+
//|      evaluate the model                                          |
//+------------------------------------------------------------------+
virtual double    evalModel(ulong num_preds,ulong row_start,ulong row_stop,vector& targets)
  {
   if(m_ols.Loglikelihood()==EMPTY_VALUE)
     {
      Print(__FUNCTION__, " OLS error ");
      return EMPTY_VALUE;
     }

   int k1,k2;

   ulong nvars = num_preds + num_preds * (num_preds+1)/2;
   double prediction;
   vector row(nvars);
   double error = 0.0;
   for(ulong sample=row_start; sample<row_stop; sample++)
     {
      k1=0;
      k2=1;
      for(ulong var=0; var<nvars; var++)
        {
         if(var<num_preds)
            row[var]=m_data[sample][m_trial_vars[var]];
         else
           {
            if(var<(num_preds*2))
              {
               row[var]=pow(m_data[sample][m_trial_vars[var-num_preds]],2.0);
              }
            else
              {
               row[var]=m_data[sample][m_trial_vars[k1]]*m_data[sample][m_trial_vars[k2]];
               ++k2;
               if(ulong(k2)==num_preds)
                 {
                  ++k1;
                  k2 = k1 + 1;
                 }
              }
           }
        }

      prediction = m_ols.Predict(row);
      if(prediction == EMPTY_VALUE)
        {
         Print(__FUNCTION__, " OLS predict() error ");
         return EMPTY_VALUE;
        }
      error+=pow(prediction-targets[sample],2.0);
     }

   return error;
  }

Nach der Definition der abgeleiteten Klasse CStepwise generiert das Skript eine zufällige 100 x 10-Matrix, bezeichnet als mat, mit Werten zwischen 0 und 1. Diese Matrix dient als Datensatz, der die Vorhersagevariablen enthält.

//---
   MathSrand(120);
//---
   matrix mat(100,10);
//---
   mat.Random(0.0,1.0);
//---

Ein Zielvektor wird durch Summierung bestimmter Spalten erstellt. Die Zielvariable ist definiert als die Summe der Werte der Spalten 0, 5, 4 und 6 der Matrix. Damit wird die abhängige Variable (oder der Output) definiert, die das Modell auf der Grundlage der unabhängigen Variablen (die übrigen Spalten) vorhersagen soll.

//---
   vector target = mat.Col(0) + mat.Col(5) + mat.Col(4) + mat.Col(6);
//---

Es wird eine Instanz der Klasse CStepwise erstellt, die als schrittweise bezeichnet wird. Diese Instanz verwaltet den schrittweisen Merkmalsauswahlprozess, der auf das linear-quadratische Regressionsmodell zugeschnitten ist. Die Methode setParams() wird für die Instanz stepwise aufgerufen, um die Parameter für den schrittweisen Auswahlprozess zu konfigurieren.

//---
   CStepwise stepwise;
   stepwise.setParams(NumKeptVars,NumFolds,MinNumVars,MaxNumVars,NumReplications,true);
   if(!stepwise.stepwiseSelection(mat,target))
      return;

Zu den Parametern, die vom Nutzer eingestellt werden können, gehören:

input ulong NumKeptVars = 1;
input ulong NumFolds = 10;
input ulong MinNumVars = 3;
input ulong MaxNumVars = 5;
input long NumReplications = 1;
  • NumKeptVars: Gibt die gewünschte Anzahl von Merkmalen an, die in jeder Iteration des Auswahlprozesses beibehalten werden sollen.
  • NumFolds: Bestimmt die Anzahl der Faltungen, die für die Kreuzvalidierung verwendet werden sollen, um eine robuste Bewertung der Leistung des Modells zu ermöglichen.
  • MinNumVars: Legt die Mindestanzahl von Merkmalen fest, die der endgültige ausgewählte Merkmalssatz enthalten kann, um sicherzustellen, dass das Modell für die Vorhersage angemessen ausgestattet bleibt.
  • MaxNumVars: Legt die maximale Anzahl von Merkmalen fest, die im endgültigen Merkmalssatz zulässig sind, und verhindert so eine Überanpassung durch Kontrolle der Modellkomplexität.
  • NumReplications: Gibt die Anzahl der Iterationen für den Monte-Carlo-Permutationstest an, wodurch die Zuverlässigkeit der bei der Merkmalsauswahl durchgeführten Signifikanztests erhöht wird.

Nach dem Aufruf der Methode stepwiseSelection() beginnt der Prozess der schrittweisen Merkmalsauswahl. Diese Methode orchestriert den gesamten Auswahlalgorithmus, indem sie die zuvor erörterten Strategien zur Ermittlung der relevantesten Prädiktoren für das linear-quadratische Regressionsmodell umsetzt. Die vom Skript StepWiseFeatureSelection_Demo.mq5 erzeugte Ausgabe kann wertvolle Erkenntnisse darüber liefern, wie verschiedene Hyperparameter den Merkmalsauswahlprozess beeinflussen. Gehen wir nun auf die verschiedenen Aspekte der Ausgabe ein und wie sie mit den angegebenen Eingabeparametern zusammenhängen.

Die wichtigsten Ausgabekomponenten des Algorithmus sind:

  1. Ausgewählte Feature-Sets: Die primäre Ausgabe ist die Menge der Merkmale, die der Algorithmus nach der schrittweisen Auswahl ausgewählt hat. Jede Merkmalsgruppe stellt eine Kombination von Prädiktoren dar, die aufgrund ihres Beitrags zur Modellleistung als besonders relevant erachtet werden.
  2. Leistungsmetriken: Für jede Iteration des Auswahlprozesses gibt das Skript in der Regel Leistungsmetriken aus, wie z. B:
    •  Kriterienwerten: Die Summe der quadrierten Fehler oder ein anderes gewähltes Kriterium, das ein quantifizierbares Maß für die Anpassung des Modells liefert.
    •  p-Werte: Wenn Monte-Carlo-Permutationstests durchgeführt werden, enthält die Ausgabe p-Werte, die die Wahrscheinlichkeit angeben, dass die beobachteten Leistungsverbesserungen auf Zufall beruhen.

Im ersten Durchlauf des Skripts StepWiseFeatureSelection_Demo.mq5 mit Standardparametern werden die Voraussetzungen geschaffen, um zu beobachten, wie der schrittweise Auswahlprozess in einem eingeschränkten Umfeld funktioniert. Im Folgenden finden Sie eine Aufschlüsselung dessen, was Sie von der Ausgabe erwarten können, die Gründe für diese Erwartungen und wie die Ergebnisse mit unseren Beobachtungen übereinstimmen.

Da kein MCP-Test angegeben ist, enthält die Ausgabe in der Tat nur eine einzige Spalte mit den Kriterienwerten für jede Iteration. Dies wird die Leistung des Modells widerspiegeln, wenn Prädiktoren hinzugefügt werden. Es wird davon ausgegangen, dass die Kriterienwerte bei jeder Iteration steigen. Dies zeigt, dass sich die Leistung des Modells verbessert, wenn relevante Prädiktoren hinzugefügt werden. Der Algorithmus wählt strategisch Merkmale aus, die die Vorhersagegenauigkeit erhöhen, wodurch das Kriterium nach oben getrieben wird. Wenn die relevanten Prädiktoren identifiziert sind, wird das Kriterium schließlich einen optimalen Wert erreichen (in diesem Fall 1). Dies deutet darauf hin, dass das Modell erfolgreich die informativsten Prädiktoren integriert hat und so gut wie möglich funktioniert, wenn man die Einschränkungen des Modells berücksichtigt.

MM      0       20:20:28.754    StepWiseFeatureSelection_Demo (BTCUSD,D1)        Stepwise selection successfully completed 
QM      0       21:05:21.050    StepWiseFeatureSelection_Demo (BTCUSD,D1)        STEPWISE SELECTION PARAMETERS 
KG      0       21:05:21.050    StepWiseFeatureSelection_Demo (BTCUSD,D1)        Number of retained candidate variables for each iteration 1
QH      0       21:05:21.050    StepWiseFeatureSelection_Demo (BTCUSD,D1)        Number of folds in cross validation procedure 10
FI      0       21:05:21.050    StepWiseFeatureSelection_Demo (BTCUSD,D1)        Final minimum number of selected predictors 3
FM      0       21:05:21.050    StepWiseFeatureSelection_Demo (BTCUSD,D1)        Final maximum number of selected predictors 5
PM      0       21:05:21.050    StepWiseFeatureSelection_Demo (BTCUSD,D1)        Number of replications for MCP test 1
KR      0       21:05:21.050    StepWiseFeatureSelection_Demo (BTCUSD,D1)       Criterion  || Column Indices
ED      0       21:05:21.067    StepWiseFeatureSelection_Demo (BTCUSD,D1)       0.26300861 6
NO      0       21:05:21.089    StepWiseFeatureSelection_Demo (BTCUSD,D1)       0.57220902 4 6
RG      0       21:05:21.122    StepWiseFeatureSelection_Demo (BTCUSD,D1)       0.75419718 4 5 6
HH      0       21:05:21.170    StepWiseFeatureSelection_Demo (BTCUSD,D1)       1.00000000 0 4 5 6
GL      0       21:05:21.240    StepWiseFeatureSelection_Demo (BTCUSD,D1)       CStepwisebase::stepwise_search procedure terminated early because adding a new variable caused performance degradation

Im Ausgabeprotokoll wird bei jeder Iteration der aktuelle Kriterienwert nach Auswertung der ausgewählten Prädiktoren angezeigt. Die zunehmende Tendenz wird ein klares Bild davon vermitteln, wie jedes hinzugefügte Merkmal zur Modellleistung beiträgt. In der Ausgabe sollte auch der Grund für den Abbruch angegeben werden, der wahrscheinlich besagt, dass zusätzliche Merkmale nicht zu einer verbesserten Leistung geführt haben, wodurch der Entscheidungsprozess des Algorithmus in Echtzeit bestätigt wird. Dies wird durch den Parameter ermöglicht, der die Mindestanzahl der Prädiktoren angibt, die in einem Merkmalssatz zulässig sind.

In einem weiteren Durchlauf des Skripts StepWiseFeatureSelection_Demo.mq5 führen wir einen Monte-Carlo-Permutationstest (MCP) mit 100 Permutationen ein, wobei wir die Standardwerte für die anderen Parameter beibehalten. Durch diese Anpassung wird das Ergebnis erheblich bereichert und bietet tiefere Einblicke in den Prozess der Merkmalsauswahl.

Wenn der MCP-Test aktiviert ist, besteht die Ausgabe nun aus mehreren Spalten. Jede Zeile entspricht einer Iteration und zeigt den Wert des Kriteriums sowie die Wahrscheinlichkeit des zufälligen Auftretens und die Wahrscheinlichkeit einer falschen Verbesserung an. Ein niedriger p-Wert (in der Regel ≤ 0,05) zeigt an, dass die beobachtete Leistung wahrscheinlich nicht zufällig ist, was die Aufnahme des entsprechenden Merkmals unterstützt.

Die beiden dargestellten p-Werte helfen den Nutzern bei der Bewertung der Zuverlässigkeit der ausgewählten Prädiktoren.

RI      0       21:10:57.100    StepWiseFeatureSelection_Demo (BTCUSD,D1)        STEPWISE SELECTION PARAMETERS 
LK      0       21:10:57.100    StepWiseFeatureSelection_Demo (BTCUSD,D1)        Number of retained candidate variables for each iteration 1
RD      0       21:10:57.100    StepWiseFeatureSelection_Demo (BTCUSD,D1)        Number of folds in cross validation procedure 10
EM      0       21:10:57.100    StepWiseFeatureSelection_Demo (BTCUSD,D1)        Final minimum number of selected predictors 3
EQ      0       21:10:57.100    StepWiseFeatureSelection_Demo (BTCUSD,D1)        Final maximum number of selected predictors 5
OQ      0       21:10:57.100    StepWiseFeatureSelection_Demo (BTCUSD,D1)        Number of replications for MCP test 100
PI      0       21:10:57.101    StepWiseFeatureSelection_Demo (BTCUSD,D1)       Criterion  || New pval  || Diff pval  || Column Indices
RD      0       21:10:58.851    StepWiseFeatureSelection_Demo (BTCUSD,D1)       0.26300861 0.01000000 0.01000000   6
EI      0       21:11:01.243    StepWiseFeatureSelection_Demo (BTCUSD,D1)       0.57220902 0.01000000 0.01000000   4 6
KM      0       21:11:04.482    StepWiseFeatureSelection_Demo (BTCUSD,D1)       0.75419718 0.01000000 0.01000000   4 5 6
FP      0       21:11:09.151    StepWiseFeatureSelection_Demo (BTCUSD,D1)       1.00000000 0.01000000 0.01000000   0 4 5 6
CI      0       21:11:09.214    StepWiseFeatureSelection_Demo (BTCUSD,D1)       CStepwisebase::stepwise_search procedure terminated early because adding a new variable caused performance degradation

Dieser Lauf, der durch die Einbeziehung eines Monte-Carlo-Permutationstests verbessert wurde, liefert ein umfassenderes und informativeres Ergebnis. Die mehreren Spalten ermöglichen es den Nutzern, fundiertere Entscheidungen über den optimalen Merkmalssatz auf der Grundlage von Leistungskriterien und statistischer Signifikanz zu treffen. Auf diese Weise können die Nutzer sicher erkennen, welche Prädiktoren im endgültigen Modell beibehalten werden sollten, wobei sie sich an hohen Kriterienwerte und niedrigen p-Werten orientieren können.

LL      0       07:22:46.657    StepWiseFeatureSelection_Demo (BTCUSD,D1)        STEPWISE SELECTION PARAMETERS 
GH      0       07:22:46.658    StepWiseFeatureSelection_Demo (BTCUSD,D1)        Number of retained candidate variables for each iteration 1
ID      0       07:22:46.658    StepWiseFeatureSelection_Demo (BTCUSD,D1)        Number of folds in cross validation procedure 5
FH      0       07:22:46.658    StepWiseFeatureSelection_Demo (BTCUSD,D1)        Final minimum number of selected predictors 10
JO      0       07:22:46.658    StepWiseFeatureSelection_Demo (BTCUSD,D1)        Final maximum number of selected predictors 10
FL      0       07:22:46.658    StepWiseFeatureSelection_Demo (BTCUSD,D1)        Number of replications for MCP test 100
FL      0       07:22:46.658    StepWiseFeatureSelection_Demo (BTCUSD,D1)       Criterion  || New pval  || Diff pval  || Column Indices
LG      0       07:22:47.303    StepWiseFeatureSelection_Demo (BTCUSD,D1)       0.26726680 0.01000000 0.01000000   6
LJ      0       07:22:48.153    StepWiseFeatureSelection_Demo (BTCUSD,D1)       0.56836766 0.01000000 0.01000000   4 6
FH      0       07:22:49.518    StepWiseFeatureSelection_Demo (BTCUSD,D1)       0.74542884 0.01000000 0.01000000   4 5 6
NM      0       07:22:51.488    StepWiseFeatureSelection_Demo (BTCUSD,D1)       1.00000000 0.01000000 0.01000000   0 4 5 6
KQ      0       07:22:54.163    StepWiseFeatureSelection_Demo (BTCUSD,D1)       1.00000000 0.01000000 0.71000000   0 1 4 5 6
JF      0       07:22:57.793    StepWiseFeatureSelection_Demo (BTCUSD,D1)       1.00000000 0.01000000 0.85000000   0 1 2 4 5 6
DD      0       07:23:02.436    StepWiseFeatureSelection_Demo (BTCUSD,D1)       1.00000000 0.01000000 0.94000000   0 1 2 3 4 5 6
MK      0       07:23:08.007    StepWiseFeatureSelection_Demo (BTCUSD,D1)       1.00000000 0.01000000 1.00000000   0 1 2 3 4 5 6 7
DH      0       07:23:13.842    StepWiseFeatureSelection_Demo (BTCUSD,D1)       1.00000000 0.01000000 1.00000000   0 1 2 3 4 5 6 7 8
HO      0       07:23:18.949    StepWiseFeatureSelection_Demo (BTCUSD,D1)       1.00000000 0.01000000 1.00000000   0 1 2 3 4 5 6 7 8 9
PL      0       07:23:18.949    StepWiseFeatureSelection_Demo (BTCUSD,D1)        Stepwise selection successfully completed 

Im letzten Durchlauf des Skripts StepWiseFeatureSelection_Demo.mq5 liegt der Schwerpunkt auf der Erweiterung des Umfangs des Merkmalsauswahlprozesses, indem der Parameter MinNumVars auf mindestens 10 Prädiktoren erhöht wird. Wenn mehr Prädiktoren in die endgültige Teilmenge aufgenommen werden, kann der Algorithmus größere Merkmalskombinationen bewerten und möglicherweise Wechselwirkungen oder nichtlineare Beziehungen aufdecken, die bei einer kleineren Teilmenge übersehen werden könnten. Die Erhöhung der Mindestanzahl von Prädiktoren ermöglicht zwar eine umfangreichere Suche, doch ist zu beachten, dass dies auch zu einer längeren Laufzeit des Algorithmus führt. Die zusätzliche Komplexität der Auswertung größerer Merkmalsmengen erfordert mehr Rechenressourcen und Zeit.

Die in den Algorithmus eingebauten Überprüfungen stellen sicher, dass, wenn MinNumVars oder MaxNumVars auf Werte gesetzt werden, die die Gesamtzahl der Kandidatenprädiktoren (in diesem Fall 10) überschreiten, diese automatisch an die verfügbare Anzahl der Prädiktoren im Datensatz angepasst werden. Dadurch werden Konfigurationsfehler vermieden, die zu Laufzeitproblemen oder unangemessenem Verhalten bei der Merkmalsauswahl führen könnten. Durch die Änderung des Parameters MinNumVars können die Nutzer den schrittweisen Merkmalsauswahlprozess so anpassen, dass ein breiteres Spektrum von Merkmalen untersucht wird, was möglicherweise zur Entdeckung informativerer Prädiktorsätze führt. Diese Flexibilität erhöht die Nutzerfreundlichkeit des Algorithmus und ermöglicht es Praktikern, den Auswahlprozess an die spezifischen Merkmale ihrer Datensätze und Analyseziele anzupassen.

Die Laufzeit des Algorithmus hängt von den gewählten Hyperparametern ab. Der Monte-Carlo-Permutationstest (MCP-Test) kann den Prozess erheblich verlangsamen, insbesondere wenn die Anzahl der Permutationen steigt. Jede Permutation erfordert eine vollständige Auswertung des Modells, wodurch sich die Gesamtzahl der durchgeführten Berechnungen erhöht. So können mehr Permutationen zwar die Robustheit der statistischen Signifikanzergebnisse verbessern, erfordern aber auch mehr Verarbeitungszeit.

Außerhalb des MCP-Tests ist der nächste Parameter, der sich auf die Laufzeit des Algorithmus auswirken kann, die Anzahl der für die Kreuzvalidierung festgelegten Faltungen. Bei der Kreuzvalidierung wird der Datensatz in mehrere Teilmengen aufgeteilt und das Modell wiederholt in diesen verschiedenen Teilmengen trainiert und getestet. Mit zunehmender Anzahl von Faltungen muss der Algorithmus mehr Trainings- und Bewertungsiterationen durchführen. Dies ist besonders zeitaufwändig, vor allem bei größeren Datensätzen, da für jede Faltung eine vollständige Modellanpassung und -bewertung erforderlich ist.

Schließlich sollte die Auswahl der geeigneten Mindest- und Höchstzahl von Prädiktoren, die im endgültigen Modell zulässig sind, sorgfältig geprüft werden. Je mehr Prädiktoren für die Einbeziehung in Betracht kommen, desto mehr Kombinationen muss der Algorithmus während des Merkmalsauswahlprozesses bewerten, was zu einer Erhöhung der Anzahl der Suchzyklen führt, die zur Ermittlung der optimalen Merkmalsuntergruppen erforderlich sind. In Anbetracht der Kompromisse, die mit diesen Parametern verbunden sind, ist es für Praktiker wichtig, Hyperparameter auf der Grundlage ihres spezifischen Anwendungsfalls mit Bedacht auszuwählen.


Schlussfolgerung

In diesem Artikel haben wir eine Verbesserung des schrittweisen Algorithmus zur Auswahl von Merkmalen untersucht, der die Identifizierung von prädiktiven Merkmalen aus einem größeren Satz von Variablen optimieren soll. Ergänzt durch Kreuzvalidierung und optionale Monte-Carlo-Permutationstests bietet der Algorithmus einen Rahmen, der die Grenzen traditioneller schrittweiser Methoden überwindet und die Auswahl von Merkmalsuntergruppen ermöglicht, die die Vorhersageleistung maximieren und gleichzeitig das Risiko einer Überanpassung minimieren.

Wir haben untersucht, wie wichtig die Abstimmung der Hyperparameter ist, um ein Gleichgewicht zwischen rechnerischer Effizienz und statistischer Zuverlässigkeit herzustellen. Dabei haben wir gezeigt, wie sich Parameter wie die Anzahl der Faltungen für die Kreuzvalidierung, die Anzahl der Permutationen für Signifikanztests und die Beschränkungen für die minimale und maximale Anzahl von Prädiktoren erheblich auf die Laufzeit des Algorithmus auswirken können.

Die praktische Demonstration mit dem Skript StepWiseFeatureSelection_Demo.mq5 veranschaulichte, wie der Algorithmus effektiv auf reale Datensätze angewandt werden kann, um Einblicke in die Bedeutung ausgewählter Teilmengen von Merkmalen zu erhalten. Zusammenfassend lässt sich sagen, dass die vorgeschlagene Methode nicht nur den Prozess der Merkmalsauswahl verbessert, sondern auch Praktiker in die Lage versetzen könnte, fundierte Entscheidungen zur Modellbildung zu treffen. Der gesamte Code, auf den im Text verwiesen wird, ist in den nachstehenden Dateien enthalten.

Datei
Beschreibung
MQL5/include/np.mqh
Enthält die Definition von Hilfsfunktionen für die Bearbeitung von Matrizen und Vektoren.
MQL5/include/OLS.mqh
Header-Datei für die OLS-Klasse, die die Regression der gewöhnlichen kleinsten Quadrate implementiert.
MQL5/include/stepwise.mqh
Header-Datei für die Klasse CStepwisebase, die eine erweiterte schrittweise Merkmalsauswahlmethode implementiert.
MQL5/scripts/StepWiseFeatureSelection_Demo.mq5
Ein Skript, das die Klasse CStepwisebase anwendet, um eine schrittweise Merkmalsauswahl für einen beliebigen Datensatz durchzuführen.

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

Beigefügte Dateien |
np.mqh (74.16 KB)
OLS.mqh (13.32 KB)
stepwise.mqh (21.13 KB)
MQL5.zip (19.51 KB)
Klassische Strategien neu interpretieren (Teil XI): Kreuzung gleitender Durchschnitte (II) Klassische Strategien neu interpretieren (Teil XI): Kreuzung gleitender Durchschnitte (II)
Die gleitenden Durchschnitte und der Stochastik-Oszillator können verwendet werden, um trendfolgende Handelssignale zu generieren. Diese Signale werden jedoch erst nach dem Eintreten der Preisaktion beobachtet. Diese den technischen Indikatoren innewohnende Verzögerung können wir mit Hilfe von KI wirksam überwinden. In diesem Artikel erfahren Sie, wie Sie einen vollständig autonomen KI-gesteuerten Expert Advisor erstellen, der Ihre bestehenden Handelsstrategien verbessern kann. Selbst die älteste mögliche Handelsstrategie kann verbessert werden.
Handel mit dem MQL5 Wirtschaftskalender (Teil 2): Erstellen eines News Dashboard Panels Handel mit dem MQL5 Wirtschaftskalender (Teil 2): Erstellen eines News Dashboard Panels
In diesem Artikel erstellen wir ein praktisches Nachrichten-Dashboard-Panel mit dem MQL5-Wirtschaftskalender, um unsere Handelsstrategie zu verbessern. Wir beginnen mit der Gestaltung des Layouts und konzentrieren uns dabei auf Schlüsselelemente wie Ereignisnamen, Wichtigkeit und Zeitplanung, bevor wir mit der Einrichtung in MQL5 beginnen. Schließlich implementieren wir ein Filtersystem, das nur die relevantesten Nachrichten anzeigt und den Händlern einen schnellen Zugang zu wichtigen wirtschaftlichen Ereignissen ermöglicht.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 48): Bill Williams Alligator MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 48): Bill Williams Alligator
Der Alligator-Indikator, der von Bill Williams entwickelt wurde, ist ein vielseitiger Indikator zur Trenderkennung, der klare Signale liefert und häufig mit anderen Indikatoren kombiniert wird. Die MQL5-Assistenten-Klassen und die Assemblierung ermöglichen es uns, eine Vielzahl von Signalen auf der Basis von Mustern zu testen, und so betrachten wir auch diesen Indikator.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 46): Ichimoku MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 46): Ichimoku
Der Ichimuko Kinko Hyo ist ein bekannter japanischer Indikator, der als Trenderkennungssystem dient. Wir untersuchen dies, wie schon in früheren ähnlichen Artikeln, Muster für Muster und bewerten auch die Strategien und Testberichte mit Hilfe der MQL5-Assistentenbibliothek Klassen und Assembly.