Моя конечная цель — заменить некоторые методы, использующие kneighbors_graph, на преобразователи из пакета sklearn-ann. Все методы в sklearn-ann реализованы как объекты-трансформеры, совместимые со sklearn. Однако функция, которую я пытаюсь заменить, использует kneighbors_graph(mode = "connectivity", include_self=True), и мне трудно преобразовать вывод расстояния с помощью include_self=False в матрицу связности этого типа. Не все объекты-трансформеры допускают режим подключения, включая self, но все обеспечивают доступ к расчетам расстояний без self.
Я могу воспроизвести kneighbors_graph(mode = "connectivity", include_self=True) из kneighbors_graph(mode = "distance", include_self=True) (обозначаемого nn_with_self). Однако я не могу воспроизвести его из kneighbors_graph(mode = "distance", include_self=False) (имеется в виду nn_without_self), что является тем же результатом, что и KNeighborsTransformer(mode = "distance").fit_transform.
Я вижу, что nn_without_self — это супернабор nn_with_self, но я не знаю, как внутренний алгоритм выбирает, какие поля сохраняются.
Как мне воссоздать nn_with_self из матрицы nn_without_self ниже?
Далее, как я могу все время работать с разреженными матрицами, не конвертируя их в плотные?
Я попытался просмотреть внутренний код, но это похоже на начало наследования классов, и я обнаружил, что просматриваю несколько файлов одновременно, теряя след на GitHub.
from sklearn.datasets import make_classification
from sklearn.neighbors import kneighbors_graph, KNeighborsTransformer
X, _ = make_classification(n_samples=10, n_features=4, n_classes=2, n_clusters_per_class=1, random_state=0)
n_neighbors=3
# Nearest neighbors
nn_with_self = kneighbors_graph(X, n_neighbors=n_neighbors, mode = "distance", metric = "euclidean", include_self=True,n_jobs=-1).todense()
nn_without_self = kneighbors_graph(X, n_neighbors=n_neighbors, mode = "distance", metric = "euclidean", include_self=False,n_jobs=-1).todense()
nn_from_transformer = KNeighborsTransformer(mode = "distance", n_neighbors=n_neighbors, metric = "euclidean", n_jobs=-1).fit_transform(X)
np.all(nn_from_transformer == nn_without_self)
# True
np.all(nn_with_self == nn_without_self)
# False
# Is `nn_with_self` symmetric?
np.allclose(nn_with_self,nn_with_self.T)
# False
# Is `nn_without_self` symmetric?
np.allclose(nn_without_self,nn_without_self.T)
# False
Вот реальные массивы:
nn_with_self
# matrix([[0. , 0.70550439, 0. , 0.20463097, 0. ,
# 0. , 0. , 0. , 0. , 0. ],
# [0. , 0. , 0. , 0.51947869, 0. ,
# 0. , 0. , 0. , 0. , 0.44145655],
# [0. , 0. , 0. , 0. , 0.50025504,
# 0. , 0. , 0. , 0.49481662, 0. ],
# [0.20463097, 0.51947869, 0. , 0. , 0. ,
# 0. , 0. , 0. , 0. , 0. ],
# [0. , 0. , 0.50025504, 0. , 0. ,
# 0. , 0. , 0. , 0.34132965, 0. ],
# [0. , 0.88867318, 0. , 0. , 0. ,
# 0. , 0. , 0. , 0. , 0.44956691],
# [0. , 0. , 1.10390699, 0. , 1.52953542,
# 0. , 0. , 0. , 0. , 0. ],
# [0. , 0. , 0. , 0. , 0. ,
# 3.62670755, 0. , 0. , 0. , 3.83571739],
# [0. , 0. , 0.49481662, 0. , 0.34132965,
# 0. , 0. , 0. , 0. , 0. ],
# [0. , 0.44145655, 0. , 0. , 0. ,
# 0.44956691, 0. , 0. , 0. , 0. ]])
nn_without_self
# matrix([[0. , 0.70550439, 0. , 0.20463097, 1.02852831,
# 0. , 0. , 0. , 0. , 0. ],
# [0.70550439, 0. , 0. , 0.51947869, 0. ,
# 0. , 0. , 0. , 0. , 0.44145655],
# [0. , 0. , 0. , 0. , 0.50025504,
# 0. , 1.10390699, 0. , 0.49481662, 0. ],
# [0.20463097, 0.51947869, 0. , 0. , 0. ,
# 0. , 0. , 0. , 0. , 0.95611187],
# [1.02852831, 0. , 0.50025504, 0. , 0. ,
# 0. , 0. , 0. , 0.34132965, 0. ],
# [0. , 0.88867318, 0. , 1.40547465, 0. ,
# 0. , 0. , 0. , 0. , 0.44956691],
# [0. , 0. , 1.10390699, 0. , 1.52953542,
# 0. , 0. , 0. , 1.59848513, 0. ],
# [0. , 4.1280709 , 0. , 0. , 0. ,
# 3.62670755, 0. , 0. , 0. , 3.83571739],
# [1.36553076, 0. , 0.49481662, 0. , 0.34132965,
# 0. , 0. , 0. , 0. , 0. ],
# [0. , 0.44145655, 0. , 0.95611187, 0. ,
# 0.44956691, 0. , 0. , 0. , 0. ]])






В следующем коде операции выполняются с использованием разреженных матриц на протяжении всего процесса. Результирующая разреженная матрица nn_with_self_sparse включает в себя как k-соседей, так и самосоединения без необходимости преобразования в плотную матрицу в любой точке.
import numpy as np
from sklearn.datasets import make_classification
from sklearn.neighbors import KNeighborsTransformer
from scipy.sparse import coo_matrix, diags
# Create a sample dataset
X, _ = make_classification(n_samples=10, n_features=4, n_classes=2, n_clusters_per_class=1, random_state=0)
n_neighbors = 3
# Generate the kneighbors graph without self-connections
knn_transformer = KNeighborsTransformer(n_neighbors=n_neighbors, mode = "distance", metric = "euclidean", n_jobs=-1)
knn_transformer.fit(X)
nn_without_self = knn_transformer.transform(X)
# Create a sparse diagonal matrix for self-connections with zero distance
num_samples = X.shape[0]
self_connections = diags([0] * num_samples, offsets=0, shape=(num_samples, num_samples), format='coo')
# Combine the k-neighbors graph without self-connections and the self-connections matrix
nn_with_self_sparse = nn_without_self + self_connections
print("Sparse matrix with self-connections:")
print(nn_with_self_sparse)
Просто сделайте n_neighbors - 1 ради include_self=True.
from sklearn.datasets import make_classification
from sklearn.neighbors import kneighbors_graph, KNeighborsTransformer
X, _ = make_classification(n_samples=10, n_features=4, n_classes=2, n_clusters_per_class=1, random_state=0)
n_neighbors=3
# Nearest neighbors
nn_with_self = kneighbors_graph(X, n_neighbors=n_neighbors, mode = "distance", metric = "euclidean", include_self=True,n_jobs=-1).todense()
nn_from_transformer = KNeighborsTransformer(mode = "distance", n_neighbors=n_neighbors - 1, metric = "euclidean", n_jobs=-1).fit_transform(X).todense()
np.allclose(nn_from_transformer, nn_with_self)
# True