scikit-network 0.28.3__cp39-cp39-macosx_12_0_arm64.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.
- scikit_network-0.28.3.dist-info/AUTHORS.rst +41 -0
- scikit_network-0.28.3.dist-info/LICENSE +34 -0
- scikit_network-0.28.3.dist-info/METADATA +457 -0
- scikit_network-0.28.3.dist-info/RECORD +240 -0
- scikit_network-0.28.3.dist-info/WHEEL +5 -0
- scikit_network-0.28.3.dist-info/top_level.txt +1 -0
- sknetwork/__init__.py +21 -0
- sknetwork/classification/__init__.py +8 -0
- sknetwork/classification/base.py +84 -0
- sknetwork/classification/base_rank.py +143 -0
- sknetwork/classification/diffusion.py +134 -0
- sknetwork/classification/knn.py +162 -0
- sknetwork/classification/metrics.py +205 -0
- sknetwork/classification/pagerank.py +66 -0
- sknetwork/classification/propagation.py +152 -0
- sknetwork/classification/tests/__init__.py +1 -0
- sknetwork/classification/tests/test_API.py +35 -0
- sknetwork/classification/tests/test_diffusion.py +37 -0
- sknetwork/classification/tests/test_knn.py +24 -0
- sknetwork/classification/tests/test_metrics.py +53 -0
- sknetwork/classification/tests/test_pagerank.py +20 -0
- sknetwork/classification/tests/test_propagation.py +24 -0
- sknetwork/classification/vote.cpython-39-darwin.so +0 -0
- sknetwork/classification/vote.pyx +58 -0
- sknetwork/clustering/__init__.py +7 -0
- sknetwork/clustering/base.py +102 -0
- sknetwork/clustering/kmeans.py +142 -0
- sknetwork/clustering/louvain.py +255 -0
- sknetwork/clustering/louvain_core.cpython-39-darwin.so +0 -0
- sknetwork/clustering/louvain_core.pyx +134 -0
- sknetwork/clustering/metrics.py +91 -0
- sknetwork/clustering/postprocess.py +66 -0
- sknetwork/clustering/propagation_clustering.py +108 -0
- sknetwork/clustering/tests/__init__.py +1 -0
- sknetwork/clustering/tests/test_API.py +37 -0
- sknetwork/clustering/tests/test_kmeans.py +47 -0
- sknetwork/clustering/tests/test_louvain.py +104 -0
- sknetwork/clustering/tests/test_metrics.py +50 -0
- sknetwork/clustering/tests/test_post_processing.py +23 -0
- sknetwork/clustering/tests/test_postprocess.py +39 -0
- sknetwork/data/__init__.py +5 -0
- sknetwork/data/load.py +408 -0
- sknetwork/data/models.py +459 -0
- sknetwork/data/parse.py +621 -0
- sknetwork/data/test_graphs.py +84 -0
- sknetwork/data/tests/__init__.py +1 -0
- sknetwork/data/tests/test_API.py +30 -0
- sknetwork/data/tests/test_load.py +95 -0
- sknetwork/data/tests/test_models.py +52 -0
- sknetwork/data/tests/test_parse.py +253 -0
- sknetwork/data/tests/test_test_graphs.py +30 -0
- sknetwork/data/tests/test_toy_graphs.py +68 -0
- sknetwork/data/toy_graphs.py +619 -0
- sknetwork/embedding/__init__.py +10 -0
- sknetwork/embedding/base.py +90 -0
- sknetwork/embedding/force_atlas.py +197 -0
- sknetwork/embedding/louvain_embedding.py +174 -0
- sknetwork/embedding/louvain_hierarchy.py +142 -0
- sknetwork/embedding/metrics.py +66 -0
- sknetwork/embedding/random_projection.py +133 -0
- sknetwork/embedding/spectral.py +214 -0
- sknetwork/embedding/spring.py +198 -0
- sknetwork/embedding/svd.py +363 -0
- sknetwork/embedding/tests/__init__.py +1 -0
- sknetwork/embedding/tests/test_API.py +73 -0
- sknetwork/embedding/tests/test_force_atlas.py +35 -0
- sknetwork/embedding/tests/test_louvain_embedding.py +33 -0
- sknetwork/embedding/tests/test_louvain_hierarchy.py +19 -0
- sknetwork/embedding/tests/test_metrics.py +29 -0
- sknetwork/embedding/tests/test_random_projection.py +28 -0
- sknetwork/embedding/tests/test_spectral.py +84 -0
- sknetwork/embedding/tests/test_spring.py +50 -0
- sknetwork/embedding/tests/test_svd.py +37 -0
- sknetwork/flow/__init__.py +3 -0
- sknetwork/flow/flow.py +73 -0
- sknetwork/flow/tests/__init__.py +1 -0
- sknetwork/flow/tests/test_flow.py +17 -0
- sknetwork/flow/tests/test_utils.py +69 -0
- sknetwork/flow/utils.py +91 -0
- sknetwork/gnn/__init__.py +10 -0
- sknetwork/gnn/activation.py +117 -0
- sknetwork/gnn/base.py +155 -0
- sknetwork/gnn/base_activation.py +89 -0
- sknetwork/gnn/base_layer.py +109 -0
- sknetwork/gnn/gnn_classifier.py +381 -0
- sknetwork/gnn/layer.py +153 -0
- sknetwork/gnn/layers.py +127 -0
- sknetwork/gnn/loss.py +180 -0
- sknetwork/gnn/neighbor_sampler.py +65 -0
- sknetwork/gnn/optimizer.py +163 -0
- sknetwork/gnn/tests/__init__.py +1 -0
- sknetwork/gnn/tests/test_activation.py +56 -0
- sknetwork/gnn/tests/test_base.py +79 -0
- sknetwork/gnn/tests/test_base_layer.py +37 -0
- sknetwork/gnn/tests/test_gnn_classifier.py +192 -0
- sknetwork/gnn/tests/test_layers.py +80 -0
- sknetwork/gnn/tests/test_loss.py +33 -0
- sknetwork/gnn/tests/test_neigh_sampler.py +23 -0
- sknetwork/gnn/tests/test_optimizer.py +43 -0
- sknetwork/gnn/tests/test_utils.py +93 -0
- sknetwork/gnn/utils.py +219 -0
- sknetwork/hierarchy/__init__.py +7 -0
- sknetwork/hierarchy/base.py +69 -0
- sknetwork/hierarchy/louvain_hierarchy.py +264 -0
- sknetwork/hierarchy/metrics.py +234 -0
- sknetwork/hierarchy/paris.cpython-39-darwin.so +0 -0
- sknetwork/hierarchy/paris.pyx +317 -0
- sknetwork/hierarchy/postprocess.py +350 -0
- sknetwork/hierarchy/tests/__init__.py +1 -0
- sknetwork/hierarchy/tests/test_API.py +25 -0
- sknetwork/hierarchy/tests/test_algos.py +29 -0
- sknetwork/hierarchy/tests/test_metrics.py +62 -0
- sknetwork/hierarchy/tests/test_postprocess.py +57 -0
- sknetwork/hierarchy/tests/test_ward.py +25 -0
- sknetwork/hierarchy/ward.py +94 -0
- sknetwork/linalg/__init__.py +9 -0
- sknetwork/linalg/basics.py +37 -0
- sknetwork/linalg/diteration.cpython-39-darwin.so +0 -0
- sknetwork/linalg/diteration.pyx +49 -0
- sknetwork/linalg/eig_solver.py +93 -0
- sknetwork/linalg/laplacian.py +15 -0
- sknetwork/linalg/normalization.py +66 -0
- sknetwork/linalg/operators.py +225 -0
- sknetwork/linalg/polynome.py +76 -0
- sknetwork/linalg/ppr_solver.py +170 -0
- sknetwork/linalg/push.cpython-39-darwin.so +0 -0
- sknetwork/linalg/push.pyx +73 -0
- sknetwork/linalg/sparse_lowrank.py +142 -0
- sknetwork/linalg/svd_solver.py +91 -0
- sknetwork/linalg/tests/__init__.py +1 -0
- sknetwork/linalg/tests/test_eig.py +44 -0
- sknetwork/linalg/tests/test_laplacian.py +18 -0
- sknetwork/linalg/tests/test_normalization.py +38 -0
- sknetwork/linalg/tests/test_operators.py +70 -0
- sknetwork/linalg/tests/test_polynome.py +38 -0
- sknetwork/linalg/tests/test_ppr.py +50 -0
- sknetwork/linalg/tests/test_sparse_lowrank.py +61 -0
- sknetwork/linalg/tests/test_svd.py +38 -0
- sknetwork/linkpred/__init__.py +4 -0
- sknetwork/linkpred/base.py +80 -0
- sknetwork/linkpred/first_order.py +508 -0
- sknetwork/linkpred/first_order_core.cpython-39-darwin.so +0 -0
- sknetwork/linkpred/first_order_core.pyx +315 -0
- sknetwork/linkpred/postprocessing.py +98 -0
- sknetwork/linkpred/tests/__init__.py +1 -0
- sknetwork/linkpred/tests/test_API.py +49 -0
- sknetwork/linkpred/tests/test_postprocessing.py +21 -0
- sknetwork/path/__init__.py +4 -0
- sknetwork/path/metrics.py +148 -0
- sknetwork/path/search.py +65 -0
- sknetwork/path/shortest_path.py +186 -0
- sknetwork/path/tests/__init__.py +1 -0
- sknetwork/path/tests/test_metrics.py +29 -0
- sknetwork/path/tests/test_search.py +25 -0
- sknetwork/path/tests/test_shortest_path.py +45 -0
- sknetwork/ranking/__init__.py +9 -0
- sknetwork/ranking/base.py +56 -0
- sknetwork/ranking/betweenness.cpython-39-darwin.so +0 -0
- sknetwork/ranking/betweenness.pyx +99 -0
- sknetwork/ranking/closeness.py +95 -0
- sknetwork/ranking/harmonic.py +82 -0
- sknetwork/ranking/hits.py +94 -0
- sknetwork/ranking/katz.py +81 -0
- sknetwork/ranking/pagerank.py +107 -0
- sknetwork/ranking/postprocess.py +25 -0
- sknetwork/ranking/tests/__init__.py +1 -0
- sknetwork/ranking/tests/test_API.py +34 -0
- sknetwork/ranking/tests/test_betweenness.py +38 -0
- sknetwork/ranking/tests/test_closeness.py +34 -0
- sknetwork/ranking/tests/test_hits.py +20 -0
- sknetwork/ranking/tests/test_pagerank.py +69 -0
- sknetwork/regression/__init__.py +4 -0
- sknetwork/regression/base.py +56 -0
- sknetwork/regression/diffusion.py +190 -0
- sknetwork/regression/tests/__init__.py +1 -0
- sknetwork/regression/tests/test_API.py +34 -0
- sknetwork/regression/tests/test_diffusion.py +48 -0
- sknetwork/sknetwork.py +3 -0
- sknetwork/topology/__init__.py +9 -0
- sknetwork/topology/dag.py +74 -0
- sknetwork/topology/dag_core.cpython-39-darwin.so +0 -0
- sknetwork/topology/dag_core.pyx +38 -0
- sknetwork/topology/kcliques.cpython-39-darwin.so +0 -0
- sknetwork/topology/kcliques.pyx +193 -0
- sknetwork/topology/kcore.cpython-39-darwin.so +0 -0
- sknetwork/topology/kcore.pyx +120 -0
- sknetwork/topology/structure.py +234 -0
- sknetwork/topology/tests/__init__.py +1 -0
- sknetwork/topology/tests/test_cliques.py +28 -0
- sknetwork/topology/tests/test_cores.py +21 -0
- sknetwork/topology/tests/test_dag.py +26 -0
- sknetwork/topology/tests/test_structure.py +99 -0
- sknetwork/topology/tests/test_triangles.py +42 -0
- sknetwork/topology/tests/test_wl_coloring.py +49 -0
- sknetwork/topology/tests/test_wl_kernel.py +31 -0
- sknetwork/topology/triangles.cpython-39-darwin.so +0 -0
- sknetwork/topology/triangles.pyx +166 -0
- sknetwork/topology/weisfeiler_lehman.py +163 -0
- sknetwork/topology/weisfeiler_lehman_core.cpython-39-darwin.so +0 -0
- sknetwork/topology/weisfeiler_lehman_core.pyx +116 -0
- sknetwork/utils/__init__.py +40 -0
- sknetwork/utils/base.py +35 -0
- sknetwork/utils/check.py +354 -0
- sknetwork/utils/co_neighbor.py +71 -0
- sknetwork/utils/format.py +219 -0
- sknetwork/utils/kmeans.py +89 -0
- sknetwork/utils/knn.py +166 -0
- sknetwork/utils/knn1d.cpython-39-darwin.so +0 -0
- sknetwork/utils/knn1d.pyx +80 -0
- sknetwork/utils/membership.py +82 -0
- sknetwork/utils/minheap.cpython-39-darwin.so +0 -0
- sknetwork/utils/minheap.pxd +22 -0
- sknetwork/utils/minheap.pyx +111 -0
- sknetwork/utils/neighbors.py +115 -0
- sknetwork/utils/seeds.py +75 -0
- sknetwork/utils/simplex.py +140 -0
- sknetwork/utils/tests/__init__.py +1 -0
- sknetwork/utils/tests/test_base.py +28 -0
- sknetwork/utils/tests/test_bunch.py +16 -0
- sknetwork/utils/tests/test_check.py +190 -0
- sknetwork/utils/tests/test_co_neighbor.py +43 -0
- sknetwork/utils/tests/test_format.py +61 -0
- sknetwork/utils/tests/test_kmeans.py +21 -0
- sknetwork/utils/tests/test_knn.py +32 -0
- sknetwork/utils/tests/test_membership.py +24 -0
- sknetwork/utils/tests/test_neighbors.py +41 -0
- sknetwork/utils/tests/test_projection_simplex.py +33 -0
- sknetwork/utils/tests/test_seeds.py +67 -0
- sknetwork/utils/tests/test_verbose.py +15 -0
- sknetwork/utils/tests/test_ward.py +20 -0
- sknetwork/utils/timeout.py +38 -0
- sknetwork/utils/verbose.py +37 -0
- sknetwork/utils/ward.py +60 -0
- sknetwork/visualization/__init__.py +4 -0
- sknetwork/visualization/colors.py +34 -0
- sknetwork/visualization/dendrograms.py +229 -0
- sknetwork/visualization/graphs.py +819 -0
- sknetwork/visualization/tests/__init__.py +1 -0
- sknetwork/visualization/tests/test_dendrograms.py +53 -0
- sknetwork/visualization/tests/test_graphs.py +167 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# coding: utf-8
|
|
3
|
+
"""
|
|
4
|
+
Created on January, 15 2021
|
|
5
|
+
@author: Thomas Bonald <bonald@enst.fr>
|
|
6
|
+
"""
|
|
7
|
+
from typing import Union
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from scipy import sparse
|
|
11
|
+
|
|
12
|
+
from sknetwork.embedding.base import BaseEmbedding
|
|
13
|
+
from sknetwork.linalg import Regularizer, Normalizer, normalize
|
|
14
|
+
from sknetwork.utils.check import check_format, check_random_state
|
|
15
|
+
from sknetwork.utils.format import get_adjacency
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RandomProjection(BaseEmbedding):
|
|
19
|
+
"""Embedding of graphs based the random projection of the adjacency matrix:
|
|
20
|
+
|
|
21
|
+
:math:`(I + \\alpha A +... + (\\alpha A)^K)G`
|
|
22
|
+
|
|
23
|
+
where :math:`A` is the adjacency matrix, :math:`G` is a random Gaussian matrix,
|
|
24
|
+
:math:`\\alpha` is some smoothing factor and :math:`K` some non-negative integer.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
n_components : int (default = 2)
|
|
29
|
+
Dimension of the embedding space.
|
|
30
|
+
alpha : float (default = 0.5)
|
|
31
|
+
Smoothing parameter.
|
|
32
|
+
n_iter : int (default = 3)
|
|
33
|
+
Number of power iterations of the adjacency matrix.
|
|
34
|
+
random_walk : bool (default = ``False``)
|
|
35
|
+
If ``True``, use the transition matrix of the random walk, :math:`P = D^{-1}A`, instead of the adjacency matrix.
|
|
36
|
+
regularization : float (default = ``-1``)
|
|
37
|
+
Regularization factor :math:`\\alpha` so that the matrix is :math:`A + \\alpha \\frac{11^T}{n}`.
|
|
38
|
+
If negative, regularization is applied only if the graph is disconnected (and then equal to the absolute value
|
|
39
|
+
of the parameter).
|
|
40
|
+
normalized : bool (default = ``True``)
|
|
41
|
+
If ``True``, normalize the embedding so that each vector has norm 1 in the embedding space, i.e.,
|
|
42
|
+
each vector lies on the unit sphere.
|
|
43
|
+
random_state : int, optional
|
|
44
|
+
Seed used by the random number generator.
|
|
45
|
+
|
|
46
|
+
Attributes
|
|
47
|
+
----------
|
|
48
|
+
embedding_ : array, shape = (n, n_components)
|
|
49
|
+
Embedding of the nodes.
|
|
50
|
+
embedding_row_ : array, shape = (n_row, n_components)
|
|
51
|
+
Embedding of the rows, for bipartite graphs.
|
|
52
|
+
embedding_col_ : array, shape = (n_col, n_components)
|
|
53
|
+
Embedding of the columns, for bipartite graphs.
|
|
54
|
+
|
|
55
|
+
Example
|
|
56
|
+
-------
|
|
57
|
+
>>> from sknetwork.embedding import RandomProjection
|
|
58
|
+
>>> from sknetwork.data import karate_club
|
|
59
|
+
>>> projection = RandomProjection()
|
|
60
|
+
>>> adjacency = karate_club()
|
|
61
|
+
>>> embedding = projection.fit_transform(adjacency)
|
|
62
|
+
>>> embedding.shape
|
|
63
|
+
(34, 2)
|
|
64
|
+
|
|
65
|
+
References
|
|
66
|
+
----------
|
|
67
|
+
Zhang, Z., Cui, P., Li, H., Wang, X., & Zhu, W. (2018).
|
|
68
|
+
Billion-scale network embedding with iterative random projection, ICDM.
|
|
69
|
+
"""
|
|
70
|
+
def __init__(self, n_components: int = 2, alpha: float = 0.5, n_iter: int = 3, random_walk: bool = False,
|
|
71
|
+
regularization: float = -1, normalized: bool = True, random_state: int = None):
|
|
72
|
+
super(RandomProjection, self).__init__()
|
|
73
|
+
|
|
74
|
+
self.n_components = n_components
|
|
75
|
+
self.alpha = alpha
|
|
76
|
+
self.n_iter = n_iter
|
|
77
|
+
self.random_walk = random_walk
|
|
78
|
+
self.regularization = regularization
|
|
79
|
+
self.normalized = normalized
|
|
80
|
+
self.random_state = random_state
|
|
81
|
+
self.bipartite = None
|
|
82
|
+
self.regularized = None
|
|
83
|
+
|
|
84
|
+
def fit(self, input_matrix: Union[sparse.csr_matrix, np.ndarray], force_bipartite: bool = False) \
|
|
85
|
+
-> 'RandomProjection':
|
|
86
|
+
"""Compute the graph embedding.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
input_matrix :
|
|
91
|
+
Adjacency matrix or biadjacency matrix of the graph.
|
|
92
|
+
force_bipartite : bool (default = ``False``)
|
|
93
|
+
If ``True``, force the input matrix to be considered as a biadjacency matrix.
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
self: :class:`RandomProjection`
|
|
97
|
+
"""
|
|
98
|
+
# input
|
|
99
|
+
input_matrix = check_format(input_matrix)
|
|
100
|
+
adjacency, self.bipartite = get_adjacency(input_matrix, force_bipartite=force_bipartite)
|
|
101
|
+
n = adjacency.shape[0]
|
|
102
|
+
|
|
103
|
+
# regularization
|
|
104
|
+
regularization = self._get_regularization(self.regularization, adjacency)
|
|
105
|
+
self.regularized = regularization > 0
|
|
106
|
+
|
|
107
|
+
# multiplier
|
|
108
|
+
if self.random_walk:
|
|
109
|
+
multiplier = Normalizer(adjacency, regularization)
|
|
110
|
+
else:
|
|
111
|
+
multiplier = Regularizer(adjacency, regularization)
|
|
112
|
+
|
|
113
|
+
# random matrix
|
|
114
|
+
random_generator = check_random_state(self.random_state)
|
|
115
|
+
random_matrix = random_generator.normal(size=(n, self.n_components))
|
|
116
|
+
random_matrix, _ = np.linalg.qr(random_matrix)
|
|
117
|
+
|
|
118
|
+
# random projection
|
|
119
|
+
factor = random_matrix
|
|
120
|
+
embedding = factor.copy()
|
|
121
|
+
for t in range(self.n_iter):
|
|
122
|
+
factor = self.alpha * multiplier.dot(factor)
|
|
123
|
+
embedding += factor
|
|
124
|
+
|
|
125
|
+
# normalization
|
|
126
|
+
if self.normalized:
|
|
127
|
+
embedding = normalize(embedding, p=2)
|
|
128
|
+
|
|
129
|
+
# output
|
|
130
|
+
self.embedding_ = embedding
|
|
131
|
+
if self.bipartite:
|
|
132
|
+
self._split_vars(input_matrix.shape)
|
|
133
|
+
return self
|
|
@@ -0,0 +1,214 @@
|
|
|
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
|
|
142
|
+
|
|
143
|
+
def predict(self, adjacency_vectors: Union[sparse.csr_matrix, np.ndarray]) -> np.ndarray:
|
|
144
|
+
"""Predict the embedding of new nodes, when possible (otherwise return 0).
|
|
145
|
+
|
|
146
|
+
Each new node is defined by its adjacency row vector.
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
adjacency_vectors :
|
|
151
|
+
Adjacency vectors of nodes.
|
|
152
|
+
Array of shape (n_col,) (single vector) or (n_vectors, n_col)
|
|
153
|
+
|
|
154
|
+
Returns
|
|
155
|
+
-------
|
|
156
|
+
embedding_vectors : np.ndarray
|
|
157
|
+
Embedding of the nodes.
|
|
158
|
+
|
|
159
|
+
Example
|
|
160
|
+
-------
|
|
161
|
+
>>> from sknetwork.embedding import Spectral
|
|
162
|
+
>>> from sknetwork.data import karate_club
|
|
163
|
+
>>> spectral = Spectral(n_components=3)
|
|
164
|
+
>>> adjacency = karate_club()
|
|
165
|
+
>>> adjacency_vector = np.arange(34) < 5
|
|
166
|
+
>>> _ = spectral.fit(adjacency)
|
|
167
|
+
>>> len(spectral.predict(adjacency_vector))
|
|
168
|
+
3
|
|
169
|
+
"""
|
|
170
|
+
self._check_fitted()
|
|
171
|
+
|
|
172
|
+
# input
|
|
173
|
+
if self.bipartite:
|
|
174
|
+
n = len(self.embedding_col_)
|
|
175
|
+
else:
|
|
176
|
+
n = len(self.embedding_)
|
|
177
|
+
adjacency_vectors = check_adjacency_vector(adjacency_vectors, n)
|
|
178
|
+
check_nonnegative(adjacency_vectors)
|
|
179
|
+
|
|
180
|
+
if self.bipartite:
|
|
181
|
+
shape = (adjacency_vectors.shape[0], self.embedding_row_.shape[0])
|
|
182
|
+
adjacency_vectors = sparse.csr_matrix(adjacency_vectors)
|
|
183
|
+
adjacency_vectors = sparse.hstack([sparse.csr_matrix(shape), adjacency_vectors], format='csr')
|
|
184
|
+
eigenvectors = self.eigenvectors_
|
|
185
|
+
eigenvalues = self.eigenvalues_
|
|
186
|
+
|
|
187
|
+
# regularization
|
|
188
|
+
if self.regularized:
|
|
189
|
+
regularization = np.abs(self.regularization)
|
|
190
|
+
else:
|
|
191
|
+
regularization = 0
|
|
192
|
+
normalizer = Normalizer(adjacency_vectors, regularization)
|
|
193
|
+
|
|
194
|
+
# prediction
|
|
195
|
+
embedding_vectors = normalizer.dot(eigenvectors)
|
|
196
|
+
normalized_laplacian = self.decomposition == 'rw'
|
|
197
|
+
if normalized_laplacian:
|
|
198
|
+
norm_vect = eigenvalues.copy()
|
|
199
|
+
norm_vect[norm_vect == 0] = 1
|
|
200
|
+
embedding_vectors /= norm_vect
|
|
201
|
+
else:
|
|
202
|
+
norm_matrix = sparse.csr_matrix(1 - np.outer(normalizer.norm_diag.data, eigenvalues))
|
|
203
|
+
norm_matrix.data = 1 / norm_matrix.data
|
|
204
|
+
embedding_vectors *= norm_matrix.toarray()
|
|
205
|
+
|
|
206
|
+
# normalization
|
|
207
|
+
if self.normalized:
|
|
208
|
+
embedding_vectors = normalize(embedding_vectors, p=2)
|
|
209
|
+
|
|
210
|
+
# shape
|
|
211
|
+
if len(embedding_vectors) == 1:
|
|
212
|
+
embedding_vectors = embedding_vectors.ravel()
|
|
213
|
+
|
|
214
|
+
return embedding_vectors
|
|
@@ -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
|