nystrom-ncut 0.0.9__tar.gz → 0.0.11__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (21) hide show
  1. {nystrom_ncut-0.0.9/src/nystrom_ncut.egg-info → nystrom_ncut-0.0.11}/PKG-INFO +1 -1
  2. {nystrom_ncut-0.0.9 → nystrom_ncut-0.0.11}/pyproject.toml +1 -1
  3. nystrom_ncut-0.0.11/src/__init__.py +0 -0
  4. {nystrom_ncut-0.0.9 → nystrom_ncut-0.0.11}/src/nystrom_ncut/__init__.py +4 -3
  5. nystrom_ncut-0.0.11/src/nystrom_ncut/nystrom/__init__.py +7 -0
  6. nystrom_ncut-0.0.11/src/nystrom_ncut/nystrom/distance_realization.py +141 -0
  7. nystrom_ncut-0.0.9/src/nystrom_ncut/ncut_pytorch.py → nystrom_ncut-0.0.11/src/nystrom_ncut/nystrom/normalized_cut.py +17 -114
  8. {nystrom_ncut-0.0.9/src/nystrom_ncut → nystrom_ncut-0.0.11/src/nystrom_ncut/nystrom}/nystrom.py +102 -1
  9. {nystrom_ncut-0.0.9 → nystrom_ncut-0.0.11}/src/nystrom_ncut/propagation_utils.py +1 -0
  10. {nystrom_ncut-0.0.9 → nystrom_ncut-0.0.11}/src/nystrom_ncut/visualize_utils.py +21 -22
  11. {nystrom_ncut-0.0.9 → nystrom_ncut-0.0.11/src/nystrom_ncut.egg-info}/PKG-INFO +1 -1
  12. {nystrom_ncut-0.0.9 → nystrom_ncut-0.0.11}/src/nystrom_ncut.egg-info/SOURCES.txt +5 -2
  13. {nystrom_ncut-0.0.9 → nystrom_ncut-0.0.11}/src/nystrom_ncut.egg-info/top_level.txt +1 -0
  14. {nystrom_ncut-0.0.9 → nystrom_ncut-0.0.11}/tests/test.py +3 -4
  15. {nystrom_ncut-0.0.9 → nystrom_ncut-0.0.11}/LICENSE +0 -0
  16. {nystrom_ncut-0.0.9 → nystrom_ncut-0.0.11}/MANIFEST.in +0 -0
  17. {nystrom_ncut-0.0.9 → nystrom_ncut-0.0.11}/README.md +0 -0
  18. {nystrom_ncut-0.0.9 → nystrom_ncut-0.0.11}/requirements.txt +0 -0
  19. {nystrom_ncut-0.0.9 → nystrom_ncut-0.0.11}/setup.cfg +0 -0
  20. {nystrom_ncut-0.0.9 → nystrom_ncut-0.0.11}/src/nystrom_ncut/common.py +0 -0
  21. {nystrom_ncut-0.0.9 → nystrom_ncut-0.0.11}/src/nystrom_ncut.egg-info/dependency_links.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: nystrom_ncut
3
- Version: 0.0.9
3
+ Version: 0.0.11
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/
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nystrom_ncut"
7
- version = "0.0.9"
7
+ version = "0.0.11"
8
8
  authors = [
9
9
  { name = "Huzheng Yang", email = "huze.yann@gmail.com" },
10
10
  { name = "Wentinn Liao", email = "wentinn.liao@gmail.com" },
File without changes
@@ -1,12 +1,13 @@
1
- from .ncut_pytorch import (
2
- NCUT,
1
+ from .nystrom import (
2
+ DistanceRealization,
3
+ NCut,
3
4
  axis_align,
4
5
  )
5
6
  from .propagation_utils import (
7
+ distance_from_features,
6
8
  affinity_from_features,
7
9
  extrapolate_knn_with_subsampling,
8
10
  extrapolate_knn,
9
- quantile_normalize,
10
11
  )
11
12
  from .visualize_utils import (
12
13
  rgb_from_tsne_3d,
@@ -0,0 +1,7 @@
1
+ from .distance_realization import (
2
+ DistanceRealization,
3
+ )
4
+ from .normalized_cut import (
5
+ NCut,
6
+ axis_align,
7
+ )
@@ -0,0 +1,141 @@
1
+ from typing import Tuple
2
+
3
+ import torch
4
+
5
+ from .nystrom import (
6
+ EigSolverOptions,
7
+ OnlineKernel,
8
+ OnlineNystromSubsampleFit,
9
+ solve_eig,
10
+ )
11
+ from ..common import (
12
+ DistanceOptions,
13
+ SampleOptions,
14
+ )
15
+ from ..propagation_utils import (
16
+ distance_from_features,
17
+ )
18
+
19
+
20
+ class GramKernel(OnlineKernel):
21
+ def __init__(
22
+ self,
23
+ distance: DistanceOptions,
24
+ eig_solver: EigSolverOptions,
25
+ ):
26
+ self.distance: DistanceOptions = distance
27
+ self.eig_solver: EigSolverOptions = eig_solver
28
+
29
+ # Anchor matrices
30
+ self.anchor_features: torch.Tensor = None # [n x d]
31
+ self.A: torch.Tensor = None # [n x n]
32
+ self.Ainv: torch.Tensor = None # [n x n]
33
+
34
+ # Updated matrices
35
+ self.a_r: torch.Tensor = None # [n]
36
+ self.b_r: torch.Tensor = None # [n]
37
+ self.matrix_sum: torch.Tensor = torch.zeros(()) # []
38
+ self.n_features: int = None # N
39
+
40
+ def fit(self, features: torch.Tensor) -> None:
41
+ self.anchor_features = features # [n x d]
42
+ self.A = -0.5 * distance_from_features(
43
+ self.anchor_features, # [n x d]
44
+ self.anchor_features,
45
+ distance=self.distance,
46
+ ) # [n x n]
47
+ d = features.shape[-1]
48
+ U, L = solve_eig(
49
+ self.A,
50
+ num_eig=d + 1, # d * (d + 3) // 2 + 1,
51
+ eig_solver=self.eig_solver,
52
+ ) # [n x (d + 1)], [d + 1]
53
+ self.Ainv = U @ torch.diag(1 / L) @ U.mT # [n x n]
54
+ self.a_r = torch.sum(self.A, dim=-1) # [n]
55
+ self.b_r = torch.zeros_like(self.a_r) # [n]
56
+ self.matrix_sum = torch.sum(self.a_r) # []
57
+ self.n_features = features.shape[0] # n
58
+
59
+ def update(self, features: torch.Tensor) -> torch.Tensor:
60
+ B = -0.5 * distance_from_features(
61
+ self.anchor_features, # [n x d]
62
+ features, # [m x d]
63
+ distance=self.distance,
64
+ ) # [n x m]
65
+ b_r = torch.sum(B, dim=-1) # [n]
66
+ b_c = torch.sum(B, dim=-2) # [m]
67
+ self.b_r = self.b_r + b_r # [n]
68
+ self.matrix_sum = (
69
+ torch.sum(self.a_r)
70
+ + 2 * torch.sum(self.b_r)
71
+ + self.Ainv @ self.b_r @ self.b_r
72
+ ) # []
73
+ self.n_features += features.shape[0] # N
74
+
75
+ row_sum = self.a_r + self.b_r # [n]
76
+ col_sum = b_c + B.mT @ self.Ainv @ self.b_r # [m]
77
+ shift = -(row_sum[:, None] + col_sum) / self.n_features + self.matrix_sum / (self.n_features ** 2) # [n x m]
78
+ return (B + shift).mT # [m x n]
79
+
80
+ def transform(self, features: torch.Tensor = None) -> torch.Tensor:
81
+ row_sum = self.a_r + self.b_r
82
+ if features is None:
83
+ B = self.A # [n x n]
84
+ col_sum = row_sum # [n]
85
+ else:
86
+ B = -0.5 * distance_from_features(
87
+ self.anchor_features,
88
+ features,
89
+ distance=self.distance,
90
+ )
91
+ b_c = torch.sum(B, dim=-2) # [m]
92
+ col_sum = b_c + B.mT @ self.Ainv @ self.b_r # [m]
93
+ shift = -(row_sum[:, None] + col_sum) / self.n_features + self.matrix_sum / (self.n_features ** 2) # [n x m]
94
+ return (B + shift).mT # [m x n]
95
+
96
+
97
+ class DistanceRealization(OnlineNystromSubsampleFit):
98
+ """Nystrom Distance Realization for large scale graph."""
99
+
100
+ def __init__(
101
+ self,
102
+ n_components: int = 100,
103
+ num_sample: int = 10000,
104
+ sample_method: SampleOptions = "farthest",
105
+ distance: DistanceOptions = "cosine",
106
+ eig_solver: EigSolverOptions = "svd_lowrank",
107
+ chunk_size: int = 8192,
108
+ ):
109
+ """
110
+ Args:
111
+ n_components (int): number of top eigenvectors to return
112
+ num_sample (int): number of samples for Nystrom-like approximation,
113
+ reduce only if memory is not enough, increase for better approximation
114
+ sample_method (str): subgraph sampling, ['farthest', 'random'].
115
+ farthest point sampling is recommended for better Nystrom-approximation accuracy
116
+ distance (str): distance metric for affinity matrix, ['cosine', 'euclidean', 'rbf'].
117
+ eig_solver (str): eigen decompose solver, ['svd_lowrank', 'lobpcg', 'svd', 'eigh'].
118
+ chunk_size (int): chunk size for large-scale matrix multiplication
119
+ """
120
+ OnlineNystromSubsampleFit.__init__(
121
+ self,
122
+ n_components=n_components,
123
+ kernel=GramKernel(distance, eig_solver),
124
+ num_sample=num_sample,
125
+ sample_method=sample_method,
126
+ eig_solver=eig_solver,
127
+ chunk_size=chunk_size,
128
+ )
129
+ self.distance: DistanceOptions = distance
130
+
131
+ def fit_transform(
132
+ self,
133
+ features: torch.Tensor,
134
+ precomputed_sampled_indices: torch.Tensor = None,
135
+ ) -> torch.Tensor:
136
+ V, L = OnlineNystromSubsampleFit.fit_transform(self, features, precomputed_sampled_indices)
137
+ return V * (L ** 0.5)
138
+
139
+ def transform(self, features: torch.Tensor = None) -> torch.Tensor:
140
+ V, L = OnlineNystromSubsampleFit.transform(features)
141
+ return V * (L ** 0.5)
@@ -1,22 +1,18 @@
1
- import logging
2
- from typing import Tuple
3
-
4
1
  import torch
5
2
  import torch.nn.functional as Fn
6
3
 
7
- from .common import (
8
- DistanceOptions,
9
- SampleOptions,
10
- )
11
4
  from .nystrom import (
12
5
  EigSolverOptions,
13
6
  OnlineKernel,
14
- OnlineNystrom,
7
+ OnlineNystromSubsampleFit,
15
8
  solve_eig,
16
9
  )
17
- from .propagation_utils import (
10
+ from ..common import (
11
+ DistanceOptions,
12
+ SampleOptions,
13
+ )
14
+ from ..propagation_utils import (
18
15
  affinity_from_features,
19
- run_subgraph_sampling,
20
16
  )
21
17
 
22
18
 
@@ -68,16 +64,16 @@ class LaplacianKernel(OnlineKernel):
68
64
  b_c = torch.sum(B, dim=-2) # [m]
69
65
  self.b_r = self.b_r + b_r # [n]
70
66
 
71
- rowscale = self.a_r + self.b_r # [n]
72
- colscale = b_c + B.mT @ self.Ainv @ self.b_r # [m]
73
- scale = (rowscale[:, None] * colscale) ** -0.5 # [n x m]
67
+ row_sum = self.a_r + self.b_r # [n]
68
+ col_sum = b_c + B.mT @ self.Ainv @ self.b_r # [m]
69
+ scale = (row_sum[:, None] * col_sum) ** -0.5 # [n x m]
74
70
  return (B * scale).mT # [m x n]
75
71
 
76
72
  def transform(self, features: torch.Tensor = None) -> torch.Tensor:
77
- rowscale = self.a_r + self.b_r # [n]
73
+ row_sum = self.a_r + self.b_r # [n]
78
74
  if features is None:
79
75
  B = self.A # [n x n]
80
- colscale = rowscale # [n]
76
+ col_sum = row_sum # [n]
81
77
  else:
82
78
  B = affinity_from_features(
83
79
  self.anchor_features, # [n x d]
@@ -86,12 +82,12 @@ class LaplacianKernel(OnlineKernel):
86
82
  distance=self.distance,
87
83
  ) # [n x m]
88
84
  b_c = torch.sum(B, dim=-2) # [m]
89
- colscale = b_c + B.mT @ self.Ainv @ self.b_r # [m]
90
- scale = (rowscale[:, None] * colscale) ** -0.5 # [n x m]
85
+ col_sum = b_c + B.mT @ self.Ainv @ self.b_r # [m]
86
+ scale = (row_sum[:, None] * col_sum) ** -0.5 # [n x m]
91
87
  return (B * scale).mT # [m x n]
92
88
 
93
89
 
94
- class NCUT(OnlineNystrom):
90
+ class NCut(OnlineNystromSubsampleFit):
95
91
  """Nystrom Normalized Cut for large scale graph."""
96
92
 
97
93
  def __init__(
@@ -102,7 +98,6 @@ class NCUT(OnlineNystrom):
102
98
  sample_method: SampleOptions = "farthest",
103
99
  distance: DistanceOptions = "cosine",
104
100
  eig_solver: EigSolverOptions = "svd_lowrank",
105
- normalize_features: bool = None,
106
101
  chunk_size: int = 8192,
107
102
  ):
108
103
  """
@@ -116,110 +111,18 @@ class NCUT(OnlineNystrom):
116
111
  farthest point sampling is recommended for better Nystrom-approximation accuracy
117
112
  distance (str): distance metric for affinity matrix, ['cosine', 'euclidean', 'rbf'].
118
113
  eig_solver (str): eigen decompose solver, ['svd_lowrank', 'lobpcg', 'svd', 'eigh'].
119
- normalize_features (bool): normalize input features before computing affinity matrix,
120
- default 'None' is True for cosine distance, False for euclidean distance and rbf
121
114
  chunk_size (int): chunk size for large-scale matrix multiplication
122
115
  """
123
- OnlineNystrom.__init__(
116
+ OnlineNystromSubsampleFit.__init__(
124
117
  self,
125
118
  n_components=n_components,
126
119
  kernel=LaplacianKernel(affinity_focal_gamma, distance, eig_solver),
120
+ num_sample=num_sample,
121
+ sample_method=sample_method,
127
122
  eig_solver=eig_solver,
128
123
  chunk_size=chunk_size,
129
124
  )
130
- self.num_sample: int = num_sample
131
- self.sample_method: SampleOptions = sample_method
132
- self.anchor_indices: torch.Tensor = None
133
125
  self.distance: DistanceOptions = distance
134
- self.normalize_features: bool = normalize_features
135
- if self.normalize_features is None:
136
- if distance in ["cosine"]:
137
- self.normalize_features = True
138
- if distance in ["euclidean", "rbf"]:
139
- self.normalize_features = False
140
-
141
- self.chunk_size: int = chunk_size
142
-
143
- def _fit_helper(
144
- self,
145
- features: torch.Tensor,
146
- precomputed_sampled_indices: torch.Tensor,
147
- ) -> Tuple[torch.Tensor, torch.Tensor]:
148
- _n = features.shape[0]
149
- if self.num_sample >= _n:
150
- logging.info(
151
- f"NCUT nystrom num_sample is larger than number of input samples, nyström approximation is not needed, setting num_sample={_n}"
152
- )
153
- self.num_sample = _n
154
-
155
- assert self.distance in ["cosine", "euclidean", "rbf"], "distance should be 'cosine', 'euclidean', 'rbf'"
156
-
157
- if self.normalize_features:
158
- # features need to be normalized for affinity matrix computation (cosine distance)
159
- features = torch.nn.functional.normalize(features, dim=-1)
160
-
161
- if precomputed_sampled_indices is not None:
162
- _sampled_indices = precomputed_sampled_indices
163
- else:
164
- _sampled_indices = run_subgraph_sampling(
165
- features,
166
- self.num_sample,
167
- sample_method=self.sample_method,
168
- )
169
- self.anchor_indices = torch.sort(_sampled_indices).values
170
- sampled_features = features[self.anchor_indices]
171
- OnlineNystrom.fit(self, sampled_features)
172
-
173
- _n_not_sampled = _n - len(sampled_features)
174
- if _n_not_sampled > 0:
175
- unsampled_indices = torch.full((_n,), True, device=features.device).scatter_(0, self.anchor_indices, False)
176
- unsampled_features = features[unsampled_indices]
177
- V_unsampled, _ = OnlineNystrom.update(self, unsampled_features)
178
- else:
179
- unsampled_indices = V_unsampled = None
180
- return unsampled_indices, V_unsampled
181
-
182
- def fit(
183
- self,
184
- features: torch.Tensor,
185
- precomputed_sampled_indices: torch.Tensor = None,
186
- ):
187
- """Fit Nystrom Normalized Cut on the input features.
188
- Args:
189
- features (torch.Tensor): input features, shape (n_samples, n_features)
190
- precomputed_sampled_indices (torch.Tensor): precomputed sampled indices, shape (num_sample,)
191
- override the sample_method, if not None
192
- Returns:
193
- (NCUT): self
194
- """
195
- NCUT._fit_helper(self, features, precomputed_sampled_indices)
196
- return self
197
-
198
- def fit_transform(
199
- self,
200
- features: torch.Tensor,
201
- precomputed_sampled_indices: torch.Tensor = None,
202
- ) -> Tuple[torch.Tensor, torch.Tensor]:
203
- """
204
- Args:
205
- features (torch.Tensor): input features, shape (n_samples, n_features)
206
- precomputed_sampled_indices (torch.Tensor): precomputed sampled indices, shape (num_sample,)
207
- override the sample_method, if not None
208
-
209
- Returns:
210
- (torch.Tensor): eigen_vectors, shape (n_samples, num_eig)
211
- (torch.Tensor): eigen_values, sorted in descending order, shape (num_eig,)
212
- """
213
- unsampled_indices, V_unsampled = NCUT._fit_helper(self, features, precomputed_sampled_indices)
214
- V_sampled, L = OnlineNystrom.transform(self)
215
-
216
- if unsampled_indices is not None:
217
- V = torch.zeros((len(unsampled_indices), self.n_components), device=features.device)
218
- V[~unsampled_indices] = V_sampled
219
- V[unsampled_indices] = V_unsampled
220
- else:
221
- V = V_sampled
222
- return V, L
223
126
 
224
127
 
225
128
  def axis_align(eigen_vectors: torch.Tensor, max_iter=300):
@@ -1,10 +1,15 @@
1
+ import logging
1
2
  from typing import Literal, Tuple
2
3
 
3
4
  import torch
4
5
 
5
- from .common import (
6
+ from ..common import (
7
+ SampleOptions,
6
8
  ceildiv,
7
9
  )
10
+ from ..propagation_utils import (
11
+ run_subgraph_sampling,
12
+ )
8
13
 
9
14
 
10
15
  EigSolverOptions = Literal["svd_lowrank", "lobpcg", "svd", "eigh"]
@@ -134,6 +139,102 @@ class OnlineNystrom:
134
139
  return VS, self.LS # [m x n_components], [n_components]
135
140
 
136
141
 
142
+ class OnlineNystromSubsampleFit(OnlineNystrom):
143
+ def __init__(
144
+ self,
145
+ n_components: int,
146
+ kernel: OnlineKernel,
147
+ num_sample: int,
148
+ sample_method: SampleOptions,
149
+ eig_solver: EigSolverOptions = "svd_lowrank",
150
+ chunk_size: int = 8192,
151
+ ):
152
+ OnlineNystrom.__init__(
153
+ self,
154
+ n_components=n_components,
155
+ kernel=kernel,
156
+ eig_solver=eig_solver,
157
+ chunk_size=chunk_size,
158
+ )
159
+ self.num_sample: int = num_sample
160
+ self.sample_method: SampleOptions = sample_method
161
+ self.anchor_indices: torch.Tensor = None
162
+
163
+ def _fit_helper(
164
+ self,
165
+ features: torch.Tensor,
166
+ precomputed_sampled_indices: torch.Tensor,
167
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
168
+ _n = features.shape[0]
169
+ if self.num_sample >= _n:
170
+ logging.info(
171
+ f"NCUT nystrom num_sample is larger than number of input samples, nyström approximation is not needed, setting num_sample={_n}"
172
+ )
173
+ self.num_sample = _n
174
+
175
+ if precomputed_sampled_indices is not None:
176
+ self.anchor_indices = precomputed_sampled_indices
177
+ else:
178
+ self.anchor_indices = run_subgraph_sampling(
179
+ features,
180
+ self.num_sample,
181
+ sample_method=self.sample_method,
182
+ )
183
+ sampled_features = features[self.anchor_indices]
184
+ OnlineNystrom.fit(self, sampled_features)
185
+
186
+ _n_not_sampled = _n - len(sampled_features)
187
+ if _n_not_sampled > 0:
188
+ unsampled_indices = torch.full((_n,), True, device=features.device).scatter_(0, self.anchor_indices, False)
189
+ unsampled_features = features[unsampled_indices]
190
+ V_unsampled, _ = OnlineNystrom.update(self, unsampled_features)
191
+ else:
192
+ unsampled_indices = V_unsampled = None
193
+ return unsampled_indices, V_unsampled
194
+
195
+ def fit(
196
+ self,
197
+ features: torch.Tensor,
198
+ precomputed_sampled_indices: torch.Tensor = None,
199
+ ):
200
+ """Fit Nystrom Normalized Cut on the input features.
201
+ Args:
202
+ features (torch.Tensor): input features, shape (n_samples, n_features)
203
+ precomputed_sampled_indices (torch.Tensor): precomputed sampled indices, shape (num_sample,)
204
+ override the sample_method, if not None
205
+ Returns:
206
+ (NCut): self
207
+ """
208
+ OnlineNystromSubsampleFit._fit_helper(self, features, precomputed_sampled_indices)
209
+ return self
210
+
211
+ def fit_transform(
212
+ self,
213
+ features: torch.Tensor,
214
+ precomputed_sampled_indices: torch.Tensor = None,
215
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
216
+ """
217
+ Args:
218
+ features (torch.Tensor): input features, shape (n_samples, n_features)
219
+ precomputed_sampled_indices (torch.Tensor): precomputed sampled indices, shape (num_sample,)
220
+ override the sample_method, if not None
221
+
222
+ Returns:
223
+ (torch.Tensor): eigen_vectors, shape (n_samples, num_eig)
224
+ (torch.Tensor): eigen_values, sorted in descending order, shape (num_eig,)
225
+ """
226
+ unsampled_indices, V_unsampled = OnlineNystromSubsampleFit._fit_helper(self, features, precomputed_sampled_indices)
227
+ V_sampled, L = OnlineNystrom.transform(self)
228
+
229
+ if unsampled_indices is not None:
230
+ V = torch.zeros((len(unsampled_indices), self.n_components), device=features.device)
231
+ V[~unsampled_indices] = V_sampled
232
+ V[unsampled_indices] = V_unsampled
233
+ else:
234
+ V = V_sampled
235
+ return V, L
236
+
237
+
137
238
  def solve_eig(
138
239
  A: torch.Tensor,
139
240
  num_eig: int,
@@ -47,6 +47,7 @@ def run_subgraph_sampling(
47
47
  sampled_indices = torch.randperm(features.shape[0])[:num_sample]
48
48
  else:
49
49
  raise ValueError("sample_method should be 'farthest' or 'random'")
50
+ sampled_indices = torch.sort(sampled_indices).values
50
51
  return sampled_indices.to(features.device)
51
52
 
52
53
 
@@ -11,6 +11,9 @@ from .common import (
11
11
  quantile_min_max,
12
12
  quantile_normalize,
13
13
  )
14
+ from .nystrom import (
15
+ DistanceRealization,
16
+ )
14
17
  from .propagation_utils import (
15
18
  run_subgraph_sampling,
16
19
  extrapolate_knn,
@@ -36,12 +39,18 @@ def _rgb_with_dimensionality_reduction(
36
39
  device: str,
37
40
  ) -> Tuple[torch.Tensor, torch.Tensor]:
38
41
 
39
- features = extrapolate_knn(
40
- features,
41
- features,
42
- features,
43
- distance="cosine",
44
- )
42
+ if True:
43
+ _subgraph_indices = run_subgraph_sampling(
44
+ features,
45
+ num_sample=10000,
46
+ sample_method="farthest",
47
+ )
48
+ features = extrapolate_knn(
49
+ features[_subgraph_indices],
50
+ features[_subgraph_indices],
51
+ features,
52
+ distance="cosine",
53
+ )
45
54
 
46
55
  subgraph_indices = run_subgraph_sampling(
47
56
  features,
@@ -186,18 +195,9 @@ def rgb_from_cosine_tsne_3d(
186
195
  )
187
196
  perplexity = num_sample // 2
188
197
 
189
-
190
198
  def cosine_to_rbf(X: torch.Tensor) -> torch.Tensor: # [B... x N x 3]
191
- normalized_X = X / torch.norm(X, p=2, dim=-1, keepdim=True) # [B... x N x 3]
192
- D = 1 - normalized_X @ normalized_X.mT # [B... x N x N]
193
-
194
- G = (D[..., :1, 1:] ** 2 + D[..., 1:, :1] ** 2 - D[..., 1:, 1:] ** 2) / 2 # [B... x (N - 1) x (N - 1)]
195
- L, V = torch.linalg.eigh(G) # [B... x (N - 1)], [B... x (N - 1) x (N - 1)]
196
- sqrtG = V[..., -3:] * (L[..., None, -3:] ** 0.5) # [B... x (N - 1) x 3]
197
-
198
- Y = torch.cat((torch.zeros_like(sqrtG[..., :1, :]), sqrtG), dim=-2) # [B... x N x 3]
199
- Y = Y - torch.mean(Y, dim=-2, keepdim=True)
200
- return Y
199
+ dr = DistanceRealization(n_components=3, num_sample=20000, distance="cosine", eig_solver="lobpcg")
200
+ return dr.fit_transform(X)
201
201
 
202
202
  def rgb_from_cosine(X_3d: torch.Tensor, q: float) -> torch.Tensor:
203
203
  return rgb_from_3d_rgb_cube(cosine_to_rbf(X_3d), q=q)
@@ -373,7 +373,6 @@ def rotate_rgb_cube(rgb, position=1):
373
373
 
374
374
  def rgb_from_3d_rgb_cube(X_3d, q=0.95):
375
375
  """convert 3D t-SNE to RGB color space
376
-
377
376
  Args:
378
377
  X_3d (torch.Tensor): 3D t-SNE embedding, shape (n_samples, 3)
379
378
  q (float): quantile, default 0.95
@@ -383,10 +382,10 @@ def rgb_from_3d_rgb_cube(X_3d, q=0.95):
383
382
  """
384
383
  assert X_3d.shape[1] == 3, "input should be (n_samples, 3)"
385
384
  assert len(X_3d.shape) == 2, "input should be (n_samples, 3)"
386
- rgb = []
387
- for i in range(3):
388
- rgb.append(quantile_normalize(X_3d[:, i], q=q))
389
- rgb = torch.stack(rgb, dim=-1)
385
+ rgb = torch.stack([
386
+ quantile_normalize(x, q=q)
387
+ for x in torch.unbind(X_3d, dim=1)
388
+ ], dim=-1)
390
389
  return rgb
391
390
 
392
391
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: nystrom_ncut
3
- Version: 0.0.9
3
+ Version: 0.0.11
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/
@@ -3,14 +3,17 @@ MANIFEST.in
3
3
  README.md
4
4
  pyproject.toml
5
5
  requirements.txt
6
+ src/__init__.py
6
7
  src/nystrom_ncut/__init__.py
7
8
  src/nystrom_ncut/common.py
8
- src/nystrom_ncut/ncut_pytorch.py
9
- src/nystrom_ncut/nystrom.py
10
9
  src/nystrom_ncut/propagation_utils.py
11
10
  src/nystrom_ncut/visualize_utils.py
12
11
  src/nystrom_ncut.egg-info/PKG-INFO
13
12
  src/nystrom_ncut.egg-info/SOURCES.txt
14
13
  src/nystrom_ncut.egg-info/dependency_links.txt
15
14
  src/nystrom_ncut.egg-info/top_level.txt
15
+ src/nystrom_ncut/nystrom/__init__.py
16
+ src/nystrom_ncut/nystrom/distance_realization.py
17
+ src/nystrom_ncut/nystrom/normalized_cut.py
18
+ src/nystrom_ncut/nystrom/nystrom.py
16
19
  tests/test.py
@@ -1,10 +1,9 @@
1
1
  import numpy as np
2
2
  import torch
3
- import torch.nn.functional as Fn
4
3
  from matplotlib import pyplot as plt
5
4
 
6
- from src.nystrom_ncut.ncut_pytorch import NCUT, axis_align, affinity_from_features
7
- from ncut_pytorch import NCUT as OldNCUT
5
+ from src.nystrom_ncut import NCut, affinity_from_features
6
+
8
7
  # from ncut_pytorch.src import rgb_from_umap_sphere
9
8
  # from ncut_pytorch.src.new_ncut_pytorch import NewNCUT
10
9
 
@@ -73,7 +72,7 @@ if __name__ == "__main__":
73
72
  def print_re(re):
74
73
  print(f"max: {re.max().item()}, mean: {re.mean().item()}, min: {re.min().item()}")
75
74
 
76
- nc0 = NCUT(n_components=n_components, num_sample=num_sample, distance=distance, eig_solver=eig_solver)
75
+ nc0 = NCut(n_components=n_components, num_sample=num_sample, distance=distance, eig_solver=eig_solver)
77
76
  X0, eigs0 = nc0.fit_transform(M)
78
77
 
79
78
  re0 = rel_error(X0, eigs0)
File without changes
File without changes
File without changes
File without changes