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,363 @@
|
|
|
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, diag_pinv, 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 = diag_pinv(np.power(weights_row, self.factor_row))
|
|
134
|
+
diag_col = diag_pinv(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 = diag_pinv(np.power(weights_row, self.factor_row))
|
|
199
|
+
diag_col = diag_pinv(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
|
+
@staticmethod
|
|
281
|
+
def _check_adj_vector(adjacency_vectors: np.ndarray):
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class PCA(SVD):
|
|
286
|
+
"""Graph embedding by Principal Component Analysis of the adjacency or biadjacency matrix.
|
|
287
|
+
|
|
288
|
+
Parameters
|
|
289
|
+
----------
|
|
290
|
+
n_components : int
|
|
291
|
+
Dimension of the embedding.
|
|
292
|
+
normalized : bool (default = ``False``)
|
|
293
|
+
If ``True``, normalized the embedding so that each vector has norm 1 in the embedding space, i.e.,
|
|
294
|
+
each vector lies on the unit sphere.
|
|
295
|
+
solver : ``'lanczos'`` (Lanczos algorithm, default) or :class:`SVDSolver` (custom solver)
|
|
296
|
+
Which solver to use.
|
|
297
|
+
|
|
298
|
+
Attributes
|
|
299
|
+
----------
|
|
300
|
+
embedding_ : array, shape = (n, n_components)
|
|
301
|
+
Embedding of the nodes.
|
|
302
|
+
embedding_row_ : array, shape = (n_row, n_components)
|
|
303
|
+
Embedding of the rows, for bipartite graphs.
|
|
304
|
+
embedding_col_ : array, shape = (n_col, n_components)
|
|
305
|
+
Embedding of the columns, for bipartite graphs.
|
|
306
|
+
singular_values_ : np.ndarray, shape = (n_components)
|
|
307
|
+
Singular values.
|
|
308
|
+
singular_vectors_left_ : np.ndarray, shape = (n_row, n_components)
|
|
309
|
+
Left singular vectors.
|
|
310
|
+
singular_vectors_right_ : np.ndarray, shape = (n_col, n_components)
|
|
311
|
+
Right singular vectors.
|
|
312
|
+
|
|
313
|
+
Example
|
|
314
|
+
-------
|
|
315
|
+
>>> from sknetwork.embedding import PCA
|
|
316
|
+
>>> from sknetwork.data import karate_club
|
|
317
|
+
>>> pca = PCA()
|
|
318
|
+
>>> adjacency = karate_club()
|
|
319
|
+
>>> embedding = pca.fit_transform(adjacency)
|
|
320
|
+
>>> embedding.shape
|
|
321
|
+
(34, 2)
|
|
322
|
+
|
|
323
|
+
References
|
|
324
|
+
----------
|
|
325
|
+
Jolliffe, I.T. (2002).
|
|
326
|
+
`Principal Component Analysis`
|
|
327
|
+
Series: Springer Series in Statistics.
|
|
328
|
+
"""
|
|
329
|
+
def __init__(self, n_components=2, normalized: bool = False, solver: Union[str, SVDSolver] = 'lanczos'):
|
|
330
|
+
super(PCA, self).__init__()
|
|
331
|
+
self.n_components = n_components
|
|
332
|
+
self.normalized = normalized
|
|
333
|
+
if isinstance(solver, str):
|
|
334
|
+
self.solver = LanczosSVD()
|
|
335
|
+
else:
|
|
336
|
+
self.solver = solver
|
|
337
|
+
|
|
338
|
+
def fit(self, adjacency: Union[sparse.csr_matrix, np.ndarray]) -> 'PCA':
|
|
339
|
+
"""Compute the embedding of the graph.
|
|
340
|
+
|
|
341
|
+
Parameters
|
|
342
|
+
----------
|
|
343
|
+
adjacency :
|
|
344
|
+
Adjacency or biadjacency matrix of the graph.
|
|
345
|
+
|
|
346
|
+
Returns
|
|
347
|
+
-------
|
|
348
|
+
self: :class:`PCA`
|
|
349
|
+
"""
|
|
350
|
+
adjacency = check_format(adjacency).asfptype()
|
|
351
|
+
n_row, n_col = adjacency.shape
|
|
352
|
+
adjacency_centered = SparseLR(adjacency, (-np.ones(n_row), adjacency.T.dot(np.ones(n_row)) / n_row))
|
|
353
|
+
|
|
354
|
+
svd = self.solver
|
|
355
|
+
svd.fit(adjacency_centered, self.n_components)
|
|
356
|
+
self.embedding_row_ = svd.singular_vectors_left_
|
|
357
|
+
self.embedding_col_ = svd.singular_vectors_right_
|
|
358
|
+
self.embedding_ = svd.singular_vectors_left_
|
|
359
|
+
self.singular_values_ = svd.singular_values_
|
|
360
|
+
self.singular_vectors_left_ = svd.singular_vectors_left_
|
|
361
|
+
self.singular_vectors_right_ = svd.singular_vectors_right_
|
|
362
|
+
|
|
363
|
+
return self
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""tests for embedding"""
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""tests for embeddings"""
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
from sknetwork.data.test_graphs import *
|
|
8
|
+
from sknetwork.embedding import Spectral, SVD, GSVD, Spring
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestEmbeddings(unittest.TestCase):
|
|
12
|
+
|
|
13
|
+
def setUp(self):
|
|
14
|
+
"""Algorithms by input types."""
|
|
15
|
+
self.methods = [Spectral(), GSVD(), SVD()]
|
|
16
|
+
self.bimethods = [GSVD(), SVD()]
|
|
17
|
+
|
|
18
|
+
def test_undirected(self):
|
|
19
|
+
adjacency = test_graph()
|
|
20
|
+
n = adjacency.shape[0]
|
|
21
|
+
|
|
22
|
+
method = Spring()
|
|
23
|
+
embedding = method.fit_transform(adjacency)
|
|
24
|
+
self.assertEqual(embedding.shape, (n, 2))
|
|
25
|
+
pred1 = method.predict(adjacency[0])
|
|
26
|
+
pred2 = method.predict(adjacency[0].toarray())
|
|
27
|
+
self.assertEqual(pred1.shape, (2,))
|
|
28
|
+
self.assertAlmostEqual(np.linalg.norm(pred1 - pred2), 0)
|
|
29
|
+
|
|
30
|
+
pred1 = method.predict(adjacency)
|
|
31
|
+
pred2 = method.predict(adjacency.toarray())
|
|
32
|
+
self.assertTupleEqual(pred1.shape, (n, 2))
|
|
33
|
+
self.assertAlmostEqual(np.linalg.norm(pred1 - pred2), 0)
|
|
34
|
+
|
|
35
|
+
def test_bimethods(self):
|
|
36
|
+
|
|
37
|
+
for adjacency in [test_digraph(), test_bigraph()]:
|
|
38
|
+
n_row, n_col = adjacency.shape
|
|
39
|
+
|
|
40
|
+
for method in self.bimethods:
|
|
41
|
+
method.fit(adjacency)
|
|
42
|
+
|
|
43
|
+
self.assertEqual(method.embedding_.shape, (n_row, 2))
|
|
44
|
+
self.assertEqual(method.embedding_row_.shape, (n_row, 2))
|
|
45
|
+
self.assertEqual(method.embedding_col_.shape, (n_col, 2))
|
|
46
|
+
|
|
47
|
+
ref = method.embedding_[0]
|
|
48
|
+
pred1 = method.predict(adjacency[0])
|
|
49
|
+
pred2 = method.predict(adjacency[0].toarray())
|
|
50
|
+
|
|
51
|
+
self.assertEqual(pred1.shape, (2,))
|
|
52
|
+
self.assertAlmostEqual(np.linalg.norm(pred1 - pred2), 0)
|
|
53
|
+
self.assertAlmostEqual(np.linalg.norm(pred1 - ref), 0)
|
|
54
|
+
|
|
55
|
+
ref = method.embedding_
|
|
56
|
+
pred1 = method.predict(adjacency)
|
|
57
|
+
pred2 = method.predict(adjacency.toarray())
|
|
58
|
+
|
|
59
|
+
self.assertTupleEqual(pred1.shape, (n_row, 2))
|
|
60
|
+
self.assertAlmostEqual(np.linalg.norm(pred1 - pred2), 0)
|
|
61
|
+
self.assertAlmostEqual(np.linalg.norm(pred1 - ref), 0)
|
|
62
|
+
|
|
63
|
+
def test_disconnected(self):
|
|
64
|
+
n = 10
|
|
65
|
+
adjacency = np.eye(n)
|
|
66
|
+
for method in self.methods:
|
|
67
|
+
embedding = method.fit_transform(adjacency)
|
|
68
|
+
self.assertEqual(embedding.shape, (n, 2))
|
|
69
|
+
|
|
70
|
+
def test_regularization(self):
|
|
71
|
+
adjacency = test_graph()
|
|
72
|
+
method = Spectral()
|
|
73
|
+
self.assertEqual(method._get_regularization(-1, adjacency), 0)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""tests for force atlas2 embeddings"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from sknetwork.data.test_graphs import test_graph, test_digraph
|
|
9
|
+
from sknetwork.embedding.force_atlas import ForceAtlas
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestEmbeddings(unittest.TestCase):
|
|
13
|
+
|
|
14
|
+
def test_options(self):
|
|
15
|
+
for adjacency in [test_graph(), test_digraph()]:
|
|
16
|
+
n = adjacency.shape[0]
|
|
17
|
+
|
|
18
|
+
force_atlas = ForceAtlas()
|
|
19
|
+
layout = force_atlas.fit_transform(adjacency)
|
|
20
|
+
self.assertEqual((n, 2), layout.shape)
|
|
21
|
+
|
|
22
|
+
force_atlas = ForceAtlas(lin_log=True)
|
|
23
|
+
layout = force_atlas.fit_transform(adjacency)
|
|
24
|
+
self.assertEqual((n, 2), layout.shape)
|
|
25
|
+
|
|
26
|
+
force_atlas = ForceAtlas(approx_radius=1.)
|
|
27
|
+
layout = force_atlas.fit_transform(adjacency)
|
|
28
|
+
self.assertEqual((n, 2), layout.shape)
|
|
29
|
+
|
|
30
|
+
force_atlas.fit(adjacency, pos_init=layout, n_iter=1)
|
|
31
|
+
|
|
32
|
+
def test_errors(self):
|
|
33
|
+
adjacency = test_graph()
|
|
34
|
+
with self.assertRaises(ValueError):
|
|
35
|
+
ForceAtlas().fit(adjacency, pos_init=np.ones((5, 7)))
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""tests for Louvain embedding"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from sknetwork.data.test_graphs import test_graph, test_bigraph
|
|
9
|
+
from sknetwork.embedding import LouvainEmbedding
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestLouvainEmbedding(unittest.TestCase):
|
|
13
|
+
|
|
14
|
+
def test_predict(self):
|
|
15
|
+
louvain = LouvainEmbedding()
|
|
16
|
+
louvain.fit(test_graph())
|
|
17
|
+
self.assertEqual(louvain.embedding_.shape[0], 10)
|
|
18
|
+
louvain.fit(test_graph(), force_bipartite=True)
|
|
19
|
+
self.assertEqual(louvain.embedding_.shape[0], 10)
|
|
20
|
+
|
|
21
|
+
for method in ['remove', 'merge', 'keep']:
|
|
22
|
+
louvain = LouvainEmbedding(isolated_nodes=method)
|
|
23
|
+
louvain.fit(test_graph())
|
|
24
|
+
embedding_vector = louvain.predict(np.array([1, 0, 0, 0, 1, 1, 0, 0, 0, 1]))
|
|
25
|
+
self.assertEqual(embedding_vector.shape[0], 1)
|
|
26
|
+
|
|
27
|
+
for method in ['remove', 'merge', 'keep']:
|
|
28
|
+
bilouvain = LouvainEmbedding(isolated_nodes=method)
|
|
29
|
+
bilouvain.fit(test_bigraph())
|
|
30
|
+
embedding_vector = bilouvain.predict(np.array([1, 0, 0, 0, 1, 1, 0, 1]))
|
|
31
|
+
self.assertEqual(embedding_vector.shape[0], 1)
|
|
32
|
+
|
|
33
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""tests for LouvainNE"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from sknetwork.data.test_graphs import test_graph, test_graph_disconnect, test_digraph, test_bigraph
|
|
7
|
+
from sknetwork.embedding import LouvainNE
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestLouvainNE(unittest.TestCase):
|
|
11
|
+
|
|
12
|
+
def test_louvain_hierarchy(self):
|
|
13
|
+
louvain = LouvainNE()
|
|
14
|
+
for adjacency in [test_graph(), test_graph_disconnect(), test_digraph()]:
|
|
15
|
+
self.assertTupleEqual(louvain.fit_transform(adjacency).shape, (10, 2))
|
|
16
|
+
louvain.fit(test_bigraph())
|
|
17
|
+
self.assertTupleEqual(louvain.embedding_.shape, (6, 2))
|
|
18
|
+
louvain.fit(test_graph(), force_bipartite=True)
|
|
19
|
+
self.assertTupleEqual(louvain.embedding_.shape, (10, 2))
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""tests for embeddings metrics"""
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
from sknetwork.data.test_graphs import test_bigraph, test_graph
|
|
8
|
+
from sknetwork.embedding import Spectral
|
|
9
|
+
from sknetwork.embedding.metrics import get_cosine_similarity
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestClusteringMetrics(unittest.TestCase):
|
|
13
|
+
|
|
14
|
+
def test_cosine(self):
|
|
15
|
+
biadjacency = test_bigraph()
|
|
16
|
+
method = Spectral(3)
|
|
17
|
+
|
|
18
|
+
embedding = method.fit_transform(biadjacency)
|
|
19
|
+
embedding_col = method.embedding_col_
|
|
20
|
+
similarity = get_cosine_similarity(biadjacency, embedding, embedding_col)
|
|
21
|
+
self.assertAlmostEqual(similarity, 0.56, 2)
|
|
22
|
+
|
|
23
|
+
adjacency = test_graph()
|
|
24
|
+
embedding = method.fit_transform(adjacency)
|
|
25
|
+
similarity = get_cosine_similarity(adjacency, embedding)
|
|
26
|
+
self.assertAlmostEqual(similarity, 0.32, 2)
|
|
27
|
+
|
|
28
|
+
with self.assertRaises(ValueError):
|
|
29
|
+
get_cosine_similarity(biadjacency, embedding)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for random projection"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from sknetwork.data.test_graphs import test_graph, test_bigraph, test_digraph, test_graph_disconnect
|
|
7
|
+
from sknetwork.embedding import RandomProjection
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestEmbeddings(unittest.TestCase):
|
|
11
|
+
|
|
12
|
+
def test_random_projection(self):
|
|
13
|
+
for algo in [RandomProjection(), RandomProjection(random_walk=True)]:
|
|
14
|
+
adjacency = test_graph()
|
|
15
|
+
embedding = algo.fit_transform(adjacency)
|
|
16
|
+
self.assertEqual(embedding.shape[1], 2)
|
|
17
|
+
embedding = algo.fit_transform(adjacency, force_bipartite=True)
|
|
18
|
+
self.assertEqual(embedding.shape[1], 2)
|
|
19
|
+
adjacency = test_digraph()
|
|
20
|
+
embedding = algo.fit_transform(adjacency)
|
|
21
|
+
self.assertEqual(embedding.shape[1], 2)
|
|
22
|
+
adjacency = test_graph_disconnect()
|
|
23
|
+
embedding = algo.fit_transform(adjacency)
|
|
24
|
+
self.assertEqual(embedding.shape[1], 2)
|
|
25
|
+
biadjacency = test_bigraph()
|
|
26
|
+
embedding = algo.fit_transform(biadjacency)
|
|
27
|
+
self.assertEqual(embedding.shape[1], 2)
|
|
28
|
+
self.assertEqual(algo.embedding_col_.shape[1], 2)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for spectral embedding."""
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
from sknetwork.data.test_graphs import *
|
|
8
|
+
from sknetwork.embedding import Spectral
|
|
9
|
+
from sknetwork.utils.check import is_weakly_connected
|
|
10
|
+
from sknetwork.utils.format import bipartite2undirected
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestEmbeddings(unittest.TestCase):
|
|
14
|
+
|
|
15
|
+
def test_undirected(self):
|
|
16
|
+
for adjacency in [test_graph(), test_graph_disconnect()]:
|
|
17
|
+
n = adjacency.shape[0]
|
|
18
|
+
# random walk
|
|
19
|
+
spectral = Spectral(3, normalized=False)
|
|
20
|
+
embedding = spectral.fit_transform(adjacency)
|
|
21
|
+
weights = adjacency.dot(np.ones(n))
|
|
22
|
+
if not is_weakly_connected(adjacency):
|
|
23
|
+
weights += 1
|
|
24
|
+
self.assertAlmostEqual(np.linalg.norm(embedding.T.dot(weights)), 0)
|
|
25
|
+
self.assertAlmostEqual(np.linalg.norm(embedding[1:4] - spectral.predict(adjacency[1:4])), 0)
|
|
26
|
+
# Laplacian
|
|
27
|
+
spectral = Spectral(3, decomposition='laplacian', normalized=False)
|
|
28
|
+
embedding = spectral.fit_transform(adjacency)
|
|
29
|
+
self.assertAlmostEqual(np.linalg.norm(embedding.sum(axis=0)), 0)
|
|
30
|
+
self.assertAlmostEqual(np.linalg.norm(embedding[1:4] - spectral.predict(adjacency[1:4])), 0)
|
|
31
|
+
|
|
32
|
+
def test_directed(self):
|
|
33
|
+
for adjacency in [test_digraph(), test_digraph().astype(bool)]:
|
|
34
|
+
# random walk
|
|
35
|
+
spectral = Spectral(3, normalized=False)
|
|
36
|
+
embedding = spectral.fit_transform(adjacency)
|
|
37
|
+
self.assertAlmostEqual(np.linalg.norm(embedding[6:8] - spectral.predict(adjacency[6:8])), 0)
|
|
38
|
+
# Laplacian
|
|
39
|
+
spectral = Spectral(3, decomposition='laplacian', normalized=False)
|
|
40
|
+
embedding = spectral.fit_transform(adjacency)
|
|
41
|
+
self.assertAlmostEqual(np.linalg.norm(spectral.eigenvectors_.sum(axis=0)), 0)
|
|
42
|
+
self.assertAlmostEqual(np.linalg.norm(embedding[6:8] - spectral.predict(adjacency[6:8])), 0)
|
|
43
|
+
|
|
44
|
+
def test_regularization(self):
|
|
45
|
+
for adjacency in [test_graph(), test_graph_disconnect()]:
|
|
46
|
+
n = adjacency.shape[0]
|
|
47
|
+
# random walk
|
|
48
|
+
regularization = 0.1
|
|
49
|
+
spectral = Spectral(3, regularization=regularization, normalized=False)
|
|
50
|
+
embedding = spectral.fit_transform(adjacency)
|
|
51
|
+
weights = adjacency.dot(np.ones(n)) + regularization
|
|
52
|
+
self.assertAlmostEqual(np.linalg.norm(embedding.T.dot(weights)), 0)
|
|
53
|
+
# Laplacian
|
|
54
|
+
spectral = Spectral(3, decomposition='laplacian', regularization=1, normalized=False)
|
|
55
|
+
embedding = spectral.fit_transform(adjacency)
|
|
56
|
+
self.assertAlmostEqual(np.linalg.norm(embedding.sum(axis=0)), 0)
|
|
57
|
+
# without regularization
|
|
58
|
+
spectral = Spectral(3, decomposition='laplacian', regularization=-1, normalized=False)
|
|
59
|
+
embedding = spectral.fit_transform(adjacency)
|
|
60
|
+
self.assertAlmostEqual(np.linalg.norm(embedding.sum(axis=0)), 0)
|
|
61
|
+
|
|
62
|
+
def test_bipartite(self):
|
|
63
|
+
for biadjacency in [test_digraph(), test_bigraph(), test_bigraph_disconnect()]:
|
|
64
|
+
n_row, n_col = biadjacency.shape
|
|
65
|
+
adjacency = bipartite2undirected(biadjacency)
|
|
66
|
+
# random walk
|
|
67
|
+
spectral = Spectral(3, normalized=False)
|
|
68
|
+
spectral.fit(biadjacency)
|
|
69
|
+
embedding_full = np.vstack([spectral.embedding_row_, spectral.embedding_col_])
|
|
70
|
+
weights = adjacency.dot(np.ones(n_row + n_col))
|
|
71
|
+
if not is_weakly_connected(adjacency):
|
|
72
|
+
weights += 1
|
|
73
|
+
self.assertAlmostEqual(np.linalg.norm(embedding_full.T.dot(weights)), 0)
|
|
74
|
+
# Laplacian
|
|
75
|
+
spectral = Spectral(3, decomposition='laplacian', normalized=False)
|
|
76
|
+
spectral.fit(biadjacency)
|
|
77
|
+
embedding_full = np.vstack([spectral.embedding_row_, spectral.embedding_col_])
|
|
78
|
+
self.assertAlmostEqual(np.linalg.norm(embedding_full.sum(axis=0)), 0)
|
|
79
|
+
|
|
80
|
+
def test_normalization(self):
|
|
81
|
+
for adjacency in [test_graph(), test_graph_disconnect()]:
|
|
82
|
+
spectral = Spectral(3)
|
|
83
|
+
embedding = spectral.fit_transform(adjacency)
|
|
84
|
+
self.assertAlmostEqual(np.linalg.norm(np.linalg.norm(embedding, axis=1) - np.ones(adjacency.shape[0])), 0)
|