nystrom-ncut 0.3.2__py3-none-any.whl → 0.3.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nystrom_ncut/__init__.py +4 -1
- nystrom_ncut/distance_utils.py +41 -30
- nystrom_ncut/global_settings.py +2 -0
- nystrom_ncut/kernel/__init__.py +3 -0
- nystrom_ncut/kernel/kernel_ncut.py +113 -0
- nystrom_ncut/nystrom/__init__.py +1 -1
- nystrom_ncut/nystrom/distance_realization.py +0 -3
- nystrom_ncut/nystrom/normalized_cut.py +26 -24
- nystrom_ncut/nystrom/nystrom_utils.py +25 -132
- nystrom_ncut/sampling_utils.py +98 -4
- nystrom_ncut/transformer/__init__.py +1 -0
- nystrom_ncut/transformer/transformer_mixin.py +16 -3
- nystrom_ncut/visualize_utils.py +10 -7
- {nystrom_ncut-0.3.2.dist-info → nystrom_ncut-0.3.4.dist-info}/METADATA +1 -1
- nystrom_ncut-0.3.4.dist-info/RECORD +21 -0
- nystrom_ncut-0.3.2.dist-info/RECORD +0 -18
- {nystrom_ncut-0.3.2.dist-info → nystrom_ncut-0.3.4.dist-info}/LICENSE +0 -0
- {nystrom_ncut-0.3.2.dist-info → nystrom_ncut-0.3.4.dist-info}/WHEEL +0 -0
- {nystrom_ncut-0.3.2.dist-info → nystrom_ncut-0.3.4.dist-info}/top_level.txt +0 -0
nystrom_ncut/__init__.py
CHANGED
nystrom_ncut/distance_utils.py
CHANGED
@@ -6,13 +6,25 @@ import torch
|
|
6
6
|
from .common import lazy_normalize
|
7
7
|
|
8
8
|
|
9
|
-
DistanceOptions = Literal[
|
10
|
-
|
9
|
+
DistanceOptions = Literal[
|
10
|
+
"cosine",
|
11
|
+
"euclidean",
|
12
|
+
]
|
13
|
+
AffinityOptions = Literal[
|
14
|
+
"cosine",
|
15
|
+
"rbf",
|
16
|
+
# "laplacian",
|
17
|
+
]
|
11
18
|
|
12
19
|
# noinspection PyTypeChecker
|
13
20
|
DISTANCE_TO_AFFINITY: OrderedDict[DistanceOptions, List[AffinityOptions]] = collections.OrderedDict([
|
14
|
-
("cosine", [
|
15
|
-
|
21
|
+
("cosine", [
|
22
|
+
"cosine",
|
23
|
+
]),
|
24
|
+
("euclidean", [
|
25
|
+
"rbf",
|
26
|
+
# "laplacian",
|
27
|
+
]),
|
16
28
|
])
|
17
29
|
# noinspection PyTypeChecker
|
18
30
|
AFFINITY_TO_DISTANCE: OrderedDict[AffinityOptions, DistanceOptions] = collections.OrderedDict(sum([
|
@@ -32,45 +44,51 @@ def to_euclidean(x: torch.Tensor, distance_type: DistanceOptions) -> torch.Tenso
|
|
32
44
|
|
33
45
|
|
34
46
|
def distance_from_features(
|
35
|
-
|
47
|
+
features_A: torch.Tensor,
|
36
48
|
features_B: torch.Tensor,
|
37
49
|
distance_type: DistanceOptions,
|
38
50
|
):
|
39
51
|
"""Compute distance matrix from input features.
|
40
52
|
Args:
|
41
|
-
|
53
|
+
features_A (torch.Tensor): input features, shape (n_samples, n_features)
|
42
54
|
features_B (torch.Tensor, optional): optional, if not None, compute affinity between two features
|
43
55
|
distance_type (str): distance metric, 'cosine' (default) or 'euclidean', 'rbf'.
|
44
56
|
Returns:
|
45
57
|
(torch.Tensor): affinity matrix, shape (n_samples, n_samples)
|
46
58
|
"""
|
47
59
|
# compute distance matrix from input features
|
48
|
-
shape: torch.Size =
|
49
|
-
|
60
|
+
shape: torch.Size = features_A.shape[:-2]
|
61
|
+
features_A = features_A.view((-1, *features_A.shape[-2:]))
|
50
62
|
features_B = features_B.view((-1, *features_B.shape[-2:]))
|
51
63
|
|
52
64
|
match distance_type:
|
53
65
|
case "cosine":
|
54
|
-
|
66
|
+
features_A = lazy_normalize(features_A, dim=-1)
|
55
67
|
features_B = lazy_normalize(features_B, dim=-1)
|
56
|
-
D = 1 -
|
68
|
+
D = 1 - features_A @ features_B.mT
|
57
69
|
case "euclidean":
|
58
|
-
D = torch.cdist(
|
70
|
+
D = torch.cdist(features_A, features_B, p=2)
|
59
71
|
case _:
|
60
72
|
raise ValueError("Distance should be 'cosine' or 'euclidean'")
|
61
73
|
return D.view((*shape, *D.shape[-2:]))
|
62
74
|
|
63
75
|
|
76
|
+
def get_normalization_factor(features: torch.Tensor, c: float = 2.0) -> torch.Tensor:
|
77
|
+
p = torch.erf(torch.tensor((-c, c), device=features.device) * (2 ** -0.5))
|
78
|
+
lo, hi = torch.nanquantile(features, q=(p + 1) / 2, dim=-2) # [... x d], [... x d]
|
79
|
+
return torch.norm(hi - lo, dim=-1) / (2 * c) # [...]
|
80
|
+
|
81
|
+
|
64
82
|
def affinity_from_features(
|
65
|
-
|
66
|
-
features_B: torch.Tensor
|
67
|
-
|
68
|
-
|
83
|
+
features_A: torch.Tensor,
|
84
|
+
features_B: torch.Tensor,
|
85
|
+
affinity_type: AffinityOptions,
|
86
|
+
affinity_focal_gamma: float,
|
69
87
|
):
|
70
88
|
"""Compute affinity matrix from input features.
|
71
89
|
|
72
90
|
Args:
|
73
|
-
|
91
|
+
features_A (torch.Tensor): input features, shape (n_samples, n_features)
|
74
92
|
features_B (torch.Tensor, optional): optional, if not None, compute affinity between two features
|
75
93
|
affinity_focal_gamma (float): affinity matrix parameter, lower t reduce the edge weights
|
76
94
|
on weak connections, default 1.0
|
@@ -82,24 +100,17 @@ def affinity_from_features(
|
|
82
100
|
|
83
101
|
# if feature_B is not provided, compute affinity matrix on features x features
|
84
102
|
# if feature_B is provided, compute affinity matrix on features x feature_B
|
85
|
-
|
86
|
-
|
87
|
-
# compute distance matrix from input features
|
88
|
-
D = distance_from_features(features, features_B, AFFINITY_TO_DISTANCE[affinity_type])
|
103
|
+
D = distance_from_features(features_A, features_B, AFFINITY_TO_DISTANCE[affinity_type])
|
89
104
|
|
90
105
|
# lower affinity_focal_gamma reduce the weak edge weights
|
91
106
|
match affinity_type:
|
92
|
-
case "cosine"
|
93
|
-
|
107
|
+
case "cosine":
|
108
|
+
pass
|
109
|
+
# case "laplacian":
|
110
|
+
# D = D / get_normalization_factor(features_A)[..., None, None]
|
94
111
|
case "rbf":
|
95
|
-
|
96
|
-
c = 2.0
|
97
|
-
p = torch.erf(torch.tensor((-c, c), device=features.device) * (2 ** -0.5))
|
98
|
-
stds = torch.nanquantile(features, q=(p + 1) / 2, dim=-2) # [2 x ... x d]
|
99
|
-
stds = (stds[1] - stds[0]) / (2 * c) # [... x d]
|
100
|
-
D = 0.5 * (D / torch.norm(stds, dim=-1)[..., None, None]) ** 2
|
101
|
-
A = torch.exp(-D / affinity_focal_gamma)
|
112
|
+
D = 0.5 * (D / get_normalization_factor(features_A)[..., None, None]) ** 2
|
102
113
|
case _:
|
103
114
|
raise ValueError("Affinity should be 'cosine', 'rbf', or 'laplacian'")
|
104
|
-
|
115
|
+
A = torch.exp(-D / affinity_focal_gamma) # [... x n x n]
|
105
116
|
return A
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import torch
|
2
|
+
|
3
|
+
from ..distance_utils import (
|
4
|
+
AffinityOptions,
|
5
|
+
AFFINITY_TO_DISTANCE,
|
6
|
+
get_normalization_factor,
|
7
|
+
)
|
8
|
+
from ..sampling_utils import (
|
9
|
+
SampleConfig,
|
10
|
+
OnlineTransformerSubsampleFit,
|
11
|
+
)
|
12
|
+
from ..transformer import (
|
13
|
+
OnlineTorchTransformerMixin,
|
14
|
+
)
|
15
|
+
|
16
|
+
|
17
|
+
class KernelNCutBaseTransformer(OnlineTorchTransformerMixin):
|
18
|
+
def __init__(
|
19
|
+
self,
|
20
|
+
n_components: int,
|
21
|
+
kernel_dim: int,
|
22
|
+
affinity_type: AffinityOptions,
|
23
|
+
affinity_focal_gamma: float,
|
24
|
+
):
|
25
|
+
self.n_components: int = n_components
|
26
|
+
self.kernel_dim: int = kernel_dim
|
27
|
+
self.affinity_type: AffinityOptions = affinity_type
|
28
|
+
self.affinity_focal_gamma = affinity_focal_gamma
|
29
|
+
|
30
|
+
# Anchor matrices
|
31
|
+
self.W: torch.Tensor = None # [... x d x kernel_dim]
|
32
|
+
self.kernelized_anchor: torch.Tensor = None # [... x n x (2 * kernel_dim)]
|
33
|
+
|
34
|
+
# Updated matrices
|
35
|
+
self.r: torch.Tensor = None # [... x (2 * kernel_dim)]
|
36
|
+
self.transform_matrix: torch.Tensor = None # [... x (2 * kernel_dim) x n_components]
|
37
|
+
self.eigenvalues_: torch.Tensor = None # [... x n_components]
|
38
|
+
|
39
|
+
def _update(self) -> None:
|
40
|
+
row_sum = self.kernelized_anchor @ self.r[..., None] # [... x n x 1]
|
41
|
+
normalized_kernelized_anchor = self.kernelized_anchor / (row_sum ** 0.5) # [... x n x (2 * kernel_dim)]
|
42
|
+
_, S, V = torch.svd_lowrank(torch.nan_to_num(
|
43
|
+
normalized_kernelized_anchor, nan=0.0,
|
44
|
+
), q=self.n_components) # [... x n_components], [... x (2 * kernel_dim) x n_components]
|
45
|
+
self.transform_matrix = V * torch.nan_to_num(1 / S, posinf=0.0, neginf=0.0)[..., None, :] # [... x (2 * kernel_dim) x n_components]
|
46
|
+
self.eigenvalues_ = S ** 2
|
47
|
+
|
48
|
+
def fit(self, features: torch.Tensor) -> "KernelNCutBaseTransformer":
|
49
|
+
d = features.shape[-1]
|
50
|
+
scale = get_normalization_factor(features) * (self.affinity_focal_gamma ** 0.5) # [...]
|
51
|
+
self.W = torch.randn((*features.shape[:-2], d, self.kernel_dim)) / scale[..., None, None] # [... x d x kernel_dim]
|
52
|
+
|
53
|
+
W_anchor = features @ self.W # [... x n x kernel_dim]
|
54
|
+
self.kernelized_anchor = torch.cat((
|
55
|
+
torch.cos(W_anchor),
|
56
|
+
torch.sin(W_anchor),
|
57
|
+
), dim=-1) / (self.kernel_dim ** 0.5) # [... x n * (2 * kernel_dim)]
|
58
|
+
self.r = torch.sum(torch.nan_to_num(self.kernelized_anchor, nan=0.0), dim=-2) # [... x (2 * kernel_dim)]
|
59
|
+
self._update()
|
60
|
+
return self
|
61
|
+
|
62
|
+
def update(self, features: torch.Tensor) -> torch.Tensor:
|
63
|
+
W_features = features @ self.W # [... x m x kernel_dim]
|
64
|
+
kernelized_features = torch.cat((
|
65
|
+
torch.cos(W_features),
|
66
|
+
torch.sin(W_features),
|
67
|
+
), dim=-1) / (self.kernel_dim ** 0.5) # [... x m x (2 * kernel_dim)]
|
68
|
+
b_r = torch.sum(torch.nan_to_num(kernelized_features, nan=0.0), dim=-2) # [... x (2 * kernel_dim)]
|
69
|
+
self.r = self.r + b_r
|
70
|
+
self._update()
|
71
|
+
|
72
|
+
row_sum = kernelized_features @ self.r[..., None] # [... x m x 1]
|
73
|
+
normalized_kernelized_features = kernelized_features / (row_sum ** 0.5) # [... x m x (2 * kernel_dim)]
|
74
|
+
return normalized_kernelized_features @ self.transform_matrix # [... x m x n_components]
|
75
|
+
|
76
|
+
def transform(self, features: torch.Tensor = None) -> torch.Tensor:
|
77
|
+
if features is None:
|
78
|
+
kernelized_features = self.kernelized_anchor # [... x n x (2 * kernel_dim)]
|
79
|
+
else:
|
80
|
+
W_features = features @ self.W
|
81
|
+
kernelized_features = torch.cat((
|
82
|
+
torch.cos(W_features),
|
83
|
+
torch.sin(W_features),
|
84
|
+
), dim=-1) / (self.kernel_dim ** 0.5) # [... x m x (2 * kernel_dim)]
|
85
|
+
row_sum = kernelized_features @ self.r[..., None] # [... x m x 1]
|
86
|
+
normalized_kernelized_features = kernelized_features / (row_sum ** 0.5) # [... x m x (2 * kernel_dim)]
|
87
|
+
return normalized_kernelized_features @ self.transform_matrix # [... x m x n_components]
|
88
|
+
|
89
|
+
|
90
|
+
class KernelNCut(OnlineTransformerSubsampleFit):
|
91
|
+
"""Kernelized Normalized Cut for large scale graph."""
|
92
|
+
|
93
|
+
def __init__(
|
94
|
+
self,
|
95
|
+
n_components: int,
|
96
|
+
kernel_dim: int = 1024,
|
97
|
+
affinity_type: AffinityOptions = "cosine",
|
98
|
+
affinity_focal_gamma: float = 1.0,
|
99
|
+
sample_config: SampleConfig = SampleConfig(),
|
100
|
+
):
|
101
|
+
OnlineTransformerSubsampleFit.__init__(
|
102
|
+
self,
|
103
|
+
base_transformer=KernelNCutBaseTransformer(
|
104
|
+
n_components=n_components,
|
105
|
+
kernel_dim=kernel_dim,
|
106
|
+
affinity_type=affinity_type,
|
107
|
+
affinity_focal_gamma=affinity_focal_gamma,
|
108
|
+
),
|
109
|
+
distance_type=AFFINITY_TO_DISTANCE[affinity_type],
|
110
|
+
sample_config=sample_config,
|
111
|
+
)
|
112
|
+
|
113
|
+
|
nystrom_ncut/nystrom/__init__.py
CHANGED
@@ -101,7 +101,6 @@ class DistanceRealization(OnlineNystromSubsampleFit):
|
|
101
101
|
distance_type: DistanceOptions = "cosine",
|
102
102
|
sample_config: SampleConfig = SampleConfig(),
|
103
103
|
eig_solver: EigSolverOptions = "svd_lowrank",
|
104
|
-
chunk_size: int = 8192,
|
105
104
|
):
|
106
105
|
"""
|
107
106
|
Args:
|
@@ -110,7 +109,6 @@ class DistanceRealization(OnlineNystromSubsampleFit):
|
|
110
109
|
farthest point sampling is recommended for better Nystrom-approximation accuracy
|
111
110
|
distance (str): distance metric for affinity matrix, ['cosine', 'euclidean', 'rbf'].
|
112
111
|
eig_solver (str): eigen decompose solver, ['svd_lowrank', 'lobpcg', 'svd', 'eigh'].
|
113
|
-
chunk_size (int): chunk size for large-scale matrix multiplication
|
114
112
|
"""
|
115
113
|
OnlineNystromSubsampleFit.__init__(
|
116
114
|
self,
|
@@ -119,7 +117,6 @@ class DistanceRealization(OnlineNystromSubsampleFit):
|
|
119
117
|
distance_type=distance_type,
|
120
118
|
sample_config=sample_config,
|
121
119
|
eig_solver=eig_solver,
|
122
|
-
chunk_size=chunk_size,
|
123
120
|
)
|
124
121
|
|
125
122
|
def fit_transform(
|
@@ -4,7 +4,7 @@ import torch
|
|
4
4
|
from .nystrom_utils import (
|
5
5
|
EigSolverOptions,
|
6
6
|
OnlineKernel,
|
7
|
-
|
7
|
+
OnlineNystrom,
|
8
8
|
solve_eig,
|
9
9
|
)
|
10
10
|
from ..distance_utils import (
|
@@ -14,19 +14,20 @@ from ..distance_utils import (
|
|
14
14
|
)
|
15
15
|
from ..sampling_utils import (
|
16
16
|
SampleConfig,
|
17
|
+
OnlineTransformerSubsampleFit,
|
17
18
|
)
|
18
19
|
|
19
20
|
|
20
21
|
class LaplacianKernel(OnlineKernel):
|
21
22
|
def __init__(
|
22
23
|
self,
|
23
|
-
affinity_focal_gamma: float,
|
24
24
|
affinity_type: AffinityOptions,
|
25
|
+
affinity_focal_gamma: float,
|
25
26
|
adaptive_scaling: bool,
|
26
27
|
eig_solver: EigSolverOptions,
|
27
28
|
):
|
28
|
-
self.affinity_focal_gamma = affinity_focal_gamma
|
29
29
|
self.affinity_type: AffinityOptions = affinity_type
|
30
|
+
self.affinity_focal_gamma = affinity_focal_gamma
|
30
31
|
self.adaptive_scaling: bool = adaptive_scaling
|
31
32
|
self.eig_solver: EigSolverOptions = eig_solver
|
32
33
|
|
@@ -44,27 +45,29 @@ class LaplacianKernel(OnlineKernel):
|
|
44
45
|
self.anchor_features = features # [... x n x d]
|
45
46
|
self.anchor_mask = torch.all(torch.isnan(self.anchor_features), dim=-1) # [... x n]
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
48
|
+
|
49
|
+
self.A = torch.where(self.anchor_mask[..., None], 0.0, affinity_from_features(
|
50
|
+
features_A=self.anchor_features, # [... x n x d]
|
51
|
+
features_B=self.anchor_features, # [... x n x d]
|
50
52
|
affinity_type=self.affinity_type,
|
51
|
-
|
53
|
+
affinity_focal_gamma=self.affinity_focal_gamma,
|
54
|
+
)) # [... x n x n]
|
52
55
|
d = features.shape[-1]
|
53
56
|
U, L = solve_eig(
|
54
|
-
self.A,
|
57
|
+
torch.nan_to_num(self.A, nan=0.0),
|
55
58
|
num_eig=d + 1, # d * (d + 3) // 2 + 1,
|
56
59
|
eig_solver=self.eig_solver,
|
57
60
|
) # [... x n x (d + 1)], [... x (d + 1)]
|
58
61
|
self.Ainv = U @ torch.nan_to_num(torch.diag_embed(1 / L), posinf=0.0, neginf=0.0) @ U.mT # [... x n x n]
|
59
|
-
self.a_r = torch.where(self.anchor_mask, torch.inf, torch.sum(self.A, dim=-1))
|
62
|
+
self.a_r = torch.where(self.anchor_mask, torch.inf, torch.sum(self.A.mT, dim=-1)) # [... x n]
|
60
63
|
self.b_r = torch.zeros_like(self.a_r) # [... x n]
|
61
64
|
|
62
65
|
def _affinity(self, features: torch.Tensor) -> torch.Tensor:
|
63
66
|
B = torch.where(self.anchor_mask[..., None], 0.0, affinity_from_features(
|
64
|
-
self.anchor_features,
|
65
|
-
features,
|
66
|
-
affinity_focal_gamma=self.affinity_focal_gamma,
|
67
|
+
features_A=self.anchor_features, # [... x n x d]
|
68
|
+
features_B=features, # [... x m x d]
|
67
69
|
affinity_type=self.affinity_type,
|
70
|
+
affinity_focal_gamma=self.affinity_focal_gamma,
|
68
71
|
)) # [... x n x m]
|
69
72
|
if self.adaptive_scaling:
|
70
73
|
diagonal = (
|
@@ -93,44 +96,43 @@ class LaplacianKernel(OnlineKernel):
|
|
93
96
|
B = self.A # [... x n x n]
|
94
97
|
col_sum = row_sum # [... x n]
|
95
98
|
else:
|
96
|
-
B = self._affinity(features)
|
99
|
+
B = self._affinity(features) # [... x n x m]
|
97
100
|
b_c = torch.sum(B, dim=-2) # [... x m]
|
98
101
|
col_sum = b_c + (B.mT @ (self.Ainv @ self.b_r[..., None]))[..., 0] # [... x m]
|
99
102
|
scale = (row_sum[..., :, None] * col_sum[..., None, :]) ** -0.5 # [... x n x m]
|
100
103
|
return (B * scale).mT # [... x m x n]
|
101
104
|
|
102
105
|
|
103
|
-
class
|
106
|
+
class NystromNCut(OnlineTransformerSubsampleFit):
|
104
107
|
"""Nystrom Normalized Cut for large scale graph."""
|
105
108
|
|
106
109
|
def __init__(
|
107
110
|
self,
|
108
|
-
n_components: int
|
109
|
-
affinity_focal_gamma: float = 1.0,
|
111
|
+
n_components: int,
|
110
112
|
affinity_type: AffinityOptions = "cosine",
|
113
|
+
affinity_focal_gamma: float = 1.0,
|
111
114
|
adaptive_scaling: bool = False,
|
112
115
|
sample_config: SampleConfig = SampleConfig(),
|
113
116
|
eig_solver: EigSolverOptions = "svd_lowrank",
|
114
|
-
chunk_size: int = 8192,
|
115
117
|
):
|
116
118
|
"""
|
117
119
|
Args:
|
118
120
|
n_components (int): number of top eigenvectors to return
|
121
|
+
affinity_type (str): distance metric for affinity matrix, ['cosine', 'euclidean', 'rbf'].
|
119
122
|
affinity_focal_gamma (float): affinity matrix temperature, lower t reduce the not-so-connected edge weights,
|
120
123
|
smaller t result in more sharp eigenvectors.
|
121
|
-
distance (str): distance metric for affinity matrix, ['cosine', 'euclidean', 'rbf'].
|
122
124
|
adaptive_scaling (bool): whether to scale off-diagonal affinity vectors so extended diagonal equals 1
|
123
125
|
sample_config (str): subgraph sampling, ['farthest', 'random'].
|
124
126
|
farthest point sampling is recommended for better Nystrom-approximation accuracy
|
125
127
|
eig_solver (str): eigen decompose solver, ['svd_lowrank', 'lobpcg', 'svd', 'eigh'].
|
126
|
-
chunk_size (int): chunk size for large-scale matrix multiplication
|
127
128
|
"""
|
128
|
-
|
129
|
+
OnlineTransformerSubsampleFit.__init__(
|
129
130
|
self,
|
130
|
-
|
131
|
-
|
131
|
+
base_transformer=OnlineNystrom(
|
132
|
+
n_components=n_components,
|
133
|
+
kernel=LaplacianKernel(affinity_type, affinity_focal_gamma, adaptive_scaling, eig_solver),
|
134
|
+
eig_solver=eig_solver,
|
135
|
+
),
|
132
136
|
distance_type=AFFINITY_TO_DISTANCE[affinity_type],
|
133
137
|
sample_config=sample_config,
|
134
|
-
eig_solver=eig_solver,
|
135
|
-
chunk_size=chunk_size,
|
136
138
|
)
|
@@ -1,5 +1,3 @@
|
|
1
|
-
import copy
|
2
|
-
import logging
|
3
1
|
from abc import abstractmethod
|
4
2
|
from typing import Literal, Tuple
|
5
3
|
|
@@ -8,15 +6,11 @@ import torch
|
|
8
6
|
from ..common import (
|
9
7
|
ceildiv,
|
10
8
|
)
|
11
|
-
from ..
|
12
|
-
|
13
|
-
)
|
14
|
-
from ..sampling_utils import (
|
15
|
-
SampleConfig,
|
16
|
-
subsample_features,
|
9
|
+
from ..global_settings import (
|
10
|
+
CHUNK_SIZE,
|
17
11
|
)
|
18
12
|
from ..transformer import (
|
19
|
-
|
13
|
+
OnlineTorchTransformerMixin,
|
20
14
|
)
|
21
15
|
|
22
16
|
|
@@ -37,13 +31,12 @@ class OnlineKernel:
|
|
37
31
|
""""""
|
38
32
|
|
39
33
|
|
40
|
-
class OnlineNystrom(
|
34
|
+
class OnlineNystrom(OnlineTorchTransformerMixin):
|
41
35
|
def __init__(
|
42
36
|
self,
|
43
37
|
n_components: int,
|
44
38
|
kernel: OnlineKernel,
|
45
39
|
eig_solver: EigSolverOptions,
|
46
|
-
chunk_size: int = 8192,
|
47
40
|
):
|
48
41
|
"""
|
49
42
|
Args:
|
@@ -56,8 +49,6 @@ class OnlineNystrom(TorchTransformerMixin):
|
|
56
49
|
self.eig_solver: EigSolverOptions = eig_solver
|
57
50
|
self.shape: torch.Size = None # ...
|
58
51
|
|
59
|
-
self.chunk_size = chunk_size
|
60
|
-
|
61
52
|
# Anchor matrices
|
62
53
|
self.anchor_features: torch.Tensor = None # [... x n x d]
|
63
54
|
self.A: torch.Tensor = None # [... x n x n]
|
@@ -68,12 +59,13 @@ class OnlineNystrom(TorchTransformerMixin):
|
|
68
59
|
# Updated matrices
|
69
60
|
self.S: torch.Tensor = None # [... x n x n]
|
70
61
|
self.transform_matrix: torch.Tensor = None # [... x n x n_components]
|
71
|
-
self.eigenvalues_: torch.Tensor = None # [... x
|
62
|
+
self.eigenvalues_: torch.Tensor = None # [... x n_components]
|
72
63
|
|
73
64
|
def _update_to_kernel(self, d: int) -> Tuple[torch.Tensor, torch.Tensor]:
|
74
|
-
self.A = self.
|
65
|
+
self.A = self.kernel.transform()
|
66
|
+
self.S = torch.nan_to_num(self.A, nan=0.0)
|
75
67
|
U, L = solve_eig(
|
76
|
-
self.
|
68
|
+
self.S,
|
77
69
|
num_eig=d + 1, # d * (d + 3) // 2 + 1,
|
78
70
|
eig_solver=self.eig_solver,
|
79
71
|
) # [... x n x (? + 1)], [... x (? + 1)]
|
@@ -83,10 +75,6 @@ class OnlineNystrom(TorchTransformerMixin):
|
|
83
75
|
return U, L
|
84
76
|
|
85
77
|
def fit(self, features: torch.Tensor) -> "OnlineNystrom":
|
86
|
-
OnlineNystrom.fit_transform(self, features)
|
87
|
-
return self
|
88
|
-
|
89
|
-
def fit_transform(self, features: torch.Tensor) -> torch.Tensor:
|
90
78
|
self.anchor_features = features
|
91
79
|
|
92
80
|
self.kernel.fit(self.anchor_features)
|
@@ -94,11 +82,11 @@ class OnlineNystrom(TorchTransformerMixin):
|
|
94
82
|
|
95
83
|
self.transform_matrix = (U / L[..., None, :])[..., :, :self.n_components] # [... x n x n_components]
|
96
84
|
self.eigenvalues_ = L[..., :self.n_components] # [... x n_components]
|
97
|
-
return
|
85
|
+
return self
|
98
86
|
|
99
87
|
def update(self, features: torch.Tensor) -> torch.Tensor:
|
100
88
|
d = features.shape[-1]
|
101
|
-
n_chunks = ceildiv(features.shape[-2],
|
89
|
+
n_chunks = ceildiv(features.shape[-2], CHUNK_SIZE)
|
102
90
|
if n_chunks > 1:
|
103
91
|
""" Chunked version """
|
104
92
|
chunks = torch.chunk(features, n_chunks, dim=-2)
|
@@ -134,119 +122,24 @@ class OnlineNystrom(TorchTransformerMixin):
|
|
134
122
|
|
135
123
|
return B.mT @ self.transform_matrix # [... x m x n_components]
|
136
124
|
|
137
|
-
def transform(self, features: torch.Tensor) -> torch.Tensor:
|
138
|
-
|
139
|
-
|
140
|
-
""" Chunked version """
|
141
|
-
chunks = torch.chunk(features, n_chunks, dim=-2)
|
142
|
-
VS = []
|
143
|
-
for chunk in chunks:
|
144
|
-
VS.append(self.kernel.transform(chunk) @ self.transform_matrix) # [... x _m x n_components]
|
145
|
-
VS = torch.cat(VS, dim=-2)
|
125
|
+
def transform(self, features: torch.Tensor = None) -> torch.Tensor:
|
126
|
+
if features is None:
|
127
|
+
VS = self.A @ self.transform_matrix # [... x n x n_components]
|
146
128
|
else:
|
147
|
-
|
148
|
-
|
129
|
+
n_chunks = ceildiv(features.shape[-2], CHUNK_SIZE)
|
130
|
+
if n_chunks > 1:
|
131
|
+
""" Chunked version """
|
132
|
+
chunks = torch.chunk(features, n_chunks, dim=-2)
|
133
|
+
VS = []
|
134
|
+
for chunk in chunks:
|
135
|
+
VS.append(self.kernel.transform(chunk) @ self.transform_matrix) # [... x _m x n_components]
|
136
|
+
VS = torch.cat(VS, dim=-2)
|
137
|
+
else:
|
138
|
+
""" Unchunked version """
|
139
|
+
VS = self.kernel.transform(features) @ self.transform_matrix # [... x m x n_components]
|
149
140
|
return VS # [... x m x n_components]
|
150
141
|
|
151
142
|
|
152
|
-
class OnlineNystromSubsampleFit(OnlineNystrom):
|
153
|
-
def __init__(
|
154
|
-
self,
|
155
|
-
n_components: int,
|
156
|
-
kernel: OnlineKernel,
|
157
|
-
distance_type: DistanceOptions,
|
158
|
-
sample_config: SampleConfig,
|
159
|
-
eig_solver: EigSolverOptions = "svd_lowrank",
|
160
|
-
chunk_size: int = 8192,
|
161
|
-
):
|
162
|
-
OnlineNystrom.__init__(
|
163
|
-
self,
|
164
|
-
n_components=n_components,
|
165
|
-
kernel=kernel,
|
166
|
-
eig_solver=eig_solver,
|
167
|
-
chunk_size=chunk_size,
|
168
|
-
)
|
169
|
-
self.distance_type: DistanceOptions = distance_type
|
170
|
-
self.sample_config: SampleConfig = sample_config
|
171
|
-
self.sample_config._ncut_obj = copy.deepcopy(self)
|
172
|
-
self.anchor_indices: torch.Tensor = None
|
173
|
-
|
174
|
-
def _fit_helper(
|
175
|
-
self,
|
176
|
-
features: torch.Tensor,
|
177
|
-
precomputed_sampled_indices: torch.Tensor,
|
178
|
-
) -> Tuple[torch.Tensor, torch.Tensor]:
|
179
|
-
_n = features.shape[-2]
|
180
|
-
if self.sample_config.num_sample >= _n:
|
181
|
-
logging.info(
|
182
|
-
f"NCUT nystrom num_sample is larger than number of input samples, nyström approximation is not needed, setting num_sample={_n}"
|
183
|
-
)
|
184
|
-
self.num_sample = _n
|
185
|
-
|
186
|
-
if precomputed_sampled_indices is not None:
|
187
|
-
self.anchor_indices = precomputed_sampled_indices
|
188
|
-
else:
|
189
|
-
self.anchor_indices = subsample_features(
|
190
|
-
features=features,
|
191
|
-
distance_type=self.distance_type,
|
192
|
-
config=self.sample_config,
|
193
|
-
)
|
194
|
-
sampled_features = torch.gather(features, -2, self.anchor_indices[..., None].expand([-1] * self.anchor_indices.ndim + [features.shape[-1]]))
|
195
|
-
OnlineNystrom.fit(self, sampled_features)
|
196
|
-
|
197
|
-
_n_not_sampled = _n - self.anchor_indices.shape[-1]
|
198
|
-
if _n_not_sampled > 0:
|
199
|
-
unsampled_mask = torch.full(features.shape[:-1], True, device=features.device).scatter_(-1, self.anchor_indices, False)
|
200
|
-
unsampled_indices = torch.where(unsampled_mask)[-1].view((*features.shape[:-2], -1))
|
201
|
-
unsampled_features = torch.gather(features, -2, unsampled_indices[..., None].expand([-1] * unsampled_indices.ndim + [features.shape[-1]]))
|
202
|
-
V_unsampled = OnlineNystrom.update(self, unsampled_features)
|
203
|
-
else:
|
204
|
-
unsampled_indices = V_unsampled = None
|
205
|
-
return unsampled_indices, V_unsampled
|
206
|
-
|
207
|
-
def fit(
|
208
|
-
self,
|
209
|
-
features: torch.Tensor,
|
210
|
-
precomputed_sampled_indices: torch.Tensor = None,
|
211
|
-
) -> "OnlineNystromSubsampleFit":
|
212
|
-
"""Fit Nystrom Normalized Cut on the input features.
|
213
|
-
Args:
|
214
|
-
features (torch.Tensor): input features, shape (n_samples, n_features)
|
215
|
-
precomputed_sampled_indices (torch.Tensor): precomputed sampled indices, shape (num_sample,)
|
216
|
-
override the sample_method, if not None
|
217
|
-
Returns:
|
218
|
-
(NCut): self
|
219
|
-
"""
|
220
|
-
OnlineNystromSubsampleFit._fit_helper(self, features, precomputed_sampled_indices)
|
221
|
-
return self
|
222
|
-
|
223
|
-
def fit_transform(
|
224
|
-
self,
|
225
|
-
features: torch.Tensor,
|
226
|
-
precomputed_sampled_indices: torch.Tensor = None,
|
227
|
-
) -> torch.Tensor:
|
228
|
-
"""
|
229
|
-
Args:
|
230
|
-
features (torch.Tensor): input features, shape (n_samples, n_features)
|
231
|
-
precomputed_sampled_indices (torch.Tensor): precomputed sampled indices, shape (num_sample,)
|
232
|
-
override the sample_method, if not None
|
233
|
-
|
234
|
-
Returns:
|
235
|
-
(torch.Tensor): eigen_vectors, shape (n_samples, num_eig)
|
236
|
-
(torch.Tensor): eigen_values, sorted in descending order, shape (num_eig,)
|
237
|
-
"""
|
238
|
-
unsampled_indices, V_unsampled = OnlineNystromSubsampleFit._fit_helper(self, features, precomputed_sampled_indices)
|
239
|
-
V_sampled = OnlineNystrom.transform(self, self.anchor_features)
|
240
|
-
|
241
|
-
if unsampled_indices is not None:
|
242
|
-
V = torch.zeros((*features.shape[:-1], self.n_components), device=features.device)
|
243
|
-
for (indices, _V) in [(self.anchor_indices, V_sampled), (unsampled_indices, V_unsampled)]:
|
244
|
-
V.scatter_(-2, indices[..., None].expand([-1] * indices.ndim + [self.n_components]), _V)
|
245
|
-
else:
|
246
|
-
V = V_sampled
|
247
|
-
return V
|
248
|
-
|
249
|
-
|
250
143
|
def solve_eig(
|
251
144
|
A: torch.Tensor,
|
252
145
|
num_eig: int,
|
@@ -269,7 +162,7 @@ def solve_eig(
|
|
269
162
|
bsz: int = A.shape[0]
|
270
163
|
|
271
164
|
A = A + eig_value_buffer * torch.eye(A.shape[-1], device=A.device)
|
272
|
-
|
165
|
+
num_eig = min(A.shape[-1], num_eig)
|
273
166
|
# compute eigenvectors
|
274
167
|
if eig_solver == "svd_lowrank": # default
|
275
168
|
# only top q eigenvectors, fastest
|
nystrom_ncut/sampling_utils.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
+
import copy
|
1
2
|
from dataclasses import dataclass
|
2
|
-
from typing import Literal
|
3
|
+
from typing import Any, Literal, Tuple
|
3
4
|
|
4
5
|
import torch
|
5
6
|
from pytorch3d.ops import sample_farthest_points
|
@@ -13,6 +14,7 @@ from .distance_utils import (
|
|
13
14
|
)
|
14
15
|
from .transformer import (
|
15
16
|
TorchTransformerMixin,
|
17
|
+
OnlineTorchTransformerMixin,
|
16
18
|
)
|
17
19
|
|
18
20
|
|
@@ -21,11 +23,11 @@ SampleOptions = Literal["full", "random", "fps", "fps_recursive"]
|
|
21
23
|
|
22
24
|
@dataclass
|
23
25
|
class SampleConfig:
|
24
|
-
method: SampleOptions = "
|
26
|
+
method: SampleOptions = "full"
|
25
27
|
num_sample: int = 10000
|
26
28
|
fps_dim: int = 12
|
27
29
|
n_iter: int = None
|
28
|
-
|
30
|
+
_recursive_obj: TorchTransformerMixin = None
|
29
31
|
|
30
32
|
|
31
33
|
@torch.no_grad()
|
@@ -56,7 +58,7 @@ def subsample_features(
|
|
56
58
|
distance_type=distance_type,
|
57
59
|
config=SampleConfig(method="fps", num_sample=config.num_sample, fps_dim=config.fps_dim)
|
58
60
|
) # int: [... x num_sample]
|
59
|
-
nc = config.
|
61
|
+
nc = config._recursive_obj
|
60
62
|
for _ in range(config.n_iter):
|
61
63
|
fps_features, eigenvalues = nc.fit_transform(features, precomputed_sampled_indices=sampled_indices)
|
62
64
|
|
@@ -99,3 +101,95 @@ def fpsample(
|
|
99
101
|
sample_indices = torch.gather(order, 1, sample_indices) # int: [(...) x num_sample]
|
100
102
|
|
101
103
|
return sample_indices.view((*shape, *sample_indices.shape[-1:])) # int: [... x num_sample]
|
104
|
+
|
105
|
+
|
106
|
+
class OnlineTransformerSubsampleFit(TorchTransformerMixin, OnlineTorchTransformerMixin):
|
107
|
+
def __init__(
|
108
|
+
self,
|
109
|
+
base_transformer: OnlineTorchTransformerMixin,
|
110
|
+
distance_type: DistanceOptions,
|
111
|
+
sample_config: SampleConfig,
|
112
|
+
):
|
113
|
+
OnlineTorchTransformerMixin.__init__(self)
|
114
|
+
self.base_transformer: OnlineTorchTransformerMixin = base_transformer
|
115
|
+
self.distance_type: DistanceOptions = distance_type
|
116
|
+
self.sample_config: SampleConfig = sample_config
|
117
|
+
self.sample_config._recursive_obj = copy.deepcopy(self)
|
118
|
+
self.anchor_indices: torch.Tensor = None
|
119
|
+
|
120
|
+
def _fit_helper(
|
121
|
+
self,
|
122
|
+
features: torch.Tensor,
|
123
|
+
precomputed_sampled_indices: torch.Tensor,
|
124
|
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
125
|
+
_n = features.shape[-2]
|
126
|
+
self.sample_config.num_sample = min(self.sample_config.num_sample, _n)
|
127
|
+
|
128
|
+
if precomputed_sampled_indices is not None:
|
129
|
+
self.anchor_indices = precomputed_sampled_indices
|
130
|
+
else:
|
131
|
+
self.anchor_indices = subsample_features(
|
132
|
+
features=features,
|
133
|
+
distance_type=self.distance_type,
|
134
|
+
config=self.sample_config,
|
135
|
+
)
|
136
|
+
sampled_features = torch.gather(features, -2, self.anchor_indices[..., None].expand([-1] * self.anchor_indices.ndim + [features.shape[-1]]))
|
137
|
+
self.base_transformer.fit(sampled_features)
|
138
|
+
|
139
|
+
_n_not_sampled = _n - self.anchor_indices.shape[-1]
|
140
|
+
if _n_not_sampled > 0:
|
141
|
+
unsampled_mask = torch.full(features.shape[:-1], True, device=features.device).scatter_(-1, self.anchor_indices, False)
|
142
|
+
unsampled_indices = torch.where(unsampled_mask)[-1].view((*features.shape[:-2], -1))
|
143
|
+
unsampled_features = torch.gather(features, -2, unsampled_indices[..., None].expand([-1] * unsampled_indices.ndim + [features.shape[-1]]))
|
144
|
+
V_unsampled = self.base_transformer.update(unsampled_features)
|
145
|
+
else:
|
146
|
+
unsampled_indices = V_unsampled = None
|
147
|
+
return unsampled_indices, V_unsampled
|
148
|
+
|
149
|
+
def fit(
|
150
|
+
self,
|
151
|
+
features: torch.Tensor,
|
152
|
+
precomputed_sampled_indices: torch.Tensor = None,
|
153
|
+
) -> "OnlineTransformerSubsampleFit":
|
154
|
+
"""Fit Nystrom Normalized Cut on the input features.
|
155
|
+
Args:
|
156
|
+
features (torch.Tensor): input features, shape (n_samples, n_features)
|
157
|
+
precomputed_sampled_indices (torch.Tensor): precomputed sampled indices, shape (num_sample,)
|
158
|
+
override the sample_method, if not None
|
159
|
+
Returns:
|
160
|
+
(NCut): self
|
161
|
+
"""
|
162
|
+
self._fit_helper(features, precomputed_sampled_indices)
|
163
|
+
return self
|
164
|
+
|
165
|
+
def fit_transform(
|
166
|
+
self,
|
167
|
+
features: torch.Tensor,
|
168
|
+
precomputed_sampled_indices: torch.Tensor = None,
|
169
|
+
) -> torch.Tensor:
|
170
|
+
"""
|
171
|
+
Args:
|
172
|
+
features (torch.Tensor): input features, shape (n_samples, n_features)
|
173
|
+
precomputed_sampled_indices (torch.Tensor): precomputed sampled indices, shape (num_sample,)
|
174
|
+
override the sample_method, if not None
|
175
|
+
|
176
|
+
Returns:
|
177
|
+
(torch.Tensor): eigen_vectors, shape (n_samples, num_eig)
|
178
|
+
(torch.Tensor): eigen_values, sorted in descending order, shape (num_eig,)
|
179
|
+
"""
|
180
|
+
unsampled_indices, V_unsampled = self._fit_helper(features, precomputed_sampled_indices)
|
181
|
+
V_sampled = self.base_transformer.transform()
|
182
|
+
|
183
|
+
if unsampled_indices is not None:
|
184
|
+
V = torch.zeros((*features.shape[:-1], V_sampled.shape[-1]), device=features.device)
|
185
|
+
for (indices, _V) in [(self.anchor_indices, V_sampled), (unsampled_indices, V_unsampled)]:
|
186
|
+
V.scatter_(-2, indices[..., None].expand([-1] * indices.ndim + [V_sampled.shape[-1]]), _V)
|
187
|
+
else:
|
188
|
+
V = V_sampled
|
189
|
+
return V
|
190
|
+
|
191
|
+
def update(self, features: torch.Tensor) -> torch.Tensor:
|
192
|
+
return self.base_transformer.update(features)
|
193
|
+
|
194
|
+
def transform(self, features: torch.Tensor = None, **transform_kwargs) -> torch.Tensor:
|
195
|
+
return self.base_transformer.transform(features)
|
@@ -36,15 +36,28 @@ class TorchTransformerMixin:
|
|
36
36
|
>>> transformer.fit_transform(X)
|
37
37
|
array([1, 1, 1])
|
38
38
|
"""
|
39
|
+
@abstractmethod
|
40
|
+
def fit(self, X: torch.Tensor, **fit_kwargs: Any) -> "OnlineTorchTransformerMixin":
|
41
|
+
""""""
|
42
|
+
|
43
|
+
@abstractmethod
|
44
|
+
def transform(self, X: torch.Tensor = None, **transform_kwargs: Any) -> torch.Tensor:
|
45
|
+
""""""
|
46
|
+
|
47
|
+
@abstractmethod
|
48
|
+
def fit_transform(self, X: torch.Tensor, **fit_transform_kwargs: Any) -> torch.Tensor:
|
49
|
+
""""""
|
50
|
+
|
39
51
|
|
52
|
+
class OnlineTorchTransformerMixin:
|
40
53
|
@abstractmethod
|
41
|
-
def fit(self, X: torch.Tensor
|
54
|
+
def fit(self, X: torch.Tensor) -> "OnlineTorchTransformerMixin":
|
42
55
|
""""""
|
43
56
|
|
44
57
|
@abstractmethod
|
45
|
-
def transform(self, X: torch.Tensor
|
58
|
+
def transform(self, X: torch.Tensor = None) -> torch.Tensor:
|
46
59
|
""""""
|
47
60
|
|
48
61
|
@abstractmethod
|
49
|
-
def
|
62
|
+
def update(self, X: torch.Tensor) -> torch.Tensor:
|
50
63
|
""""""
|
nystrom_ncut/visualize_utils.py
CHANGED
@@ -17,6 +17,9 @@ from .distance_utils import (
|
|
17
17
|
to_euclidean,
|
18
18
|
affinity_from_features,
|
19
19
|
)
|
20
|
+
from .global_settings import (
|
21
|
+
CHUNK_SIZE,
|
22
|
+
)
|
20
23
|
from .sampling_utils import (
|
21
24
|
SampleConfig,
|
22
25
|
subsample_features,
|
@@ -30,7 +33,6 @@ def extrapolate_knn(
|
|
30
33
|
affinity_type: AffinityOptions,
|
31
34
|
knn: int = 10, # k
|
32
35
|
affinity_focal_gamma: float = 1.0,
|
33
|
-
chunk_size: int = 8192,
|
34
36
|
device: str = None,
|
35
37
|
move_output_to_cpu: bool = False,
|
36
38
|
) -> torch.Tensor: # [m x d']
|
@@ -42,7 +44,6 @@ def extrapolate_knn(
|
|
42
44
|
extrapolation_features (torch.Tensor): features from existing nodes, shape (new_num_samples, n_features)
|
43
45
|
knn (int): number of KNN to propagate eige nvectors
|
44
46
|
affinity_type (str): distance metric, 'cosine' (default) or 'euclidean', 'rbf'
|
45
|
-
chunk_size (int): chunk size for matrix multiplication
|
46
47
|
device (str): device to use for computation, if None, will not change device
|
47
48
|
Returns:
|
48
49
|
torch.Tensor: propagated eigenvectors, shape (new_num_samples, D)
|
@@ -61,12 +62,17 @@ def extrapolate_knn(
|
|
61
62
|
# propagate eigen_vector from subgraph to full graph
|
62
63
|
anchor_output = anchor_output.to(device)
|
63
64
|
|
64
|
-
n_chunks = ceildiv(extrapolation_features.shape[0],
|
65
|
+
n_chunks = ceildiv(extrapolation_features.shape[0], CHUNK_SIZE)
|
65
66
|
V_list = []
|
66
67
|
for _v in torch.chunk(extrapolation_features, n_chunks, dim=0):
|
67
68
|
_v = _v.to(device) # [_m x d]
|
68
69
|
|
69
|
-
_A = affinity_from_features(
|
70
|
+
_A = affinity_from_features(
|
71
|
+
features_A=anchor_features,
|
72
|
+
features_B=_v,
|
73
|
+
affinity_type=affinity_type,
|
74
|
+
affinity_focal_gamma=affinity_focal_gamma,
|
75
|
+
).mT # [_m x n]
|
70
76
|
if knn is not None:
|
71
77
|
_A, indices = _A.topk(k=knn, dim=-1, largest=True) # [_m x k], [_m x k]
|
72
78
|
_anchor_output = anchor_output[indices] # [_m x k x d]
|
@@ -93,7 +99,6 @@ def extrapolate_knn_with_subsampling(
|
|
93
99
|
affinity_type: AffinityOptions,
|
94
100
|
knn: int = 10, # k
|
95
101
|
affinity_focal_gamma: float = 1.0,
|
96
|
-
chunk_size: int = 8192,
|
97
102
|
device: str = None,
|
98
103
|
move_output_to_cpu: bool = False,
|
99
104
|
) -> torch.Tensor: # [m x d']
|
@@ -104,7 +109,6 @@ def extrapolate_knn_with_subsampling(
|
|
104
109
|
extrapolation_features (torch.Tensor): features from new nodes, shape (n_new_samples, n_features)
|
105
110
|
knn (int): number of KNN to propagate eigenvectors, default 3
|
106
111
|
sample_config (str): sample method, 'farthest' (default) or 'random'
|
107
|
-
chunk_size (int): chunk size for matrix multiplication, default 8192
|
108
112
|
device (str): device to use for computation, if None, will not change device
|
109
113
|
Returns:
|
110
114
|
torch.Tensor: propagated eigenvectors, shape (n_new_samples, num_eig)
|
@@ -138,7 +142,6 @@ def extrapolate_knn_with_subsampling(
|
|
138
142
|
affinity_type,
|
139
143
|
knn=knn,
|
140
144
|
affinity_focal_gamma=affinity_focal_gamma,
|
141
|
-
chunk_size=chunk_size,
|
142
145
|
device=device,
|
143
146
|
move_output_to_cpu=move_output_to_cpu,
|
144
147
|
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: nystrom_ncut
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.4
|
4
4
|
Summary: Normalized Cut and Nyström Approximation
|
5
5
|
Author-email: Huzheng Yang <huze.yann@gmail.com>, Wentinn Liao <wentinn.liao@gmail.com>
|
6
6
|
Project-URL: Documentation, https://github.com/JophiArcana/Nystrom-NCUT/
|
@@ -0,0 +1,21 @@
|
|
1
|
+
__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
nystrom_ncut/__init__.py,sha256=4qNyWD5s1Uvd9OpfiMV4mF-3yFCi_K2QVRJIcAOXh70,587
|
3
|
+
nystrom_ncut/common.py,sha256=eie19AHTMk6AGTxNnYq1UcFkHJVimeywAUYryXwaiHk,2428
|
4
|
+
nystrom_ncut/distance_utils.py,sha256=zMI651RlIbd6ygvIxRp6jY5Ilfu7j9WQ5FD4I1wmmeo,4198
|
5
|
+
nystrom_ncut/global_settings.py,sha256=TckHuF8geWM2ofd99jBupHD3TQdeEB583_3pdIVRU34,24
|
6
|
+
nystrom_ncut/sampling_utils.py,sha256=lPWWNcDMBIvK9MmtNkVUeokeCOH9P1Fm8TZZ4sjlCpM,9037
|
7
|
+
nystrom_ncut/visualize_utils.py,sha256=A1qmL8eNZjtvOOlyl9KIeObnpPVUGZVsCB9QBRV7n9I,22762
|
8
|
+
nystrom_ncut/kernel/__init__.py,sha256=pvJ3tFukmlNZqw8VUB_iKPY9gbchUAlNkmnRCZcp0uU,44
|
9
|
+
nystrom_ncut/kernel/kernel_ncut.py,sha256=u2oIu-SmNGn9tg-SXBJY4G9WGJ0SeGSnyZcvX8DGAE0,5276
|
10
|
+
nystrom_ncut/nystrom/__init__.py,sha256=NHse-dW4nTo9wJUEJ6G4_Gw8uAi2vP6Hxm9aeBWxmqc,49
|
11
|
+
nystrom_ncut/nystrom/distance_realization.py,sha256=kvPS-jGUn85MRJx-Dh2IZJ3IwvavRDCbXq6wh_aEBxc,5684
|
12
|
+
nystrom_ncut/nystrom/normalized_cut.py,sha256=BD1F9Wz1BXbTGC-AVwT4IGmsPp334z-7jE9CuwhpNjY,7415
|
13
|
+
nystrom_ncut/nystrom/nystrom_utils.py,sha256=5cMoF8UFgi_N-nEzbSQqVGhuep_eOn-FzErCFMqT7VM,9784
|
14
|
+
nystrom_ncut/transformer/__init__.py,sha256=2FJEG9CXavfDDdDk1i9OOGkd8uSOHMkP8LBH49nnPnM,138
|
15
|
+
nystrom_ncut/transformer/axis_align.py,sha256=j3LlAPrp8O_jQAlwZz-gu3D7n_wICEJranye-YK5wvA,4880
|
16
|
+
nystrom_ncut/transformer/transformer_mixin.py,sha256=9wYdWknnJP7jijz70lRAyDn_kpC9aWwYN7Pbt5Mf6gQ,2012
|
17
|
+
nystrom_ncut-0.3.4.dist-info/LICENSE,sha256=2bm9uFabQZ3Ykb_SaSU_uUbAj2-htc6WJQmS_65qD00,1073
|
18
|
+
nystrom_ncut-0.3.4.dist-info/METADATA,sha256=I3V55oiDNJl4hw9v7TdEb1ibmJWtU5Ife9KwJ03rJPQ,6058
|
19
|
+
nystrom_ncut-0.3.4.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
20
|
+
nystrom_ncut-0.3.4.dist-info/top_level.txt,sha256=gM8IWWHYysIRTCvCTcdS4RShOyl9pxpylgSwPUZR2XM,22
|
21
|
+
nystrom_ncut-0.3.4.dist-info/RECORD,,
|
@@ -1,18 +0,0 @@
|
|
1
|
-
__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
nystrom_ncut/__init__.py,sha256=tKq9-2QRNFetckHY77qAaKEMjMCYTYcorS2f74aNtvk,540
|
3
|
-
nystrom_ncut/common.py,sha256=eie19AHTMk6AGTxNnYq1UcFkHJVimeywAUYryXwaiHk,2428
|
4
|
-
nystrom_ncut/distance_utils.py,sha256=pJA8NcIKyS7-YDpRGOkc7mwBQQEsYVemdkHiTjyU4n8,4300
|
5
|
-
nystrom_ncut/sampling_utils.py,sha256=6lP8F6gftl4mgkavPsD7Vuk4erj4RtgILPhcj3YqLXk,4840
|
6
|
-
nystrom_ncut/visualize_utils.py,sha256=Sfi_kKpvFFzBFoJnbo-pQpH2jhs-A6tH64SV_WGoq58,22740
|
7
|
-
nystrom_ncut/nystrom/__init__.py,sha256=1aUXK87g4cXRXqNt6XkZsfyauw1-yv3sv0NmdmkWo-8,42
|
8
|
-
nystrom_ncut/nystrom/distance_realization.py,sha256=RTI1_Q8fCUGAPSbXaVuNA-2B-11CEAfy2CwKWPJj6xQ,5830
|
9
|
-
nystrom_ncut/nystrom/normalized_cut.py,sha256=cjkG8JeDmTPDK8KwfkAIqF9f1dI-D9s1muJ9WWZlUoc,7237
|
10
|
-
nystrom_ncut/nystrom/nystrom_utils.py,sha256=hksDO8uuAb9xKoA1ZafGwXDlQN_gZJn_qHscaSoO8JE,14120
|
11
|
-
nystrom_ncut/transformer/__init__.py,sha256=jjXjcNp3LrxeF6mqG9VY5k3asrqaY6bXzJz6wTpH78Q,105
|
12
|
-
nystrom_ncut/transformer/axis_align.py,sha256=j3LlAPrp8O_jQAlwZz-gu3D7n_wICEJranye-YK5wvA,4880
|
13
|
-
nystrom_ncut/transformer/transformer_mixin.py,sha256=YAjrDWTL5Hjnk9J2OsoxvtwT2N0u8IdgMSx0rRFmZzE,1653
|
14
|
-
nystrom_ncut-0.3.2.dist-info/LICENSE,sha256=2bm9uFabQZ3Ykb_SaSU_uUbAj2-htc6WJQmS_65qD00,1073
|
15
|
-
nystrom_ncut-0.3.2.dist-info/METADATA,sha256=4E42fHnXLNvbErWrwxE5K_oeOdpi5Bfabed9VF-YkV0,6058
|
16
|
-
nystrom_ncut-0.3.2.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
17
|
-
nystrom_ncut-0.3.2.dist-info/top_level.txt,sha256=gM8IWWHYysIRTCvCTcdS4RShOyl9pxpylgSwPUZR2XM,22
|
18
|
-
nystrom_ncut-0.3.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|