Pfadmaschine: Eine Algorithmusidee

Hintergrund


Vor ungefähr 15 Jahren habe ich etwas über die Existenz grundlegender Pfade gelernt - Gruppen, die topologische Räume durch Konnektivität unterscheiden können. Der Rest wird nicht über sie sein, aber sie kamen auf die Idee eines Regressors und eines Klassifikators - ohne Optimierungen basierend auf der Speicherung der Probe.

Weitere Details.

Ideen und Vermutungen


Wie können solche Schleifen in Daten gefunden werden, da grundlegende Pfade Schleifen von einem ausgewählten Punkt und deren Äquivalenz sind? Und ist es notwendig?

Die Idee kam in Analogie zu KNN und SVM - "analogen" Algorithmen. Was ist, wenn die Schleife durch ein „Rohr“ ersetzt wird, ein Zylinder von einem Datenpunkt zum anderen, und was in das Rohr gefallen ist, derselben Klasse zugeordnet ist wie diese beiden Punkte des Pfades (nicht mehr grundlegend)? Das "Rohr" muss so breit sein, dass Klassen korrekt klassifiziert werden können ... und im Grenzfall ist es eine gerade Linie. Der Abstand zu einem neuen, unbekannten Punkt sollte auf dem Weg minimal sein, dann können wir ihn derselben Klasse wie dem Pfad zuordnen.



Ein Regressor kann konstruiert werden, indem ein Punkt auf eine gerade Linie zwischen Datenpunkten projiziert wird und Zielwerte interpoliert werden, die Datenpunkten mit demselben Verhältnis entsprechen, in dem die Projektion den Pfad teilt.

Diese Konstruktionsmethode speichert die gesamte Stichprobe und liefert genaue Vorhersagen zum Trainingssatz.

Primitive Implementierung


Wie baue ich einen Pfad? Ich nahm das maximale Element gemäß der Norm und begann, nach dem nächstgelegenen zu suchen, wobei ich den empfangenen Pfad verband. Am Ende - mit dem Anfang geschlossen (natürlich umstritten, aber so).

Schätzer
Dies ist die allererste Version des Codes, siehe das aktualisierte Notizbuch unten.

class PathMachine(BaseEstimator): def __init__(self, norm=np.linalg.norm, classify=False): self.norm = norm self.classify = classify def find_start(self, X): index_max = None value_max = -np.inf for index, x in enumerate(X): value = self.norm(x) if value > value_max: index_max = index value_max = value return index_max def find_next(self, point, target, X, y): index_min = None value_min = np.inf for index, x in enumerate(X): if self.classify and (y[index] != target): continue value = self.norm(x - point) if value < value_min: index_min = index value_min = value return index_min def fit(self, X, y): X = np.copy(X) y = np.copy(y).flatten() self.paths = {} if self.classify else [] start_index = self.find_start(X) start_value = X[start_index] start_target = y[start_index] X = np.delete(X, start_index, axis=0) y = np.delete(y, start_index, axis=0) while len(X) > 0: next_index = self.find_next(start_value, start_target, X, y) if self.classify and next_index is None: start_index = self.find_start(X) start_value = X[start_index] start_target = y[start_index] continue next_target = y[next_index] if self.classify: if not next_target in self.paths: self.paths[next_target] = [] self.paths[next_target].append({ 'start': start_value, 'next': X[next_index] }) else: self.paths.append({ 'start': start_value, 'next': X[next_index], 'value': start_target, 'target': next_target }) start_value = X[next_index] start_target = y[next_index] X = np.delete(X, next_index, axis=0) y = np.delete(y, next_index, axis=0) if self.classify: self.paths[start_target].append({ 'start': start_value, 'next': self.paths[start_target][0]['start'] }) else: self.paths.append({ 'start': start_value, 'next': self.paths[0]['start'], 'value': start_target, 'target': self.paths[0]['target'] }) return self def predict(self, X): result = [] for x in X: if self.classify: predicted = None min_distance = np.inf for target in self.paths: for path in self.paths[target]: point = x - path['start'] line = path['next'] - path['start'] if np.allclose(self.norm(line), 0): continue direction = line / self.norm(line) product = np.dot(point, direction) projection = product * direction distance = self.norm(projection - point) if distance < min_distance: predicted = target min_distance = distance result.append(predicted) else: predicted = None min_distance = np.inf for path in self.paths: point = x - path['start'] line = path['next'] - path['start'] if np.allclose(self.norm(line), 0): continue direction = line / self.norm(line) product = np.dot(point, direction) projection = product * direction parameter = np.sign(product) * self.norm(projection) /\ self.norm(line) distance = self.norm(projection - point) if distance < min_distance: predicted = (1 - parameter) * path['value'] +\ parameter * path['target'] min_distance = distance result.append(predicted) return np.array(result) def score(self, X, y): if self.classify: return f1_score(y.flatten(), self.predict(X), average='micro') else: return r2_score(y.flatten(), self.predict(X)) 


Theoretisch (und technisch) ist es möglich, Predict_Proba auf dieselbe Weise wie 1 - normalisierte Entfernungen zu erstellen. Aber ich hatte keine Gelegenheit, die Wahrscheinlichkeiten wirklich zu testen, also ...

Einige Tests


Ich habe mit dem klassischen Boston Housing und Iris angefangen und als Basislinie Ridge und LogisticRegression gewählt. Nun, was, was wäre wenn.

Im Allgemeinen empfehle ich einen Blick auf das Jupiter-Notizbuch .

Kurz gesagt: Der Regressor arbeitete schlechter, der Klassifikator besser.
Update: Nach StandardScaler funktionierte der Regressor besser.

Ich bin auch auf synthetischen Datensätzen gefahren. Im Wald gibt es im Allgemeinen etwas, nämlich Brennholz.

Dies wurde jedoch bemerkt:

  1. Der Regressor arbeitet gut in Räumen mit kleinen Dimensionen.
  2. Sowohl der Regressor als auch der Klassifikator sind ab einer bestimmten Schwelle geräuschempfindlich.
  3. Der Regressor vermutete im Gegensatz zur Linie Multimodalität (in Boston Housing),
  4. Durch die Konstruktion funktionierte der Klassifikator gut (das Beste von allem ... :)) auf einem linear untrennbaren Satz von Monden.

Schlussfolgerungen, Vor- und Nachteile sowie Anwendbarkeit


Ich persönlich sehe in der aktuellen Implementierung keine Vorteile. Sowohl technisch als auch mathematisch. Vielleicht kann dies zu etwas Vernünftigerem geändert werden. Dementsprechend sehe ich auch keine besondere Anwendbarkeit. Ist es möglich, mit sehr kleinen Daten ohne Vorverarbeitung zu arbeiten ...

Es gibt viele Nachteile: Es ist erforderlich, den gesamten Datensatz im Speicher zu halten, die Verallgemeinerungsfähigkeit basiert auf Extrapolation, der Haupt- und einzige Hyperparameter - die Norm - ist für eine spezielle Auswahlaufzählung nicht zugänglich.

Trotzdem war das Empfangen für mich eine Überraschung, die ich hier teilen wollte.

Source: https://habr.com/ru/post/de429974/


All Articles