English Русский 中文 Español 日本語
preview
Anwendung der lokalisierten Merkmalsauswahl in Python und MQL5

Anwendung der lokalisierten Merkmalsauswahl in Python und MQL5

MetaTrader 5Beispiele | 13 Mai 2025, 07:35
38 0
Francis Dube
Francis Dube

Einführung

Bei der Finanzmarktanalyse zeigen die Indikatoren oft eine unterschiedliche Wirksamkeit, wenn sich die zugrunde liegenden Bedingungen ändern. So kann beispielsweise die schwankende Volatilität bisher zuverlässige Indikatoren unwirksam machen, wenn sich die Marktbedingungen ändern. Diese Variabilität erklärt die Vielzahl der von den Händlern verwendeten Indikatoren, da kein einziger Indikator unter allen Marktbedingungen gleichbleibend gute Ergebnisse liefern kann. Aus der Perspektive des maschinellen Lernens erfordert dies eine flexible Technik zur Auswahl von Merkmalen, die ein solches dynamisches Verhalten berücksichtigen kann.

Viele gängige Algorithmen zur Merkmalsauswahl bevorzugen Merkmale, die eine Vorhersagekraft für den gesamten Merkmalsraum aufweisen. Diese Merkmale werden oft auch dann bevorzugt, wenn ihre Beziehungen zur Zielvariablen nichtlinear sind oder von anderen Merkmalen beeinflusst werden. Diese globale Verzerrung kann jedoch problematisch sein, da moderne nichtlineare Modelle wertvolle Erkenntnisse aus Merkmalen mit starken lokalen Vorhersagefähigkeiten gewinnen können oder deren Beziehungen zur Zielvariablen sich innerhalb bestimmter Regionen des Merkmalsraums verschieben.

In diesem Artikel untersuchen wir einen Algorithmus zur Auswahl von Merkmalen, der in dem Artikel „Local Feature Selection for Data Classification“ von Narges Armanfard, James P. Reilly und Majid Komeili vorgestellt wurde. Diese Methode zielt darauf ab, prädiktive Merkmale zu identifizieren, die von herkömmlichen Auswahlverfahren aufgrund ihres begrenzten globalen Nutzens häufig übersehen werden. Wir beginnen mit einem allgemeinen Überblick über den Algorithmus, gefolgt von seiner Implementierung in Python, um Klassifizierungsmodelle zu erstellen, die für den Export in MetaTrader 5 geeignet sind.



Auswahl lokaler Merkmale

Erfolgreiches maschinelles Lernen beruht auf der Auswahl informativer Merkmale, die zur Lösung des Problems beitragen. Bei der überwachten Klassifizierung sollten die Merkmale eine wirksame Unterscheidung zwischen den Datenkategorien ermöglichen. Die Identifizierung dieser informativen Merkmale kann jedoch eine Herausforderung darstellen, da uninformative Merkmale zu Rauschen führen und die Modellleistung beeinträchtigen können. Daher ist die Auswahl der Merkmale oft ein entscheidender erster Schritt bei der Erstellung von Vorhersagemodellen.

Im Gegensatz zu herkömmlichen Methoden, die eine einzige optimale Merkmalsuntergruppe für alle Daten suchen, ermittelt die lokale Merkmalsauswahl (Local Feature Selection, LFS) optimale Untergruppen für bestimmte lokale Regionen. Diese Anpassungsfähigkeit könnte vor allem bei der Verarbeitung nicht-stationärer Daten von Nutzen sein. Darüber hinaus beinhaltet LFS einen Klassifikator, der die unterschiedlichen Untergruppen von Merkmalen berücksichtigt, die in verschiedenen Stichproben verwendet werden. Dies wird durch klassenweises Clustering erreicht, bei dem Merkmale ausgewählt werden, die die Abstände innerhalb der Klassen minimieren und die Abstände zwischen den Klassen maximieren.

Lokalisierte Merkmalsauswahl

Bei diesem Ansatz wird ein lokal optimaler Merkmalsunterraum in sich überschneidenden Regionen identifiziert, wodurch sichergestellt wird, dass jede Probe in mehreren Merkmalsräumen vertreten ist. Um das Konzept besser zu verstehen, betrachten wir ein Szenario, in dem ein Telekommunikationsunternehmen die Abwanderung von Kunden vorhersagen möchte, indem es Kunden identifiziert, die ihre Konten wahrscheinlich schließen. Das Unternehmen erhebt verschiedene Kundenmerkmale, darunter:

  • Dauerhaftigkeit der Kunden: Wie lange ist der Kunde schon bei dem Unternehmen?
  • Monatliche Rechnungen: Wie viel zahlt der Kunde jeden Monat?
  • Gewicht und Größe des Kunden.
  • Anzahl der Anrufe beim Kundendienst: Wie oft kontaktiert der Kunde den Support.

Stellen Sie sich vor, Sie wählen zwei treue Kunden aus, die dem Unternehmen schon seit vielen Jahren treu sind. Für jedes der beschriebenen Merkmale gäbe es wahrscheinlich nur minimale Unterschiede zwischen diesen treuen Kunden, da sie zur gleichen Klasse gehören. Vergleichen Sie dies mit dem Unterschied zwischen einem langjährigen Kunden und einem Kunden, der sein Abonnement kurz nach der Anmeldung gekündigt hat. Während sich Gewicht und Größe vielleicht nicht stark unterscheiden, würden andere relevante Prädiktoren wahrscheinlich erhebliche Unterschiede aufweisen.

Der treue Kunde hat natürlich eine viel längere Verweildauer, ist vielleicht eher bereit, sich für ein höherpreisiges Abonnementpaket zu entscheiden, und wendet sich eher an den Kundendienst, wenn Probleme auftreten, als dass er aus Frust kündigt. In der Zwischenzeit würden Metriken wie Gewicht und Größe in der Nähe des Bevölkerungsdurchschnitts bleiben und würden nicht wesentlich zur Unterscheidung dieser Kundentypen beitragen.

Die paarweise Analyse der einzelnen Merkmalswerte unter Verwendung des Euklidischen Abstands zeigt, dass die relevantesten Prädiktoren den größten Abstand zwischen den Kunden aufweisen, während die am wenigsten relevanten Prädiktoren den geringsten Abstand zwischen den Klassen haben. Dies macht die Auswahl effektiver Prädiktoren deutlich: Wir bevorzugen Paare mit geringem Intra-Klassenabstand und hohem Inter-Klassenabstand.

Dieser Ansatz scheint zwar effektiv zu sein, kann aber lokale Unterschiede in den Daten nicht berücksichtigen. Um dieses Problem zu lösen, müssen wir berücksichtigen, wie sich die Vorhersagekraft in verschiedenen Merkmalsbereichen unterscheiden kann. Stellen Sie sich einen Datensatz mit zwei Klassen vor, wobei eine Klasse in zwei unterschiedliche Teilmengen unterteilt ist. Ein Streudiagramm von zwei Merkmalen aus diesem Datensatz zeigt, dass die erste Untergruppe anhand der Variable x1 gut von Klasse 1 unterschieden werden kann, nicht aber x2. Umgekehrt kann die zweite Teilmenge mit x2, aber nicht mit x1 gut getrennt werden.

Hypothetisches Streudiagramm

Wenn wir nur die Trennung zwischen den Klassen berücksichtigen, könnte der Algorithmus fälschlicherweise sowohl x1 als auch x2 auswählen, obwohl nur eines in jeder Teilmenge wirklich effektiv ist. Dies liegt daran, dass der Algorithmus den großen Gesamtabstand zwischen den beiden Teilmengen gegenüber den kleineren, relevanteren Abständen innerhalb jeder Teilmenge bevorzugen könnte. Um dieses Problem zu lösen, haben die Autoren der zitierten Arbeit ein Gewichtungsschema für die Entfernungen eingeführt. Indem näher beieinander liegende Fallpaare höher und weiter auseinander liegende Paare niedriger gewichtet werden, kann der Algorithmus den Einfluss von Ausreißern innerhalb einer Klasse verringern. Dabei werden sowohl die Klassenzugehörigkeit als auch die globale Verteilung der Entfernungen berücksichtigt.

Zusammenfassend lässt sich sagen, dass der LFS-Algorithmus, wie in der zitierten Arbeit beschrieben, aus zwei Hauptkomponenten besteht. Der erste ist der Prozess der Merkmalsauswahl, bei dem für jede Stichprobe eine Teilmenge von Merkmalen ausgewählt wird. Die zweite Komponente umfasst einen lokalisierten Mechanismus, der die Ähnlichkeit eines Testmusters mit einer bestimmten Klasse misst, die für Schlussfolgerungen verwendet wird.



Merkmalsauswahl

In diesem Abschnitt wird das Lernverfahren der LFS-Methode Schritt für Schritt und mit ein wenig Mathematik beschrieben. Wir beginnen mit der erwarteten Struktur der Trainingsdaten. Die Implementierung der lokalisierten Merkmalsauswahl erfolgt auf einem Datensatz mit N Trainingsstichproben, die in Z Klassenkennzeichnungen klassifiziert sind und von M Merkmalen oder Prädiktorkandidaten begleitet werden.

Die Trainingsdaten können als Matrix X dargestellt werden, wobei die Zeilen den Stichproben und die Spalten den einzelnen Prädiktoren entsprechen. Die Matrix X hat also N Zeilen und M Spalten. Jede Stichprobe wird als X(i) bezeichnet, was sich auf die i-te Zeile in der Matrix bezieht. Die Klassenbezeichnungen werden in einem separaten Spaltenvektor Y gespeichert, wobei jede Bezeichnung einer entsprechenden Probe (Zeile) in der Matrix zugeordnet ist.

Das ultimative Ziel der Anwendung der LFS-Methode ist es, für jede Trainingsstichprobe X(i) einen M-großen binären Vektor F(i) zu bestimmen, der angibt, welche Kandidatenprädiktoren für die Bestimmung des entsprechenden Klassenkennzeichnungen am relevantesten sind. Die Matrix F hat die gleichen Abmessungen wie X.

Unter Verwendung des euklidischen Abstands zielt der Algorithmus darauf ab, den durchschnittlichen Abstand zwischen der aktuellen Stichprobe und anderen Stichproben mit derselben Klassenkennzeichnung zu minimieren, während der durchschnittliche Abstand zwischen der aktuellen Stichprobe und Stichproben mit anderen Klassenkennzeichnungen maximiert wird. Zusätzlich müssen die Entfernungen gewichtet werden, um Proben in der gleichen Nachbarschaft wie die aktuelle Probe zu bevorzugen, was den Gewichtungsspaltenvektor W einführt. Da die Gewichte (W) und der binäre Vektor F(i) anfangs nicht verfügbar sind, wird ein iteratives Verfahren verwendet, um sowohl die optimalen Vektoren W als auch F(i) zu schätzen.

Abstandsformel


Berechnung von klasseninternen und klassenübergreifenden Abständen

Jeder in den folgenden Abschnitten beschriebene Schritt bezieht sich auf Berechnungen, die für eine einzelne Probe X(i) durchgeführt werden, um den optimalen F(i)-Vektor zu bestimmen. Der Prozess beginnt damit, dass alle Einträge von F mit Null initialisiert werden und die Anfangsgewichte auf 1 gesetzt werden. Als Nächstes berechnen wir die klasseninternen und klassenübergreifenden Abstände in Bezug auf X(i). Durch die Einbeziehung des F(i)-Vektors in die Abstandsberechnungen wird sichergestellt, dass nur die als relevant erachteten Variablen (die den Wert 1 haben) berücksichtigt werden. Zur mathematischen Vereinfachung werden die euklidischen Abstände quadriert, was zu der folgenden Abstandsgleichung führt.

Quadrierte euklidische Distanz

Der Kreis mit dem eingeschlossenen „x“ kennzeichnet einen Operator für die elementweise Multiplikation. Die klasseninternen und klassenübergreifenden Abstände werden anhand der obigen Formel berechnet, jedoch mit unterschiedlichen j Elementen (Zeilen) von X. Der klasseninterne Abstand wird anhand der j Elemente berechnet, die das gleiche Klassenkennzeichnung wie X(i) haben,

Klasseninterner Abstand

während der Abstand zwischen den Klassen anhand der j Elemente berechnet wird, deren Klassenbezeichnung sich von Y(i) unterscheidet.

Abstand zwischen den Klassen



Berechnung der Gewichte

Für die Stichprobe X(i) berechnen wir einen Vektor von Gewichten (W), der N lang ist, sodass, wenn X(j) weit von X(i) entfernt ist, sein Gewicht klein sein sollte, und umgekehrt, wenn es in der Nähe ist, das Gewicht größer sein sollte. Die Gewichtung sollte keine Proben benachteiligen, nur weil sie eine andere Klassenbezeichnung haben. Da F(i) noch nicht optimal ist, sind die Variablen, die zur Definition der Basis der Nachbarschaften ausgewählt werden, noch unbekannt. In der zitierten Arbeit wird dieses Problem durch die Berechnung des Durchschnitts der Gewichte aus früheren Iterationen der Gewichtsverfeinerung gelöst.

Wenn ein Vektor F in die Definition des Abstands zwischen zwei Stichproben einbezogen wird, wird er innerhalb des durch F(i) definierten metrischen Raums betrachtet. Die Berechnung der optimalen Gewichte erfolgt durch die Definition von Abständen in einem anderen metrischen Raum, den wir als F(z) bezeichnen, wie in der folgenden Formel angegeben.

Abstand im metrischen Raum z

Um sicherzustellen, dass die Gewichte die Stichproben nicht allein deshalb benachteiligen, weil sie einer anderen Klasse angehören, berechnen wir den Mindestabstand zwischen X(i) und allen anderen Stichproben derselben Klasse in dem durch F(z) definierten metrischen Raum.

Minimaler Abstand zwischen gleich klassifizierten Stichproben

Zusätzlich berechnen wir den minimalen Abstand von Stichproben mit einer anderen Klassenbezeichnung zu X(i).

Minimaler Abstand zwischen ungleich klassifizierten Proben

Dies sind die endgültigen Werte, die zur Festlegung der Gewichte benötigt werden. Die Gewichte werden als Durchschnitt über alle metrischen Räume berechnet, der sich aus dem negativen Exponentialwert der Differenz zwischen dem Abstand und dem Mindestabstand für einen bestimmten metrischen Raum, z, ergibt.

Formel für Gewichte


Widersprüchliche Ziele

In diesem Stadium haben wir die optimalen Gewichte erhalten, sodass wir uns der Herausforderung stellen können, das richtige Gleichgewicht zwischen der Trennung zwischen den Klassen und innerhalb der Klassen zu finden. Dabei gilt es, zwei gegensätzliche Ziele miteinander in Einklang zu bringen: Minimierung der Intra-Klassentrennung (Datenpunkte innerhalb derselben Klasse so ähnlich wie möglich machen) und Maximierung der Inter-Klassentrennung (verschiedene Klassen so unterschiedlich wie möglich machen). Beide Ziele mit demselben Satz von Prädiktoren perfekt zu erreichen, ist in der Regel nicht machbar.

Ein praktikabler Ansatz ist die Epsilon-Constraint-Methode, die einen Kompromiss zwischen diesen widersprüchlichen Zielen findet. Bei dieser Methode wird zunächst eines der Optimierungsprobleme gelöst (in der Regel das Maximierungsproblem) und dann das Minimierungsproblem mit der zusätzlichen Einschränkung, dass die maximierte Funktion über einem bestimmten Schwellenwert liegen muss.

Zunächst maximieren wir die Trennung zwischen den Klassen und erfassen den Maximalwert dieser Funktion, bezeichnet als Epsilon (ϵ), der die höchstmögliche Trennung zwischen den Klassen darstellt. Als Nächstes minimieren wir die Intra-Klassentrennung für verschiedene Werte eines Parameters β (von 0 bis 1), mit der Einschränkung, dass die Inter-Klassentrennung für die minimierte Lösung größer oder gleich βϵ bleiben muss.

Der Parameter β dient als Kompromissfaktor, der den Fokus zwischen den beiden Zielen ausgleicht: Wenn β auf 1 gesetzt wird, hat die Trennung zwischen den Klassen volle Priorität, während sich der Fokus bei β auf 0 vollständig auf die Minimierung der Trennung innerhalb der Klasse verlagert. Für beide Optimierungsaufgaben gelten vier Beschränkungen:

  • Alle Elemente von F müssen zwischen 0 und 1 liegen, einschließlich.
  • Die Summe der Elemente eines F-Vektors muss kleiner oder gleich einem nutzerdefinierten Hyperparameter sein, der die maximale Anzahl der aktivierbaren Prädiktoren bestimmt.
  • Die Summe der Elemente eines F-Vektors muss größer oder gleich eins sein, um sicherzustellen, dass für jede Stichprobe mindestens ein Prädiktor aktiviert wird.

Für die klasseninterne Minimierung gibt es eine zusätzliche Einschränkung, die von der anfänglichen Maximierungsoperation übernommen wurde: Der Wert der Funktionsmaximierung muss mindestens gleich dem Produkt aus β und ϵ sein.

Die beteiligten Funktionen und Nebenbedingungen sind linear, was bedeutet, dass es sich bei den Optimierungsaufgaben um lineare Programmierprobleme handelt. Standardprobleme der linearen Programmierung zielen darauf ab, eine Zielfunktion zu maximieren, die Beschränkungen unterliegt, die Schwellenwerte festlegen, die nicht überschritten werden dürfen.

Bei der linearen Programmierung geht es um die Optimierung einer linearen Zielfunktion mit linearen Nebenbedingungen. Die Zielfunktion, die in der Regel mit „z“ bezeichnet wird, ist eine lineare Kombination von Entscheidungsvariablen. Beschränkungen werden als lineare Ungleichungen oder Gleichheiten ausgedrückt, die die Werte der Entscheidungsvariablen begrenzen. Neben den nutzerdefinierten Beschränkungen gibt es implizite Nicht-Negativitäts-Beschränkungen für die Entscheidungsvariablen und Nicht-Negativitäts-Beschränkungen auf den rechten Seiten der Ungleichungen.

Während die Standardform nichtnegative Entscheidungsvariablen und die Ungleichungen „kleiner-oder-gleich“ voraussetzt, können diese Einschränkungen durch Transformationen gelockert werden. Indem man beide Seiten einer Ungleichung mit -1 multipliziert, kann man mit der Ungleichung „größer-oder-gleich“ und negativen rechten Seiten umgehen. Außerdem können nicht-positive Koeffizienten, die Entscheidungsvariablen betreffen, durch die Schaffung neuer Variablen in positive Koeffizienten umgewandelt werden.

Das Innere-Punkte-Verfahren ist ein effizienter Algorithmus zur Lösung von linearen Programmierproblemen, insbesondere bei großen Optimierungsaufgaben. Unsere Python-Implementierung wird diese Methode anwenden, um eine optimale Lösung zu finden. Sobald die Konvergenz erreicht ist, erhalten wir einen optimalen Vektor F(i). Es ist jedoch zu beachten, dass diese Werte nicht das erforderliche Format haben (entweder 1s oder 0s). Dies wird im letzten Schritt der LFS-Methode korrigiert.



Beta-Versuche

Das Problem mit dem berechneten F(i)-Vektor ist, dass er aus realen Werten und nicht aus binären Werten besteht. Das Ziel des LFS-Verfahrens besteht darin, die relevantesten Variablen für jede Stichprobe zu ermitteln, die durch eine binäre F-Matrix dargestellt wird, deren Werte entweder 0 oder 1 sind. Ein Wert von 0 bedeutet, dass die entsprechende Variable als irrelevant betrachtet oder übersprungen wird.

Um die realen Werte des Vektors F(i) in binäre Werte umzuwandeln, verwenden wir ein Monte-Carlo-Verfahren, um das beste binäre Äquivalent zu finden. Dabei wird der Prozess eine vom Nutzer festgelegte Anzahl von Malen wiederholt, was ein wichtiger Hyperparameter der LFS-Methode ist. Bei jeder Iteration beginnen wir mit einem binären Vektor, bei dem jeder Prädiktorkandidat zunächst auf 1 gesetzt wird, wobei die kontinuierlichen F(i)-Werte als Wahrscheinlichkeiten für jeden Prädiktor verwendet werden. Anschließend wird geprüft, ob der binäre Vektor die Randbedingungen des Minimierungsverfahrens erfüllt, und sein Zielfunktionswert berechnet. Der binäre Vektor mit dem minimalen Zielfunktionswert wird als endgültiger F(i)-Vektor ausgewählt.



Nachbearbeitung für die Merkmalsauswahl

LFS wählt unabhängig voneinander optimale Prädiktorenkandidaten für jede Probe aus, sodass es unpraktisch ist, einen einzigen endgültigen Satz zu melden. Um dieses Problem zu lösen, zählen wir die Häufigkeit, mit der jeder Prädiktor in optimalen Teilmengen enthalten ist. So können die Nutzer einen Schwellenwert festlegen und die am häufigsten vorkommenden Prädiktoren als die wichtigsten identifizieren. Wichtig ist, dass die Relevanz eines Prädiktors innerhalb dieser Gruppe nicht seinen individuellen Wert impliziert; sein Wert könnte in seiner Interaktion mit anderen Prädiktoren liegen.

Dies ist ein entscheidender Vorteil der LFS: Sie ist in der Lage, Prädiktoren zu ermitteln, die für sich genommen unbedeutend sein mögen, aber in Kombination mit anderen wertvoll sind. Dieser Vorverarbeitungsschritt ist wichtig für moderne Vorhersagemodelle, die sich durch die Erkennung komplexer Beziehungen zwischen Variablen auszeichnen. Durch die Eliminierung irrelevanter Prädiktoren rationalisiert LFS den Modellierungsprozess und verbessert die Modellleistung.


Python-Implementierung: LFSpy

In diesem Abschnitt untersuchen wir die praktische Anwendung des LFS-Algorithmus, wobei wir uns zunächst auf seine Verwendung als Merkmalsauswahlverfahren konzentrieren und kurz auf seine Fähigkeiten zur Datenklassifizierung eingehen. Alle Demonstrationen werden in Python unter Verwendung des LFSpy-Pakets durchgeführt, das sowohl die Merkmalsauswahl als auch die Datenklassifizierung des LFS-Algorithmus implementiert. Das Paket ist unter PyPI verfügbar, wo auch ausführliche Informationen darüber zu finden sind.

Zunächst installieren wir das LFSpy-Paket.

pip install LFSpy

Als Nächstes importieren wir die Klasse LocalFeatureSelection aus LFSpy.

from LFSpy import LocalFeatureSelection

Eine Instanz von LocalFeatureSelection kann durch Aufruf des parametrischen Konstruktors erstellt werden.

lfs = LocalFeatureSelection(alpha=8,tau=2,n_beta=20,nrrp=2000)

Der Konstruktor unterstützt die folgenden optionalen Parameter:

 Parameter NameDatentyp
Beschreibung
alpha
integer
Die maximale Anzahl der ausgewählten Prädiktoren aus allen Prädiktorkandidaten. Der Standardwert ist 19.
gamma
double
Ein Toleranzwert, der das Verhältnis von Proben mit unterschiedlichen Klassenkennzeichnungen zu denen mit demselben Klassenkennzeichnung innerhalb einer lokalen Region bestimmt. Der Standardwert ist 0,2.
tau
integer
Die Anzahl der Iterationen durch den gesamten Datensatz (entspricht der Anzahl der Epochen beim traditionellen maschinellen Lernen). Der Standardwert ist 2, und es wird empfohlen, diesen Wert auf eine einstellige Zahl zu setzen, normalerweise nicht mehr als 5.
sigma
double
Steuert die Gewichtung der Beobachtungen auf der Grundlage ihrer Entfernung. Ein Wert größer als 1 verringert die Gewichtung. Der Standardwert ist 1.
     
n_beta
integer
Die Anzahl der Beta-Werte, die bei der Umwandlung der kontinuierlichen F-Vektoren in ihre binären Entsprechungen getestet werden.
nrrp
integer
Die Anzahl der Iterationen für Beta-Versuche. Dieser Wert sollte mindestens 500 betragen und mit zunehmender Größe des Trainingsdatensatzes steigen. Der Standardwert ist 2000.
knn
integer
Gilt speziell für Klassifizierungsaufgaben. Sie gibt die Anzahl der nächsten Nachbarn an, die für die Kategorisierung verglichen werden sollen. Der Standardwert ist 1.

Nach der Initialisierung einer Instanz der Klasse LFSpy verwenden wir die Methode fit() mit mindestens zwei Eingabeparametern: einer zweidimensionalen Matrix von Trainingsstichproben, die aus Kandidatenprädiktoren besteht, und einem eindimensionalen Array mit entsprechenden Klassenkennzeichnungen.

lfs.fit(xtrain,ytrain)

Sobald das Modell angepasst ist, gibt der Aufruf von fstar die Einschlussmatrix F zurück, die aus Einsen und Nullen besteht, um die ausgewählten Merkmale anzuzeigen. Beachten Sie, dass diese Matrix in Bezug auf die Orientierung der Übungsbeispiele transponiert ist.

fstar = lfs.fstar

Die Methode predict() wird verwendet, um die Testproben auf der Grundlage des gelernten Modells zu klassifizieren und gibt die den Testdaten entsprechenden Klassenbezeichnungen zurück.

predicted_classes = lfs.predict(test_samples)

Die Methode score() berechnet die Genauigkeit des Modells, indem sie die vorhergesagten Klassenbezeichnungen mit den bekannten Bezeichnungen vergleicht. Sie gibt den Anteil der Prüfmuster an, die korrekt klassifiziert wurden.

accuracy = lfs.score(test_data,test_labels)



Beispiele für LFSpy

Für die erste praktische Demonstration erzeugen wir mehrere tausend gleichmäßig verteilte Zufallsvariablen im Intervall [-1,1][-1,1]. Diese Variablen werden in einer Matrix mit einer bestimmten Anzahl von Spalten angeordnet. Anschließend wird für jede Zeile ein Vektor von {0, 1} Kennzeichnungen erstellt, je nachdem, ob die Werte in zwei beliebigen Spalten beide negativ oder beide positiv sind. Ziel dieser Demonstration ist es, festzustellen, ob die LFS-Methode die wichtigsten Prädiktoren in diesem Datensatz identifizieren kann. Wir werten die Ergebnisse aus, indem wir addieren, wie oft jeder Prädiktor in der binären F-Einschlussmatrix ausgewählt wurde (gekennzeichnet durch eine 1). Der Code zur Durchführung dieses Tests ist unten dargestellt.

import numpy as np
import pandas as pd
from LFSpy import LocalFeatureSelection
from timeit import default_timer as timer

#number of random numbers to generate
datalen = 500 

#number of features the dataset will have
datavars = 5

#set random number seed
rng_seed = 125
rng = np.random.default_rng(rng_seed)

#generate the numbers
data = rng.uniform(-1.0,1.0,size=datalen)

#shape our dataset
data = data.reshape([datalen//datavars,datavars])

#set up container for class labels
class_labels = np.zeros(shape=data.shape[0],dtype=np.uint8)

#set the class labels
for i in range(data.shape[0]):
    class_labels[i] = 1 if (data[i,1] > 0.0 and data[i,2] > 0.0) or (data[i,1] < 0.0 and data[i,2] < 0.0) else 0

#partition our training data
xtrain = data
ytrain = class_labels

#initialize the LFS object
lfs = LocalFeatureSelection(rr_seed=rng_seed,alpha=8,tau=2,n_beta=20,nrrp=2000)

#start timer
start = timer()

#train the model
lfs.fit(xtrain,ytrain)

#output training duration
print("Training done in ", timer()-start , " seconds. ")

#get the inclusion matrix
fstar = lfs.fstar

#add up all ones for each row of the inclusion matrix
ibins = fstar.sum(axis=1)

#calculate the percent of times a candidate was selected
original_crits = 100.0 * ibins.astype(np.float64)/np.float64(ytrain.shape[0])

#output the results
print("------------------------------> Percent of times selected <------------------------------" )
for i in range(original_crits.shape[0]):
   print( f" Variable at column {i}, selected {original_crits[i]} %")

Die Ausgabe nach Ausführung von LFSdemo.py

Training done in  45.84896759999992  seconds. 
Python  ------------------------------> Percent of times selected <------------------------------
Python   Variable at column 0, selected 19.0 %
Python   Variable at column 1, selected 81.0 %
Python   Variable at column 2, selected 87.0 %
Python   Variable at column 3, selected 20.0 %
Python   Variable at column 4, selected 18.0 %

Interessant ist, dass eine der relevanten Variablen etwas häufiger ausgewählt wurde als die andere, obwohl sie die gleiche Rolle bei der Vorhersage der Klasse spielen. Dies deutet darauf hin, dass subtile Nuancen innerhalb der Daten den Auswahlprozess beeinflussen könnten. Deutlich ist, dass beide Variablen durchweg häufiger gewählt wurden als irrelevante Prädiktoren, was auf ihre Bedeutung für die Bestimmung der Klasse hinweist. Die relativ langsame Ausführung des Algorithmus ist wahrscheinlich auf seinen Einzel-Thread-Charakter zurückzuführen, was seine Leistung bei größeren Datensätzen beeinträchtigen könnte.



LFS für die Datenklassifizierung

Angesichts der lokalen Natur von LFS erfordert die Konstruktion eines Klassifikators mehr Aufwand im Vergleich zu traditionellen, global ausgerichteten Merkmalsauswahlmethoden. In dem zitierten Dokument wird eine vorgeschlagene Klassifizierungsarchitektur erörtert, auf die wir hier nicht näher eingehen wollen. Interessierten Lesern wird empfohlen, sich in dem zitierten Dokument über alle Einzelheiten zu informieren. In diesem Abschnitt werden wir uns auf die Umsetzung konzentrieren.

LFS für die Datenklassifizierung

Die Methode predict() der Klasse LocalFeatureSelection bewertet die Klassenähnlichkeit. Es nimmt Testdaten, die mit der Struktur der Trainingsdaten übereinstimmen, und liefert vorhergesagte Klassenbezeichnungen auf der Grundlage der vom trainierten LFS-Modell gelernten Muster. In der nächsten Code-Demonstration werden wir das vorherige Skript erweitern, um ein LFS-Klassifikatormodell zu erstellen, es im JSON-Format zu exportieren, es mit einem MQL5-Skript zu laden und einen Out-of-Sample-Datensatz zu klassifizieren. Der Code für den Export eines LFS-Modells ist in JsonModel.py enthalten. Diese Datei definiert die Funktion lfspy2json(), die den Zustand und die Parameter eines LocalFeatureSelection-Modells in eine JSON-Datei serialisiert. Dadurch kann das Modell in einem Format gespeichert werden, das leicht gelesen und in MQL5-Code verwendet werden kann, was die Integration mit MetaTrader 5 erleichtert. Der vollständige Code ist nachstehend aufgeführt.

# Copyright 2024, MetaQuotes Ltd.
# https://www.mql5.com

from LFSpy import LocalFeatureSelection
import json 

MQL5_FILES_FOLDER = "MQL5\\FILES"
MQL5_COMMON_FOLDER = "FILES"

def lfspy2json(lfs_model:LocalFeatureSelection, filename:str):
    """
    function export a LFSpy model to json format 
    readable from MQL5 code.
    
    param: lfs_model should be an instance of LocalFeatureSelection
    param: filename or path to file where lfs_model
    parameters will be written to
    
    """
    if not isinstance(lfs_model,LocalFeatureSelection):
        raise TypeError(f'invalid type supplied, "lfs_model" should be an instance of LocalFeatureSelection')
    if len(filename) < 1 or not isinstance(filename,str):
        raise TypeError(f'invalid filename supplied')
    jm  = {
            "alpha":lfs_model.alpha,
            "gamma":lfs_model.gamma,
            "tau":lfs_model.tau,
            "sigma":lfs_model.sigma, 
            "n_beta":lfs_model.n_beta,
            "nrrp":lfs_model.nrrp,
            "knn":lfs_model.knn,
            "rr_seed":lfs_model.rr_seed,
            "num_observations":lfs_model.training_data.shape[1],
            "num_features":lfs_model.training_data.shape[0],
            "training_data":lfs_model.training_data.tolist(),
            "training_labels":lfs_model.training_labels.tolist(),
            "fstar":lfs_model.fstar.tolist()
          }
          
    
    with open(filename,'w') as file:
        json.dump(jm,file,indent=None,separators=(',', ':')) 
    return 

Die Funktion benötigt ein LocalFeatureSelection-Objekt und einen Dateinamen als Eingabe. Es serialisiert die Modellparameter als JSON-Objekt und speichert es unter dem angegebenen Dateinamen. Das Modul definiert auch zwei Konstanten, MQL5_FILES_FOLDER und MQL5_COMMON_FOLDER, die die Verzeichnispfade für zugängliche Ordner in einer Standardinstallation von MetaTrader 5 darstellen. Dies ist nur ein Teil der Lösung für die Integration mit MetaTrader 5. Der andere Teil ist in MQL5-Code implementiert, der in lfspy.mqh zu finden ist. Diese eingeschlossene Datei enthält die Definition der Klasse Clfspy, die das Laden eines im JSON-Format gespeicherten LFS-Modells zu Inferenzzwecken erleichtert. Der vollständige Code ist nachstehend aufgeführt.

//+------------------------------------------------------------------+
//|                                                        lfspy.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include<JAson.mqh>
#include<Files/FileTxt.mqh>
#include<np.mqh>
//+------------------------------------------------------------------+
//|structure of model parameters                                     |
//+------------------------------------------------------------------+
struct LFS_PARAMS
  {
   int               alpha;
   int               tau;
   int               n_beta;
   int               nrrp;
   int               knn;
   int               rr_seed;
   int               sigma;
   ulong             num_features;
   double            gamma;
  };
//+------------------------------------------------------------------+
//|  class encapsulates LFSpy model                                  |
//+------------------------------------------------------------------+
class Clfspy
  {
private:
   bool              loaded;
   LFS_PARAMS        model_params;
   matrix train_data,
          fstar;
   vector            train_labels;
   //+------------------------------------------------------------------+
   //|  helper function for parsing model from file                     |
   //+------------------------------------------------------------------+
   bool              fromJSON(CJAVal &jsonmodel)
     {

      model_params.alpha = (int)jsonmodel["alpha"].ToInt();
      model_params.tau = (int)jsonmodel["tau"].ToInt();
      model_params.sigma = (int)jsonmodel["sigma"].ToInt();
      model_params.n_beta = (int)jsonmodel["n_beta"].ToInt();
      model_params.nrrp = (int)jsonmodel["nrrp"].ToInt();
      model_params.knn = (int)jsonmodel["knn"].ToInt();
      model_params.rr_seed = (int)jsonmodel["rr_seed"].ToInt();
      model_params.gamma = jsonmodel["gamma"].ToDbl();

      ulong observations = (ulong)jsonmodel["num_observations"].ToInt();
      model_params.num_features = (ulong)jsonmodel["num_features"].ToInt();

      if(!train_data.Resize(model_params.num_features,observations) || !train_labels.Resize(observations) ||
         !fstar.Resize(model_params.num_features,observations))
        {
         Print(__FUNCTION__, " error ", GetLastError());
         return false;
        }


      for(int i=0; i<int(model_params.num_features); i++)
        {
         for(int j = 0; j<int(observations); j++)
           {
            if(i==0)
               train_labels[j] = jsonmodel["training_labels"][j].ToDbl();
            train_data[i][j] = jsonmodel["training_data"][i][j].ToDbl();
            fstar[i][j] = jsonmodel["fstar"][i][j].ToDbl();
           }
        }

      return true;
     }
   //+------------------------------------------------------------------+
   //| helper classification function                                   |
   //+------------------------------------------------------------------+
   matrix            classification(matrix &testing_data)
     {
      int N = int(train_labels.Size());
      int H = int(testing_data.Cols());

      matrix out(H,2);

      for(int i = 0; i<H; i++)
        {
         vector column = testing_data.Col(i);
         vector result = class_sim(column,train_data,train_labels,fstar,model_params.gamma,model_params.knn);
         if(!out.Row(result,i))
           {
            Print(__FUNCTION__, " row insertion failure ", GetLastError());
            return matrix::Zeros(1,1);
           }
        }

      return out;
     }
   //+------------------------------------------------------------------+
   //| internal feature classification function                         |
   //+------------------------------------------------------------------+
   vector            class_sim(vector &test,matrix &patterns,vector& targets, matrix &f_star, double gamma, int knn)
     {
      int N = int(targets.Size());
      int n_nt_cls_1 = (int)targets.Sum();
      int n_nt_cls_2 = N - n_nt_cls_1;
      int M = int(patterns.Rows());
      int NC1 = 0;
      int NC2 = 0;
      vector S = vector::Zeros(N);

      S.Fill(double("inf"));

      vector NoNNC1knn = vector::Zeros(N);
      vector NoNNC2knn = vector::Zeros(N);
      vector NoNNC1 = vector::Zeros(N);
      vector NoNNC2 = vector::Zeros(N);
      vector radious = vector::Zeros(N);
      double r = 0;
      int k = 0;
      for(int i = 0; i<N; i++)
        {
         vector fs = f_star.Col(i);
         matrix xpatterns = patterns * np::repeat_vector_as_rows_cols(fs,patterns.Cols(),false);
         vector testpr = test * fs;
         vector mtestpr = (-1.0 * testpr);
         matrix testprmat = np::repeat_vector_as_rows_cols(mtestpr,xpatterns.Cols(),false);
         vector dist = MathAbs(sqrt((pow(testprmat + xpatterns,2.0)).Sum(0)));
         vector min1 = dist;
         np::sort(min1);
         vector min_uniq = np::unique(min1);
         int m = -1;
         int no_nereser = 0;
         vector NN(dist.Size());
         while(no_nereser<int(knn))
           {
            m+=1;
            double a1  = min_uniq[m];
            for(ulong j = 0; j<dist.Size(); j++)
               NN[j]=(dist[j]<=a1)?1.0:0.0;
            no_nereser = (int)NN.Sum();
           }
         vector bitNN = np::bitwiseAnd(NN,targets);
         vector Not = np::bitwiseNot(targets);
         NoNNC1knn[i] = bitNN.Sum();
         bitNN = np::bitwiseAnd(NN,Not);
         NoNNC2knn[i] = bitNN.Sum();
         vector A(fs.Size());
         for(ulong v =0; v<A.Size(); v++)
            A[v] = (fs[v]==0.0)?1.0:0.0;
         vector f1(patterns.Cols());
         vector f2(patterns.Cols());
         if(A.Sum()<double(M))
           {
            for(ulong v =0; v<A.Size(); v++)
               A[v] = (A[v]==1.0)?0.0:1.0;
            matrix amask = matrix::Ones(patterns.Rows(), patterns.Cols());
            amask *= np::repeat_vector_as_rows_cols(A,patterns.Cols(),false);
            matrix patternsp = patterns*amask;
            vector testp = test*(amask.Col(0));
            vector testa = patternsp.Col(i) - testp;
            vector col = patternsp.Col(i);
            matrix colmat = np::repeat_vector_as_rows_cols(col,patternsp.Cols(),false);
            double Dist_test = MathAbs(sqrt((pow(col - testp,2.0)).Sum()));
            vector Dist_pat  = MathAbs(sqrt((pow(patternsp - colmat,2.0)).Sum(0)));
            vector eerep = Dist_pat;
            np::sort(eerep);
            int remove = 0;
            if(targets[i] == 1.0)
              {
               vector unq = np::unique(eerep);
               k = -1;
               NC1+=1;
               if(remove!=1)
                 {
                  int Next = 1;
                  while(Next == 1)
                    {
                     k+=1;
                     r = unq[k];
                     for(ulong j = 0; j<Dist_pat.Size(); j++)
                       {
                        if(Dist_pat[j] == r)
                           f1[j] = 1.0;
                        else
                           f1[j] = 0.0;
                        if(Dist_pat[j]<=r)
                           f2[j] = 1.0;
                        else
                           f2[j] = 0.0;
                       }
                     vector f2t = np::bitwiseAnd(f2,targets);
                     vector tn = np::bitwiseNot(targets);
                     vector f2tn = np::bitwiseAnd(f2,tn);
                     double nocls1clst = f2t.Sum() - 1.0;
                     double nocls2clst = f2tn.Sum();
                     if(gamma *(nocls1clst/double(n_nt_cls_1-1)) < (nocls2clst/(double(n_nt_cls_2))))
                       {
                        Next = 0 ;
                        if((k-1) == 0)
                           r = unq[k];
                        else
                           r = 0.5 * (unq[k-1] + unq[k]);
                        if(r==0.0)
                           r = pow(10.0,-6.0);
                        r = 1.0*r;
                        for(ulong j = 0; j<Dist_pat.Size(); j++)
                          {
                           if(Dist_pat[j]<=r)
                              f2[j] = 1.0;
                           else
                              f2[j] = 0.0;
                          }
                        f2t = np::bitwiseAnd(f2,targets);
                        f2tn = np::bitwiseAnd(f2,tn);
                        nocls1clst = f2t.Sum() - 1.0;
                        nocls2clst = f2tn.Sum();
                       }
                    }
                  if(Dist_test<r)
                    {
                     patternsp = patterns * np::repeat_vector_as_rows_cols(fs,patterns.Cols(),false);
                     testp = test * fs;
                     dist = MathAbs(sqrt((pow(patternsp - np::repeat_vector_as_rows_cols(testp,patternsp.Cols(),false),2.0)).Sum(0)));
                     min1 = dist;
                     np::sort(min1);
                     min_uniq = np::unique(min1);
                     m = -1;
                     no_nereser = 0;
                     while(no_nereser<int(knn))
                       {
                        m+=1;
                        double a1  = min_uniq[m];
                        for(ulong j = 0; j<dist.Size(); j++)
                           NN[j]=(dist[j]<a1)?1.0:0.0;
                        no_nereser = (int)NN.Sum();
                       }
                     bitNN = np::bitwiseAnd(NN,targets);
                     Not = np::bitwiseNot(targets);
                     NoNNC1[i] = bitNN.Sum();
                     bitNN = np::bitwiseAnd(NN,Not);
                     NoNNC2[i] = bitNN.Sum();
                     if(NoNNC1[i]>NoNNC2[i])
                        S[i] = 1.0;
                    }
                 }
              }
            if(targets[i] == 0.0)
              {
               vector unq = np::unique(eerep);
               k=-1;
               NC2+=1;
               int Next;
               if(remove!=1)
                 {
                  Next =1;
                  while(Next==1)
                    {
                     k+=1;
                     r = unq[k];
                     for(ulong j = 0; j<Dist_pat.Size(); j++)
                       {
                        if(Dist_pat[j] == r)
                           f1[j] = 1.0;
                        else
                           f1[j] = 0.0;
                        if(Dist_pat[j]<=r)
                           f2[j] = 1.0;
                        else
                           f2[j] = 0.0;
                       }
                     vector f2t = np::bitwiseAnd(f2,targets);
                     vector tn = np::bitwiseNot(targets);
                     vector f2tn = np::bitwiseAnd(f2,tn);
                     double nocls1clst = f2t.Sum() ;
                     double nocls2clst = f2tn.Sum() -1.0;
                     if(gamma *(nocls2clst/double(n_nt_cls_2-1)) < (nocls1clst/(double(n_nt_cls_1))))
                       {
                        Next = 0 ;
                        if((k-1) == 0)
                           r = unq[k];
                        else
                           r = 0.5 * (unq[k-1] + unq[k]);
                        if(r==0.0)
                           r = pow(10.0,-6.0);
                        r = 1.0*r;
                        for(ulong j = 0; j<Dist_pat.Size(); j++)
                          {
                           if(Dist_pat[j]<=r)
                              f2[j] = 1.0;
                           else
                              f2[j] = 0.0;
                          }
                        f2t = np::bitwiseAnd(f2,targets);
                        f2tn = np::bitwiseAnd(f2,tn);
                        nocls1clst = f2t.Sum();
                        nocls2clst = f2tn.Sum() -1.0;
                       }
                    }
                  if(Dist_test<r)
                    {
                     patternsp = patterns * np::repeat_vector_as_rows_cols(fs,patterns.Cols(),false);
                     testp = test * fs;
                     dist = MathAbs(sqrt((pow(patternsp - np::repeat_vector_as_rows_cols(testp,patternsp.Cols(),false),2.0)).Sum(0)));
                     min1 = dist;
                     np::sort(min1);
                     min_uniq = np::unique(min1);
                     m = -1;
                     no_nereser = 0;
                     while(no_nereser<int(knn))
                       {
                        m+=1;
                        double a1  = min_uniq[m];
                        for(ulong j = 0; j<dist.Size(); j++)
                           NN[j]=(dist[j]<a1)?1.0:0.0;
                        no_nereser = (int)NN.Sum();
                       }
                     bitNN = np::bitwiseAnd(NN,targets);
                     Not = np::bitwiseNot(targets);
                     NoNNC1[i] = bitNN.Sum();
                     bitNN = np::bitwiseAnd(NN,Not);
                     NoNNC2[i] = bitNN.Sum();
                     if(NoNNC2[i]>NoNNC1[i])
                        S[i] = 1.0;
                    }
                 }
              }
           }
         radious[i] = r;
        }
      vector q1 = vector::Zeros(N);
      vector q2 = vector::Zeros(N);
      for(int i = 0; i<N; i++)
        {
         if(NoNNC1[i] > NoNNC2knn[i])
            q1[i] = 1.0;
         if(NoNNC2[i] > NoNNC1knn[i])
            q2[i] = 1.0;
        }

      vector ntargs = np::bitwiseNot(targets);
      vector c1 = np::bitwiseAnd(q1,targets);
      vector c2 = np::bitwiseAnd(q2,ntargs);

      double sc1 = c1.Sum()/NC1;
      double sc2 = c2.Sum()/NC2;

      if(sc1==0.0 && sc2==0.0)
        {
         q1.Fill(0.0);
         q2.Fill(0.0);

         for(int i = 0; i<N; i++)
           {
            if(NoNNC1knn[i] > NoNNC2knn[i])
               q1[i] = 1.0;
            if(NoNNC2knn[i] > NoNNC1knn[i])
               q2[i] = 1.0;

            if(!targets[i])
               ntargs[i] = 1.0;
            else
               ntargs[i] = 0.0;
           }

         c1 = np::bitwiseAnd(q1,targets);
         c2 = np::bitwiseAnd(q2,ntargs);

         sc1 = c1.Sum()/NC1;
         sc2 = c2.Sum()/NC2;
        }

      vector out(2);

      out[0] = sc1;
      out[1] = sc2;

      return out;
     }
public:
   //+------------------------------------------------------------------+
   //|    constructor                                                   |
   //+------------------------------------------------------------------+
                     Clfspy(void)
     {
      loaded = false;
     }
   //+------------------------------------------------------------------+
   //|  destructor                                                      |
   //+------------------------------------------------------------------+
                    ~Clfspy(void)
     {
     }
   //+------------------------------------------------------------------+
   //|  load a LFSpy trained model from file                            |
   //+------------------------------------------------------------------+
   bool              load(const string file_name, bool FILE_IN_COMMON_DIRECTORY = false)
     {
      loaded = false;
      CFileTxt modelFile;
      CJAVal js;
      ResetLastError();
      if(modelFile.Open(file_name,FILE_IN_COMMON_DIRECTORY?FILE_READ|FILE_COMMON:FILE_READ,0)==INVALID_HANDLE)
        {
         Print(__FUNCTION__," failed to open file ",file_name," .Error - ",::GetLastError());
         return false;
        }
      else
        {
         if(!js.Deserialize(modelFile.ReadString()))
           {
            Print("failed to read from ",file_name,".Error -",::GetLastError());
            return false;
           }
         loaded = fromJSON(js);
        }
      return loaded;
     }
   //+------------------------------------------------------------------+
   //|   make a prediction based specific inputs                        |
   //+------------------------------------------------------------------+
   vector            predict(matrix &inputs)
     {
      if(!loaded)
        {
         Print(__FUNCTION__, " No model available, Load a model first before calling this method ");
         return vector::Zeros(1);
        }

      if(inputs.Cols()!=train_data.Rows())
        {
         Print(__FUNCTION__, " input matrix does np::bitwiseNot match with shape of expected model inputs (columns)");
         return vector::Zeros(1);
        }

      matrix testdata = inputs.Transpose();

      matrix probs = classification(testdata);
      vector classes = vector::Zeros(probs.Rows());

      for(ulong i = 0; i<classes.Size(); i++)
         if(probs[i][0] > probs[i][1])
            classes[i] = 1.0;

      return classes;

     }
   //+------------------------------------------------------------------+
   //| get the parameters of the loaded model                           |
   //+------------------------------------------------------------------+
   LFS_PARAMS        getmodelparams(void)
     {
      return model_params;
     }


  };
//+------------------------------------------------------------------+

In dieser Klasse gibt es zwei Hauptmethoden, die die Nutzer verstehen müssen:

  • Die Methode load() benötigt einen Dateinamen als Eingabe, der auf das exportierte LFS-Modell verweisen sollte.
  • Die Methode predict() übernimmt eine Matrix mit der erforderlichen Anzahl von Spalten und gibt einen Vektor von Klassenbezeichnungen zurück, der der Anzahl von Zeilen in der Eingabematrix entspricht.

Schauen wir uns an, wie das alles in der Praxis funktioniert. Wir beginnen mit dem Python-Code. Die Datei LFSmodelExportDemo.py bereitet In-Sample- und Out-of-Sample-Datensätze mit zufällig generierten Zahlen vor. Die Daten außerhalb der Stichprobe werden als CSV-Datei gespeichert. Ein LFS-Modell wird mit den Daten aus der Stichprobe trainiert, dann serialisiert und im JSON-Format gespeichert. Wir testen das Modell mit den Daten außerhalb der Stichprobe und zeichnen die Ergebnisse auf, damit wir sie später mit dem gleichen Test in MetaTrader 5 vergleichen können. Der Python-Code wird im Folgenden gezeigt.

# Copyright 2024, MetaQuotes Ltd.
# https://www.mql5.com
# imports
import MetaTrader5 as mt5
import numpy as np
import pandas as pd
from JsonModel import lfspy2json, LocalFeatureSelection, MQL5_COMMON_FOLDER, MQL5_FILES_FOLDER
from os import path
from sklearn.metrics import accuracy_score, classification_report

#initialize MT5 terminal
if not mt5.initialize():
    print("MT5 initialization failed ")
    mt5.shutdown()
    exit() # stop the script if mt5 not initialized
  
#we want to get the path to the MT5 file sandbox
#initialize TerminalInfo instance    
terminal_info = mt5.terminal_info()

#model file name
filename = "lfsmodel.json"

#build the full path
modelfilepath = path.join(terminal_info.data_path,MQL5_FILES_FOLDER,filename)

#number of random numbers to generate
datalen = 1000

#number of features the dataset will have
datavars = 5

#set random number seed
rng_seed = 125
rng = np.random.default_rng(rng_seed)

#generate the numbers
data = rng.uniform(-1.0,1.0,size=datalen)

#shape our dataset
data = data.reshape([datalen//datavars,datavars])

#set up container for class labels
class_labels = np.zeros(shape=data.shape[0],dtype=np.uint8)

#set the class labels
for i in range(data.shape[0]):
    class_labels[i] = 1 if (data[i,1] > 0.0 and data[i,2] > 0.0) or (data[i,1] < 0.0 and data[i,2] < 0.0) else 0

#partition our data
train_size = 100
xtrain = data[:train_size,:]
ytrain = class_labels[:train_size]

#load testing data (out of sample)
test_data = data[train_size:,:]
test_labels = class_labels[train_size:]

#here we prepare the out of sample data for export using pandas
#the data will be exported in a single csv file
colnames = [ f"var_{str(col+1)}" for col in range(test_data.shape[1])]
testdata = pd.DataFrame(test_data,columns=colnames)

#the last column will be the target labels
testdata["c_labels"]=test_labels

#display first 5 samples
print("Out of sample dataframe head \n", testdata.head())
#display last 5 samples
print("Out of sample dataframe tail \n", testdata.tail())

#build the full path of the csv file
testdatafilepath=path.join(terminal_info.data_path,MQL5_FILES_FOLDER,"testdata.csv")

#try save the file
try:
    testdata.to_csv(testdatafilepath)
except Exception as e:
    print(" Error saving iris test data ")
    print(e) 
else:
    print(" test data successfully saved to csv file ")  

#initialize the LFS object
lfs = LocalFeatureSelection(rr_seed=rng_seed,alpha=8,tau=2,n_beta=20,nrrp=2000)

#train the model
lfs.fit(xtrain,ytrain)

#get the inclusion matrix
fstar = lfs.fstar

#add up all ones for each row of the inclusion matrix
bins = fstar.sum(axis=1)

#calculate the percent of times a candidate was selected
percents = 100.0 * bins.astype(np.float64)/np.float64(ytrain.shape[0])
index = np.argsort(percents)[::-1]

#output the results
print("------------------------------> Percent of times selected <------------------------------" )
for i in range(percents.shape[0]):
   print(f" Variable  {colnames[index[i]]}, selected {percents[index[i]]} %")


#conduct out of sample test of trained model
accuracy = lfs.score(test_data,test_labels)
print(f" Out of sample accuracy is {accuracy*100.0} %")


#export the model
try:
    lfspy2json(lfs,modelfilepath)
except Exception as e:
    print(" Error saving lfs model ")
    print(e)
else:
   print("lfs model saved to \n ", modelfilepath)

Als Nächstes konzentrieren wir uns auf ein MetaTrader 5-Skript, LFSmodelImportDemo.mq5. Hier lesen wir die vom Python-Skript erzeugten Out-of-Sample-Daten ein und laden das trainierte Modell. Anschließend wird der Datensatz außerhalb der Stichprobe getestet, und die Ergebnisse werden mit denen des Python-Tests verglichen. Der MQL5-Code wird im Folgenden vorgestellt.

//+------------------------------------------------------------------+
//|                                           LFSmodelImportDemo.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
#include<lfspy.mqh>
//script inputs
input string OutOfSampleDataFile = "testdata.csv";
input bool   OutOfSampleDataInCommonFolder = false;
input string LFSModelFileName = "lfsmodel.json";
input bool   LFSModelInCommonFolder = false;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   matrix testdata = np::readcsv(OutOfSampleDataFile,OutOfSampleDataInCommonFolder);
   
   if(testdata.Rows()<1)
    {
     Print(" failed to read csv file ");
     return;
    }
    
   
   vector testlabels = testdata.Col(testdata.Cols()-1);
   testdata = np::sliceMatrixCols(testdata,1,testdata.Cols()-1);
   
  
   Clfspy lfsmodel;
   
   if(!lfsmodel.load(LFSModelFileName,LFSModelInCommonFolder))
    {
     Print(" failed to load the iris lfs model ");
     return;
    }
    
   vector y_pred = lfsmodel.predict(testdata);
   
   vector check = MathAbs(testlabels-y_pred);
   
   Print("Accuracy is " , (1.0 - (check.Sum()/double(check.Size()))) * 100.0, " %");
  }
//+------------------------------------------------------------------+

Die Ausgabe der Ausführung des Python-Skripts LFSmodelExportDemo.py.

Python  Out of sample dataframe head 
Python         var_1     var_2     var_3     var_4     var_5  c_labels
Python  0  0.337773 -0.210114 -0.706754  0.940513  0.434695         1
Python  1 -0.009701 -0.119561 -0.904122 -0.409922  0.619245         1
Python  2  0.442703  0.295811  0.692888  0.618308  0.682659         1
Python  3  0.694853  0.244405 -0.414633 -0.965176  0.929655         0
Python  4  0.120284  0.247607 -0.477527 -0.993267  0.317743         0
Python  Out of sample dataframe tail 
Python          var_1     var_2     var_3     var_4     var_5  c_labels
Python  95  0.988951  0.559262 -0.959583  0.353533 -0.570316         0
Python  96  0.088504  0.250962 -0.876172  0.309089 -0.158381         0
Python  97 -0.215093 -0.267556  0.634200  0.644492  0.938260         0
Python  98  0.639926  0.526517  0.561968  0.129514  0.089443         1
Python  99 -0.772519 -0.462499  0.085293  0.423162  0.391327         0
Python  test data successfully saved to csv file 

Python  ------------------------------> Percent of times selected <------------------------------
Python   Variable  var_3, selected 87.0 %
Python   Variable  var_2, selected 81.0 %
Python   Variable  var_4, selected 20.0 %
Python   Variable  var_1, selected 19.0 %
Python   Variable  var_5, selected 18.0 %
Python   Out of sample accuracy is 92.0 %
Python  lfs model saved to 
Python    C:\Users\Zwelithini\AppData\Roaming\MetaQuotes\Terminal\FB9A56D617EDDDFE29EE54EBEFFE96C1\MQL5\FILES\lfsmodel.json

Ausgabe der Ausführung des MQL5-Skripts LFSmodelImportDemo.mq5.

LFSmodelImportDemo (BTCUSD,D1)  Accuracy is 92.0 %

Der Vergleich der Ergebnisse zeigt, dass die Ausgaben beider Programme übereinstimmen, was darauf hindeutet, dass die Methode des Modellexports wie erwartet funktioniert.



Schlussfolgerung

Local Feature Selection bietet einen innovativen Ansatz zur Merkmalsauswahl, der sich besonders für dynamische Umgebungen wie Finanzmärkte eignet. Durch die Identifizierung lokal relevanter Merkmale überwindet LFS die Einschränkungen herkömmlicher Methoden, die sich auf einen einzigen, globalen Merkmalssatz stützen. Die Anpassungsfähigkeit des Algorithmus an unterschiedliche Datenmuster, seine Fähigkeit, nicht-lineare Beziehungen zu verwalten, und seine Fähigkeit, widersprüchliche Ziele auszugleichen, machen ihn zu einem wertvollen Werkzeug für die Erstellung von Modellen für maschinelles Lernen. Das LFSpy-Paket bietet zwar eine praktische Implementierung von LFS, aber es besteht die Möglichkeit, seine Recheneffizienz weiter zu optimieren, insbesondere für große Datensätze. Zusammenfassend lässt sich sagen, dass LFS ein vielversprechender Ansatz für Klassifizierungsaufgaben in Bereichen ist, die durch komplexe und sich verändernde Daten gekennzeichnet sind.
 Datei NameBeschreibung
Mql5/include/np.mqh
Include-Datei mit allgemeinen Definitionen für verschiedene Matrix- und Vektor-Utility-Funktionen.
Mql5/include/lfspy.mqh
Eine Include-Datei mit der Definition der Clfspy-Klasse, die die LFS-Modellinferenzfunktionalität in MetaTrader 5-Programmen bereitstellt.
Mql5/scripts/JsonModel.py
Ein lokales Python-Modul mit der Definition einer Funktion, die den Export des LFS-Modells im JSON-Format ermöglicht.
Mql5/scripts/LFSdemo.py
Ein Python-Skript, das zeigt, wie man die Klasse LocalFeatureSelection für die Auswahl von Merkmalen mit Zufallsvariablen verwendet
Mql5/scripts/LFSmodelExportDemo.py
Ein Python-Skript, das demonstriert, wie man das LFS-Modell zur Verwendung in MetaTrader 5 exportiert.
Mql5/scripts/LFSmodelImportDemo.mq5
Ein MQL5-Skript, das zeigt, wie man ein exportiertes LFS-Modell in ein MetaTrader 5-Programm lädt und verwendet.

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

Beigefügte Dateien |
np.mqh (74.31 KB)
lfspy.mqh (17.22 KB)
JsonModel.py (1.52 KB)
LFSdemo.py (1.55 KB)
Mql5.zip (18.2 KB)
Entwicklung eines Replay-Systems (Teil 65): Abspielen des Dienstes (VI) Entwicklung eines Replay-Systems (Teil 65): Abspielen des Dienstes (VI)
In diesem Artikel werden wir uns ansehen, wie das Mauszeigerproblem bei der Verwendung in Verbindung mit einer Wiedergabe-/Simulationsanwendung implementiert und gelöst werden kann. Der hier dargestellte Inhalt ist ausschließlich für Bildungszwecke bestimmt. Die Anwendung sollte unter keinen Umständen zu einem anderen Zweck als zum Erlernen und Beherrschen der vorgestellten Konzepte verwendet werden.
Neuronale Netze im Handel: Transformer für die Punktwolke (Pointformer) Neuronale Netze im Handel: Transformer für die Punktwolke (Pointformer)
In diesem Artikel geht es um Algorithmen für die Verwendung von Aufmerksamkeitsmethoden zur Lösung von Problemen bei der Erkennung von Objekten in einer Punktwolke. Die Erkennung von Objekten in Punktwolken ist für viele reale Anwendungen wichtig.
Neuronale Netze im Handel: Szenenspezifische Objekterkennung (HyperDet3D) Neuronale Netze im Handel: Szenenspezifische Objekterkennung (HyperDet3D)
Wir laden Sie ein, einen neuen Ansatz zur Erkennung von Objekten mit Hilfe von Hypernetzwerken kennen zu lernen. Ein Hypernetwork generiert Gewichte für das Hauptmodell, wodurch die Besonderheiten der aktuellen Marktsituation berücksichtigt werden können. Dieser Ansatz ermöglicht es uns, die Vorhersagegenauigkeit zu verbessern, indem wir das Modell an unterschiedliche Handelsbedingungen anpassen.
Entwicklung eines Replay-Systems (Teil 64): Abspielen des Dienstes (V) Entwicklung eines Replay-Systems (Teil 64): Abspielen des Dienstes (V)
In diesem Artikel werden wir uns ansehen, wie zwei Fehler im Code behoben werden können. Ich werde jedoch versuchen, sie so zu erklären, dass Sie als Programmieranfänger verstehen, dass die Dinge nicht immer so laufen, wie Sie es erwarten. Wie auch immer, dies ist eine Gelegenheit, zu lernen. Der hier dargestellte Inhalt ist ausschließlich für Bildungszwecke bestimmt. Dieser Antrag sollte keinesfalls als endgültiges Dokument betrachtet werden, das lediglich der Erkundung der vorgestellten Konzepte dient.