scikit-network 0.33.3__cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.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.33.3.dist-info/METADATA +122 -0
- scikit_network-0.33.3.dist-info/RECORD +229 -0
- scikit_network-0.33.3.dist-info/WHEEL +6 -0
- scikit_network-0.33.3.dist-info/licenses/AUTHORS.rst +43 -0
- scikit_network-0.33.3.dist-info/licenses/LICENSE +34 -0
- scikit_network-0.33.3.dist-info/top_level.txt +1 -0
- scikit_network.libs/libgomp-d22c30c5.so.1.0.0 +0 -0
- sknetwork/__init__.py +21 -0
- sknetwork/base.py +67 -0
- sknetwork/classification/__init__.py +8 -0
- sknetwork/classification/base.py +142 -0
- sknetwork/classification/base_rank.py +133 -0
- sknetwork/classification/diffusion.py +134 -0
- sknetwork/classification/knn.py +139 -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 +30 -0
- sknetwork/classification/tests/test_diffusion.py +77 -0
- sknetwork/classification/tests/test_knn.py +23 -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.cpp +27587 -0
- sknetwork/classification/vote.cpython-39-aarch64-linux-gnu.so +0 -0
- sknetwork/classification/vote.pyx +56 -0
- sknetwork/clustering/__init__.py +8 -0
- sknetwork/clustering/base.py +172 -0
- sknetwork/clustering/kcenters.py +253 -0
- sknetwork/clustering/leiden.py +242 -0
- sknetwork/clustering/leiden_core.cpp +31578 -0
- sknetwork/clustering/leiden_core.cpython-39-aarch64-linux-gnu.so +0 -0
- sknetwork/clustering/leiden_core.pyx +124 -0
- sknetwork/clustering/louvain.py +286 -0
- sknetwork/clustering/louvain_core.cpp +31223 -0
- sknetwork/clustering/louvain_core.cpython-39-aarch64-linux-gnu.so +0 -0
- sknetwork/clustering/louvain_core.pyx +124 -0
- sknetwork/clustering/metrics.py +91 -0
- sknetwork/clustering/postprocess.py +66 -0
- sknetwork/clustering/propagation_clustering.py +104 -0
- sknetwork/clustering/tests/__init__.py +1 -0
- sknetwork/clustering/tests/test_API.py +38 -0
- sknetwork/clustering/tests/test_kcenters.py +60 -0
- sknetwork/clustering/tests/test_leiden.py +34 -0
- sknetwork/clustering/tests/test_louvain.py +135 -0
- sknetwork/clustering/tests/test_metrics.py +50 -0
- sknetwork/clustering/tests/test_postprocess.py +39 -0
- sknetwork/data/__init__.py +6 -0
- sknetwork/data/base.py +33 -0
- sknetwork/data/load.py +406 -0
- sknetwork/data/models.py +459 -0
- sknetwork/data/parse.py +644 -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_base.py +14 -0
- sknetwork/data/tests/test_load.py +95 -0
- sknetwork/data/tests/test_models.py +52 -0
- sknetwork/data/tests/test_parse.py +250 -0
- sknetwork/data/tests/test_test_graphs.py +29 -0
- sknetwork/data/tests/test_toy_graphs.py +68 -0
- sknetwork/data/timeout.py +38 -0
- sknetwork/data/toy_graphs.py +611 -0
- sknetwork/embedding/__init__.py +8 -0
- sknetwork/embedding/base.py +94 -0
- sknetwork/embedding/force_atlas.py +198 -0
- sknetwork/embedding/louvain_embedding.py +148 -0
- sknetwork/embedding/random_projection.py +135 -0
- sknetwork/embedding/spectral.py +141 -0
- sknetwork/embedding/spring.py +198 -0
- sknetwork/embedding/svd.py +359 -0
- sknetwork/embedding/tests/__init__.py +1 -0
- sknetwork/embedding/tests/test_API.py +49 -0
- sknetwork/embedding/tests/test_force_atlas.py +35 -0
- sknetwork/embedding/tests/test_louvain_embedding.py +33 -0
- sknetwork/embedding/tests/test_random_projection.py +28 -0
- sknetwork/embedding/tests/test_spectral.py +81 -0
- sknetwork/embedding/tests/test_spring.py +50 -0
- sknetwork/embedding/tests/test_svd.py +43 -0
- sknetwork/gnn/__init__.py +10 -0
- sknetwork/gnn/activation.py +117 -0
- sknetwork/gnn/base.py +181 -0
- sknetwork/gnn/base_activation.py +90 -0
- sknetwork/gnn/base_layer.py +109 -0
- sknetwork/gnn/gnn_classifier.py +305 -0
- sknetwork/gnn/layer.py +153 -0
- sknetwork/gnn/loss.py +180 -0
- sknetwork/gnn/neighbor_sampler.py +65 -0
- sknetwork/gnn/optimizer.py +164 -0
- sknetwork/gnn/tests/__init__.py +1 -0
- sknetwork/gnn/tests/test_activation.py +56 -0
- sknetwork/gnn/tests/test_base.py +75 -0
- sknetwork/gnn/tests/test_base_layer.py +37 -0
- sknetwork/gnn/tests/test_gnn_classifier.py +130 -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 +41 -0
- sknetwork/gnn/utils.py +127 -0
- sknetwork/hierarchy/__init__.py +6 -0
- sknetwork/hierarchy/base.py +96 -0
- sknetwork/hierarchy/louvain_hierarchy.py +272 -0
- sknetwork/hierarchy/metrics.py +234 -0
- sknetwork/hierarchy/paris.cpp +37889 -0
- sknetwork/hierarchy/paris.cpython-39-aarch64-linux-gnu.so +0 -0
- sknetwork/hierarchy/paris.pyx +316 -0
- sknetwork/hierarchy/postprocess.py +350 -0
- sknetwork/hierarchy/tests/__init__.py +1 -0
- sknetwork/hierarchy/tests/test_API.py +24 -0
- sknetwork/hierarchy/tests/test_algos.py +34 -0
- sknetwork/hierarchy/tests/test_metrics.py +62 -0
- sknetwork/hierarchy/tests/test_postprocess.py +57 -0
- sknetwork/linalg/__init__.py +9 -0
- sknetwork/linalg/basics.py +37 -0
- sknetwork/linalg/diteration.cpp +27403 -0
- sknetwork/linalg/diteration.cpython-39-aarch64-linux-gnu.so +0 -0
- sknetwork/linalg/diteration.pyx +47 -0
- sknetwork/linalg/eig_solver.py +93 -0
- sknetwork/linalg/laplacian.py +15 -0
- sknetwork/linalg/normalizer.py +86 -0
- sknetwork/linalg/operators.py +225 -0
- sknetwork/linalg/polynome.py +76 -0
- sknetwork/linalg/ppr_solver.py +170 -0
- sknetwork/linalg/push.cpp +31093 -0
- sknetwork/linalg/push.cpython-39-aarch64-linux-gnu.so +0 -0
- sknetwork/linalg/push.pyx +71 -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 +34 -0
- sknetwork/linalg/tests/test_operators.py +66 -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 +2 -0
- sknetwork/linkpred/base.py +46 -0
- sknetwork/linkpred/nn.py +126 -0
- sknetwork/linkpred/tests/__init__.py +1 -0
- sknetwork/linkpred/tests/test_nn.py +27 -0
- sknetwork/log.py +19 -0
- sknetwork/path/__init__.py +5 -0
- sknetwork/path/dag.py +54 -0
- sknetwork/path/distances.py +98 -0
- sknetwork/path/search.py +31 -0
- sknetwork/path/shortest_path.py +61 -0
- sknetwork/path/tests/__init__.py +1 -0
- sknetwork/path/tests/test_dag.py +37 -0
- sknetwork/path/tests/test_distances.py +62 -0
- sknetwork/path/tests/test_search.py +40 -0
- sknetwork/path/tests/test_shortest_path.py +40 -0
- sknetwork/ranking/__init__.py +8 -0
- sknetwork/ranking/base.py +61 -0
- sknetwork/ranking/betweenness.cpp +9710 -0
- sknetwork/ranking/betweenness.cpython-39-aarch64-linux-gnu.so +0 -0
- sknetwork/ranking/betweenness.pyx +97 -0
- sknetwork/ranking/closeness.py +92 -0
- sknetwork/ranking/hits.py +94 -0
- sknetwork/ranking/katz.py +83 -0
- sknetwork/ranking/pagerank.py +110 -0
- sknetwork/ranking/postprocess.py +37 -0
- sknetwork/ranking/tests/__init__.py +1 -0
- sknetwork/ranking/tests/test_API.py +32 -0
- sknetwork/ranking/tests/test_betweenness.py +38 -0
- sknetwork/ranking/tests/test_closeness.py +30 -0
- sknetwork/ranking/tests/test_hits.py +20 -0
- sknetwork/ranking/tests/test_pagerank.py +62 -0
- sknetwork/ranking/tests/test_postprocess.py +26 -0
- sknetwork/regression/__init__.py +4 -0
- sknetwork/regression/base.py +61 -0
- sknetwork/regression/diffusion.py +210 -0
- sknetwork/regression/tests/__init__.py +1 -0
- sknetwork/regression/tests/test_API.py +32 -0
- sknetwork/regression/tests/test_diffusion.py +56 -0
- sknetwork/sknetwork.py +3 -0
- sknetwork/test_base.py +35 -0
- sknetwork/test_log.py +15 -0
- sknetwork/topology/__init__.py +8 -0
- sknetwork/topology/cliques.cpp +32586 -0
- sknetwork/topology/cliques.cpython-39-aarch64-linux-gnu.so +0 -0
- sknetwork/topology/cliques.pyx +149 -0
- sknetwork/topology/core.cpp +30672 -0
- sknetwork/topology/core.cpython-39-aarch64-linux-gnu.so +0 -0
- sknetwork/topology/core.pyx +90 -0
- sknetwork/topology/cycles.py +243 -0
- sknetwork/topology/minheap.cpp +27335 -0
- sknetwork/topology/minheap.cpython-39-aarch64-linux-gnu.so +0 -0
- sknetwork/topology/minheap.pxd +20 -0
- sknetwork/topology/minheap.pyx +109 -0
- sknetwork/topology/structure.py +194 -0
- sknetwork/topology/tests/__init__.py +1 -0
- sknetwork/topology/tests/test_cliques.py +28 -0
- sknetwork/topology/tests/test_core.py +19 -0
- sknetwork/topology/tests/test_cycles.py +65 -0
- sknetwork/topology/tests/test_structure.py +85 -0
- sknetwork/topology/tests/test_triangles.py +38 -0
- sknetwork/topology/tests/test_wl.py +72 -0
- sknetwork/topology/triangles.cpp +8897 -0
- sknetwork/topology/triangles.cpython-39-aarch64-linux-gnu.so +0 -0
- sknetwork/topology/triangles.pyx +151 -0
- sknetwork/topology/weisfeiler_lehman.py +133 -0
- sknetwork/topology/weisfeiler_lehman_core.cpp +27638 -0
- sknetwork/topology/weisfeiler_lehman_core.cpython-39-aarch64-linux-gnu.so +0 -0
- sknetwork/topology/weisfeiler_lehman_core.pyx +114 -0
- sknetwork/utils/__init__.py +7 -0
- sknetwork/utils/check.py +355 -0
- sknetwork/utils/format.py +221 -0
- sknetwork/utils/membership.py +82 -0
- sknetwork/utils/neighbors.py +115 -0
- sknetwork/utils/tests/__init__.py +1 -0
- sknetwork/utils/tests/test_check.py +190 -0
- sknetwork/utils/tests/test_format.py +63 -0
- sknetwork/utils/tests/test_membership.py +24 -0
- sknetwork/utils/tests/test_neighbors.py +41 -0
- sknetwork/utils/tests/test_tfidf.py +18 -0
- sknetwork/utils/tests/test_values.py +66 -0
- sknetwork/utils/tfidf.py +37 -0
- sknetwork/utils/values.py +76 -0
- sknetwork/visualization/__init__.py +4 -0
- sknetwork/visualization/colors.py +34 -0
- sknetwork/visualization/dendrograms.py +277 -0
- sknetwork/visualization/graphs.py +1039 -0
- sknetwork/visualization/tests/__init__.py +1 -0
- sknetwork/visualization/tests/test_dendrograms.py +53 -0
- 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"""
|