Path Machine: una idea de algoritmo

Antecedentes


Hace unos 15 años, aprendí sobre la existencia de caminos fundamentales , grupos que pueden distinguir los espacios topológicos por la conectividad. El resto no será sobre ellos, pero se les ocurrió la idea de un regresor y un clasificador, sin ninguna optimización basada en el almacenamiento de la muestra.

Más detalles

Ideas y conjeturas.


Dado que las rutas fundamentales son bucles desde un punto seleccionado y equivalencia en ellos, entonces, ¿cómo pueden encontrarse dichos bucles en los datos? ¿Y es necesario?

La idea surgió por analogía con KNN y SVM: algoritmos "análogos". ¿Qué sucede si el bucle se reemplaza con una "tubería", un cilindro de un punto de datos a otro, y lo que cayó dentro de la tubería se asigna a la misma clase que estos dos puntos de la ruta (ya no es fundamental)? La "tubería" debe ser de un ancho tal que clasifique correctamente las clases ... y en el límite es una línea recta. La distancia a un nuevo punto desconocido debe ser mínima en el camino, entonces podemos atribuirla a la misma clase que la ruta.



Se puede construir un regresor proyectando un punto en una línea recta entre los puntos de datos e interpolando valores objetivo correspondientes a puntos de datos con la misma relación en la que la proyección divide la ruta.

Este método de construcción recuerda la muestra completa y proporciona predicciones precisas sobre el conjunto de entrenamiento.

Implementación primitiva


¿Cómo construir un camino? Tomé el elemento máximo de acuerdo con la norma y comencé a buscar el más cercano, conectando la ruta recibida. Al final, cerrado con el principio (discutible, por supuesto, pero así).

Estimador
Esta es la primera versión del código, vea el cuaderno actualizado a continuación.

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)) 


Teóricamente (y técnicamente), es posible hacer predic_proba de la misma manera, como 1, distancias normalizadas. Pero no tuve la oportunidad de probar realmente las probabilidades, así que ...

Algunas pruebas


Comencé con el clásico Boston Housing e Iris, y para la línea de base elegí Ridge y LogisticRegression. Bueno, qué, y si.

En general, sugiero mirar el cuaderno jupyter .

En resumen: el regresor funcionó peor, el clasificador funcionó mejor.
Actualización: después de StandardScaler, el regresor funcionó mejor.

También monté en conjuntos de datos sintéticos. Generalmente hay algo en el bosque, que es leña.

Pero esto se notó:

  1. El regresor funciona bien en espacios de pequeñas dimensiones,
  2. Tanto el regresor como el clasificador son sensibles al ruido, comenzando desde un cierto umbral,
  3. El regresor, a diferencia de la línea, sospechaba multimodalidad (en Boston Housing),
  4. Por construcción, el clasificador funcionó bien (lo mejor de todo ... :)) en un conjunto de lunas linealmente inseparable.

Conclusiones, pros, contras y aplicabilidad


Personalmente, no veo ninguna ventaja en la implementación actual. Tanto técnica como matemáticamente. Quizás esto se pueda modificar a algo más sensato. Por consiguiente, tampoco veo ninguna aplicabilidad particular. ¿Es posible trabajar con datos muy pequeños sin preprocesamiento ...

Hay muchas desventajas: es necesario mantener todo el conjunto de datos en la memoria, la capacidad de generalización se basa en la extrapolación, el hiperparámetro principal y único, la norma, no es susceptible de una enumeración de selección especial.

Pero, de todos modos, para mí lo recibido fue una sorpresa, que decidí compartir aquí.

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


All Articles