scikit-network 0.33.4__cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.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.
Files changed (229) hide show
  1. scikit_network-0.33.4.dist-info/METADATA +122 -0
  2. scikit_network-0.33.4.dist-info/RECORD +229 -0
  3. scikit_network-0.33.4.dist-info/WHEEL +6 -0
  4. scikit_network-0.33.4.dist-info/licenses/AUTHORS.rst +43 -0
  5. scikit_network-0.33.4.dist-info/licenses/LICENSE +34 -0
  6. scikit_network-0.33.4.dist-info/top_level.txt +1 -0
  7. scikit_network.libs/libgomp-a34b3233.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 +138 -0
  12. sknetwork/classification/base_rank.py +129 -0
  13. sknetwork/classification/diffusion.py +127 -0
  14. sknetwork/classification/knn.py +131 -0
  15. sknetwork/classification/metrics.py +205 -0
  16. sknetwork/classification/pagerank.py +58 -0
  17. sknetwork/classification/propagation.py +144 -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 +27593 -0
  26. sknetwork/classification/vote.cpython-312-x86_64-linux-gnu.so +0 -0
  27. sknetwork/classification/vote.pyx +56 -0
  28. sknetwork/clustering/__init__.py +8 -0
  29. sknetwork/clustering/base.py +168 -0
  30. sknetwork/clustering/kcenters.py +251 -0
  31. sknetwork/clustering/leiden.py +238 -0
  32. sknetwork/clustering/leiden_core.cpp +31928 -0
  33. sknetwork/clustering/leiden_core.cpython-312-x86_64-linux-gnu.so +0 -0
  34. sknetwork/clustering/leiden_core.pyx +124 -0
  35. sknetwork/clustering/louvain.py +282 -0
  36. sknetwork/clustering/louvain_core.cpp +31573 -0
  37. sknetwork/clustering/louvain_core.cpython-312-x86_64-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 +100 -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 +292 -0
  52. sknetwork/data/models.py +459 -0
  53. sknetwork/data/parse.py +644 -0
  54. sknetwork/data/test_graphs.py +93 -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 +61 -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 +90 -0
  67. sknetwork/embedding/force_atlas.py +198 -0
  68. sknetwork/embedding/louvain_embedding.py +142 -0
  69. sknetwork/embedding/random_projection.py +131 -0
  70. sknetwork/embedding/spectral.py +137 -0
  71. sknetwork/embedding/spring.py +198 -0
  72. sknetwork/embedding/svd.py +351 -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 +90 -0
  104. sknetwork/hierarchy/louvain_hierarchy.py +260 -0
  105. sknetwork/hierarchy/metrics.py +234 -0
  106. sknetwork/hierarchy/paris.cpp +37877 -0
  107. sknetwork/hierarchy/paris.cpython-312-x86_64-linux-gnu.so +0 -0
  108. sknetwork/hierarchy/paris.pyx +310 -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 +27409 -0
  118. sknetwork/linalg/diteration.cpython-312-x86_64-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 +31081 -0
  127. sknetwork/linalg/push.cpython-312-x86_64-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 +26 -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 +57 -0
  158. sknetwork/ranking/betweenness.cpp +9716 -0
  159. sknetwork/ranking/betweenness.cpython-312-x86_64-linux-gnu.so +0 -0
  160. sknetwork/ranking/betweenness.pyx +97 -0
  161. sknetwork/ranking/closeness.py +92 -0
  162. sknetwork/ranking/hits.py +90 -0
  163. sknetwork/ranking/katz.py +79 -0
  164. sknetwork/ranking/pagerank.py +106 -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 +57 -0
  175. sknetwork/regression/diffusion.py +204 -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 +32574 -0
  184. sknetwork/topology/cliques.cpython-312-x86_64-linux-gnu.so +0 -0
  185. sknetwork/topology/cliques.pyx +149 -0
  186. sknetwork/topology/core.cpp +30660 -0
  187. sknetwork/topology/core.cpython-312-x86_64-linux-gnu.so +0 -0
  188. sknetwork/topology/core.pyx +90 -0
  189. sknetwork/topology/cycles.py +243 -0
  190. sknetwork/topology/minheap.cpp +27341 -0
  191. sknetwork/topology/minheap.cpython-312-x86_64-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 +8903 -0
  203. sknetwork/topology/triangles.cpython-312-x86_64-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 +27644 -0
  207. sknetwork/topology/weisfeiler_lehman_core.cpython-312-x86_64-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,130 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """tests for gnn classifier"""
4
+
5
+ import unittest
6
+
7
+ import numpy as np
8
+ from scipy import sparse
9
+
10
+ from sknetwork.data.test_graphs import test_graph
11
+ from sknetwork.gnn.gnn_classifier import GNNClassifier
12
+
13
+
14
+ class TestGNNClassifier(unittest.TestCase):
15
+
16
+ def setUp(self) -> None:
17
+ """Test graph for tests."""
18
+ self.adjacency = test_graph()
19
+ self.n = self.adjacency.shape[0]
20
+ self.features = self.adjacency
21
+ self.labels = np.array(4 * [0, 1] + 2 * [-1])
22
+
23
+ def test_gnn_classifier_sparse_feat(self):
24
+ gnn = GNNClassifier([3, 2], 'Conv', 'Softmax')
25
+ self.assertTrue(gnn.layers[0].activation.name == 'Softmax')
26
+ self.assertTrue(gnn.layers[1].activation.name == 'Cross entropy')
27
+ labels_pred = gnn.fit_predict(self.adjacency, self.features, self.labels)
28
+ embedding = gnn.embedding_
29
+ self.assertTrue(len(labels_pred) == self.n)
30
+ self.assertTrue(embedding.shape == (self.n, 2))
31
+
32
+ def test_gnn_classifier_dense_feat(self):
33
+ # features not in nparray
34
+ features = self.adjacency.todense()
35
+ gnn = GNNClassifier(2)
36
+ with self.assertRaises(TypeError):
37
+ gnn.fit_predict(self.adjacency, features, self.labels)
38
+
39
+ # features in numpy array
40
+ features = np.array(self.adjacency.todense())
41
+ gnn = GNNClassifier(2, 'Conv')
42
+ y_pred = gnn.fit_predict(self.adjacency, features, self.labels, validation=0.2)
43
+ embedding = gnn.embedding_
44
+ self.assertTrue(len(y_pred) == self.n)
45
+ self.assertTrue(embedding.shape == (self.n, 2))
46
+
47
+ def test_gnn_classifier_no_bias(self):
48
+ gnn = GNNClassifier([3, 2], 'Conv', 'Softmax', use_bias=[True, False])
49
+ labels_pred = gnn.fit_predict(self.adjacency, self.features, self.labels)
50
+ embedding = gnn.embedding_
51
+ self.assertTrue(len(labels_pred) == self.n)
52
+ self.assertTrue(embedding.shape == (self.n, 2))
53
+ self.assertTrue(gnn.layers[1].bias is None)
54
+
55
+ def test_gnn_classifier_optimizer(self):
56
+ optimizers = ['GD', 'Adam']
57
+ for optimizer in optimizers:
58
+ gnn = GNNClassifier(2, 'Conv', optimizer=optimizer)
59
+ y_pred = gnn.fit_predict(self.adjacency, self.features, self.labels)
60
+ embedding = gnn.embedding_
61
+ self.assertTrue(len(y_pred) == self.n)
62
+ self.assertTrue(embedding.shape == (self.n, 2))
63
+
64
+ def test_gnn_classifier_binary(self):
65
+ gnn = GNNClassifier([5, 1], 'Conv', 'Softmax')
66
+ self.assertTrue(gnn.layers[1].activation.name == 'Binary cross entropy')
67
+ labels_pred = gnn.fit_predict(self.adjacency, self.features, self.labels)
68
+ self.assertTrue(len(labels_pred) == self.n)
69
+
70
+ def test_gnn_classifier_norm(self):
71
+ n_labels = len(set(self.labels))
72
+ gnn = GNNClassifier([5, n_labels], 'Conv', normalizations=['left', 'both'])
73
+ labels_pred = gnn.fit_predict(self.adjacency, self.features, self.labels)
74
+ self.assertTrue(len(labels_pred) == self.n)
75
+
76
+ def test_gnn_classifier_1label(self):
77
+ gnn = GNNClassifier(1, 'Conv', 'Relu')
78
+ labels_pred = gnn.fit_predict(self.adjacency, self.features, self.labels)
79
+ self.assertTrue(len(labels_pred) == self.n)
80
+
81
+ def test_gnn_classifier_validation(self):
82
+ gnn = GNNClassifier(2, 'Conv', 'Softmax', early_stopping=False)
83
+ labels_pred = gnn.fit_predict(self.adjacency, self.features, self.labels, validation=0.5, random_state=42)
84
+ self.assertTrue(len(labels_pred) == self.n)
85
+
86
+ def test_gnn_classifier_dim_output(self):
87
+ gnn = GNNClassifier(2)
88
+ labels = np.arange(len(self.labels))
89
+ with self.assertRaises(ValueError):
90
+ gnn.fit(self.adjacency, self.features, labels)
91
+
92
+ def test_gnn_classifier_verbose(self):
93
+ gnn = GNNClassifier(2, verbose=True)
94
+ self.assertTrue(isinstance(gnn, GNNClassifier))
95
+
96
+ def test_gnn_classifier_early_stopping(self):
97
+ gnn = GNNClassifier(2, patience=2)
98
+ labels = {0: 0, 1: 1}
99
+ _ = gnn.fit_predict(self.adjacency, self.features, labels, n_epochs=100, validation=0.5,
100
+ random_state=42)
101
+ self.assertTrue(len(gnn.history_['val_accuracy']) < 100)
102
+
103
+ gnn = GNNClassifier(2, early_stopping=False)
104
+ _ = gnn.fit_predict(self.adjacency, self.features, labels, n_epochs=100, validation=0.5,
105
+ random_state=42)
106
+ self.assertTrue(len(gnn.history_['val_accuracy']) == 100)
107
+
108
+ def test_gnn_classifier_reinit(self):
109
+ gnn = GNNClassifier([4, 2])
110
+ gnn.fit(self.adjacency, self.features, self.labels)
111
+ gnn.fit(self.adjacency, self.features, self.labels, n_epochs=1, reinit=True)
112
+ self.assertTrue(gnn.embedding_.shape == (self.n, 2))
113
+
114
+ def test_gnn_classifier_sageconv(self):
115
+ gnn = GNNClassifier([4, 2], ['SAGEConv', 'SAGEConv'], sample_sizes=[5, 3])
116
+ _ = gnn.fit_predict(self.adjacency, self.features, self.labels, n_epochs=100)
117
+ self.assertTrue(gnn.layers[0].sample_size == 5 and gnn.layers[0].normalization == 'left')
118
+ self.assertTrue(gnn.layers[1].sample_size == 3 and gnn.layers[1].normalization == 'left')
119
+
120
+ def test_gnn_classifier_predict(self):
121
+ gnn = GNNClassifier([4, 2])
122
+ labels_pred = gnn.fit_predict(self.adjacency, self.features, self.labels)
123
+ labels_pred_ = gnn.predict()
124
+ self.assertTrue(all(labels_pred == gnn.labels_))
125
+ self.assertTrue(all(labels_pred == labels_pred_))
126
+
127
+ def test_gnn_classifier_predict_proba(self):
128
+ gnn = GNNClassifier([4, 2])
129
+ probs = gnn.fit_predict_proba(self.adjacency, self.features, self.labels)
130
+ self.assertTrue(probs.shape[1] == 2)
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """tests for layers"""
4
+
5
+ import unittest
6
+
7
+ import numpy as np
8
+
9
+ from sknetwork.data.test_graphs import test_graph
10
+ from sknetwork.gnn.layer import Convolution, get_layer
11
+
12
+
13
+ class TestLayer(unittest.TestCase):
14
+
15
+ def setUp(self) -> None:
16
+ """Test graph for tests."""
17
+ self.adjacency = test_graph()
18
+ self.features = self.adjacency
19
+ self.labels = np.array([0] * 5 + [1] * 5)
20
+
21
+ def test_graph_conv_shapes(self):
22
+ conv1 = Convolution('Conv', 4)
23
+ conv1._initialize_weights(self.features.shape[1])
24
+ conv2 = Convolution('Conv', 2)
25
+ conv2._initialize_weights(4)
26
+
27
+ self.assertTrue(conv1.weight.shape == (self.features.shape[1], 4))
28
+ self.assertTrue(conv1.bias.shape == (1, 4))
29
+ self.assertTrue(conv1.weights_initialized)
30
+ self.assertTrue(conv2.weight.shape == (4, 2))
31
+ self.assertTrue(conv2.bias.shape == (1, 2))
32
+ self.assertTrue(conv2.weights_initialized)
33
+
34
+ h = conv1.forward(self.adjacency, self.features)
35
+ self.assertTrue(h.shape == (self.adjacency.shape[0], 4))
36
+ emb = conv2.forward(self.adjacency, h)
37
+ self.assertTrue(emb.shape == (self.adjacency.shape[0], 2))
38
+
39
+ def test_graph_conv_bias_use(self):
40
+ conv1 = Convolution('Conv', 4, use_bias=False)
41
+ conv2 = Convolution('Conv', 2, use_bias=False)
42
+ h = conv1.forward(self.adjacency, self.features)
43
+ emb = conv2.forward(self.adjacency, h)
44
+ self.assertTrue(emb.shape == (self.adjacency.shape[0], 2))
45
+
46
+ def test_graph_conv_self_embeddings(self):
47
+ conv1 = Convolution('Conv', 4, self_embeddings=False)
48
+ conv2 = Convolution('Conv', 2, self_embeddings=False)
49
+ h = conv1.forward(self.adjacency, self.features)
50
+ emb = conv2.forward(self.adjacency, h)
51
+ self.assertTrue(emb.shape == (self.adjacency.shape[0], 2))
52
+
53
+ def test_graph_conv_norm(self):
54
+ conv1 = Convolution('Conv', 4, normalization='left')
55
+ conv2 = Convolution('Conv', 2, normalization='right')
56
+ h = conv1.forward(self.adjacency, self.features)
57
+ emb = conv2.forward(self.adjacency, h)
58
+ self.assertTrue(emb.shape == (self.adjacency.shape[0], 2))
59
+
60
+ def test_graph_conv_activation(self):
61
+ activations = ['Relu', 'Sigmoid']
62
+ for a in activations:
63
+ conv1 = Convolution('Conv', 4, activation=a)
64
+ conv2 = Convolution('Conv', 2)
65
+ h = conv1.forward(self.adjacency, self.features)
66
+ emb = conv2.forward(self.adjacency, h)
67
+ self.assertTrue(emb.shape == (self.adjacency.shape[0], 2))
68
+
69
+ def test_graph_sage(self):
70
+ conv1 = Convolution('Sage', 4, normalization='left', self_embeddings=True)
71
+ conv2 = Convolution('Sage', 2, normalization='right', self_embeddings=True)
72
+ h = conv1.forward(self.adjacency, self.features)
73
+ emb = conv2.forward(self.adjacency, h)
74
+ self.assertTrue(emb.shape == (self.adjacency.shape[0], 2))
75
+
76
+ def test_get_layer(self):
77
+ with self.assertRaises(ValueError):
78
+ get_layer('toto')
79
+ with self.assertRaises(TypeError):
80
+ get_layer(Convolution)
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """tests for loss"""
4
+
5
+ import unittest
6
+
7
+ from sknetwork.gnn.loss import *
8
+
9
+
10
+ class TestLoss(unittest.TestCase):
11
+
12
+ def test_get_loss(self):
13
+ self.assertTrue(isinstance(get_loss('CrossEntropy'), CrossEntropy))
14
+ self.assertTrue(isinstance(get_loss('BinaryCrossEntropy'), BinaryCrossEntropy))
15
+ with self.assertRaises(ValueError):
16
+ get_loss('foo')
17
+
18
+ base_loss = BaseLoss()
19
+ self.assertTrue(base_loss == get_loss(base_loss))
20
+ with self.assertRaises(TypeError):
21
+ get_loss(0)
22
+
23
+ def test_ce_loss(self):
24
+ cross_entropy = CrossEntropy()
25
+ signal = np.array([[0, 5]])
26
+ labels = np.array([1])
27
+ self.assertAlmostEqual(cross_entropy.loss(signal, labels), 0.00671534848911828)
28
+
29
+ def test_bce_loss(self):
30
+ binary_cross_entropy = BinaryCrossEntropy()
31
+ signal = np.array([[0, 5]])
32
+ labels = np.array([1])
33
+ self.assertAlmostEqual(binary_cross_entropy.loss(signal, labels), 0.6998625290490632)
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """tests for neighbor sampler"""
4
+
5
+ import unittest
6
+
7
+ from sknetwork.data.test_graphs import test_graph
8
+ from sknetwork.gnn.neighbor_sampler import *
9
+ from sknetwork.utils import get_degrees
10
+
11
+
12
+ class TestNeighSampler(unittest.TestCase):
13
+
14
+ def setUp(self) -> None:
15
+ """Test graph for tests."""
16
+ self.adjacency = test_graph()
17
+ self.n = self.adjacency.shape[0]
18
+
19
+ def test_uni_node_sampler(self):
20
+ uni_sampler = UniformNeighborSampler(sample_size=2)
21
+ sampled_adj = uni_sampler(self.adjacency)
22
+ self.assertTrue(sampled_adj.shape == self.adjacency.shape)
23
+ self.assertTrue(all(get_degrees(sampled_adj) <= 2))
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """tests for optimizer"""
4
+
5
+ import unittest
6
+
7
+ import numpy as np
8
+
9
+ from sknetwork.data.test_graphs import test_graph
10
+ from sknetwork.gnn.gnn_classifier import GNNClassifier
11
+ from sknetwork.gnn.optimizer import get_optimizer
12
+
13
+
14
+ class TestOptimizer(unittest.TestCase):
15
+
16
+ def setUp(self) -> None:
17
+ self.adjacency = test_graph()
18
+ self.features = self.adjacency
19
+ self.labels = np.array(4 * [0, 1] + 2 * [-1])
20
+
21
+ def test_get_optimizer(self):
22
+ with self.assertRaises(ValueError):
23
+ get_optimizer('foo')
24
+ with self.assertRaises(TypeError):
25
+ get_optimizer(GNNClassifier())
26
+
27
+ def test_optimizer(self):
28
+ for optimizer in ['Adam', 'GD']:
29
+ gnn = GNNClassifier([4, 2], 'Conv', ['Relu', 'Softmax'], optimizer=optimizer)
30
+ _ = gnn.fit_predict(self.adjacency, self.features, self.labels, n_epochs=1)
31
+ conv0_weight, conv1_weight = gnn.layers[0].weight.copy(), gnn.layers[1].weight.copy()
32
+ conv0_b, conv1_b = gnn.layers[0].bias.copy(), gnn.layers[1].bias.copy()
33
+ gnn.optimizer.step(gnn)
34
+ # Test weight matrix
35
+ self.assertTrue(gnn.layers[0].weight.shape == conv0_weight.shape)
36
+ self.assertTrue(gnn.layers[1].weight.shape == conv1_weight.shape)
37
+ self.assertTrue((gnn.layers[0].weight != conv0_weight).any())
38
+ self.assertTrue((gnn.layers[1].weight != conv1_weight).any())
39
+ # Test bias vector
40
+ self.assertTrue(gnn.layers[0].bias.shape == conv0_b.shape)
41
+ self.assertTrue(gnn.layers[1].bias.shape == conv1_b.shape)
42
+ self.assertTrue((gnn.layers[0].bias != conv0_b).any())
43
+ self.assertTrue((gnn.layers[1].bias != conv1_b).any())
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """tests for gnn utils"""
4
+
5
+ import unittest
6
+
7
+ from sknetwork.gnn.utils import *
8
+
9
+
10
+ class TestUtils(unittest.TestCase):
11
+
12
+ def test_check_norm(self):
13
+ with self.assertRaises(ValueError):
14
+ check_normalizations('foo')
15
+ with self.assertRaises(ValueError):
16
+ check_normalizations(['foo', 'bar'])
17
+
18
+ def test_early_stopping(self):
19
+ self.assertTrue(check_early_stopping(True, np.array([True, False]), 2))
20
+ self.assertFalse(check_early_stopping(True, None, 2))
21
+ self.assertFalse(check_early_stopping(True, np.array([True, False, True]), None))
22
+ self.assertFalse(check_early_stopping(True, np.array([False, False, False]), 5))
23
+
24
+ def test_get_layers(self):
25
+ with self.assertRaises(ValueError):
26
+ get_layers([4, 2], 'Conv', activations=['Relu', 'Sigmoid', 'Relu'], use_bias=True, normalizations='Both',
27
+ self_embeddings=True, sample_sizes=5, loss=None)
28
+ # Type compatibility
29
+ layers = get_layers([4], 'Conv', activations=['Relu'], use_bias=[True], normalizations=['Both'],
30
+ self_embeddings=[True], sample_sizes=[5], loss='Cross entropy')
31
+ self.assertTrue(len(np.ravel(layers)) == 1)
32
+ # Broadcasting parameters
33
+ layers = get_layers([4, 2], ['Conv', 'Conv'], activations='Relu', use_bias=True, normalizations='Both',
34
+ self_embeddings=True, sample_sizes=5, loss='Cross entropy')
35
+ self.assertTrue(len(layers) == 2)
36
+
37
+ def test_check_loss(self):
38
+ layer = get_layers([4], 'Conv', activations=['Relu'], use_bias=[True], normalizations=['Both'],
39
+ self_embeddings=[True], sample_sizes=[5], loss=None)
40
+ with self.assertRaises(ValueError):
41
+ check_loss(layer[0])
sknetwork/gnn/utils.py ADDED
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ """
4
+ Created in April 2022
5
+ @author: Simon Delarue <sdelarue@enst.fr>
6
+ """
7
+ from typing import Iterable, Union
8
+
9
+ import numpy as np
10
+
11
+ from sknetwork.gnn.base_activation import BaseActivation, BaseLoss
12
+ from sknetwork.gnn.base_layer import BaseLayer
13
+ from sknetwork.gnn.layer import get_layer
14
+ from sknetwork.gnn.loss import BinaryCrossEntropy, CrossEntropy
15
+
16
+
17
+ def check_early_stopping(early_stopping: bool, val_mask: np.ndarray, patience: int):
18
+ """Check early stopping parameters."""
19
+ if val_mask is None or patience is None or not any(val_mask):
20
+ return False
21
+ else:
22
+ return early_stopping
23
+
24
+
25
+ def check_normalizations(normalizations: Union[str, Iterable]):
26
+ """Check if normalization is known."""
27
+ available_norms = ['left', 'right', 'both']
28
+ if isinstance(normalizations, list):
29
+ for normalization in normalizations:
30
+ if normalization.lower() not in available_norms:
31
+ raise ValueError("Normalization must be 'left', 'right' or 'both'.")
32
+ elif normalizations.lower() not in available_norms:
33
+ raise ValueError("Normalization must be 'left', 'right' or 'both'.")
34
+
35
+
36
+ def check_output(n_channels: int, labels: np.ndarray):
37
+ """Check the output of the GNN.
38
+
39
+ Parameters
40
+ ----------
41
+ n_channels : int
42
+ Number of output channels
43
+ labels : np.ndarray
44
+ Vector of labels
45
+ """
46
+ n_labels = len(set(labels[labels >= 0]))
47
+ if n_labels > 2 and n_labels > n_channels:
48
+ raise ValueError("The dimension of the output is too small for the number of labels. "
49
+ "Please check the `dims` parameter of your GNN or the `labels` parameter.")
50
+
51
+
52
+ def check_param(param, length):
53
+ """Check the length of a parameter if a list.
54
+ """
55
+ if not isinstance(param, list):
56
+ param = length * [param]
57
+ elif len(param) != length:
58
+ raise ValueError('The number of parameters must be equal to the number of layers.')
59
+ return param
60
+
61
+
62
+ def check_loss(layer: BaseLayer):
63
+ """Check the length of a parameter if a list.
64
+ """
65
+ if not issubclass(type(layer.activation), BaseLoss):
66
+ raise ValueError('No loss specified for the last layer.')
67
+ if isinstance(layer.activation, CrossEntropy) and layer.out_channels == 1:
68
+ layer.activation = BinaryCrossEntropy()
69
+ return layer.activation
70
+
71
+
72
+ def get_layers(dims: Union[int, Iterable], layer_types: Union[str, BaseLayer, Iterable],
73
+ activations: Union[str, BaseActivation, list], use_bias: Union[bool, Iterable],
74
+ normalizations: Union[str, Iterable], self_embeddings: Union[bool, Iterable],
75
+ sample_sizes: Union[int, Iterable], loss: Union[str, BaseLoss]) -> list:
76
+ """Get the list of layers.
77
+
78
+ Parameters
79
+ ----------
80
+ dims :
81
+ Dimensions of layers (in forward direction).
82
+ layer_types :
83
+ Layer types.
84
+ activations :
85
+ Activation functions.
86
+ use_bias :
87
+ ``True`` if a bias vector is added.
88
+ normalizations :
89
+ Normalizations of adjacency matrix.
90
+ self_embeddings :
91
+ ``True`` if self embeddings are added. Allowed input are booleans and lists.
92
+ sample_sizes
93
+ Size of neighborhood sampled for each node.
94
+ loss :
95
+ Loss function.
96
+
97
+ Returns
98
+ -------
99
+ list
100
+ List of layers.
101
+ """
102
+ check_normalizations(normalizations)
103
+
104
+ if isinstance(dims, int):
105
+ dims = [dims]
106
+ n_layers = len(dims)
107
+
108
+ layer_types = check_param(layer_types, n_layers)
109
+ activations = check_param(activations, n_layers)
110
+ use_bias = check_param(use_bias, n_layers)
111
+ normalizations = check_param(normalizations, n_layers)
112
+ self_embeddings = check_param(self_embeddings, n_layers)
113
+ sample_sizes = check_param(sample_sizes, n_layers)
114
+
115
+ layers = []
116
+ names_params = ['layer', 'out_channels', 'activation', 'use_bias', 'normalization', 'self_embeddings',
117
+ 'sample_size']
118
+ for i in range(n_layers):
119
+ params = [layer_types[i], dims[i], activations[i], use_bias[i], normalizations[i], self_embeddings[i],
120
+ sample_sizes[i]]
121
+ if i == n_layers - 1:
122
+ params.append(loss)
123
+ names_params.append('loss')
124
+ dict_params = dict(zip(names_params, params))
125
+ layers.append(get_layer(**dict_params))
126
+
127
+ return layers
@@ -0,0 +1,6 @@
1
+ """hierarchy module"""
2
+ from sknetwork.hierarchy.paris import Paris
3
+ from sknetwork.hierarchy.base import BaseHierarchy
4
+ from sknetwork.hierarchy.louvain_hierarchy import LouvainIteration, LouvainHierarchy
5
+ from sknetwork.hierarchy.metrics import dasgupta_cost, dasgupta_score, tree_sampling_divergence
6
+ from sknetwork.hierarchy.postprocess import cut_straight, cut_balanced, aggregate_dendrogram, reorder_dendrogram
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created in November 2019
5
+ @author: Nathan de Lara <nathan.delara@polytechnique.org>
6
+ """
7
+ from abc import ABC
8
+
9
+ import numpy as np
10
+
11
+ from sknetwork.hierarchy.postprocess import split_dendrogram
12
+ from sknetwork.base import Algorithm
13
+
14
+
15
+ class BaseHierarchy(Algorithm, ABC):
16
+ """Base class for hierarchical clustering algorithms.
17
+ Attributes
18
+ ----------
19
+ dendrogram\_ :
20
+ Dendrogram of the graph.
21
+ """
22
+
23
+ def __init__(self):
24
+ self._init_vars()
25
+
26
+ def predict(self, columns: bool = False) -> np.ndarray:
27
+ """Return the dendrogram predicted by the algorithm.
28
+
29
+ Parameters
30
+ ----------
31
+ columns : bool
32
+ If ``True``, return the prediction for columns.
33
+
34
+ Returns
35
+ -------
36
+ dendrogram : np.ndarray
37
+ Dendrogram.
38
+ """
39
+ if columns:
40
+ return self.dendrogram_col_
41
+ return self.dendrogram_
42
+
43
+ def transform(self) -> np.ndarray:
44
+ """Return the dendrogram predicted by the algorithm.
45
+
46
+ Returns
47
+ -------
48
+ dendrogram : np.ndarray
49
+ Dendrogram.
50
+ """
51
+ return self.dendrogram_
52
+
53
+ def fit_predict(self, *args, **kwargs) -> np.ndarray:
54
+ """Fit algorithm to data and return the dendrogram. Same parameters as the ``fit`` method.
55
+
56
+ Returns
57
+ -------
58
+ dendrogram : np.ndarray
59
+ Dendrogram.
60
+ """
61
+ self.fit(*args, **kwargs)
62
+ return self.dendrogram_
63
+
64
+ def fit_transform(self, *args, **kwargs) -> np.ndarray:
65
+ """Fit algorithm to data and return the dendrogram. Alias for ``fit_predict``.
66
+ Same parameters as the ``fit`` method.
67
+
68
+ Returns
69
+ -------
70
+ dendrogram : np.ndarray
71
+ Dendrogram.
72
+ """
73
+ self.fit(*args, **kwargs)
74
+ return self.dendrogram_
75
+
76
+ def _init_vars(self):
77
+ """Init variables."""
78
+ self.dendrogram_ = None
79
+ self.dendrogram_row_ = None
80
+ self.dendrogram_col_ = None
81
+ self.dendrogram_full_ = None
82
+
83
+ def _split_vars(self, shape):
84
+ """Split variables."""
85
+ dendrogram_row, dendrogram_col = split_dendrogram(self.dendrogram_, shape)
86
+ self.dendrogram_full_ = self.dendrogram_
87
+ self.dendrogram_ = dendrogram_row
88
+ self.dendrogram_row_ = dendrogram_row
89
+ self.dendrogram_col_ = dendrogram_col
90
+ return self