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.

Files changed (240) hide show
  1. scikit_network-0.28.3.dist-info/AUTHORS.rst +41 -0
  2. scikit_network-0.28.3.dist-info/LICENSE +34 -0
  3. scikit_network-0.28.3.dist-info/METADATA +457 -0
  4. scikit_network-0.28.3.dist-info/RECORD +240 -0
  5. scikit_network-0.28.3.dist-info/WHEEL +5 -0
  6. scikit_network-0.28.3.dist-info/top_level.txt +1 -0
  7. sknetwork/__init__.py +21 -0
  8. sknetwork/classification/__init__.py +8 -0
  9. sknetwork/classification/base.py +84 -0
  10. sknetwork/classification/base_rank.py +143 -0
  11. sknetwork/classification/diffusion.py +134 -0
  12. sknetwork/classification/knn.py +162 -0
  13. sknetwork/classification/metrics.py +205 -0
  14. sknetwork/classification/pagerank.py +66 -0
  15. sknetwork/classification/propagation.py +152 -0
  16. sknetwork/classification/tests/__init__.py +1 -0
  17. sknetwork/classification/tests/test_API.py +35 -0
  18. sknetwork/classification/tests/test_diffusion.py +37 -0
  19. sknetwork/classification/tests/test_knn.py +24 -0
  20. sknetwork/classification/tests/test_metrics.py +53 -0
  21. sknetwork/classification/tests/test_pagerank.py +20 -0
  22. sknetwork/classification/tests/test_propagation.py +24 -0
  23. sknetwork/classification/vote.cpython-39-darwin.so +0 -0
  24. sknetwork/classification/vote.pyx +58 -0
  25. sknetwork/clustering/__init__.py +7 -0
  26. sknetwork/clustering/base.py +102 -0
  27. sknetwork/clustering/kmeans.py +142 -0
  28. sknetwork/clustering/louvain.py +255 -0
  29. sknetwork/clustering/louvain_core.cpython-39-darwin.so +0 -0
  30. sknetwork/clustering/louvain_core.pyx +134 -0
  31. sknetwork/clustering/metrics.py +91 -0
  32. sknetwork/clustering/postprocess.py +66 -0
  33. sknetwork/clustering/propagation_clustering.py +108 -0
  34. sknetwork/clustering/tests/__init__.py +1 -0
  35. sknetwork/clustering/tests/test_API.py +37 -0
  36. sknetwork/clustering/tests/test_kmeans.py +47 -0
  37. sknetwork/clustering/tests/test_louvain.py +104 -0
  38. sknetwork/clustering/tests/test_metrics.py +50 -0
  39. sknetwork/clustering/tests/test_post_processing.py +23 -0
  40. sknetwork/clustering/tests/test_postprocess.py +39 -0
  41. sknetwork/data/__init__.py +5 -0
  42. sknetwork/data/load.py +408 -0
  43. sknetwork/data/models.py +459 -0
  44. sknetwork/data/parse.py +621 -0
  45. sknetwork/data/test_graphs.py +84 -0
  46. sknetwork/data/tests/__init__.py +1 -0
  47. sknetwork/data/tests/test_API.py +30 -0
  48. sknetwork/data/tests/test_load.py +95 -0
  49. sknetwork/data/tests/test_models.py +52 -0
  50. sknetwork/data/tests/test_parse.py +253 -0
  51. sknetwork/data/tests/test_test_graphs.py +30 -0
  52. sknetwork/data/tests/test_toy_graphs.py +68 -0
  53. sknetwork/data/toy_graphs.py +619 -0
  54. sknetwork/embedding/__init__.py +10 -0
  55. sknetwork/embedding/base.py +90 -0
  56. sknetwork/embedding/force_atlas.py +197 -0
  57. sknetwork/embedding/louvain_embedding.py +174 -0
  58. sknetwork/embedding/louvain_hierarchy.py +142 -0
  59. sknetwork/embedding/metrics.py +66 -0
  60. sknetwork/embedding/random_projection.py +133 -0
  61. sknetwork/embedding/spectral.py +214 -0
  62. sknetwork/embedding/spring.py +198 -0
  63. sknetwork/embedding/svd.py +363 -0
  64. sknetwork/embedding/tests/__init__.py +1 -0
  65. sknetwork/embedding/tests/test_API.py +73 -0
  66. sknetwork/embedding/tests/test_force_atlas.py +35 -0
  67. sknetwork/embedding/tests/test_louvain_embedding.py +33 -0
  68. sknetwork/embedding/tests/test_louvain_hierarchy.py +19 -0
  69. sknetwork/embedding/tests/test_metrics.py +29 -0
  70. sknetwork/embedding/tests/test_random_projection.py +28 -0
  71. sknetwork/embedding/tests/test_spectral.py +84 -0
  72. sknetwork/embedding/tests/test_spring.py +50 -0
  73. sknetwork/embedding/tests/test_svd.py +37 -0
  74. sknetwork/flow/__init__.py +3 -0
  75. sknetwork/flow/flow.py +73 -0
  76. sknetwork/flow/tests/__init__.py +1 -0
  77. sknetwork/flow/tests/test_flow.py +17 -0
  78. sknetwork/flow/tests/test_utils.py +69 -0
  79. sknetwork/flow/utils.py +91 -0
  80. sknetwork/gnn/__init__.py +10 -0
  81. sknetwork/gnn/activation.py +117 -0
  82. sknetwork/gnn/base.py +155 -0
  83. sknetwork/gnn/base_activation.py +89 -0
  84. sknetwork/gnn/base_layer.py +109 -0
  85. sknetwork/gnn/gnn_classifier.py +381 -0
  86. sknetwork/gnn/layer.py +153 -0
  87. sknetwork/gnn/layers.py +127 -0
  88. sknetwork/gnn/loss.py +180 -0
  89. sknetwork/gnn/neighbor_sampler.py +65 -0
  90. sknetwork/gnn/optimizer.py +163 -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 +79 -0
  94. sknetwork/gnn/tests/test_base_layer.py +37 -0
  95. sknetwork/gnn/tests/test_gnn_classifier.py +192 -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 +93 -0
  101. sknetwork/gnn/utils.py +219 -0
  102. sknetwork/hierarchy/__init__.py +7 -0
  103. sknetwork/hierarchy/base.py +69 -0
  104. sknetwork/hierarchy/louvain_hierarchy.py +264 -0
  105. sknetwork/hierarchy/metrics.py +234 -0
  106. sknetwork/hierarchy/paris.cpython-39-darwin.so +0 -0
  107. sknetwork/hierarchy/paris.pyx +317 -0
  108. sknetwork/hierarchy/postprocess.py +350 -0
  109. sknetwork/hierarchy/tests/__init__.py +1 -0
  110. sknetwork/hierarchy/tests/test_API.py +25 -0
  111. sknetwork/hierarchy/tests/test_algos.py +29 -0
  112. sknetwork/hierarchy/tests/test_metrics.py +62 -0
  113. sknetwork/hierarchy/tests/test_postprocess.py +57 -0
  114. sknetwork/hierarchy/tests/test_ward.py +25 -0
  115. sknetwork/hierarchy/ward.py +94 -0
  116. sknetwork/linalg/__init__.py +9 -0
  117. sknetwork/linalg/basics.py +37 -0
  118. sknetwork/linalg/diteration.cpython-39-darwin.so +0 -0
  119. sknetwork/linalg/diteration.pyx +49 -0
  120. sknetwork/linalg/eig_solver.py +93 -0
  121. sknetwork/linalg/laplacian.py +15 -0
  122. sknetwork/linalg/normalization.py +66 -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.cpython-39-darwin.so +0 -0
  127. sknetwork/linalg/push.pyx +73 -0
  128. sknetwork/linalg/sparse_lowrank.py +142 -0
  129. sknetwork/linalg/svd_solver.py +91 -0
  130. sknetwork/linalg/tests/__init__.py +1 -0
  131. sknetwork/linalg/tests/test_eig.py +44 -0
  132. sknetwork/linalg/tests/test_laplacian.py +18 -0
  133. sknetwork/linalg/tests/test_normalization.py +38 -0
  134. sknetwork/linalg/tests/test_operators.py +70 -0
  135. sknetwork/linalg/tests/test_polynome.py +38 -0
  136. sknetwork/linalg/tests/test_ppr.py +50 -0
  137. sknetwork/linalg/tests/test_sparse_lowrank.py +61 -0
  138. sknetwork/linalg/tests/test_svd.py +38 -0
  139. sknetwork/linkpred/__init__.py +4 -0
  140. sknetwork/linkpred/base.py +80 -0
  141. sknetwork/linkpred/first_order.py +508 -0
  142. sknetwork/linkpred/first_order_core.cpython-39-darwin.so +0 -0
  143. sknetwork/linkpred/first_order_core.pyx +315 -0
  144. sknetwork/linkpred/postprocessing.py +98 -0
  145. sknetwork/linkpred/tests/__init__.py +1 -0
  146. sknetwork/linkpred/tests/test_API.py +49 -0
  147. sknetwork/linkpred/tests/test_postprocessing.py +21 -0
  148. sknetwork/path/__init__.py +4 -0
  149. sknetwork/path/metrics.py +148 -0
  150. sknetwork/path/search.py +65 -0
  151. sknetwork/path/shortest_path.py +186 -0
  152. sknetwork/path/tests/__init__.py +1 -0
  153. sknetwork/path/tests/test_metrics.py +29 -0
  154. sknetwork/path/tests/test_search.py +25 -0
  155. sknetwork/path/tests/test_shortest_path.py +45 -0
  156. sknetwork/ranking/__init__.py +9 -0
  157. sknetwork/ranking/base.py +56 -0
  158. sknetwork/ranking/betweenness.cpython-39-darwin.so +0 -0
  159. sknetwork/ranking/betweenness.pyx +99 -0
  160. sknetwork/ranking/closeness.py +95 -0
  161. sknetwork/ranking/harmonic.py +82 -0
  162. sknetwork/ranking/hits.py +94 -0
  163. sknetwork/ranking/katz.py +81 -0
  164. sknetwork/ranking/pagerank.py +107 -0
  165. sknetwork/ranking/postprocess.py +25 -0
  166. sknetwork/ranking/tests/__init__.py +1 -0
  167. sknetwork/ranking/tests/test_API.py +34 -0
  168. sknetwork/ranking/tests/test_betweenness.py +38 -0
  169. sknetwork/ranking/tests/test_closeness.py +34 -0
  170. sknetwork/ranking/tests/test_hits.py +20 -0
  171. sknetwork/ranking/tests/test_pagerank.py +69 -0
  172. sknetwork/regression/__init__.py +4 -0
  173. sknetwork/regression/base.py +56 -0
  174. sknetwork/regression/diffusion.py +190 -0
  175. sknetwork/regression/tests/__init__.py +1 -0
  176. sknetwork/regression/tests/test_API.py +34 -0
  177. sknetwork/regression/tests/test_diffusion.py +48 -0
  178. sknetwork/sknetwork.py +3 -0
  179. sknetwork/topology/__init__.py +9 -0
  180. sknetwork/topology/dag.py +74 -0
  181. sknetwork/topology/dag_core.cpython-39-darwin.so +0 -0
  182. sknetwork/topology/dag_core.pyx +38 -0
  183. sknetwork/topology/kcliques.cpython-39-darwin.so +0 -0
  184. sknetwork/topology/kcliques.pyx +193 -0
  185. sknetwork/topology/kcore.cpython-39-darwin.so +0 -0
  186. sknetwork/topology/kcore.pyx +120 -0
  187. sknetwork/topology/structure.py +234 -0
  188. sknetwork/topology/tests/__init__.py +1 -0
  189. sknetwork/topology/tests/test_cliques.py +28 -0
  190. sknetwork/topology/tests/test_cores.py +21 -0
  191. sknetwork/topology/tests/test_dag.py +26 -0
  192. sknetwork/topology/tests/test_structure.py +99 -0
  193. sknetwork/topology/tests/test_triangles.py +42 -0
  194. sknetwork/topology/tests/test_wl_coloring.py +49 -0
  195. sknetwork/topology/tests/test_wl_kernel.py +31 -0
  196. sknetwork/topology/triangles.cpython-39-darwin.so +0 -0
  197. sknetwork/topology/triangles.pyx +166 -0
  198. sknetwork/topology/weisfeiler_lehman.py +163 -0
  199. sknetwork/topology/weisfeiler_lehman_core.cpython-39-darwin.so +0 -0
  200. sknetwork/topology/weisfeiler_lehman_core.pyx +116 -0
  201. sknetwork/utils/__init__.py +40 -0
  202. sknetwork/utils/base.py +35 -0
  203. sknetwork/utils/check.py +354 -0
  204. sknetwork/utils/co_neighbor.py +71 -0
  205. sknetwork/utils/format.py +219 -0
  206. sknetwork/utils/kmeans.py +89 -0
  207. sknetwork/utils/knn.py +166 -0
  208. sknetwork/utils/knn1d.cpython-39-darwin.so +0 -0
  209. sknetwork/utils/knn1d.pyx +80 -0
  210. sknetwork/utils/membership.py +82 -0
  211. sknetwork/utils/minheap.cpython-39-darwin.so +0 -0
  212. sknetwork/utils/minheap.pxd +22 -0
  213. sknetwork/utils/minheap.pyx +111 -0
  214. sknetwork/utils/neighbors.py +115 -0
  215. sknetwork/utils/seeds.py +75 -0
  216. sknetwork/utils/simplex.py +140 -0
  217. sknetwork/utils/tests/__init__.py +1 -0
  218. sknetwork/utils/tests/test_base.py +28 -0
  219. sknetwork/utils/tests/test_bunch.py +16 -0
  220. sknetwork/utils/tests/test_check.py +190 -0
  221. sknetwork/utils/tests/test_co_neighbor.py +43 -0
  222. sknetwork/utils/tests/test_format.py +61 -0
  223. sknetwork/utils/tests/test_kmeans.py +21 -0
  224. sknetwork/utils/tests/test_knn.py +32 -0
  225. sknetwork/utils/tests/test_membership.py +24 -0
  226. sknetwork/utils/tests/test_neighbors.py +41 -0
  227. sknetwork/utils/tests/test_projection_simplex.py +33 -0
  228. sknetwork/utils/tests/test_seeds.py +67 -0
  229. sknetwork/utils/tests/test_verbose.py +15 -0
  230. sknetwork/utils/tests/test_ward.py +20 -0
  231. sknetwork/utils/timeout.py +38 -0
  232. sknetwork/utils/verbose.py +37 -0
  233. sknetwork/utils/ward.py +60 -0
  234. sknetwork/visualization/__init__.py +4 -0
  235. sknetwork/visualization/colors.py +34 -0
  236. sknetwork/visualization/dendrograms.py +229 -0
  237. sknetwork/visualization/graphs.py +819 -0
  238. sknetwork/visualization/tests/__init__.py +1 -0
  239. sknetwork/visualization/tests/test_dendrograms.py +53 -0
  240. sknetwork/visualization/tests/test_graphs.py +167 -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,37 @@
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
+ gsvd = GSVD(n_components=1, regularization=0.1, solver='lanczos')
28
+ gsvd.fit(biadjacency)
29
+ gsvd.predict(np.random.rand(n_col))
30
+
31
+ pca = PCA(n_components=min_dim, solver='lanczos')
32
+ pca.fit(biadjacency)
33
+ self.assertEqual(pca.embedding_row_.shape, (n_row, min_dim))
34
+
35
+ svd = SVD(n_components=min_dim, solver=LanczosSVD())
36
+ svd.fit(biadjacency)
37
+ self.assertEqual(svd.embedding_row_.shape, (n_row, min_dim))
@@ -0,0 +1,3 @@
1
+ """flow module"""
2
+ from sknetwork.flow.utils import get_residual_graph, flow_is_feasible, find_excess
3
+ from sknetwork.flow.push_relabel import get_max_flow
sknetwork/flow/flow.py ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on July 7, 2022.
5
+ @author: Henry L. Carscadden <hcarscad@gmail.com>
6
+ """
7
+ from scipy import sparse
8
+ import numpy as np
9
+
10
+ from sknetwork.flow.utils import get_residual_graph
11
+ from sknetwork.flow.flow_core import push
12
+
13
+
14
+ def residual_to_flow(adjacency: sparse.csr_matrix, residual: sparse.csr_matrix, preflow: sparse.csr_matrix):
15
+ rows, cols = adjacency.nonzero()
16
+ for i in range(rows.size):
17
+ row, col = rows[i], cols[i]
18
+ preflow[row, col] = residual[col, row]
19
+ return preflow
20
+
21
+
22
+ def push_relabel(adjacency: sparse.csr_matrix, src: int, sink: int):
23
+ """ This algorithm finds a maximum flow following the classic push-relabel algorithm.
24
+
25
+ Parameters
26
+ ----------
27
+ adjacency : sparse.csr_matrix
28
+ The adjacency matrix of the graph with weights containing the edge capacities.
29
+ src : int
30
+ The node with the flow source.
31
+ sink : int
32
+ The node with that receives the flow.
33
+
34
+ Returns
35
+ -------
36
+ flow : sparse.csr_matrix
37
+ A maximum flow for the graph.
38
+
39
+ Reference
40
+ ---------
41
+ Goldberg, A V; Tarjan, R E (1986). A new approach to the maximum flow problem.
42
+ Proceedings of the eighteenth annual ACM symposium on Theory of computing – STOC 86.
43
+ """
44
+ # Initialize preflow.
45
+ preflow_vals = np.zeros(dtype=np.int32, shape=(adjacency.nnz,))
46
+ preflow = sparse.csr_matrix((preflow_vals, adjacency.indices, adjacency.indptr), shape=adjacency.shape)
47
+ # Initialize the heights to 0.
48
+ heights = np.zeros(shape=(adjacency.shape[0],), dtype=np.int32)
49
+ # Set the height of the src to n.
50
+ heights[src] = adjacency.shape[0]
51
+ # Create the excess flow array.
52
+ excess_flow = np.zeros(shape=(adjacency.shape[0],), dtype=np.int32)
53
+ # Send all the possible flow out of the source.
54
+ for dest_node in adjacency.indices[adjacency.indptr[src]:adjacency.indptr[src + 1]]:
55
+ edge_capacity = adjacency[src, dest_node]
56
+ preflow[src, dest_node] = edge_capacity
57
+ if edge_capacity > 0:
58
+ excess_flow[dest_node] = edge_capacity
59
+ # Initialize the residual graph.
60
+ residual = get_residual_graph(adjacency, preflow)
61
+ non_zero_indices = excess_flow.nonzero()[0]
62
+ # While there are nodes with excess, push or relabel.
63
+ while non_zero_indices.size > 0:
64
+ # while nodes have excess
65
+ # Get nodes from active nodes (excess > 0)
66
+ for curr_node in non_zero_indices:
67
+ pushed = push(residual, curr_node, src, sink, heights, excess_flow)
68
+ # Relabel step
69
+ if not pushed:
70
+ heights[curr_node] = heights[curr_node] + 1
71
+ non_zero_indices = excess_flow.nonzero()[0]
72
+
73
+ return residual_to_flow(adjacency, residual, preflow)
@@ -0,0 +1 @@
1
+ "Tests for flow"
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """"tests for push_relabel.py"""
4
+ import unittest
5
+
6
+ from sknetwork.flow import get_max_flow, find_excess
7
+ from scipy import sparse
8
+
9
+ class TestFlow(unittest.TestCase):
10
+ def test_push_relabel_1(self):
11
+ adj = sparse.csr_matrix([[0, 2, 3, 0, 0, 0, 0], [0, 0, 0, 3, 0, 0, 0],
12
+ [0, 0, 0, 2, 0, 0, 0], [0, 0, 0, 0, 1, 3, 0], [0, 0, 0, 0, 0, 0, 2],
13
+ [0, 0, 0, 0, 0, 0, 2], [0, 0, 0, 0, 0, 0, 0]])
14
+ flow = get_max_flow(adj, 0, 6)
15
+ self.assertEqual(3, flow[:, 6].sum())
16
+ for i in range(1, 6):
17
+ self.assertEqual(0, find_excess(flow, i))
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """"tests for utils.py"""
4
+ import unittest
5
+
6
+ from sknetwork.flow import get_residual_graph, flow_is_feasible, find_excess
7
+ from scipy.sparse import csr_matrix
8
+
9
+ class TestUtils(unittest.TestCase):
10
+ def test_get_residual_graph(self):
11
+ adjacency = csr_matrix([[0, 2, 3, 0, 0, 0, 0], [0, 0, 0, 3, 0, 0, 0],
12
+ [0, 0, 0, 2, 0, 0, 0], [0, 0, 0, 0, 1, 3, 0], [0, 0, 0, 0, 0, 0, 2],
13
+ [0, 0, 0, 0, 0, 0, 2], [0, 0, 0, 0, 0, 0, 0]])
14
+ flow = csr_matrix([[0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0],
15
+ [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 1],
16
+ [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0]])
17
+ residual = csr_matrix([[0, 1, 3, 0, 0, 0, 0], [1, 0, 0, 2, 0, 0, 0],
18
+ [0, 0, 0, 2, 0, 0, 0], [0, 1, 0, 0, 0, 3, 0], [0, 0, 0, 1, 0, 0, 1],
19
+ [0, 0, 0, 0, 0, 0, 2], [0, 0, 0, 0, 0, 0, 0]])
20
+ res_out = get_residual_graph(adjacency, flow, 0, 6)
21
+ rows, cols = residual.nonzero()
22
+ for i in range(len(rows)):
23
+ row, col = rows[i], cols[i]
24
+ self.assertEquals(residual[row, col], res_out[row, col])
25
+ def test_flow_is_feasible_1(self):
26
+ adjacency = csr_matrix([[0, 2, 3, 0, 0, 0, 0], [0, 0, 0, 3, 0, 0, 0],
27
+ [0, 0, 0, 2, 0, 0, 0], [0, 0, 0, 0, 1, 3, 0], [0, 0, 0, 0, 0, 0, 2],
28
+ [0, 0, 0, 0, 0, 0, 2], [0, 0, 0, 0, 0, 0, 0]])
29
+ flow = csr_matrix([[0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 2, 0, 0, 0],
30
+ [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 1],
31
+ [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0]])
32
+ self.assertFalse(flow_is_feasible(adjacency, flow, 0, 6))
33
+ def test_flow_is_feasible_2(self):
34
+ adjacency = csr_matrix([[0, 2, 3, 0, 0, 0, 0], [0, 0, 0, 3, 0, 0, 0],
35
+ [0, 0, 0, 2, 0, 0, 0], [0, 0, 0, 0, 1, 3, 0], [0, 0, 0, 0, 0, 0, 2],
36
+ [0, 0, 0, 0, 0, 0, 2], [0, 0, 0, 0, 0, 0, 0]])
37
+ flow = csr_matrix([[0, 2, 0, 0, 0, 0, 0], [0, 0, 0, 2, 0, 0, 0],
38
+ [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 2, 0, 0], [0, 0, 0, 0, 0, 0, 1],
39
+ [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0]])
40
+ self.assertFalse(flow_is_feasible(adjacency, flow, 0, 6))
41
+ def test_flow_is_feasible_3(self):
42
+ adjacency = csr_matrix([[0, 2, 3, 0, 0, 0, 0], [0, 0, 0, 3, 0, 0, 0],
43
+ [0, 0, 0, 2, 0, 0, 0], [0, 0, 0, 0, 1, 3, 0], [0, 0, 0, 0, 0, 0, 2],
44
+ [0, 0, 0, 0, 0, 0, 2], [0, 0, 0, 0, 0, 0, 0]])
45
+ flow = csr_matrix([[0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0],
46
+ [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 1],
47
+ [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0]])
48
+ self.assertTrue(flow_is_feasible(adjacency, flow, 0, 6))
49
+ def test_flow_is_feasible_4(self):
50
+ adjacency = csr_matrix([[0, 2, 3, 0, 0, 0, 0], [0, 0, 0, 3, 0, 0, 0],
51
+ [0, 0, 0, 2, 0, 0, 0], [0, 0, 0, 0, 1, 3, 0], [0, 0, 0, 0, 0, 0, 2],
52
+ [0, 0, 0, 0, 0, 0, 2], [0, 0, 0, 0, 0, 0, 0]])
53
+ flow = csr_matrix([[0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0],
54
+ [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 1],
55
+ [0, 0, 0, 0, 0, 0, 0]])
56
+ self.assertRaises(ValueError, flow_is_feasible, adjacency, flow, 0, 6)
57
+ def test_flow_is_feasible_3(self):
58
+ adjacency = csr_matrix([[0, 2, 3, 0, 0, 0, 0], [0, 0, 0, 3, 0, 0, 0],
59
+ [0, 0, 0, 2, 0, 0, 0], [0, 0, 0, 0, 1, 3, 0], [0, 0, 0, 0, 0, 0, 2],
60
+ [0, 0, 0, 0, 0, 0, 2], [0, 0, 0, 0, 0, 0, 0]])
61
+ flow = csr_matrix([[0, 10, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0],
62
+ [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 1],
63
+ [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0]])
64
+ self.assertFalse(flow_is_feasible(adjacency, flow, 0, 6))
65
+ def test_find_excess(self):
66
+ flow = csr_matrix([[0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0],
67
+ [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 1],
68
+ [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0]])
69
+ self.assertEquals(0, find_excess(flow, 1))
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on July 4, 2022.
5
+ @author: Henry L. Carscadden <hcarscad@gmail.com>
6
+ """
7
+ from scipy import sparse
8
+ import numpy as np
9
+
10
+
11
+ def find_excess(flow: sparse.csr_matrix, node: int):
12
+ """
13
+ This function computes the excess flow for a node in a preflow.
14
+ Parameters
15
+ ----------
16
+ flow: sparse.csr_matrix
17
+ The matrix showing the flows in the proposed solution.
18
+ node : int
19
+ Node to compute the excess on.
20
+
21
+ Returns
22
+ -------
23
+ excess: int
24
+ The amount of excess flow.
25
+ """
26
+ return flow[:, node].sum() - flow[node, :].sum()
27
+
28
+
29
+ def flow_is_feasible(adjacency: sparse.csr_matrix, flow: sparse.csr_matrix, src: int, sink: int):
30
+ """ This utility is used to check if a provided flow satisfies the capacity and flow conservation constraints.
31
+
32
+ Parameters
33
+ ----------
34
+ adjacency : sparse.csr_matrix
35
+ The adjacency matrix of the graph with weights containing the edge capacities.
36
+ flow: sparse.csr_matrix
37
+ The matrix showing the flows in the proposed solution.
38
+ src : int
39
+ The node with the flow source.
40
+ sink : int
41
+ The node with that receives the flow.
42
+
43
+
44
+ Returns
45
+ -------
46
+ feasible: bool
47
+ Whether the follow is feasible, i.e., both the capacity and flow constraints are satisfied.
48
+ """
49
+ if adjacency.shape != flow.shape:
50
+ raise ValueError("The flow has the incorrect shape.")
51
+
52
+ rows, cols = adjacency.nonzero()
53
+ for row in rows:
54
+ if row != src and row != sink:
55
+ if flow[row, :].sum() != flow[:, row].sum():
56
+ return False
57
+ for i in range(rows.size):
58
+ row, col = rows[i], cols[i]
59
+ if adjacency[row, col] < flow[row, col]:
60
+ return False
61
+ return True
62
+
63
+
64
+ def get_residual_graph(adjacency: sparse.csr_matrix, flow: sparse.csr_matrix):
65
+ """ This utility is used for maximum flow algorithms to find the residual graph given a flow.
66
+
67
+ Parameters
68
+ ----------
69
+ adjacency : sparse.csr_matrix
70
+ The adjacency matrix of the graph with weights containing the edge capacities.
71
+ flow: sparse.csr_matrix
72
+ The matrix showing the flows in the proposed solution.
73
+
74
+ Returns
75
+ -------
76
+ residual: sparse.csr_matrix
77
+ The adjacency matrix of the residual graph.
78
+ """
79
+ rows, cols = adjacency.nonzero()
80
+ row_ind = np.zeros(shape=(adjacency.nnz * 2), dtype=np.int)
81
+ col_ind = np.zeros(shape=(adjacency.nnz * 2), dtype=np.int)
82
+ data = np.zeros(shape=(adjacency.nnz * 2), dtype=np.int)
83
+ for i in range(rows.size):
84
+ row, col = rows[i], cols[i]
85
+ curr_ind = i * 2
86
+ row_ind[curr_ind], col_ind[curr_ind] = row, col
87
+ row_ind[curr_ind + 1], col_ind[curr_ind + 1] = col, row
88
+ data[curr_ind] = adjacency[row, col] - flow[row, col]
89
+ data[curr_ind + 1] = flow[row, col]
90
+ residual = sparse.csr_matrix((data, (row_ind, col_ind)), shape=adjacency.shape)
91
+ return residual
@@ -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,155 @@
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 typing import Union
9
+
10
+ import numpy as np
11
+ from collections import defaultdict
12
+ from scipy import sparse
13
+
14
+ from sknetwork.gnn.loss import BaseLoss, get_loss
15
+ from sknetwork.gnn.optimizer import BaseOptimizer, get_optimizer
16
+ from sknetwork.utils.verbose import VerboseMixin
17
+
18
+
19
+ class BaseGNN(VerboseMixin):
20
+ """Base class for GNNs.
21
+
22
+ Parameters
23
+ ----------
24
+ loss : str or custom loss (default = ``'Cross entropy'``)
25
+ Loss function.
26
+ optimizer : str or custom optimizer (default = ``'Adam'``)
27
+ Optimizer used for training.
28
+
29
+ * ``'Adam'``, a stochastic gradient-based optimizer.
30
+ * ``'GD'``, gradient descent.
31
+ learning_rate : float
32
+ Learning rate.
33
+ verbose : bool
34
+ Verbose mode
35
+
36
+ Attributes
37
+ ----------
38
+ layers: list
39
+ List of layers.
40
+ labels_: np.ndarray
41
+ Predicted labels.
42
+ history_: dict
43
+ Training history per epoch: {'embedding', 'loss', 'train_accuracy', 'test_accuracy'}.
44
+ """
45
+ def __init__(self, loss: Union[BaseLoss, str] = 'CrossEntropy', optimizer: Union[BaseOptimizer, str] = 'Adam',
46
+ learning_rate: float = 0.01, verbose: bool = False):
47
+ VerboseMixin.__init__(self, verbose)
48
+ self.optimizer = get_optimizer(optimizer, learning_rate)
49
+ self.loss = get_loss(loss)
50
+ self.layers = []
51
+ self.derivative_weight = []
52
+ self.derivative_bias = []
53
+ self.train_mask = None
54
+ self.test_mask = None
55
+ self.val_mask = None
56
+ self.embedding_ = None
57
+ self.output_ = None
58
+ self.labels_ = None
59
+ self.history_ = defaultdict(list)
60
+
61
+ def fit(self, *args, **kwargs):
62
+ """Fit Algorithm to the data."""
63
+ raise NotImplementedError
64
+
65
+ def predict(self, *args, **kwargs):
66
+ """Predict labels."""
67
+ raise NotImplementedError
68
+
69
+ def fit_predict(self, *args, **kwargs) -> np.ndarray:
70
+ """Fit algorithm to the data and return the labels. Same parameters as the ``fit`` method.
71
+
72
+ Returns
73
+ -------
74
+ labels : np.ndarray
75
+ Labels of the nodes.
76
+ """
77
+ self.fit(*args, **kwargs)
78
+ return self.predict()
79
+
80
+ def fit_transform(self, *args, **kwargs) -> np.ndarray:
81
+ """Fit algorithm to the data and return the embedding of the nodes. Same parameters as the ``fit`` method.
82
+
83
+ Returns
84
+ -------
85
+ embedding : np.ndarray
86
+ Embedding of the nodes.
87
+ """
88
+ self.fit(*args, **kwargs)
89
+ return self.embedding_
90
+
91
+ def backward(self, features: sparse.csr_matrix, labels: np.ndarray, mask: np.ndarray):
92
+ """Compute backpropagation.
93
+
94
+ Parameters
95
+ ----------
96
+ features : sparse.csr_matrix
97
+ Features, array of shape (n_nodes, n_features).
98
+ labels : np.ndarray
99
+ Labels, array of shape (n_nodes,).
100
+ mask: np.ndarray
101
+ Boolean mask, array of shape (n_nodes,).
102
+ """
103
+ derivative_weight = []
104
+ derivative_bias = []
105
+
106
+ # discard missing labels
107
+ mask = mask & (labels >= 0)
108
+ labels = labels[mask]
109
+
110
+ # backpropagation
111
+ n_layers = len(self.layers)
112
+ layers_reverse: list = list(reversed(self.layers))
113
+ signal = layers_reverse[0].embedding
114
+ signal = signal[mask]
115
+ gradient = layers_reverse[0].activation.loss_gradient(signal, labels)
116
+
117
+ for i in range(n_layers):
118
+ if i < n_layers - 1:
119
+ signal = layers_reverse[i + 1].output
120
+ else:
121
+ signal = features
122
+ signal = signal[mask]
123
+
124
+ derivative_weight.append(signal.T.dot(gradient))
125
+ derivative_bias.append(np.mean(gradient, axis=0, keepdims=True))
126
+
127
+ if i < n_layers - 1:
128
+ signal = layers_reverse[i + 1].embedding
129
+ signal = signal[mask]
130
+ direction = layers_reverse[i].weight.dot(gradient.T).T
131
+ gradient = layers_reverse[i + 1].activation.gradient(signal, direction)
132
+
133
+ self.derivative_weight = list(reversed(derivative_weight))
134
+ self.derivative_bias = list(reversed(derivative_bias))
135
+
136
+ def _check_fitted(self):
137
+ if self.output_ is None:
138
+ raise ValueError("This embedding instance is not fitted yet. "
139
+ "Call 'fit' with appropriate arguments before using this method.")
140
+ else:
141
+ return self
142
+
143
+ def __repr__(self) -> str:
144
+ """String representation of the `GNN`, layers by layers.
145
+
146
+ Returns
147
+ -------
148
+ str
149
+ String representation of object.
150
+ """
151
+ string = f'{self.__class__.__name__}(\n'
152
+ for layer in self.layers:
153
+ string += f' {layer}\n'
154
+ string += ')'
155
+ return string