nystrom-ncut 0.0.1__tar.gz → 0.0.2__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nystrom_ncut
3
- Version: 0.0.1
3
+ Version: 0.0.2
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.1"
7
+ version = "0.0.2"
8
8
  authors = [
9
9
  { name = "Huzheng Yang", email = "huze.yann@gmail.com" },
10
10
  { name = "Wentinn Liao", email = "wentinn.liao@gmail.com" },
@@ -3,4 +3,5 @@ scikit-learn
3
3
  umap-learn
4
4
  fpsample>=0.3.2
5
5
  pycolormap-2d
6
- tqdm
6
+ tqdm
7
+ torch
@@ -1,4 +1,7 @@
1
- from .ncut_pytorch import NCUT
1
+ from .ncut_pytorch import (
2
+ NCUT,
3
+ axis_align,
4
+ )
2
5
  from .propagation_utils import (
3
6
  affinity_from_features,
4
7
  propagate_eigenvectors,
@@ -6,7 +9,6 @@ from .propagation_utils import (
6
9
  quantile_normalize,
7
10
  )
8
11
  from .visualize_utils import (
9
- eigenvector_to_rgb,
10
12
  rgb_from_tsne_3d,
11
13
  rgb_from_umap_sphere,
12
14
  rgb_from_tsne_2d,
@@ -18,5 +20,3 @@ from .visualize_utils import (
18
20
  propagate_rgb_color,
19
21
  get_mask,
20
22
  )
21
- from .ncut_pytorch import nystrom_ncut, ncut
22
- from .ncut_pytorch import kway_ncut, axis_align
@@ -0,0 +1,20 @@
1
+ from typing import Any
2
+
3
+ import numpy as np
4
+ import torch
5
+ import torch.nn.functional as Fn
6
+
7
+
8
+ def ceildiv(a: int, b: int) -> int:
9
+ return -(-a // b)
10
+
11
+
12
+ def lazy_normalize(x: torch.Tensor, n: int = 1000, **normalize_kwargs: Any) -> torch.Tensor:
13
+ numel = np.prod(x.shape[:-1])
14
+ n = min(n, numel)
15
+ random_indices = torch.randperm(numel)[:n]
16
+ _x = x.flatten(0, -2)[random_indices]
17
+ if torch.allclose(torch.norm(_x, **normalize_kwargs), torch.ones(n, device=x.device)):
18
+ return x
19
+ else:
20
+ return Fn.normalize(x, **normalize_kwargs)
@@ -2,6 +2,7 @@ import logging
2
2
  from typing import Literal, Tuple
3
3
 
4
4
  import torch
5
+ import torch.nn.functional as Fn
5
6
 
6
7
  from .nystrom import (
7
8
  EigSolverOptions,
@@ -44,7 +45,6 @@ class LaplacianKernel(OnlineKernel):
44
45
  self.anchor_features, # [n x d]
45
46
  affinity_focal_gamma=self.affinity_focal_gamma,
46
47
  distance=self.distance,
47
- fill_diagonal=False,
48
48
  ) # [n x n]
49
49
  U, L = solve_eig(
50
50
  self.A,
@@ -61,7 +61,6 @@ class LaplacianKernel(OnlineKernel):
61
61
  features, # [m x d]
62
62
  affinity_focal_gamma=self.affinity_focal_gamma,
63
63
  distance=self.distance,
64
- fill_diagonal=False,
65
64
  ) # [n x m]
66
65
  b_r = torch.sum(B, dim=-1) # [n]
67
66
  b_c = torch.sum(B, dim=-2) # [m]
@@ -83,7 +82,6 @@ class LaplacianKernel(OnlineKernel):
83
82
  features, # [m x d]
84
83
  affinity_focal_gamma=self.affinity_focal_gamma,
85
84
  distance=self.distance,
86
- fill_diagonal=False,
87
85
  ) # [n x m]
88
86
  b_c = torch.sum(B, dim=-2) # [m]
89
87
  colscale = b_c + B.mT @ self.Ainv @ self.b_r # [m]
@@ -91,7 +89,7 @@ class LaplacianKernel(OnlineKernel):
91
89
  return (B * scale).mT # [m x n]
92
90
 
93
91
 
94
- class NewNCUT(OnlineNystrom):
92
+ class NCUT(OnlineNystrom):
95
93
  """Nystrom Normalized Cut for large scale graph."""
96
94
 
97
95
  def __init__(
@@ -211,7 +209,7 @@ class NewNCUT(OnlineNystrom):
211
209
  Returns:
212
210
  (NCUT): self
213
211
  """
214
- NewNCUT._fit_helper(self, features, precomputed_sampled_indices)
212
+ NCUT._fit_helper(self, features, precomputed_sampled_indices)
215
213
  return self
216
214
 
217
215
  def fit_transform(
@@ -229,7 +227,7 @@ class NewNCUT(OnlineNystrom):
229
227
  (torch.Tensor): eigen_vectors, shape (n_samples, num_eig)
230
228
  (torch.Tensor): eigen_values, sorted in descending order, shape (num_eig,)
231
229
  """
232
- unsampled_indices, V_unsampled = NewNCUT._fit_helper(self, features, precomputed_sampled_indices)
230
+ unsampled_indices, V_unsampled = NCUT._fit_helper(self, features, precomputed_sampled_indices)
233
231
  V_sampled, L = OnlineNystrom.transform(self)
234
232
 
235
233
  if unsampled_indices is not None:
@@ -239,3 +237,50 @@ class NewNCUT(OnlineNystrom):
239
237
  else:
240
238
  V = V_sampled
241
239
  return V, L
240
+
241
+
242
+ def axis_align(eigen_vectors: torch.Tensor, max_iter=300):
243
+ """Multiclass Spectral Clustering, SX Yu, J Shi, 2003
244
+
245
+ Args:
246
+ eigen_vectors (torch.Tensor): continuous eigenvectors from NCUT, shape (n, k)
247
+ max_iter (int, optional): Maximum number of iterations.
248
+
249
+ Returns:
250
+ torch.Tensor: Discretized eigenvectors, shape (n, k), each row is a one-hot vector.
251
+ """
252
+ # Normalize eigenvectors
253
+ n, k = eigen_vectors.shape
254
+ eigen_vectors = Fn.normalize(eigen_vectors, p=2, dim=-1)
255
+
256
+ # Initialize R matrix with the first column from a random row of EigenVectors
257
+ R = torch.empty((k, k), device=eigen_vectors.device)
258
+ R[0] = eigen_vectors[torch.randint(0, n, (1,))].squeeze()
259
+
260
+ # Loop to populate R with k orthogonal directions
261
+ c = torch.zeros(n, device=eigen_vectors.device)
262
+ for i in range(1, k):
263
+ c += torch.abs(eigen_vectors @ R[i - 1])
264
+ R[i] = eigen_vectors[torch.argmin(c, dim=0)]
265
+
266
+ # Iterative optimization loop
267
+ eps = torch.finfo(torch.float32).eps
268
+ prev_objective = torch.inf
269
+ for _ in range(max_iter):
270
+ # Discretize the projected eigenvectors
271
+ idx = torch.argmax(eigen_vectors @ R.mT, dim=-1)
272
+ M = torch.zeros((k, k)).index_add_(0, idx, eigen_vectors)
273
+
274
+ # Compute the NCut value
275
+ objective = torch.norm(M)
276
+
277
+ # Check for convergence
278
+ if torch.abs(objective - prev_objective) < eps:
279
+ break
280
+ prev_objective = objective
281
+
282
+ # SVD decomposition
283
+ U, S, Vh = torch.linalg.svd(M, full_matrices=False)
284
+ R = U @ Vh
285
+
286
+ return Fn.one_hot(idx, num_classes=k).to(torch.float), R
@@ -2,6 +2,8 @@ from typing import Literal, Tuple
2
2
 
3
3
  import torch
4
4
 
5
+ from .common import ceildiv
6
+
5
7
 
6
8
  EigSolverOptions = Literal["svd_lowrank", "lobpcg", "svd", "eigh"]
7
9
 
@@ -75,7 +77,7 @@ class OnlineNystrom:
75
77
  return U[:, :self.n_components], L[:self.n_components] # [n x n_components], [n_components]
76
78
 
77
79
  def update(self, features: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
78
- n_chunks = -(-len(features) // self.chunk_size)
80
+ n_chunks = ceildiv(len(features), self.chunk_size)
79
81
  if n_chunks > 1:
80
82
  """ Chunked version """
81
83
  chunks = torch.chunk(features, n_chunks, dim=0)
@@ -111,7 +113,7 @@ class OnlineNystrom:
111
113
  if features is None:
112
114
  VS = self.A @ self.transform_matrix # [n x n_components]
113
115
  else:
114
- n_chunks = -(-len(features) // self.chunk_size)
116
+ n_chunks = ceildiv(len(features), self.chunk_size)
115
117
  if n_chunks > 1:
116
118
  """ Chunked version """
117
119
  chunks = torch.chunk(features, n_chunks, dim=0)
@@ -1,11 +1,12 @@
1
1
  import logging
2
- import math
3
2
  from typing import Literal
4
3
 
5
4
  import numpy as np
6
5
  import torch
7
6
  import torch.nn.functional as F
8
7
 
8
+ from .common import ceildiv, lazy_normalize
9
+
9
10
 
10
11
  @torch.no_grad()
11
12
  def run_subgraph_sampling(
@@ -60,14 +61,12 @@ def farthest_point_sampling(
60
61
  # PCA to reduce the dimension
61
62
  if features.shape[1] > 8:
62
63
  u, s, v = torch.pca_lowrank(features, q=8)
63
- _n = features.shape[0]
64
- s /= math.sqrt(_n)
65
64
  features = u @ torch.diag(s)
66
65
 
67
66
  h = min(h, int(np.log2(features.shape[0])))
68
67
 
69
68
  kdline_fps_samples_idx = fpsample.bucket_fps_kdline_sampling(
70
- features.cpu().numpy(), num_sample, h
69
+ features.numpy(force=True), num_sample, h
71
70
  ).astype(np.int64)
72
71
  return torch.from_numpy(kdline_fps_samples_idx)
73
72
 
@@ -76,26 +75,19 @@ def distance_from_features(
76
75
  features: torch.Tensor,
77
76
  features_B: torch.Tensor,
78
77
  distance: Literal["cosine", "euclidean", "rbf"],
79
- fill_diagonal: bool,
80
78
  ):
81
79
  """Compute affinity matrix from input features.
82
80
  Args:
83
81
  features (torch.Tensor): input features, shape (n_samples, n_features)
84
82
  features_B (torch.Tensor, optional): optional, if not None, compute affinity between two features
85
- affinity_focal_gamma (float): affinity matrix parameter, lower t reduce the edge weights
86
- on weak connections, default 1.0
87
83
  distance (str): distance metric, 'cosine' (default) or 'euclidean', 'rbf'.
88
- normalize_features (bool): normalize input features before computing affinity matrix
89
-
90
84
  Returns:
91
85
  (torch.Tensor): affinity matrix, shape (n_samples, n_samples)
92
86
  """
93
87
  # compute distance matrix from input features
94
88
  if distance == "cosine":
95
- if not check_if_normalized(features):
96
- features = F.normalize(features, dim=-1)
97
- if not check_if_normalized(features_B):
98
- features_B = F.normalize(features_B, dim=-1)
89
+ features = lazy_normalize(features, dim=-1)
90
+ features_B = lazy_normalize(features_B, dim=-1)
99
91
  D = 1 - features @ features_B.T
100
92
  elif distance == "euclidean":
101
93
  D = torch.cdist(features, features_B, p=2)
@@ -105,8 +97,6 @@ def distance_from_features(
105
97
  else:
106
98
  raise ValueError("distance should be 'cosine' or 'euclidean', 'rbf'")
107
99
 
108
- if fill_diagonal:
109
- D[torch.arange(D.shape[0]), torch.arange(D.shape[0])] = 0
110
100
  return D
111
101
 
112
102
 
@@ -115,7 +105,6 @@ def affinity_from_features(
115
105
  features_B: torch.Tensor = None,
116
106
  affinity_focal_gamma: float = 1.0,
117
107
  distance: Literal["cosine", "euclidean", "rbf"] = "cosine",
118
- fill_diagonal: bool = True,
119
108
  ):
120
109
  """Compute affinity matrix from input features.
121
110
 
@@ -125,8 +114,6 @@ def affinity_from_features(
125
114
  affinity_focal_gamma (float): affinity matrix parameter, lower t reduce the edge weights
126
115
  on weak connections, default 1.0
127
116
  distance (str): distance metric, 'cosine' (default) or 'euclidean', 'rbf'.
128
- normalize_features (bool): normalize input features before computing affinity matrix
129
-
130
117
  Returns:
131
118
  (torch.Tensor): affinity matrix, shape (n_samples, n_samples)
132
119
  """
@@ -134,12 +121,10 @@ def affinity_from_features(
134
121
 
135
122
  # if feature_B is not provided, compute affinity matrix on features x features
136
123
  # if feature_B is provided, compute affinity matrix on features x feature_B
137
- if features_B is not None:
138
- assert not fill_diagonal, "fill_diagonal should be False when feature_B is None"
139
124
  features_B = features if features_B is None else features_B
140
125
 
141
126
  # compute distance matrix from input features
142
- D = distance_from_features(features, features_B, distance, fill_diagonal)
127
+ D = distance_from_features(features, features_B, distance)
143
128
 
144
129
  # torch.exp make affinity matrix positive definite,
145
130
  # lower affinity_focal_gamma reduce the weak edge weights
@@ -156,7 +141,6 @@ def propagate_knn(
156
141
  affinity_focal_gamma: float = 1.0,
157
142
  chunk_size: int = 8096,
158
143
  device: str = None,
159
- use_tqdm: bool = False,
160
144
  move_output_to_cpu: bool = False,
161
145
  ):
162
146
  """A generic function to propagate new nodes using KNN.
@@ -169,8 +153,6 @@ def propagate_knn(
169
153
  distance (str): distance metric, 'cosine' (default) or 'euclidean', 'rbf'
170
154
  chunk_size (int): chunk size for matrix multiplication
171
155
  device (str): device to use for computation, if None, will not change device
172
- use_tqdm (bool): show progress bar when propagating eigenvectors from subgraph to full graph
173
-
174
156
  Returns:
175
157
  torch.Tensor: propagated eigenvectors, shape (new_num_samples, D)
176
158
 
@@ -197,24 +179,16 @@ def propagate_knn(
197
179
  # used in nystrom_ncut
198
180
  # propagate eigen_vector from subgraph to full graph
199
181
  subgraph_output = subgraph_output.to(device)
200
- V_list = []
201
- iterator = range(0, inp_features.shape[0], chunk_size)
202
- try:
203
- assert use_tqdm
204
- from tqdm import tqdm
205
- iterator = tqdm(iterator, "propagate by KNN")
206
- except (AssertionError, ImportError):
207
- pass
208
182
 
209
- subgraph_features = subgraph_features.to(device)
210
- for i in iterator:
211
- end = min(i + chunk_size, inp_features.shape[0])
212
- _v = inp_features[i:end].to(device)
213
- _A = affinity_from_features(subgraph_features, _v, affinity_focal_gamma, distance, False).mT
183
+ n_chunks = ceildiv(inp_features.shape[0], chunk_size)
184
+ V_list = []
185
+ for _v in torch.chunk(inp_features, n_chunks, dim=0):
186
+ _v = _v.to(device)
187
+ _A = affinity_from_features(subgraph_features, _v, affinity_focal_gamma, distance).mT
214
188
 
215
189
  if knn is not None:
216
190
  mask = torch.full_like(_A, True, dtype=torch.bool)
217
- mask[torch.arange(end - i)[:, None], _A.topk(knn, dim=-1, largest=True).indices] = False
191
+ mask[torch.arange(len(_v))[:, None], _A.topk(knn, dim=-1, largest=True).indices] = False
218
192
  _A[mask] = 0.0
219
193
  _A = F.normalize(_A, p=1, dim=-1)
220
194
 
@@ -238,10 +212,8 @@ def propagate_nearest(
238
212
  ):
239
213
  device = subgraph_output.device if device is None else device
240
214
  if distance == 'cosine':
241
- if not check_if_normalized(inp_features):
242
- inp_features = F.normalize(inp_features, dim=-1)
243
- if not check_if_normalized(subgraph_features):
244
- subgraph_features = F.normalize(subgraph_features, dim=-1)
215
+ inp_features = lazy_normalize(inp_features, dim=-1)
216
+ subgraph_features = lazy_normalize(subgraph_features, dim=-1)
245
217
 
246
218
  # used in nystrom_tsne, equivalent to propagate_by_knn with knn=1
247
219
  # propagate tSNE from subgraph to full graph
@@ -250,7 +222,7 @@ def propagate_nearest(
250
222
  for i in range(0, inp_features.shape[0], chunk_size):
251
223
  end = min(i + chunk_size, inp_features.shape[0])
252
224
  _v = inp_features[i:end].to(device)
253
- _A = -distance_from_features(subgraph_features, _v, distance, False).mT
225
+ _A = -distance_from_features(subgraph_features, _v, distance).mT
254
226
 
255
227
  # keep top1 for each row
256
228
  top_idx = _A.argmax(dim=-1).cpu()
@@ -273,7 +245,6 @@ def propagate_eigenvectors(
273
245
  sample_method: Literal["farthest", "random"],
274
246
  chunk_size: int,
275
247
  device: str,
276
- use_tqdm: bool,
277
248
  ):
278
249
  """Propagate eigenvectors to new nodes using KNN. Note: this is equivalent to the class API `NCUT.tranform(new_features)`, expect for the sampling is re-done in this function.
279
250
  Args:
@@ -285,8 +256,6 @@ def propagate_eigenvectors(
285
256
  sample_method (str): sample method, 'farthest' (default) or 'random'
286
257
  chunk_size (int): chunk size for matrix multiplication, default 8096
287
258
  device (str): device to use for computation, if None, will not change device
288
- use_tqdm (bool): show progress bar when propagating eigenvectors from subgraph to full graph
289
-
290
259
  Returns:
291
260
  torch.Tensor: propagated eigenvectors, shape (n_new_samples, num_eig)
292
261
 
@@ -319,21 +288,10 @@ def propagate_eigenvectors(
319
288
  knn=knn,
320
289
  chunk_size=chunk_size,
321
290
  device=device,
322
- use_tqdm=use_tqdm,
323
291
  )
324
-
325
292
  return new_eigenvectors
326
293
 
327
294
 
328
- def check_if_normalized(x, n=1000):
329
- """check if the input tensor is normalized (unit norm)"""
330
- n = min(n, x.shape[0])
331
- random_indices = torch.randperm(x.shape[0])[:n]
332
- _x = x[random_indices]
333
- flag = torch.allclose(torch.norm(_x, dim=-1), torch.ones(n, device=x.device))
334
- return flag
335
-
336
-
337
295
  def quantile_min_max(x, q1=0.01, q2=0.99, n_sample=10000):
338
296
  if x.shape[0] > n_sample:
339
297
  np.random.seed(0)
@@ -6,11 +6,11 @@ import torch
6
6
  import torch.nn.functional as F
7
7
  from sklearn.base import BaseEstimator
8
8
 
9
+ from .common import lazy_normalize
9
10
  from .propagation_utils import (
10
11
  run_subgraph_sampling,
11
12
  propagate_knn,
12
13
  propagate_eigenvectors,
13
- check_if_normalized,
14
14
  quantile_min_max,
15
15
  quantile_normalize
16
16
  )
@@ -20,75 +20,6 @@ def _identity(X: torch.Tensor) -> torch.Tensor:
20
20
  return X
21
21
 
22
22
 
23
- def eigenvector_to_rgb(
24
- eigen_vector: torch.Tensor,
25
- method: Literal["tsne_2d", "tsne_3d", "umap_sphere", "umap_2d", "umap_3d"] = "tsne_3d",
26
- num_sample: int = 1000,
27
- perplexity: int = 150,
28
- n_neighbors: int = 150,
29
- min_distance: float = 0.1,
30
- metric: Literal["cosine", "euclidean"] = "cosine",
31
- device: str = None,
32
- q: float = 0.95,
33
- knn: int = 10,
34
- seed: int = 0,
35
- ):
36
- """Use t-SNE or UMAP to convert eigenvectors (more than 3) to RGB color (3D RGB CUBE).
37
-
38
- Args:
39
- eigen_vector (torch.Tensor): eigenvectors, shape (n_samples, num_eig)
40
- method (str): method to convert eigenvectors to RGB,
41
- choices are: ['tsne_2d', 'tsne_3d', 'umap_sphere', 'umap_2d', 'umap_3d']
42
- num_sample (int): number of samples for Nystrom-like approximation, increase for better approximation
43
- perplexity (int): perplexity for t-SNE, increase for more global structure
44
- n_neighbors (int): number of neighbors for UMAP, increase for more global structure
45
- min_distance (float): minimum distance for UMAP
46
- metric (str): distance metric, default 'cosine'
47
- device (str): device to use for computation, if None, will not change device
48
- q (float): quantile for RGB normalization, default 0.95. lower q results in more sharp colors
49
- knn (int): number of KNN for propagating eigenvectors from subgraph to full graph,
50
- smaller knn result in more sharp colors, default 1. knn>1 will smooth-out the embedding
51
- in the t-SNE or UMAP space.
52
- seed (int): random seed for t-SNE or UMAP
53
-
54
- Examples:
55
- >>> from ncut_pytorch import eigenvector_to_rgb
56
- >>> X_3d, rgb = eigenvector_to_rgb(eigenvectors, method='tsne_3d')
57
- >>> print(X_3d.shape, rgb.shape)
58
- >>> # (10000, 3) (10000, 3)
59
-
60
- Returns:
61
- (torch.Tensor): t-SNE or UMAP embedding, shape (n_samples, 2) or (n_samples, 3)
62
- (torch.Tensor): RGB color for each data sample, shape (n_samples, 3)
63
- """
64
- kwargs = {
65
- "num_sample": num_sample,
66
- "perplexity": perplexity,
67
- "n_neighbors": n_neighbors,
68
- "min_distance": min_distance,
69
- "metric": metric,
70
- "device": device,
71
- "q": q,
72
- "knn": knn,
73
- "seed": seed,
74
- }
75
-
76
- if method == "tsne_2d":
77
- embed, rgb = rgb_from_tsne_2d(eigen_vector, **kwargs)
78
- elif method == "tsne_3d":
79
- embed, rgb = rgb_from_tsne_3d(eigen_vector, **kwargs)
80
- elif method == "umap_sphere":
81
- embed, rgb = rgb_from_umap_sphere(eigen_vector, **kwargs)
82
- elif method == "umap_2d":
83
- embed, rgb = rgb_from_umap_2d(eigen_vector, **kwargs)
84
- elif method == "umap_3d":
85
- embed, rgb = rgb_from_umap_3d(eigen_vector, **kwargs)
86
- else:
87
- raise ValueError("method should be 'tsne_2d', 'tsne_3d' or 'umap_sphere'")
88
-
89
- return embed, rgb
90
-
91
-
92
23
  def _rgb_with_dimensionality_reduction(
93
24
  features: torch.Tensor,
94
25
  num_sample: int,
@@ -126,7 +57,7 @@ def _rgb_with_dimensionality_reduction(
126
57
  move_output_to_cpu=True,
127
58
  ))
128
59
  rgb = rgb_func(X_nd, q)
129
- return X_nd.numpy(force=True), rgb
60
+ return X_nd, rgb
130
61
 
131
62
 
132
63
  def rgb_from_tsne_2d(
@@ -138,7 +69,6 @@ def rgb_from_tsne_2d(
138
69
  seed: int = 0,
139
70
  q: float = 0.95,
140
71
  knn: int = 10,
141
- **kwargs: Any,
142
72
  ):
143
73
  """
144
74
  Returns:
@@ -169,7 +99,6 @@ def rgb_from_tsne_2d(
169
99
  "perplexity": perplexity,
170
100
  },
171
101
  )
172
-
173
102
  return x2d, rgb
174
103
 
175
104
 
@@ -182,7 +111,6 @@ def rgb_from_tsne_3d(
182
111
  seed: int = 0,
183
112
  q: float = 0.95,
184
113
  knn: int = 10,
185
- **kwargs: Any,
186
114
  ):
187
115
  """
188
116
  Returns:
@@ -213,7 +141,6 @@ def rgb_from_tsne_3d(
213
141
  "perplexity": perplexity,
214
142
  },
215
143
  )
216
-
217
144
  return x3d, rgb
218
145
 
219
146
 
@@ -225,7 +152,6 @@ def rgb_from_cosine_tsne_3d(
225
152
  seed: int = 0,
226
153
  q: float = 0.95,
227
154
  knn: int = 10,
228
- **kwargs: Any,
229
155
  ):
230
156
  """
231
157
  Returns:
@@ -272,7 +198,6 @@ def rgb_from_cosine_tsne_3d(
272
198
  "perplexity": perplexity,
273
199
  },
274
200
  )
275
-
276
201
  return x3d, rgb
277
202
 
278
203
 
@@ -286,7 +211,6 @@ def rgb_from_umap_2d(
286
211
  seed: int = 0,
287
212
  q: float = 0.95,
288
213
  knn: int = 10,
289
- **kwargs: Any,
290
214
  ):
291
215
  """
292
216
  Returns:
@@ -310,7 +234,6 @@ def rgb_from_umap_2d(
310
234
  "min_dist": min_dist,
311
235
  },
312
236
  )
313
-
314
237
  return x2d, rgb
315
238
 
316
239
 
@@ -324,7 +247,6 @@ def rgb_from_umap_sphere(
324
247
  seed: int = 0,
325
248
  q: float = 0.95,
326
249
  knn: int = 10,
327
- **kwargs: Any,
328
250
  ):
329
251
  """
330
252
  Returns:
@@ -357,7 +279,6 @@ def rgb_from_umap_sphere(
357
279
  },
358
280
  transform_func=transform_func
359
281
  )
360
-
361
282
  return x3d, rgb
362
283
 
363
284
 
@@ -371,7 +292,6 @@ def rgb_from_umap_3d(
371
292
  seed: int = 0,
372
293
  q: float = 0.95,
373
294
  knn: int = 10,
374
- **kwargs: Any,
375
295
  ):
376
296
  """
377
297
  Returns:
@@ -395,7 +315,6 @@ def rgb_from_umap_3d(
395
315
  "min_dist": min_dist,
396
316
  },
397
317
  )
398
-
399
318
  return x3d, rgb
400
319
 
401
320
 
@@ -417,13 +336,11 @@ def rotate_rgb_cube(rgb, position=1):
417
336
  torch.Tensor: RGB color space, shape (n_samples, 3)
418
337
  """
419
338
  assert position in range(0, 7), "position should be 0, 1, 2, 3, 4, 5, 6"
420
- rotation_matrix = torch.tensor(
421
- [
422
- [0, 1, 0],
423
- [0, 0, 1],
424
- [1, 0, 0],
425
- ]
426
- ).float()
339
+ rotation_matrix = torch.tensor((
340
+ (0., 1., 0.),
341
+ (0., 0., 1.),
342
+ (1., 0., 0.),
343
+ ))
427
344
  n_mul = position % 3
428
345
  rotation_matrix = torch.matrix_power(rotation_matrix, n_mul)
429
346
  rgb = rgb @ rotation_matrix
@@ -505,7 +422,6 @@ def propagate_rgb_color(
505
422
  sample_method: Literal["farthest", "random"] = "farthest",
506
423
  chunk_size: int = 8096,
507
424
  device: str = None,
508
- use_tqdm: bool = False,
509
425
  ):
510
426
  """Propagate RGB color to new nodes using KNN.
511
427
  Args:
@@ -517,8 +433,6 @@ def propagate_rgb_color(
517
433
  sample_method (str): sample method, 'farthest' (default) or 'random'
518
434
  chunk_size (int): chunk size for matrix multiplication, default 8096
519
435
  device (str): device to use for computation, if None, will not change device
520
- use_tqdm (bool): show progress bar when propagating RGB color from subgraph to full graph
521
-
522
436
  Returns:
523
437
  torch.Tensor: propagated RGB color for each data sample, shape (n_new_samples, 3)
524
438
 
@@ -538,7 +452,6 @@ def propagate_rgb_color(
538
452
  sample_method=sample_method,
539
453
  chunk_size=chunk_size,
540
454
  device=device,
541
- use_tqdm=use_tqdm,
542
455
  )
543
456
 
544
457
 
@@ -627,9 +540,7 @@ def get_mask(
627
540
  """
628
541
 
629
542
  # normalize the eigenvectors to unit norm, to compute cosine similarity
630
- if not check_if_normalized(all_eigvecs.reshape(-1, all_eigvecs.shape[-1])):
631
- all_eigvecs = F.normalize(all_eigvecs, p=2, dim=-1)
632
-
543
+ all_eigvecs = lazy_normalize(all_eigvecs, p=2, dim=-1)
633
544
  prompt_eigvec = F.normalize(prompt_eigvec, p=2, dim=-1)
634
545
 
635
546
  # compute the cosine similarity
@@ -642,7 +553,7 @@ def get_mask(
642
553
  heatmap = _transform_heatmap(heatmap, gamma=gamma)
643
554
 
644
555
  masks = heatmap > threshold
645
- masks = masks.cpu().numpy().astype(np.uint8)
556
+ masks = masks.numpy(force=True).astype(np.uint8)
646
557
 
647
558
  if denoise:
648
559
  cleaned_masks = []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nystrom_ncut
3
- Version: 0.0.1
3
+ Version: 0.0.2
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,12 +4,13 @@ README.md
4
4
  pyproject.toml
5
5
  requirements.txt
6
6
  src/nystrom_ncut/__init__.py
7
+ src/nystrom_ncut/common.py
7
8
  src/nystrom_ncut/ncut_pytorch.py
8
- src/nystrom_ncut/new_ncut_pytorch.py
9
9
  src/nystrom_ncut/nystrom.py
10
10
  src/nystrom_ncut/propagation_utils.py
11
11
  src/nystrom_ncut/visualize_utils.py
12
12
  src/nystrom_ncut.egg-info/PKG-INFO
13
13
  src/nystrom_ncut.egg-info/SOURCES.txt
14
14
  src/nystrom_ncut.egg-info/dependency_links.txt
15
- src/nystrom_ncut.egg-info/top_level.txt
15
+ src/nystrom_ncut.egg-info/top_level.txt
16
+ tests/test.py