scikit-network 0.33.3__cp313-cp313-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.

Files changed (229) hide show
  1. scikit_network-0.33.3.dist-info/METADATA +122 -0
  2. scikit_network-0.33.3.dist-info/RECORD +229 -0
  3. scikit_network-0.33.3.dist-info/WHEEL +6 -0
  4. scikit_network-0.33.3.dist-info/licenses/AUTHORS.rst +43 -0
  5. scikit_network-0.33.3.dist-info/licenses/LICENSE +34 -0
  6. scikit_network-0.33.3.dist-info/top_level.txt +1 -0
  7. scikit_network.libs/libgomp-d22c30c5.so.1.0.0 +0 -0
  8. sknetwork/__init__.py +21 -0
  9. sknetwork/base.py +67 -0
  10. sknetwork/classification/__init__.py +8 -0
  11. sknetwork/classification/base.py +142 -0
  12. sknetwork/classification/base_rank.py +133 -0
  13. sknetwork/classification/diffusion.py +134 -0
  14. sknetwork/classification/knn.py +139 -0
  15. sknetwork/classification/metrics.py +205 -0
  16. sknetwork/classification/pagerank.py +66 -0
  17. sknetwork/classification/propagation.py +152 -0
  18. sknetwork/classification/tests/__init__.py +1 -0
  19. sknetwork/classification/tests/test_API.py +30 -0
  20. sknetwork/classification/tests/test_diffusion.py +77 -0
  21. sknetwork/classification/tests/test_knn.py +23 -0
  22. sknetwork/classification/tests/test_metrics.py +53 -0
  23. sknetwork/classification/tests/test_pagerank.py +20 -0
  24. sknetwork/classification/tests/test_propagation.py +24 -0
  25. sknetwork/classification/vote.cpp +27587 -0
  26. sknetwork/classification/vote.cpython-313-aarch64-linux-gnu.so +0 -0
  27. sknetwork/classification/vote.pyx +56 -0
  28. sknetwork/clustering/__init__.py +8 -0
  29. sknetwork/clustering/base.py +172 -0
  30. sknetwork/clustering/kcenters.py +253 -0
  31. sknetwork/clustering/leiden.py +242 -0
  32. sknetwork/clustering/leiden_core.cpp +31578 -0
  33. sknetwork/clustering/leiden_core.cpython-313-aarch64-linux-gnu.so +0 -0
  34. sknetwork/clustering/leiden_core.pyx +124 -0
  35. sknetwork/clustering/louvain.py +286 -0
  36. sknetwork/clustering/louvain_core.cpp +31223 -0
  37. sknetwork/clustering/louvain_core.cpython-313-aarch64-linux-gnu.so +0 -0
  38. sknetwork/clustering/louvain_core.pyx +124 -0
  39. sknetwork/clustering/metrics.py +91 -0
  40. sknetwork/clustering/postprocess.py +66 -0
  41. sknetwork/clustering/propagation_clustering.py +104 -0
  42. sknetwork/clustering/tests/__init__.py +1 -0
  43. sknetwork/clustering/tests/test_API.py +38 -0
  44. sknetwork/clustering/tests/test_kcenters.py +60 -0
  45. sknetwork/clustering/tests/test_leiden.py +34 -0
  46. sknetwork/clustering/tests/test_louvain.py +135 -0
  47. sknetwork/clustering/tests/test_metrics.py +50 -0
  48. sknetwork/clustering/tests/test_postprocess.py +39 -0
  49. sknetwork/data/__init__.py +6 -0
  50. sknetwork/data/base.py +33 -0
  51. sknetwork/data/load.py +406 -0
  52. sknetwork/data/models.py +459 -0
  53. sknetwork/data/parse.py +644 -0
  54. sknetwork/data/test_graphs.py +84 -0
  55. sknetwork/data/tests/__init__.py +1 -0
  56. sknetwork/data/tests/test_API.py +30 -0
  57. sknetwork/data/tests/test_base.py +14 -0
  58. sknetwork/data/tests/test_load.py +95 -0
  59. sknetwork/data/tests/test_models.py +52 -0
  60. sknetwork/data/tests/test_parse.py +250 -0
  61. sknetwork/data/tests/test_test_graphs.py +29 -0
  62. sknetwork/data/tests/test_toy_graphs.py +68 -0
  63. sknetwork/data/timeout.py +38 -0
  64. sknetwork/data/toy_graphs.py +611 -0
  65. sknetwork/embedding/__init__.py +8 -0
  66. sknetwork/embedding/base.py +94 -0
  67. sknetwork/embedding/force_atlas.py +198 -0
  68. sknetwork/embedding/louvain_embedding.py +148 -0
  69. sknetwork/embedding/random_projection.py +135 -0
  70. sknetwork/embedding/spectral.py +141 -0
  71. sknetwork/embedding/spring.py +198 -0
  72. sknetwork/embedding/svd.py +359 -0
  73. sknetwork/embedding/tests/__init__.py +1 -0
  74. sknetwork/embedding/tests/test_API.py +49 -0
  75. sknetwork/embedding/tests/test_force_atlas.py +35 -0
  76. sknetwork/embedding/tests/test_louvain_embedding.py +33 -0
  77. sknetwork/embedding/tests/test_random_projection.py +28 -0
  78. sknetwork/embedding/tests/test_spectral.py +81 -0
  79. sknetwork/embedding/tests/test_spring.py +50 -0
  80. sknetwork/embedding/tests/test_svd.py +43 -0
  81. sknetwork/gnn/__init__.py +10 -0
  82. sknetwork/gnn/activation.py +117 -0
  83. sknetwork/gnn/base.py +181 -0
  84. sknetwork/gnn/base_activation.py +90 -0
  85. sknetwork/gnn/base_layer.py +109 -0
  86. sknetwork/gnn/gnn_classifier.py +305 -0
  87. sknetwork/gnn/layer.py +153 -0
  88. sknetwork/gnn/loss.py +180 -0
  89. sknetwork/gnn/neighbor_sampler.py +65 -0
  90. sknetwork/gnn/optimizer.py +164 -0
  91. sknetwork/gnn/tests/__init__.py +1 -0
  92. sknetwork/gnn/tests/test_activation.py +56 -0
  93. sknetwork/gnn/tests/test_base.py +75 -0
  94. sknetwork/gnn/tests/test_base_layer.py +37 -0
  95. sknetwork/gnn/tests/test_gnn_classifier.py +130 -0
  96. sknetwork/gnn/tests/test_layers.py +80 -0
  97. sknetwork/gnn/tests/test_loss.py +33 -0
  98. sknetwork/gnn/tests/test_neigh_sampler.py +23 -0
  99. sknetwork/gnn/tests/test_optimizer.py +43 -0
  100. sknetwork/gnn/tests/test_utils.py +41 -0
  101. sknetwork/gnn/utils.py +127 -0
  102. sknetwork/hierarchy/__init__.py +6 -0
  103. sknetwork/hierarchy/base.py +96 -0
  104. sknetwork/hierarchy/louvain_hierarchy.py +272 -0
  105. sknetwork/hierarchy/metrics.py +234 -0
  106. sknetwork/hierarchy/paris.cpp +37871 -0
  107. sknetwork/hierarchy/paris.cpython-313-aarch64-linux-gnu.so +0 -0
  108. sknetwork/hierarchy/paris.pyx +316 -0
  109. sknetwork/hierarchy/postprocess.py +350 -0
  110. sknetwork/hierarchy/tests/__init__.py +1 -0
  111. sknetwork/hierarchy/tests/test_API.py +24 -0
  112. sknetwork/hierarchy/tests/test_algos.py +34 -0
  113. sknetwork/hierarchy/tests/test_metrics.py +62 -0
  114. sknetwork/hierarchy/tests/test_postprocess.py +57 -0
  115. sknetwork/linalg/__init__.py +9 -0
  116. sknetwork/linalg/basics.py +37 -0
  117. sknetwork/linalg/diteration.cpp +27403 -0
  118. sknetwork/linalg/diteration.cpython-313-aarch64-linux-gnu.so +0 -0
  119. sknetwork/linalg/diteration.pyx +47 -0
  120. sknetwork/linalg/eig_solver.py +93 -0
  121. sknetwork/linalg/laplacian.py +15 -0
  122. sknetwork/linalg/normalizer.py +86 -0
  123. sknetwork/linalg/operators.py +225 -0
  124. sknetwork/linalg/polynome.py +76 -0
  125. sknetwork/linalg/ppr_solver.py +170 -0
  126. sknetwork/linalg/push.cpp +31075 -0
  127. sknetwork/linalg/push.cpython-313-aarch64-linux-gnu.so +0 -0
  128. sknetwork/linalg/push.pyx +71 -0
  129. sknetwork/linalg/sparse_lowrank.py +142 -0
  130. sknetwork/linalg/svd_solver.py +91 -0
  131. sknetwork/linalg/tests/__init__.py +1 -0
  132. sknetwork/linalg/tests/test_eig.py +44 -0
  133. sknetwork/linalg/tests/test_laplacian.py +18 -0
  134. sknetwork/linalg/tests/test_normalization.py +34 -0
  135. sknetwork/linalg/tests/test_operators.py +66 -0
  136. sknetwork/linalg/tests/test_polynome.py +38 -0
  137. sknetwork/linalg/tests/test_ppr.py +50 -0
  138. sknetwork/linalg/tests/test_sparse_lowrank.py +61 -0
  139. sknetwork/linalg/tests/test_svd.py +38 -0
  140. sknetwork/linkpred/__init__.py +2 -0
  141. sknetwork/linkpred/base.py +46 -0
  142. sknetwork/linkpred/nn.py +126 -0
  143. sknetwork/linkpred/tests/__init__.py +1 -0
  144. sknetwork/linkpred/tests/test_nn.py +27 -0
  145. sknetwork/log.py +19 -0
  146. sknetwork/path/__init__.py +5 -0
  147. sknetwork/path/dag.py +54 -0
  148. sknetwork/path/distances.py +98 -0
  149. sknetwork/path/search.py +31 -0
  150. sknetwork/path/shortest_path.py +61 -0
  151. sknetwork/path/tests/__init__.py +1 -0
  152. sknetwork/path/tests/test_dag.py +37 -0
  153. sknetwork/path/tests/test_distances.py +62 -0
  154. sknetwork/path/tests/test_search.py +40 -0
  155. sknetwork/path/tests/test_shortest_path.py +40 -0
  156. sknetwork/ranking/__init__.py +8 -0
  157. sknetwork/ranking/base.py +61 -0
  158. sknetwork/ranking/betweenness.cpp +9710 -0
  159. sknetwork/ranking/betweenness.cpython-313-aarch64-linux-gnu.so +0 -0
  160. sknetwork/ranking/betweenness.pyx +97 -0
  161. sknetwork/ranking/closeness.py +92 -0
  162. sknetwork/ranking/hits.py +94 -0
  163. sknetwork/ranking/katz.py +83 -0
  164. sknetwork/ranking/pagerank.py +110 -0
  165. sknetwork/ranking/postprocess.py +37 -0
  166. sknetwork/ranking/tests/__init__.py +1 -0
  167. sknetwork/ranking/tests/test_API.py +32 -0
  168. sknetwork/ranking/tests/test_betweenness.py +38 -0
  169. sknetwork/ranking/tests/test_closeness.py +30 -0
  170. sknetwork/ranking/tests/test_hits.py +20 -0
  171. sknetwork/ranking/tests/test_pagerank.py +62 -0
  172. sknetwork/ranking/tests/test_postprocess.py +26 -0
  173. sknetwork/regression/__init__.py +4 -0
  174. sknetwork/regression/base.py +61 -0
  175. sknetwork/regression/diffusion.py +210 -0
  176. sknetwork/regression/tests/__init__.py +1 -0
  177. sknetwork/regression/tests/test_API.py +32 -0
  178. sknetwork/regression/tests/test_diffusion.py +56 -0
  179. sknetwork/sknetwork.py +3 -0
  180. sknetwork/test_base.py +35 -0
  181. sknetwork/test_log.py +15 -0
  182. sknetwork/topology/__init__.py +8 -0
  183. sknetwork/topology/cliques.cpp +32568 -0
  184. sknetwork/topology/cliques.cpython-313-aarch64-linux-gnu.so +0 -0
  185. sknetwork/topology/cliques.pyx +149 -0
  186. sknetwork/topology/core.cpp +30654 -0
  187. sknetwork/topology/core.cpython-313-aarch64-linux-gnu.so +0 -0
  188. sknetwork/topology/core.pyx +90 -0
  189. sknetwork/topology/cycles.py +243 -0
  190. sknetwork/topology/minheap.cpp +27335 -0
  191. sknetwork/topology/minheap.cpython-313-aarch64-linux-gnu.so +0 -0
  192. sknetwork/topology/minheap.pxd +20 -0
  193. sknetwork/topology/minheap.pyx +109 -0
  194. sknetwork/topology/structure.py +194 -0
  195. sknetwork/topology/tests/__init__.py +1 -0
  196. sknetwork/topology/tests/test_cliques.py +28 -0
  197. sknetwork/topology/tests/test_core.py +19 -0
  198. sknetwork/topology/tests/test_cycles.py +65 -0
  199. sknetwork/topology/tests/test_structure.py +85 -0
  200. sknetwork/topology/tests/test_triangles.py +38 -0
  201. sknetwork/topology/tests/test_wl.py +72 -0
  202. sknetwork/topology/triangles.cpp +8897 -0
  203. sknetwork/topology/triangles.cpython-313-aarch64-linux-gnu.so +0 -0
  204. sknetwork/topology/triangles.pyx +151 -0
  205. sknetwork/topology/weisfeiler_lehman.py +133 -0
  206. sknetwork/topology/weisfeiler_lehman_core.cpp +27638 -0
  207. sknetwork/topology/weisfeiler_lehman_core.cpython-313-aarch64-linux-gnu.so +0 -0
  208. sknetwork/topology/weisfeiler_lehman_core.pyx +114 -0
  209. sknetwork/utils/__init__.py +7 -0
  210. sknetwork/utils/check.py +355 -0
  211. sknetwork/utils/format.py +221 -0
  212. sknetwork/utils/membership.py +82 -0
  213. sknetwork/utils/neighbors.py +115 -0
  214. sknetwork/utils/tests/__init__.py +1 -0
  215. sknetwork/utils/tests/test_check.py +190 -0
  216. sknetwork/utils/tests/test_format.py +63 -0
  217. sknetwork/utils/tests/test_membership.py +24 -0
  218. sknetwork/utils/tests/test_neighbors.py +41 -0
  219. sknetwork/utils/tests/test_tfidf.py +18 -0
  220. sknetwork/utils/tests/test_values.py +66 -0
  221. sknetwork/utils/tfidf.py +37 -0
  222. sknetwork/utils/values.py +76 -0
  223. sknetwork/visualization/__init__.py +4 -0
  224. sknetwork/visualization/colors.py +34 -0
  225. sknetwork/visualization/dendrograms.py +277 -0
  226. sknetwork/visualization/graphs.py +1039 -0
  227. sknetwork/visualization/tests/__init__.py +1 -0
  228. sknetwork/visualization/tests/test_dendrograms.py +53 -0
  229. sknetwork/visualization/tests/test_graphs.py +176 -0
@@ -0,0 +1,49 @@
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
+
17
+ def test_undirected(self):
18
+ adjacency = test_graph()
19
+ n = adjacency.shape[0]
20
+
21
+ method = Spring()
22
+ embedding = method.fit_transform(adjacency)
23
+ self.assertEqual(embedding.shape, (n, 2))
24
+
25
+ embedding = method.transform()
26
+ self.assertEqual(embedding.shape, (n, 2))
27
+
28
+ def test_bipartite(self):
29
+ for adjacency in [test_digraph(), test_bigraph()]:
30
+ n_row, n_col = adjacency.shape
31
+
32
+ for method in self.methods:
33
+ method.fit(adjacency)
34
+
35
+ self.assertEqual(method.embedding_.shape, (n_row, 2))
36
+ self.assertEqual(method.embedding_row_.shape, (n_row, 2))
37
+ self.assertEqual(method.embedding_col_.shape, (n_col, 2))
38
+
39
+ def test_disconnected(self):
40
+ n = 10
41
+ adjacency = np.eye(n)
42
+ for method in self.methods:
43
+ embedding = method.fit_transform(adjacency)
44
+ self.assertEqual(embedding.shape, (n, 2))
45
+
46
+ def test_regularization(self):
47
+ adjacency = test_graph()
48
+ method = Spectral()
49
+ 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
+ adjacency = test_graph()
16
+ adjacency_vector = np.zeros(10, dtype=int)
17
+ adjacency_vector[:5] = 1
18
+ louvain = LouvainEmbedding()
19
+ louvain.fit(adjacency)
20
+ self.assertEqual(louvain.embedding_.shape[0], 10)
21
+ louvain.fit(adjacency, force_bipartite=True)
22
+ self.assertEqual(louvain.embedding_.shape[0], 10)
23
+
24
+ # bipartite
25
+ biadjacency = test_bigraph()
26
+ louvain.fit(biadjacency)
27
+ self.assertEqual(louvain.embedding_row_.shape[0], 6)
28
+ self.assertEqual(louvain.embedding_col_.shape[0], 8)
29
+
30
+ for method in ['remove', 'merge', 'keep']:
31
+ louvain = LouvainEmbedding(isolated_nodes=method)
32
+ embedding = louvain.fit_transform(adjacency)
33
+ self.assertEqual(embedding.shape[0], adjacency.shape[0])
@@ -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_disconnected_graph
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_disconnected_graph()
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,81 @@
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_disconnected_graph()]:
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
+ # Laplacian
26
+ spectral = Spectral(3, decomposition='laplacian', normalized=False)
27
+ embedding = spectral.fit_transform(adjacency)
28
+ self.assertAlmostEqual(np.linalg.norm(embedding.sum(axis=0)), 0)
29
+
30
+ def test_directed(self):
31
+ for adjacency in [test_digraph(), test_digraph().astype(bool)]:
32
+ # random walk
33
+ spectral = Spectral(3, normalized=False)
34
+ embedding = spectral.fit_transform(adjacency)
35
+ self.assertAlmostEqual(embedding.shape[0], adjacency.shape[0])
36
+ # Laplacian
37
+ spectral = Spectral(3, decomposition='laplacian', normalized=False)
38
+ spectral.fit(adjacency)
39
+ self.assertAlmostEqual(np.linalg.norm(spectral.eigenvectors_.sum(axis=0)), 0)
40
+
41
+ def test_regularization(self):
42
+ for adjacency in [test_graph(), test_disconnected_graph()]:
43
+ n = adjacency.shape[0]
44
+ # random walk
45
+ regularization = 0.1
46
+ spectral = Spectral(3, regularization=regularization, normalized=False)
47
+ embedding = spectral.fit_transform(adjacency)
48
+ weights = adjacency.dot(np.ones(n)) + regularization
49
+ self.assertAlmostEqual(np.linalg.norm(embedding.T.dot(weights)), 0)
50
+ # Laplacian
51
+ spectral = Spectral(3, decomposition='laplacian', regularization=1, normalized=False)
52
+ embedding = spectral.fit_transform(adjacency)
53
+ self.assertAlmostEqual(np.linalg.norm(embedding.sum(axis=0)), 0)
54
+ # without regularization
55
+ spectral = Spectral(3, decomposition='laplacian', regularization=-1, normalized=False)
56
+ embedding = spectral.fit_transform(adjacency)
57
+ self.assertAlmostEqual(np.linalg.norm(embedding.sum(axis=0)), 0)
58
+
59
+ def test_bipartite(self):
60
+ for biadjacency in [test_digraph(), test_bigraph(), test_bigraph_disconnect()]:
61
+ n_row, n_col = biadjacency.shape
62
+ adjacency = bipartite2undirected(biadjacency)
63
+ # random walk
64
+ spectral = Spectral(3, normalized=False)
65
+ spectral.fit(biadjacency)
66
+ embedding_full = np.vstack([spectral.embedding_row_, spectral.embedding_col_])
67
+ weights = adjacency.dot(np.ones(n_row + n_col))
68
+ if not is_weakly_connected(adjacency):
69
+ weights += 1
70
+ self.assertAlmostEqual(np.linalg.norm(embedding_full.T.dot(weights)), 0)
71
+ # Laplacian
72
+ spectral = Spectral(3, decomposition='laplacian', normalized=False)
73
+ spectral.fit(biadjacency)
74
+ embedding_full = np.vstack([spectral.embedding_row_, spectral.embedding_col_])
75
+ self.assertAlmostEqual(np.linalg.norm(embedding_full.sum(axis=0)), 0)
76
+
77
+ def test_normalization(self):
78
+ for adjacency in [test_graph(), test_disconnected_graph()]:
79
+ spectral = Spectral(3)
80
+ embedding = spectral.fit_transform(adjacency)
81
+ self.assertAlmostEqual(np.linalg.norm(np.linalg.norm(embedding, axis=1) - np.ones(adjacency.shape[0])), 0)
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """tests for spring embeddings"""
4
+
5
+ import unittest
6
+
7
+ from sknetwork.data.test_graphs import *
8
+ from sknetwork.embedding import Spring
9
+
10
+
11
+ class TestEmbeddings(unittest.TestCase):
12
+
13
+ def test_shape(self):
14
+ for adjacency in [test_graph(), test_digraph()]:
15
+ n = adjacency.shape[0]
16
+ spring = Spring()
17
+ layout = spring.fit_transform(adjacency)
18
+ self.assertEqual((n, 2), layout.shape)
19
+
20
+ spring = Spring(n_components=3)
21
+ layout = spring.fit_transform(adjacency)
22
+ self.assertEqual((n, 3), layout.shape)
23
+
24
+ def test_pos_init(self):
25
+ adjacency = test_graph()
26
+ n = adjacency.shape[0]
27
+
28
+ spring = Spring(strength=0.1, position_init='spectral', tol=1e3)
29
+ layout = spring.fit_transform(adjacency)
30
+ self.assertEqual((n, 2), layout.shape)
31
+ layout = spring.fit_transform(adjacency, position_init=layout)
32
+ self.assertEqual((n, 2), layout.shape)
33
+
34
+ def test_approx_radius(self):
35
+ adjacency = test_graph()
36
+ n = adjacency.shape[0]
37
+
38
+ spring = Spring(approx_radius=1.)
39
+ layout = spring.fit_transform(adjacency)
40
+ self.assertEqual((n, 2), layout.shape)
41
+
42
+ def test_errors(self):
43
+ adjacency = test_graph()
44
+ with self.assertRaises(ValueError):
45
+ Spring(position_init='toto')
46
+ with self.assertRaises(ValueError):
47
+ Spring().fit(adjacency, position_init=np.ones((2, 2)))
48
+ with self.assertRaises(TypeError):
49
+ # noinspection PyTypeChecker
50
+ Spring().fit(adjacency, position_init='toto')
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """tests for svd"""
4
+
5
+ import unittest
6
+
7
+ import numpy as np
8
+
9
+ from sknetwork.data import star_wars
10
+ from sknetwork.embedding import GSVD, SVD, PCA
11
+ from sknetwork.linalg import LanczosSVD
12
+
13
+
14
+ class TestSVD(unittest.TestCase):
15
+
16
+ def test_options(self):
17
+ biadjacency = star_wars(metadata=False)
18
+ n_row, n_col = biadjacency.shape
19
+ min_dim = min(n_row, n_col) - 1
20
+ gsvd = GSVD(n_components=5, regularization=0., solver='halko')
21
+
22
+ with self.assertWarns(Warning):
23
+ gsvd.fit(biadjacency)
24
+ self.assertEqual(gsvd.embedding_row_.shape, (n_row, min_dim))
25
+ self.assertEqual(gsvd.embedding_col_.shape, (n_col, min_dim))
26
+
27
+ embedding = gsvd.predict(np.array([0, 1, 1]))
28
+ self.assertEqual(embedding.shape, (min_dim,))
29
+
30
+ gsvd = GSVD(n_components=1, regularization=0.1, solver='lanczos')
31
+ gsvd.fit(biadjacency)
32
+ self.assertEqual(gsvd.embedding_row_.shape, (n_row, 1))
33
+
34
+ pca = PCA(n_components=min_dim, solver='lanczos')
35
+ pca.fit(biadjacency)
36
+ self.assertEqual(pca.embedding_row_.shape, (n_row, min_dim))
37
+ pca = PCA(n_components=min_dim, solver=LanczosSVD())
38
+ pca.fit(biadjacency)
39
+ self.assertEqual(pca.embedding_row_.shape, (n_row, min_dim))
40
+
41
+ svd = SVD(n_components=min_dim, solver=LanczosSVD())
42
+ svd.fit(biadjacency)
43
+ self.assertEqual(svd.embedding_row_.shape, (n_row, min_dim))
@@ -0,0 +1,10 @@
1
+ """gnn module"""
2
+ from sknetwork.gnn.base import BaseGNN
3
+ from sknetwork.gnn.base_activation import BaseActivation, BaseLoss
4
+ from sknetwork.gnn.base_layer import BaseLayer
5
+ from sknetwork.gnn.gnn_classifier import GNNClassifier
6
+ from sknetwork.gnn.layer import Convolution
7
+ from sknetwork.gnn.neighbor_sampler import UniformNeighborSampler
8
+ from sknetwork.gnn.activation import ReLu, Sigmoid, Softmax
9
+ from sknetwork.gnn.loss import BinaryCrossEntropy, CrossEntropy
10
+ from sknetwork.gnn.optimizer import BaseOptimizer, GD, ADAM
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ """
4
+ Created in April 2022
5
+ @author: Simon Delarue <sdelarue@enst.fr>
6
+ @author: Thomas Bonald <bonald@enst.fr>
7
+ """
8
+
9
+ from typing import Union
10
+
11
+ import numpy as np
12
+ from scipy import special
13
+
14
+ from sknetwork.gnn.base_activation import BaseActivation
15
+
16
+
17
+ class ReLu(BaseActivation):
18
+ """ReLu (Rectified Linear Unit) activation function:
19
+
20
+ :math:`\\sigma(x) = \\max(0, x)`
21
+ """
22
+ def __init__(self):
23
+ super(ReLu, self).__init__('ReLu')
24
+
25
+ @staticmethod
26
+ def output(signal: np.ndarray) -> np.ndarray:
27
+ """Output of the ReLu function."""
28
+ return np.maximum(signal, 0)
29
+
30
+ @staticmethod
31
+ def gradient(signal: np.ndarray, direction: np.ndarray) -> np.ndarray:
32
+ """Gradient of the ReLu function."""
33
+ return direction * (signal > 0)
34
+
35
+
36
+ class Sigmoid(BaseActivation):
37
+ """Sigmoid activation function:
38
+
39
+ :math:`\\sigma(x) = \\frac{1}{1+e^{-x}}`
40
+ Also known as the logistic function.
41
+ """
42
+ def __init__(self):
43
+ super(Sigmoid, self).__init__('Sigmoid')
44
+
45
+ @staticmethod
46
+ def output(signal: np.ndarray) -> np.ndarray:
47
+ """Output of the sigmoid function."""
48
+ return special.expit(signal)
49
+
50
+ @staticmethod
51
+ def gradient(signal: np.ndarray, direction: np.ndarray) -> np.ndarray:
52
+ """Gradient of the sigmoid function."""
53
+ output = Sigmoid.output(signal)
54
+ return output * (1 - output) * direction
55
+
56
+
57
+ class Softmax(BaseActivation):
58
+ """Softmax activation function:
59
+
60
+ :math:`\\sigma(x) =
61
+ (\\frac{e^{x_1}}{\\sum_{i=1}^N e^{x_i})},\\ldots,\\frac{e^{x_N}}{\\sum_{i=1}^N e^{x_i})})`
62
+
63
+ where :math:`N` is the number of channels.
64
+ """
65
+ def __init__(self):
66
+ super(Softmax, self).__init__('Softmax')
67
+
68
+ @staticmethod
69
+ def output(signal: np.ndarray) -> np.ndarray:
70
+ """Output of the softmax function (rows sum to 1)."""
71
+ return special.softmax(signal, axis=1)
72
+
73
+ @staticmethod
74
+ def gradient(signal: np.ndarray, direction: np.ndarray) -> np.ndarray:
75
+ """Gradient of the softmax function."""
76
+ output = Softmax.output(signal)
77
+ return output * (direction.T - (output * direction).sum(axis=1)).T
78
+
79
+
80
+ def get_activation(activation: Union[BaseActivation, str] = 'identity') -> BaseActivation:
81
+ """Get the activation function.
82
+
83
+ Parameters
84
+ ----------
85
+ activation : Union[BaseActivation, str]
86
+ Activation function.
87
+ If a name is given, can be either ``'Identity'``, ``'Relu'``, ``'Sigmoid'`` or ``'Softmax'``.
88
+ If a custom activation function is given, must be of class BaseActivation.
89
+
90
+ Returns
91
+ -------
92
+ activation : BaseActivation
93
+ Activation function.
94
+
95
+ Raises
96
+ ------
97
+ TypeError
98
+ Error raised if the input not a string or an object of class BaseActivation.
99
+ ValueError
100
+ Error raised if the name of the activation function is unknown.
101
+ """
102
+ if issubclass(type(activation), BaseActivation):
103
+ return activation
104
+ elif type(activation) == str:
105
+ activation = activation.lower()
106
+ if activation in ['identity', '']:
107
+ return BaseActivation()
108
+ elif activation == 'relu':
109
+ return ReLu()
110
+ elif activation == 'sigmoid':
111
+ return Sigmoid()
112
+ elif activation == 'softmax':
113
+ return Softmax()
114
+ else:
115
+ raise ValueError("Activation must be either \"Identity\", \"ReLu\", \"Sigmoid\" or \"Softmax\".")
116
+ else:
117
+ raise TypeError("Activation must be a string or an object of type \"BaseActivation\".")
sknetwork/gnn/base.py ADDED
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on July 2022
5
+ @author: Simon Delarue <sdelarue@enst.fr>
6
+ @author: Thomas Bonald <bonald@enst.fr>
7
+ """
8
+ from abc import ABC
9
+
10
+ from typing import Union
11
+
12
+ import numpy as np
13
+ from collections import defaultdict
14
+ from scipy import sparse
15
+
16
+ from sknetwork.gnn.loss import BaseLoss, get_loss
17
+ from sknetwork.gnn.optimizer import BaseOptimizer, get_optimizer
18
+ from sknetwork.base import Algorithm
19
+ from sknetwork.log import Log
20
+
21
+
22
+ class BaseGNN(ABC, Algorithm, Log):
23
+ """Base class for GNNs.
24
+
25
+ Parameters
26
+ ----------
27
+ loss : str or custom loss (default = ``'Cross entropy'``)
28
+ Loss function.
29
+ optimizer : str or custom optimizer (default = ``'Adam'``)
30
+ Optimizer used for training.
31
+
32
+ * ``'Adam'``, a stochastic gradient-based optimizer.
33
+ * ``'GD'``, gradient descent.
34
+ learning_rate : float
35
+ Learning rate.
36
+ verbose : bool
37
+ Verbose mode
38
+
39
+ Attributes
40
+ ----------
41
+ layers: list
42
+ List of layers.
43
+ labels_: np.ndarray
44
+ Predicted labels.
45
+ history_: dict
46
+ Training history per epoch: {'embedding', 'loss', 'train_accuracy', 'test_accuracy'}.
47
+ """
48
+ def __init__(self, loss: Union[BaseLoss, str] = 'CrossEntropy', optimizer: Union[BaseOptimizer, str] = 'Adam',
49
+ learning_rate: float = 0.01, verbose: bool = False):
50
+ Log.__init__(self, verbose)
51
+ self.optimizer = get_optimizer(optimizer, learning_rate)
52
+ self.loss = get_loss(loss)
53
+ self.layers = []
54
+ self.derivative_weight = []
55
+ self.derivative_bias = []
56
+ self.train_mask = None
57
+ self.test_mask = None
58
+ self.val_mask = None
59
+ self.embedding_ = None
60
+ self.output_ = None
61
+ self.labels_ = None
62
+ self.history_ = defaultdict(list)
63
+
64
+ def fit(self, *args, **kwargs):
65
+ """Fit Algorithm to the data."""
66
+ raise NotImplementedError
67
+
68
+ def predict(self):
69
+ """Return the predicted labels."""
70
+ return self.labels_
71
+
72
+ def fit_predict(self, *args, **kwargs) -> np.ndarray:
73
+ """Fit algorithm to the data and return the labels. Same parameters as the ``fit`` method.
74
+
75
+ Returns
76
+ -------
77
+ labels : np.ndarray
78
+ Labels of the nodes.
79
+ """
80
+ self.fit(*args, **kwargs)
81
+ return self.predict()
82
+
83
+ def predict_proba(self):
84
+ """Return the probability distribution over labels."""
85
+ probs = self.output_
86
+ if probs is not None:
87
+ if probs.shape[1] == 1:
88
+ probs = np.vstack(1 - probs, probs)
89
+ return probs
90
+
91
+ def fit_predict_proba(self, *args, **kwargs) -> np.ndarray:
92
+ """Fit algorithm to the data and return the distribution over labels. Same parameters as the ``fit`` method.
93
+
94
+ Returns
95
+ -------
96
+ probs : np.ndarray
97
+ Probability distribution over labels.
98
+ """
99
+ self.fit(*args, **kwargs)
100
+ return self.predict_proba()
101
+
102
+ def transform(self):
103
+ """Return the embedding of nodes."""
104
+ return self.embedding_
105
+
106
+ def fit_transform(self, *args, **kwargs) -> np.ndarray:
107
+ """Fit algorithm to the data and return the embedding of the nodes. Same parameters as the ``fit`` method.
108
+
109
+ Returns
110
+ -------
111
+ embedding : np.ndarray
112
+ Embedding of the nodes.
113
+ """
114
+ self.fit(*args, **kwargs)
115
+ return self.transform()
116
+
117
+ def backward(self, features: sparse.csr_matrix, labels: np.ndarray, mask: np.ndarray):
118
+ """Compute backpropagation.
119
+
120
+ Parameters
121
+ ----------
122
+ features : sparse.csr_matrix
123
+ Features, array of shape (n_nodes, n_features).
124
+ labels : np.ndarray
125
+ Labels, array of shape (n_nodes,).
126
+ mask: np.ndarray
127
+ Boolean mask, array of shape (n_nodes,).
128
+ """
129
+ derivative_weight = []
130
+ derivative_bias = []
131
+
132
+ # discard missing labels
133
+ mask = mask & (labels >= 0)
134
+ labels = labels[mask]
135
+
136
+ # backpropagation
137
+ n_layers = len(self.layers)
138
+ layers_reverse: list = list(reversed(self.layers))
139
+ signal = layers_reverse[0].embedding
140
+ signal = signal[mask]
141
+ gradient = layers_reverse[0].activation.loss_gradient(signal, labels)
142
+
143
+ for i in range(n_layers):
144
+ if i < n_layers - 1:
145
+ signal = layers_reverse[i + 1].output
146
+ else:
147
+ signal = features
148
+ signal = signal[mask]
149
+
150
+ derivative_weight.append(signal.T.dot(gradient))
151
+ derivative_bias.append(np.mean(gradient, axis=0, keepdims=True))
152
+
153
+ if i < n_layers - 1:
154
+ signal = layers_reverse[i + 1].embedding
155
+ signal = signal[mask]
156
+ direction = layers_reverse[i].weight.dot(gradient.T).T
157
+ gradient = layers_reverse[i + 1].activation.gradient(signal, direction)
158
+
159
+ self.derivative_weight = list(reversed(derivative_weight))
160
+ self.derivative_bias = list(reversed(derivative_bias))
161
+
162
+ def _check_fitted(self):
163
+ if self.output_ is None:
164
+ raise ValueError("This embedding instance is not fitted yet. "
165
+ "Call 'fit' with appropriate arguments before using this method.")
166
+ else:
167
+ return self
168
+
169
+ def __repr__(self) -> str:
170
+ """String representation of the `GNN`, layers by layers.
171
+
172
+ Returns
173
+ -------
174
+ str
175
+ String representation of object.
176
+ """
177
+ string = f'{self.__class__.__name__}(\n'
178
+ for layer in self.layers:
179
+ string += f' {layer}\n'
180
+ string += ')'
181
+ return string