scikit-network 0.33.3__cp312-cp312-macosx_10_13_x86_64.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.

Potentially problematic release.


This version of scikit-network might be problematic. Click here for more details.

Files changed (228) hide show
  1. scikit_network-0.33.3.dist-info/METADATA +122 -0
  2. scikit_network-0.33.3.dist-info/RECORD +228 -0
  3. scikit_network-0.33.3.dist-info/WHEEL +6 -0
  4. scikit_network-0.33.3.dist-info/licenses/AUTHORS.rst +43 -0
  5. scikit_network-0.33.3.dist-info/licenses/LICENSE +34 -0
  6. scikit_network-0.33.3.dist-info/top_level.txt +1 -0
  7. sknetwork/__init__.py +21 -0
  8. sknetwork/base.py +67 -0
  9. sknetwork/classification/__init__.py +8 -0
  10. sknetwork/classification/base.py +142 -0
  11. sknetwork/classification/base_rank.py +133 -0
  12. sknetwork/classification/diffusion.py +134 -0
  13. sknetwork/classification/knn.py +139 -0
  14. sknetwork/classification/metrics.py +205 -0
  15. sknetwork/classification/pagerank.py +66 -0
  16. sknetwork/classification/propagation.py +152 -0
  17. sknetwork/classification/tests/__init__.py +1 -0
  18. sknetwork/classification/tests/test_API.py +30 -0
  19. sknetwork/classification/tests/test_diffusion.py +77 -0
  20. sknetwork/classification/tests/test_knn.py +23 -0
  21. sknetwork/classification/tests/test_metrics.py +53 -0
  22. sknetwork/classification/tests/test_pagerank.py +20 -0
  23. sknetwork/classification/tests/test_propagation.py +24 -0
  24. sknetwork/classification/vote.cpp +27581 -0
  25. sknetwork/classification/vote.cpython-312-darwin.so +0 -0
  26. sknetwork/classification/vote.pyx +56 -0
  27. sknetwork/clustering/__init__.py +8 -0
  28. sknetwork/clustering/base.py +172 -0
  29. sknetwork/clustering/kcenters.py +253 -0
  30. sknetwork/clustering/leiden.py +242 -0
  31. sknetwork/clustering/leiden_core.cpp +31572 -0
  32. sknetwork/clustering/leiden_core.cpython-312-darwin.so +0 -0
  33. sknetwork/clustering/leiden_core.pyx +124 -0
  34. sknetwork/clustering/louvain.py +286 -0
  35. sknetwork/clustering/louvain_core.cpp +31217 -0
  36. sknetwork/clustering/louvain_core.cpython-312-darwin.so +0 -0
  37. sknetwork/clustering/louvain_core.pyx +124 -0
  38. sknetwork/clustering/metrics.py +91 -0
  39. sknetwork/clustering/postprocess.py +66 -0
  40. sknetwork/clustering/propagation_clustering.py +104 -0
  41. sknetwork/clustering/tests/__init__.py +1 -0
  42. sknetwork/clustering/tests/test_API.py +38 -0
  43. sknetwork/clustering/tests/test_kcenters.py +60 -0
  44. sknetwork/clustering/tests/test_leiden.py +34 -0
  45. sknetwork/clustering/tests/test_louvain.py +135 -0
  46. sknetwork/clustering/tests/test_metrics.py +50 -0
  47. sknetwork/clustering/tests/test_postprocess.py +39 -0
  48. sknetwork/data/__init__.py +6 -0
  49. sknetwork/data/base.py +33 -0
  50. sknetwork/data/load.py +406 -0
  51. sknetwork/data/models.py +459 -0
  52. sknetwork/data/parse.py +644 -0
  53. sknetwork/data/test_graphs.py +84 -0
  54. sknetwork/data/tests/__init__.py +1 -0
  55. sknetwork/data/tests/test_API.py +30 -0
  56. sknetwork/data/tests/test_base.py +14 -0
  57. sknetwork/data/tests/test_load.py +95 -0
  58. sknetwork/data/tests/test_models.py +52 -0
  59. sknetwork/data/tests/test_parse.py +250 -0
  60. sknetwork/data/tests/test_test_graphs.py +29 -0
  61. sknetwork/data/tests/test_toy_graphs.py +68 -0
  62. sknetwork/data/timeout.py +38 -0
  63. sknetwork/data/toy_graphs.py +611 -0
  64. sknetwork/embedding/__init__.py +8 -0
  65. sknetwork/embedding/base.py +94 -0
  66. sknetwork/embedding/force_atlas.py +198 -0
  67. sknetwork/embedding/louvain_embedding.py +148 -0
  68. sknetwork/embedding/random_projection.py +135 -0
  69. sknetwork/embedding/spectral.py +141 -0
  70. sknetwork/embedding/spring.py +198 -0
  71. sknetwork/embedding/svd.py +359 -0
  72. sknetwork/embedding/tests/__init__.py +1 -0
  73. sknetwork/embedding/tests/test_API.py +49 -0
  74. sknetwork/embedding/tests/test_force_atlas.py +35 -0
  75. sknetwork/embedding/tests/test_louvain_embedding.py +33 -0
  76. sknetwork/embedding/tests/test_random_projection.py +28 -0
  77. sknetwork/embedding/tests/test_spectral.py +81 -0
  78. sknetwork/embedding/tests/test_spring.py +50 -0
  79. sknetwork/embedding/tests/test_svd.py +43 -0
  80. sknetwork/gnn/__init__.py +10 -0
  81. sknetwork/gnn/activation.py +117 -0
  82. sknetwork/gnn/base.py +181 -0
  83. sknetwork/gnn/base_activation.py +90 -0
  84. sknetwork/gnn/base_layer.py +109 -0
  85. sknetwork/gnn/gnn_classifier.py +305 -0
  86. sknetwork/gnn/layer.py +153 -0
  87. sknetwork/gnn/loss.py +180 -0
  88. sknetwork/gnn/neighbor_sampler.py +65 -0
  89. sknetwork/gnn/optimizer.py +164 -0
  90. sknetwork/gnn/tests/__init__.py +1 -0
  91. sknetwork/gnn/tests/test_activation.py +56 -0
  92. sknetwork/gnn/tests/test_base.py +75 -0
  93. sknetwork/gnn/tests/test_base_layer.py +37 -0
  94. sknetwork/gnn/tests/test_gnn_classifier.py +130 -0
  95. sknetwork/gnn/tests/test_layers.py +80 -0
  96. sknetwork/gnn/tests/test_loss.py +33 -0
  97. sknetwork/gnn/tests/test_neigh_sampler.py +23 -0
  98. sknetwork/gnn/tests/test_optimizer.py +43 -0
  99. sknetwork/gnn/tests/test_utils.py +41 -0
  100. sknetwork/gnn/utils.py +127 -0
  101. sknetwork/hierarchy/__init__.py +6 -0
  102. sknetwork/hierarchy/base.py +96 -0
  103. sknetwork/hierarchy/louvain_hierarchy.py +272 -0
  104. sknetwork/hierarchy/metrics.py +234 -0
  105. sknetwork/hierarchy/paris.cpp +37865 -0
  106. sknetwork/hierarchy/paris.cpython-312-darwin.so +0 -0
  107. sknetwork/hierarchy/paris.pyx +316 -0
  108. sknetwork/hierarchy/postprocess.py +350 -0
  109. sknetwork/hierarchy/tests/__init__.py +1 -0
  110. sknetwork/hierarchy/tests/test_API.py +24 -0
  111. sknetwork/hierarchy/tests/test_algos.py +34 -0
  112. sknetwork/hierarchy/tests/test_metrics.py +62 -0
  113. sknetwork/hierarchy/tests/test_postprocess.py +57 -0
  114. sknetwork/linalg/__init__.py +9 -0
  115. sknetwork/linalg/basics.py +37 -0
  116. sknetwork/linalg/diteration.cpp +27397 -0
  117. sknetwork/linalg/diteration.cpython-312-darwin.so +0 -0
  118. sknetwork/linalg/diteration.pyx +47 -0
  119. sknetwork/linalg/eig_solver.py +93 -0
  120. sknetwork/linalg/laplacian.py +15 -0
  121. sknetwork/linalg/normalizer.py +86 -0
  122. sknetwork/linalg/operators.py +225 -0
  123. sknetwork/linalg/polynome.py +76 -0
  124. sknetwork/linalg/ppr_solver.py +170 -0
  125. sknetwork/linalg/push.cpp +31069 -0
  126. sknetwork/linalg/push.cpython-312-darwin.so +0 -0
  127. sknetwork/linalg/push.pyx +71 -0
  128. sknetwork/linalg/sparse_lowrank.py +142 -0
  129. sknetwork/linalg/svd_solver.py +91 -0
  130. sknetwork/linalg/tests/__init__.py +1 -0
  131. sknetwork/linalg/tests/test_eig.py +44 -0
  132. sknetwork/linalg/tests/test_laplacian.py +18 -0
  133. sknetwork/linalg/tests/test_normalization.py +34 -0
  134. sknetwork/linalg/tests/test_operators.py +66 -0
  135. sknetwork/linalg/tests/test_polynome.py +38 -0
  136. sknetwork/linalg/tests/test_ppr.py +50 -0
  137. sknetwork/linalg/tests/test_sparse_lowrank.py +61 -0
  138. sknetwork/linalg/tests/test_svd.py +38 -0
  139. sknetwork/linkpred/__init__.py +2 -0
  140. sknetwork/linkpred/base.py +46 -0
  141. sknetwork/linkpred/nn.py +126 -0
  142. sknetwork/linkpred/tests/__init__.py +1 -0
  143. sknetwork/linkpred/tests/test_nn.py +27 -0
  144. sknetwork/log.py +19 -0
  145. sknetwork/path/__init__.py +5 -0
  146. sknetwork/path/dag.py +54 -0
  147. sknetwork/path/distances.py +98 -0
  148. sknetwork/path/search.py +31 -0
  149. sknetwork/path/shortest_path.py +61 -0
  150. sknetwork/path/tests/__init__.py +1 -0
  151. sknetwork/path/tests/test_dag.py +37 -0
  152. sknetwork/path/tests/test_distances.py +62 -0
  153. sknetwork/path/tests/test_search.py +40 -0
  154. sknetwork/path/tests/test_shortest_path.py +40 -0
  155. sknetwork/ranking/__init__.py +8 -0
  156. sknetwork/ranking/base.py +61 -0
  157. sknetwork/ranking/betweenness.cpp +9704 -0
  158. sknetwork/ranking/betweenness.cpython-312-darwin.so +0 -0
  159. sknetwork/ranking/betweenness.pyx +97 -0
  160. sknetwork/ranking/closeness.py +92 -0
  161. sknetwork/ranking/hits.py +94 -0
  162. sknetwork/ranking/katz.py +83 -0
  163. sknetwork/ranking/pagerank.py +110 -0
  164. sknetwork/ranking/postprocess.py +37 -0
  165. sknetwork/ranking/tests/__init__.py +1 -0
  166. sknetwork/ranking/tests/test_API.py +32 -0
  167. sknetwork/ranking/tests/test_betweenness.py +38 -0
  168. sknetwork/ranking/tests/test_closeness.py +30 -0
  169. sknetwork/ranking/tests/test_hits.py +20 -0
  170. sknetwork/ranking/tests/test_pagerank.py +62 -0
  171. sknetwork/ranking/tests/test_postprocess.py +26 -0
  172. sknetwork/regression/__init__.py +4 -0
  173. sknetwork/regression/base.py +61 -0
  174. sknetwork/regression/diffusion.py +210 -0
  175. sknetwork/regression/tests/__init__.py +1 -0
  176. sknetwork/regression/tests/test_API.py +32 -0
  177. sknetwork/regression/tests/test_diffusion.py +56 -0
  178. sknetwork/sknetwork.py +3 -0
  179. sknetwork/test_base.py +35 -0
  180. sknetwork/test_log.py +15 -0
  181. sknetwork/topology/__init__.py +8 -0
  182. sknetwork/topology/cliques.cpp +32562 -0
  183. sknetwork/topology/cliques.cpython-312-darwin.so +0 -0
  184. sknetwork/topology/cliques.pyx +149 -0
  185. sknetwork/topology/core.cpp +30648 -0
  186. sknetwork/topology/core.cpython-312-darwin.so +0 -0
  187. sknetwork/topology/core.pyx +90 -0
  188. sknetwork/topology/cycles.py +243 -0
  189. sknetwork/topology/minheap.cpp +27329 -0
  190. sknetwork/topology/minheap.cpython-312-darwin.so +0 -0
  191. sknetwork/topology/minheap.pxd +20 -0
  192. sknetwork/topology/minheap.pyx +109 -0
  193. sknetwork/topology/structure.py +194 -0
  194. sknetwork/topology/tests/__init__.py +1 -0
  195. sknetwork/topology/tests/test_cliques.py +28 -0
  196. sknetwork/topology/tests/test_core.py +19 -0
  197. sknetwork/topology/tests/test_cycles.py +65 -0
  198. sknetwork/topology/tests/test_structure.py +85 -0
  199. sknetwork/topology/tests/test_triangles.py +38 -0
  200. sknetwork/topology/tests/test_wl.py +72 -0
  201. sknetwork/topology/triangles.cpp +8891 -0
  202. sknetwork/topology/triangles.cpython-312-darwin.so +0 -0
  203. sknetwork/topology/triangles.pyx +151 -0
  204. sknetwork/topology/weisfeiler_lehman.py +133 -0
  205. sknetwork/topology/weisfeiler_lehman_core.cpp +27632 -0
  206. sknetwork/topology/weisfeiler_lehman_core.cpython-312-darwin.so +0 -0
  207. sknetwork/topology/weisfeiler_lehman_core.pyx +114 -0
  208. sknetwork/utils/__init__.py +7 -0
  209. sknetwork/utils/check.py +355 -0
  210. sknetwork/utils/format.py +221 -0
  211. sknetwork/utils/membership.py +82 -0
  212. sknetwork/utils/neighbors.py +115 -0
  213. sknetwork/utils/tests/__init__.py +1 -0
  214. sknetwork/utils/tests/test_check.py +190 -0
  215. sknetwork/utils/tests/test_format.py +63 -0
  216. sknetwork/utils/tests/test_membership.py +24 -0
  217. sknetwork/utils/tests/test_neighbors.py +41 -0
  218. sknetwork/utils/tests/test_tfidf.py +18 -0
  219. sknetwork/utils/tests/test_values.py +66 -0
  220. sknetwork/utils/tfidf.py +37 -0
  221. sknetwork/utils/values.py +76 -0
  222. sknetwork/visualization/__init__.py +4 -0
  223. sknetwork/visualization/colors.py +34 -0
  224. sknetwork/visualization/dendrograms.py +277 -0
  225. sknetwork/visualization/graphs.py +1039 -0
  226. sknetwork/visualization/tests/__init__.py +1 -0
  227. sknetwork/visualization/tests/test_dendrograms.py +53 -0
  228. sknetwork/visualization/tests/test_graphs.py +176 -0
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ """
4
+ Created on September 2018
5
+ @author: Nathan de Lara <nathan.delara@polytechnique.org>
6
+ @author: Thomas Bonald <bonald@enst.fr>
7
+ """
8
+ from typing import Union
9
+
10
+ import numpy as np
11
+ from scipy import sparse
12
+
13
+ from sknetwork.embedding.base import BaseEmbedding
14
+ from sknetwork.linalg import LanczosEig, Laplacian, Normalizer, normalize
15
+ from sknetwork.utils.format import get_adjacency
16
+ from sknetwork.utils.check import check_format, check_adjacency_vector, check_nonnegative, check_n_components
17
+
18
+
19
+ class Spectral(BaseEmbedding):
20
+ """Spectral embedding of graphs, based the spectral decomposition of the Laplacian matrix :math:`L = D - A`
21
+ or the transition matrix of the random walk :math:`P = D^{-1}A` (default), where :math:`D` is the
22
+ diagonal matrix of degrees.
23
+
24
+ Eigenvectors are considered in increasing order (for the Laplacian matrix :math:`L`) or decreasing order
25
+ (for the transition matrix of the random walk :math:`P`) of eigenvalues, skipping the first.
26
+
27
+ Parameters
28
+ ----------
29
+ n_components : int (default = ``2``)
30
+ Dimension of the embedding space.
31
+ decomposition : str (``laplacian`` or ``rw``, default = ``rw``)
32
+ Matrix used for the spectral decomposition.
33
+ regularization : float (default = ``-1``)
34
+ Regularization factor :math:`\\alpha` so that the adjacency matrix is :math:`A + \\alpha \\frac{11^T}{n}`.
35
+ If negative, regularization is applied only if the graph is disconnected; the regularization factor
36
+ :math:`\\alpha` is then set to the absolute value of the parameter.
37
+ normalized : bool (default = ``True``)
38
+ If ``True``, normalized the embedding so that each vector has norm 1 in the embedding space, i.e.,
39
+ each vector lies on the unit sphere.
40
+ Attributes
41
+ ----------
42
+ embedding_ : array, shape = (n, n_components)
43
+ Embedding of the nodes.
44
+ embedding_row_ : array, shape = (n_row, n_components)
45
+ Embedding of the rows, for bipartite graphs.
46
+ embedding_col_ : array, shape = (n_col, n_components)
47
+ Embedding of the columns, for bipartite graphs.
48
+ eigenvalues_ : array, shape = (n_components)
49
+ Eigenvalues.
50
+ eigenvectors_ : array, shape = (n, n_components)
51
+ Eigenvectors.
52
+
53
+ Example
54
+ -------
55
+ >>> from sknetwork.embedding import Spectral
56
+ >>> from sknetwork.data import karate_club
57
+ >>> spectral = Spectral(n_components=3)
58
+ >>> adjacency = karate_club()
59
+ >>> embedding = spectral.fit_transform(adjacency)
60
+ >>> embedding.shape
61
+ (34, 3)
62
+
63
+ References
64
+ ----------
65
+ Belkin, M. & Niyogi, P. (2003). Laplacian Eigenmaps for Dimensionality Reduction and Data Representation,
66
+ Neural computation.
67
+ """
68
+ def __init__(self, n_components: int = 2, decomposition: str = 'rw', regularization: float = -1,
69
+ normalized: bool = True):
70
+ super(Spectral, self).__init__()
71
+
72
+ self.embedding_ = None
73
+ self.n_components = n_components
74
+ self.decomposition = decomposition
75
+ self.regularization = regularization
76
+ self.normalized = normalized
77
+ self.bipartite = None
78
+ self.regularized = None
79
+ self.eigenvalues_ = None
80
+ self.eigenvectors_ = None
81
+
82
+ def fit(self, input_matrix: Union[sparse.csr_matrix, np.ndarray], force_bipartite: bool = False) -> 'Spectral':
83
+ """Compute the graph embedding.
84
+
85
+ If the input matrix :math:`B` is not square (e.g., biadjacency matrix of a bipartite graph) or not symmetric
86
+ (e.g., adjacency matrix of a directed graph), use the adjacency matrix
87
+
88
+ :math:`A = \\begin{bmatrix} 0 & B \\\\ B^T & 0 \\end{bmatrix}`
89
+
90
+ and return the embedding for both rows and columns of the input matrix :math:`B`.
91
+
92
+ Parameters
93
+ ----------
94
+ input_matrix :
95
+ Adjacency matrix or biadjacency matrix of the graph.
96
+ force_bipartite : bool (default = ``False``)
97
+ If ``True``, force the input matrix to be considered as a biadjacency matrix.
98
+
99
+ Returns
100
+ -------
101
+ self: :class:`Spectral`
102
+ """
103
+ # input
104
+ input_matrix = check_format(input_matrix)
105
+ adjacency, self.bipartite = get_adjacency(input_matrix, allow_directed=False, force_bipartite=force_bipartite)
106
+ n = adjacency.shape[0]
107
+
108
+ # regularization
109
+ regularization = self._get_regularization(self.regularization, adjacency)
110
+ self.regularized = regularization > 0
111
+
112
+ # laplacian
113
+ normalized_laplacian = self.decomposition == 'rw'
114
+ laplacian = Laplacian(adjacency, regularization, normalized_laplacian)
115
+
116
+ # spectral decomposition
117
+ n_components = check_n_components(self.n_components, n - 2) + 1
118
+ solver = LanczosEig(which='SM')
119
+ solver.fit(matrix=laplacian, n_components=n_components)
120
+ index = np.argsort(solver.eigenvalues_)[1:] # increasing order, skip first
121
+
122
+ eigenvalues = solver.eigenvalues_[index]
123
+ eigenvectors = solver.eigenvectors_[:, index]
124
+
125
+ if normalized_laplacian:
126
+ eigenvectors = laplacian.norm_diag.dot(eigenvectors)
127
+ eigenvalues = 1 - eigenvalues
128
+
129
+ # embedding
130
+ embedding = eigenvectors.copy()
131
+ if self.normalized:
132
+ embedding = normalize(embedding, p=2)
133
+
134
+ # output
135
+ self.embedding_ = embedding
136
+ self.eigenvalues_ = eigenvalues
137
+ self.eigenvectors_ = eigenvectors
138
+ if self.bipartite:
139
+ self._split_vars(input_matrix.shape)
140
+
141
+ return self
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ """
4
+ Created on Apr 2020
5
+ @author: Nathan de Lara <nathan.delara@polytechnique.org>
6
+ """
7
+ from typing import Optional, Union
8
+
9
+ import numpy as np
10
+ from scipy import sparse
11
+ from scipy.spatial import cKDTree
12
+
13
+ from sknetwork.embedding.base import BaseEmbedding
14
+ from sknetwork.embedding.spectral import Spectral
15
+ from sknetwork.linalg import normalize
16
+ from sknetwork.utils.check import check_adjacency_vector, check_format, check_square, is_symmetric
17
+ from sknetwork.utils.format import directed2undirected
18
+
19
+
20
+ class Spring(BaseEmbedding):
21
+ """Spring layout for displaying small graphs.
22
+
23
+ Parameters
24
+ ----------
25
+ n_components : int
26
+ Dimension of the graph layout.
27
+ strength : float
28
+ Intensity of the force that moves the nodes.
29
+ n_iter : int
30
+ Number of iterations to update positions.
31
+ tol : float
32
+ Minimum relative change in positions to continue updating.
33
+ approx_radius : float
34
+ If a positive value is provided, only the nodes within this distance a given node are used to compute
35
+ the repulsive force.
36
+ position_init : str
37
+ How to initialize the layout. If 'spectral', use Spectral embedding in dimension 2,
38
+ otherwise, use random initialization.
39
+
40
+ Attributes
41
+ ----------
42
+ embedding_ : np.ndarray
43
+ Layout.
44
+
45
+ Example
46
+ -------
47
+ >>> from sknetwork.embedding import Spring
48
+ >>> from sknetwork.data import karate_club
49
+ >>> spring = Spring()
50
+ >>> adjacency = karate_club()
51
+ >>> embedding = spring.fit_transform(adjacency)
52
+ >>> embedding.shape
53
+ (34, 2)
54
+
55
+ Notes
56
+ -----
57
+ Simple implementation designed to display small graphs.
58
+
59
+ References
60
+ ----------
61
+ Fruchterman, T. M. J., Reingold, E. M. (1991).
62
+ `Graph Drawing by Force-Directed Placement.
63
+ <https://onlinelibrary.wiley.com/doi/pdf/10.1002/spe.4380211102>`_
64
+ Software – Practice & Experience.
65
+ """
66
+ def __init__(self, n_components: int = 2, strength: float = None, n_iter: int = 50, tol: float = 1e-4,
67
+ approx_radius: float = -1, position_init: str = 'random'):
68
+ super(Spring, self).__init__()
69
+ self.n_components = n_components
70
+ self.strength = strength
71
+ self.n_iter = n_iter
72
+ self.tol = tol
73
+ self.approx_radius = approx_radius
74
+ if position_init not in ['random', 'spectral']:
75
+ raise ValueError('Unknown initial position, try "spectral" or "random".')
76
+ else:
77
+ self.position_init = position_init
78
+
79
+ def fit(self, adjacency: Union[sparse.csr_matrix, np.ndarray], position_init: Optional[np.ndarray] = None,
80
+ n_iter: Optional[int] = None) -> 'Spring':
81
+ """Compute layout.
82
+
83
+ Parameters
84
+ ----------
85
+ adjacency :
86
+ Adjacency matrix of the graph, treated as undirected.
87
+ position_init : np.ndarray
88
+ Custom initial positions of the nodes. Shape must be (n, 2).
89
+ If ``None``, use the value of self.pos_init.
90
+ n_iter : int
91
+ Number of iterations to update positions.
92
+ If ``None``, use the value of self.n_iter.
93
+
94
+ Returns
95
+ -------
96
+ self: :class:`Spring`
97
+ """
98
+ adjacency = check_format(adjacency)
99
+ check_square(adjacency)
100
+ if not is_symmetric(adjacency):
101
+ adjacency = directed2undirected(adjacency)
102
+ n = adjacency.shape[0]
103
+
104
+ position = np.zeros((n, self.n_components))
105
+ if position_init is None:
106
+ if self.position_init == 'random':
107
+ position = np.random.randn(n, self.n_components)
108
+ elif self.position_init == 'spectral':
109
+ position = Spectral(n_components=self.n_components).fit_transform(adjacency)
110
+ elif isinstance(position_init, np.ndarray):
111
+ if position_init.shape == (n, self.n_components):
112
+ position = position_init.copy()
113
+ else:
114
+ raise ValueError('Initial position has invalid shape.')
115
+ else:
116
+ raise TypeError('Initial position must be a numpy array.')
117
+
118
+ if n_iter is None:
119
+ n_iter = self.n_iter
120
+
121
+ if self.strength is None:
122
+ strength = np.sqrt((1 / n))
123
+ else:
124
+ strength = self.strength
125
+
126
+ pos_max = position.max(axis=0)
127
+ pos_min = position.min(axis=0)
128
+ step_max: float = 0.1 * (pos_max - pos_min).max()
129
+ step: float = step_max / (n_iter + 1)
130
+ tree = None
131
+
132
+ delta = np.zeros((n, self.n_components))
133
+ for iteration in range(n_iter):
134
+ delta *= 0
135
+ if self.approx_radius > 0:
136
+ tree = cKDTree(position)
137
+
138
+ for i in range(n):
139
+ # attraction
140
+ indices = adjacency.indices[adjacency.indptr[i]:adjacency.indptr[i+1]]
141
+ attraction = adjacency.data[adjacency.indptr[i]:adjacency.indptr[i+1]] / strength
142
+
143
+ grad = position[i] - position[indices]
144
+ attraction *= np.linalg.norm(grad, axis=1)
145
+ attraction = (grad * attraction[:, np.newaxis]).sum(axis=0)
146
+
147
+ # repulsion
148
+ if tree is None:
149
+ grad: np.ndarray = (position[i] - position) # shape (n, n_components)
150
+ distance: np.ndarray = np.linalg.norm(grad, axis=1) # shape (n,)
151
+ else:
152
+ neighbors = tree.query_ball_point(position[i], self.approx_radius)
153
+ grad: np.ndarray = (position[i] - position[neighbors]) # shape (n_neigh, n_components)
154
+ distance: np.ndarray = np.linalg.norm(grad, axis=1) # shape (n_neigh,)
155
+
156
+ distance = np.where(distance < 0.01, 0.01, distance)
157
+ repulsion = (grad * (strength / distance)[:, np.newaxis] ** 2).sum(axis=0)
158
+
159
+ # total force
160
+ delta[i]: np.ndarray = repulsion - attraction
161
+
162
+ length = np.linalg.norm(delta, axis=0)
163
+ length = np.where(length < 0.01, 0.1, length)
164
+ delta = delta * step_max / length
165
+ position += delta
166
+ step_max -= step
167
+ err: float = np.linalg.norm(delta) / n
168
+ if err < self.tol:
169
+ break
170
+
171
+ self.embedding_ = position
172
+ return self
173
+
174
+ def predict(self, adjacency_vectors: Union[sparse.csr_matrix, np.ndarray]) -> np.ndarray:
175
+ """Predict the embedding of new rows, defined by their adjacency vectors.
176
+
177
+ Parameters
178
+ ----------
179
+ adjacency_vectors :
180
+ Adjacency vectors of nodes.
181
+ Array of shape (n_col,) (single vector) or (n_vectors, n_col)
182
+
183
+ Returns
184
+ -------
185
+ embedding_vectors : np.ndarray
186
+ Embedding of the nodes.
187
+ """
188
+ self._check_fitted()
189
+ embedding = self.embedding_
190
+ n = embedding.shape[0]
191
+
192
+ adjacency_vectors = check_adjacency_vector(adjacency_vectors, n)
193
+ embedding_vectors = normalize(adjacency_vectors).dot(embedding)
194
+
195
+ if embedding_vectors.shape[0] == 1:
196
+ embedding_vectors = embedding_vectors.ravel()
197
+
198
+ return embedding_vectors
@@ -0,0 +1,359 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created in May 2018
5
+ @author: Nathan de Lara <nathan.delara@polytechnique.org>
6
+ @author: Thomas Bonald <bonald@enst.fr>
7
+ """
8
+ from typing import Union
9
+
10
+ import numpy as np
11
+ from scipy import sparse
12
+
13
+ from sknetwork.embedding.base import BaseEmbedding
14
+ from sknetwork.linalg import SVDSolver, LanczosSVD, safe_sparse_dot, diagonal_pseudo_inverse, normalize, Regularizer, SparseLR
15
+ from sknetwork.utils.check import check_format, check_adjacency_vector, check_nonnegative, check_n_components
16
+
17
+
18
+ class GSVD(BaseEmbedding):
19
+ """Graph embedding by Generalized Singular Value Decomposition of the adjacency or biadjacency matrix :math:`A`.
20
+ This is equivalent to the Singular Value Decomposition of the matrix :math:`D_1^{- \\alpha_1}AD_2^{- \\alpha_2}`
21
+ where :math:`D_1, D_2` are the diagonal matrices of row weights and columns weights, respectively, and
22
+ :math:`\\alpha_1, \\alpha_2` are parameters.
23
+
24
+ Parameters
25
+ -----------
26
+ n_components : int
27
+ Dimension of the embedding.
28
+ regularization : ``None`` or float (default = ``None``)
29
+ Regularization factor :math:`\\alpha` so that the matrix is :math:`A + \\alpha \\frac{11^T}{n}`.
30
+ factor_row : float (default = 0.5)
31
+ Power factor :math:`\\alpha_1` applied to the diagonal matrix of row weights.
32
+ factor_col : float (default = 0.5)
33
+ Power factor :math:`\\alpha_2` applied to the diagonal matrix of column weights.
34
+ factor_singular : float (default = 0.)
35
+ Parameter :math:`\\alpha` applied to the singular values on right singular vectors.
36
+ The embedding of rows and columns are respectively :math:`D_1^{- \\alpha_1}U \\Sigma^{1-\\alpha}` and
37
+ :math:`D_2^{- \\alpha_2}V \\Sigma^\\alpha` where:
38
+
39
+ * :math:`U` is the matrix of left singular vectors, shape (n_row, n_components)
40
+ * :math:`V` is the matrix of right singular vectors, shape (n_col, n_components)
41
+ * :math:`\\Sigma` is the diagonal matrix of singular values, shape (n_components, n_components)
42
+
43
+ normalized : bool (default = ``True``)
44
+ If ``True``, normalized the embedding so that each vector has norm 1 in the embedding space, i.e.,
45
+ each vector lies on the unit sphere.
46
+ solver : ``'lanczos'`` (Lanczos algorithm, default) or :class:`SVDSolver` (custom solver)
47
+ Which solver to use.
48
+
49
+ Attributes
50
+ ----------
51
+ embedding_ : array, shape = (n, n_components)
52
+ Embedding of the nodes.
53
+ embedding_row_ : array, shape = (n_row, n_components)
54
+ Embedding of the rows, for bipartite graphs.
55
+ embedding_col_ : array, shape = (n_col, n_components)
56
+ Embedding of the columns, for bipartite graphs.
57
+ singular_values_ : np.ndarray, shape = (n_components)
58
+ Singular values.
59
+ singular_vectors_left_ : np.ndarray, shape = (n_row, n_components)
60
+ Left singular vectors.
61
+ singular_vectors_right_ : np.ndarray, shape = (n_col, n_components)
62
+ Right singular vectors.
63
+ weights_col_ : np.ndarray, shape = (n2)
64
+ Weights applied to columns.
65
+
66
+ Example
67
+ -------
68
+ >>> from sknetwork.embedding import GSVD
69
+ >>> from sknetwork.data import karate_club
70
+ >>> gsvd = GSVD()
71
+ >>> adjacency = karate_club()
72
+ >>> embedding = gsvd.fit_transform(adjacency)
73
+ >>> embedding.shape
74
+ (34, 2)
75
+
76
+ References
77
+ ----------
78
+ Abdi, H. (2007).
79
+ `Singular value decomposition (SVD) and generalized singular value decomposition.
80
+ <https://www.cs.cornell.edu/cv/ResearchPDF/Generalizing%20The%20Singular%20Value%20Decomposition.pdf>`_
81
+ Encyclopedia of measurement and statistics, 907-912.
82
+ """
83
+ def __init__(self, n_components=2, regularization: Union[None, float] = None,
84
+ factor_row: float = 0.5, factor_col: float = 0.5, factor_singular: float = 0., normalized: bool = True,
85
+ solver: Union[str, SVDSolver] = 'lanczos'):
86
+ super(GSVD, self).__init__()
87
+
88
+ self.n_components = n_components
89
+ if regularization == 0:
90
+ self.regularization = None
91
+ else:
92
+ self.regularization = regularization
93
+ self.factor_row = factor_row
94
+ self.factor_col = factor_col
95
+ self.factor_singular = factor_singular
96
+ self.normalized = normalized
97
+ self.solver = solver
98
+
99
+ self.singular_values_ = None
100
+ self.singular_vectors_left_ = None
101
+ self.singular_vectors_right_ = None
102
+ self.regularization_ = None
103
+ self.weights_col_ = None
104
+
105
+ def fit(self, input_matrix: Union[sparse.csr_matrix, np.ndarray]) -> 'GSVD':
106
+ """Compute the embedding of the graph.
107
+
108
+ Parameters
109
+ ----------
110
+ input_matrix :
111
+ Adjacency matrix or biadjacency matrix of the graph.
112
+
113
+ Returns
114
+ -------
115
+ self: :class:`GSVD`
116
+ """
117
+ self._init_vars()
118
+
119
+ adjacency = check_format(input_matrix).asfptype()
120
+ n_row, n_col = adjacency.shape
121
+ n_components = check_n_components(self.n_components, min(n_row, n_col) - 1)
122
+
123
+ if isinstance(self.solver, str):
124
+ self.solver = LanczosSVD()
125
+ regularization = self.regularization
126
+ if regularization:
127
+ adjacency_reg = Regularizer(adjacency, regularization)
128
+ else:
129
+ adjacency_reg = adjacency
130
+
131
+ weights_row = adjacency_reg.dot(np.ones(n_col))
132
+ weights_col = adjacency_reg.T.dot(np.ones(n_row))
133
+ diag_row = diagonal_pseudo_inverse(np.power(weights_row, self.factor_row))
134
+ diag_col = diagonal_pseudo_inverse(np.power(weights_col, self.factor_col))
135
+ self.solver.fit(safe_sparse_dot(diag_row, safe_sparse_dot(adjacency_reg, diag_col)), n_components)
136
+
137
+ singular_values = self.solver.singular_values_
138
+ index = np.argsort(-singular_values)
139
+ singular_values = singular_values[index]
140
+ singular_vectors_left = self.solver.singular_vectors_left_[:, index]
141
+ singular_vectors_right = self.solver.singular_vectors_right_[:, index]
142
+ singular_left_diag = sparse.diags(np.power(singular_values, 1 - self.factor_singular))
143
+ singular_right_diag = sparse.diags(np.power(singular_values, self.factor_singular))
144
+
145
+ embedding_row = diag_row.dot(singular_vectors_left)
146
+ embedding_col = diag_col.dot(singular_vectors_right)
147
+ embedding_row = singular_left_diag.dot(embedding_row.T).T
148
+ embedding_col = singular_right_diag.dot(embedding_col.T).T
149
+
150
+ if self.normalized:
151
+ embedding_row = normalize(embedding_row, p=2)
152
+ embedding_col = normalize(embedding_col, p=2)
153
+
154
+ self.embedding_row_ = embedding_row
155
+ self.embedding_col_ = embedding_col
156
+ self.embedding_ = embedding_row
157
+ self.singular_values_ = singular_values
158
+ self.singular_vectors_left_ = singular_vectors_left
159
+ self.singular_vectors_right_ = singular_vectors_right
160
+ self.weights_col_ = weights_col
161
+
162
+ return self
163
+
164
+ @staticmethod
165
+ def _check_adj_vector(adjacency_vectors):
166
+ check_nonnegative(adjacency_vectors)
167
+
168
+ def predict(self, adjacency_vectors: Union[sparse.csr_matrix, np.ndarray]) -> np.ndarray:
169
+ """Predict the embedding of new rows, defined by their adjacency vectors.
170
+
171
+ Parameters
172
+ ----------
173
+ adjacency_vectors :
174
+ Adjacency vectors of nodes.
175
+ Array of shape (n_col,) (single vector) or (n_vectors, n_col)
176
+
177
+ Returns
178
+ -------
179
+ embedding_vectors : np.ndarray
180
+ Embedding of the nodes.
181
+ """
182
+ self._check_fitted()
183
+ singular_vectors_right = self.singular_vectors_right_
184
+ singular_values = self.singular_values_
185
+
186
+ n_row, _ = self.embedding_row_.shape
187
+ n_col, _ = self.embedding_col_.shape
188
+
189
+ adjacency_vectors = check_adjacency_vector(adjacency_vectors, n_col)
190
+ self._check_adj_vector(adjacency_vectors)
191
+
192
+ # regularization
193
+ if self.regularization:
194
+ adjacency_vectors = Regularizer(adjacency_vectors, self.regularization)
195
+
196
+ # weighting
197
+ weights_row = adjacency_vectors.dot(np.ones(n_col))
198
+ diag_row = diagonal_pseudo_inverse(np.power(weights_row, self.factor_row))
199
+ diag_col = diagonal_pseudo_inverse(np.power(self.weights_col_, self.factor_col))
200
+ adjacency_vectors = safe_sparse_dot(diag_row, safe_sparse_dot(adjacency_vectors, diag_col))
201
+
202
+ # projection in the embedding space
203
+ averaging = adjacency_vectors
204
+ embedding_vectors = diag_row.dot(averaging.dot(singular_vectors_right))
205
+
206
+ # scaling
207
+ embedding_vectors /= np.power(singular_values, self.factor_singular)
208
+
209
+ if self.normalized:
210
+ embedding_vectors = normalize(embedding_vectors, p=2)
211
+
212
+ if len(embedding_vectors) == 1:
213
+ embedding_vectors = embedding_vectors.ravel()
214
+
215
+ return embedding_vectors
216
+
217
+
218
+ class SVD(GSVD):
219
+ """Graph embedding by Singular Value Decomposition of the adjacency or biadjacency matrix of the graph.
220
+
221
+ Parameters
222
+ ----------
223
+ n_components : int
224
+ Dimension of the embedding.
225
+ regularization : ``None`` or float (default = ``None``)
226
+ Regularization factor :math:`\\alpha` so that the matrix is :math:`A + \\alpha \\frac{11^T}{n}`.
227
+ factor_singular : float (default = 0.)
228
+ Power factor :math:`\\alpha` applied to the singular values on right singular vectors.
229
+ The embedding of rows and columns are respectively :math:`U \\Sigma^{1-\\alpha}` and
230
+ :math:`V \\Sigma^\\alpha` where:
231
+
232
+ * :math:`U` is the matrix of left singular vectors, shape (n_row, n_components)
233
+ * :math:`V` is the matrix of right singular vectors, shape (n_col, n_components)
234
+ * :math:`\\Sigma` is the diagonal matrix of singular values, shape (n_components, n_components)
235
+
236
+ normalized : bool (default = ``False``)
237
+ If ``True``, normalized the embedding so that each vector has norm 1 in the embedding space, i.e.,
238
+ each vector lies on the unit sphere.
239
+ solver : ``'lanczos'`` (Lanczos algorithm, default) or :class:`SVDSolver` (custom solver)
240
+ Which solver to use.
241
+
242
+ Attributes
243
+ ----------
244
+ embedding_ : array, shape = (n, n_components)
245
+ Embedding of the nodes.
246
+ embedding_row_ : array, shape = (n_row, n_components)
247
+ Embedding of the rows, for bipartite graphs.
248
+ embedding_col_ : array, shape = (n_col, n_components)
249
+ Embedding of the columns, for bipartite graphs.
250
+ singular_values_ : np.ndarray, shape = (n_components)
251
+ Singular values.
252
+ singular_vectors_left_ : np.ndarray, shape = (n_row, n_components)
253
+ Left singular vectors.
254
+ singular_vectors_right_ : np.ndarray, shape = (n_col, n_components)
255
+ Right singular vectors.
256
+
257
+ Example
258
+ -------
259
+ >>> from sknetwork.embedding import SVD
260
+ >>> from sknetwork.data import karate_club
261
+ >>> svd = SVD()
262
+ >>> adjacency = karate_club()
263
+ >>> embedding = svd.fit_transform(adjacency)
264
+ >>> embedding.shape
265
+ (34, 2)
266
+
267
+ References
268
+ ----------
269
+ Abdi, H. (2007).
270
+ `Singular value decomposition (SVD) and generalized singular value decomposition.
271
+ <https://www.cs.cornell.edu/cv/ResearchPDF/Generalizing%20The%20Singular%20Value%20Decomposition.pdf>`_
272
+ Encyclopedia of measurement and statistics.
273
+ """
274
+ def __init__(self, n_components=2, regularization: Union[None, float] = None, factor_singular: float = 0.,
275
+ normalized: bool = False, solver: Union[str, SVDSolver] = 'lanczos'):
276
+ super(SVD, self).__init__(n_components=n_components, regularization=regularization,
277
+ factor_singular=factor_singular, factor_row=0., factor_col=0., normalized=normalized,
278
+ solver=solver)
279
+
280
+
281
+ class PCA(SVD):
282
+ """Graph embedding by Principal Component Analysis of the adjacency or biadjacency matrix.
283
+
284
+ Parameters
285
+ ----------
286
+ n_components : int
287
+ Dimension of the embedding.
288
+ normalized : bool (default = ``False``)
289
+ If ``True``, normalized the embedding so that each vector has norm 1 in the embedding space, i.e.,
290
+ each vector lies on the unit sphere.
291
+ solver : ``'lanczos'`` (Lanczos algorithm, default) or :class:`SVDSolver` (custom solver)
292
+ Which solver to use.
293
+
294
+ Attributes
295
+ ----------
296
+ embedding_ : array, shape = (n, n_components)
297
+ Embedding of the nodes.
298
+ embedding_row_ : array, shape = (n_row, n_components)
299
+ Embedding of the rows, for bipartite graphs.
300
+ embedding_col_ : array, shape = (n_col, n_components)
301
+ Embedding of the columns, for bipartite graphs.
302
+ singular_values_ : np.ndarray, shape = (n_components)
303
+ Singular values.
304
+ singular_vectors_left_ : np.ndarray, shape = (n_row, n_components)
305
+ Left singular vectors.
306
+ singular_vectors_right_ : np.ndarray, shape = (n_col, n_components)
307
+ Right singular vectors.
308
+
309
+ Example
310
+ -------
311
+ >>> from sknetwork.embedding import PCA
312
+ >>> from sknetwork.data import karate_club
313
+ >>> pca = PCA()
314
+ >>> adjacency = karate_club()
315
+ >>> embedding = pca.fit_transform(adjacency)
316
+ >>> embedding.shape
317
+ (34, 2)
318
+
319
+ References
320
+ ----------
321
+ Jolliffe, I.T. (2002).
322
+ `Principal Component Analysis`
323
+ Series: Springer Series in Statistics.
324
+ """
325
+ def __init__(self, n_components=2, normalized: bool = False, solver: Union[str, SVDSolver] = 'lanczos'):
326
+ super(PCA, self).__init__()
327
+ self.n_components = n_components
328
+ self.normalized = normalized
329
+ if isinstance(solver, str):
330
+ self.solver = LanczosSVD()
331
+ else:
332
+ self.solver = solver
333
+
334
+ def fit(self, adjacency: Union[sparse.csr_matrix, np.ndarray]) -> 'PCA':
335
+ """Compute the embedding of the graph.
336
+
337
+ Parameters
338
+ ----------
339
+ adjacency :
340
+ Adjacency or biadjacency matrix of the graph.
341
+
342
+ Returns
343
+ -------
344
+ self: :class:`PCA`
345
+ """
346
+ adjacency = check_format(adjacency).asfptype()
347
+ n_row, n_col = adjacency.shape
348
+ adjacency_centered = SparseLR(adjacency, (-np.ones(n_row), adjacency.T.dot(np.ones(n_row)) / n_row))
349
+
350
+ svd = self.solver
351
+ svd.fit(adjacency_centered, self.n_components)
352
+ self.embedding_row_ = svd.singular_vectors_left_
353
+ self.embedding_col_ = svd.singular_vectors_right_
354
+ self.embedding_ = svd.singular_vectors_left_
355
+ self.singular_values_ = svd.singular_values_
356
+ self.singular_vectors_left_ = svd.singular_vectors_left_
357
+ self.singular_vectors_right_ = svd.singular_vectors_right_
358
+
359
+ return self
@@ -0,0 +1 @@
1
+ """tests for embedding"""