scikit-network 0.33.3__cp312-cp312-macosx_10_13_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.
Potentially problematic release.
This version of scikit-network might be problematic. Click here for more details.
- scikit_network-0.33.3.dist-info/METADATA +122 -0
- scikit_network-0.33.3.dist-info/RECORD +228 -0
- scikit_network-0.33.3.dist-info/WHEEL +6 -0
- scikit_network-0.33.3.dist-info/licenses/AUTHORS.rst +43 -0
- scikit_network-0.33.3.dist-info/licenses/LICENSE +34 -0
- scikit_network-0.33.3.dist-info/top_level.txt +1 -0
- sknetwork/__init__.py +21 -0
- sknetwork/base.py +67 -0
- sknetwork/classification/__init__.py +8 -0
- sknetwork/classification/base.py +142 -0
- sknetwork/classification/base_rank.py +133 -0
- sknetwork/classification/diffusion.py +134 -0
- sknetwork/classification/knn.py +139 -0
- sknetwork/classification/metrics.py +205 -0
- sknetwork/classification/pagerank.py +66 -0
- sknetwork/classification/propagation.py +152 -0
- sknetwork/classification/tests/__init__.py +1 -0
- sknetwork/classification/tests/test_API.py +30 -0
- sknetwork/classification/tests/test_diffusion.py +77 -0
- sknetwork/classification/tests/test_knn.py +23 -0
- sknetwork/classification/tests/test_metrics.py +53 -0
- sknetwork/classification/tests/test_pagerank.py +20 -0
- sknetwork/classification/tests/test_propagation.py +24 -0
- sknetwork/classification/vote.cpp +27581 -0
- sknetwork/classification/vote.cpython-312-darwin.so +0 -0
- sknetwork/classification/vote.pyx +56 -0
- sknetwork/clustering/__init__.py +8 -0
- sknetwork/clustering/base.py +172 -0
- sknetwork/clustering/kcenters.py +253 -0
- sknetwork/clustering/leiden.py +242 -0
- sknetwork/clustering/leiden_core.cpp +31572 -0
- sknetwork/clustering/leiden_core.cpython-312-darwin.so +0 -0
- sknetwork/clustering/leiden_core.pyx +124 -0
- sknetwork/clustering/louvain.py +286 -0
- sknetwork/clustering/louvain_core.cpp +31217 -0
- sknetwork/clustering/louvain_core.cpython-312-darwin.so +0 -0
- sknetwork/clustering/louvain_core.pyx +124 -0
- sknetwork/clustering/metrics.py +91 -0
- sknetwork/clustering/postprocess.py +66 -0
- sknetwork/clustering/propagation_clustering.py +104 -0
- sknetwork/clustering/tests/__init__.py +1 -0
- sknetwork/clustering/tests/test_API.py +38 -0
- sknetwork/clustering/tests/test_kcenters.py +60 -0
- sknetwork/clustering/tests/test_leiden.py +34 -0
- sknetwork/clustering/tests/test_louvain.py +135 -0
- sknetwork/clustering/tests/test_metrics.py +50 -0
- sknetwork/clustering/tests/test_postprocess.py +39 -0
- sknetwork/data/__init__.py +6 -0
- sknetwork/data/base.py +33 -0
- sknetwork/data/load.py +406 -0
- sknetwork/data/models.py +459 -0
- sknetwork/data/parse.py +644 -0
- sknetwork/data/test_graphs.py +84 -0
- sknetwork/data/tests/__init__.py +1 -0
- sknetwork/data/tests/test_API.py +30 -0
- sknetwork/data/tests/test_base.py +14 -0
- sknetwork/data/tests/test_load.py +95 -0
- sknetwork/data/tests/test_models.py +52 -0
- sknetwork/data/tests/test_parse.py +250 -0
- sknetwork/data/tests/test_test_graphs.py +29 -0
- sknetwork/data/tests/test_toy_graphs.py +68 -0
- sknetwork/data/timeout.py +38 -0
- sknetwork/data/toy_graphs.py +611 -0
- sknetwork/embedding/__init__.py +8 -0
- sknetwork/embedding/base.py +94 -0
- sknetwork/embedding/force_atlas.py +198 -0
- sknetwork/embedding/louvain_embedding.py +148 -0
- sknetwork/embedding/random_projection.py +135 -0
- sknetwork/embedding/spectral.py +141 -0
- sknetwork/embedding/spring.py +198 -0
- sknetwork/embedding/svd.py +359 -0
- sknetwork/embedding/tests/__init__.py +1 -0
- sknetwork/embedding/tests/test_API.py +49 -0
- sknetwork/embedding/tests/test_force_atlas.py +35 -0
- sknetwork/embedding/tests/test_louvain_embedding.py +33 -0
- sknetwork/embedding/tests/test_random_projection.py +28 -0
- sknetwork/embedding/tests/test_spectral.py +81 -0
- sknetwork/embedding/tests/test_spring.py +50 -0
- sknetwork/embedding/tests/test_svd.py +43 -0
- sknetwork/gnn/__init__.py +10 -0
- sknetwork/gnn/activation.py +117 -0
- sknetwork/gnn/base.py +181 -0
- sknetwork/gnn/base_activation.py +90 -0
- sknetwork/gnn/base_layer.py +109 -0
- sknetwork/gnn/gnn_classifier.py +305 -0
- sknetwork/gnn/layer.py +153 -0
- sknetwork/gnn/loss.py +180 -0
- sknetwork/gnn/neighbor_sampler.py +65 -0
- sknetwork/gnn/optimizer.py +164 -0
- sknetwork/gnn/tests/__init__.py +1 -0
- sknetwork/gnn/tests/test_activation.py +56 -0
- sknetwork/gnn/tests/test_base.py +75 -0
- sknetwork/gnn/tests/test_base_layer.py +37 -0
- sknetwork/gnn/tests/test_gnn_classifier.py +130 -0
- sknetwork/gnn/tests/test_layers.py +80 -0
- sknetwork/gnn/tests/test_loss.py +33 -0
- sknetwork/gnn/tests/test_neigh_sampler.py +23 -0
- sknetwork/gnn/tests/test_optimizer.py +43 -0
- sknetwork/gnn/tests/test_utils.py +41 -0
- sknetwork/gnn/utils.py +127 -0
- sknetwork/hierarchy/__init__.py +6 -0
- sknetwork/hierarchy/base.py +96 -0
- sknetwork/hierarchy/louvain_hierarchy.py +272 -0
- sknetwork/hierarchy/metrics.py +234 -0
- sknetwork/hierarchy/paris.cpp +37865 -0
- sknetwork/hierarchy/paris.cpython-312-darwin.so +0 -0
- sknetwork/hierarchy/paris.pyx +316 -0
- sknetwork/hierarchy/postprocess.py +350 -0
- sknetwork/hierarchy/tests/__init__.py +1 -0
- sknetwork/hierarchy/tests/test_API.py +24 -0
- sknetwork/hierarchy/tests/test_algos.py +34 -0
- sknetwork/hierarchy/tests/test_metrics.py +62 -0
- sknetwork/hierarchy/tests/test_postprocess.py +57 -0
- sknetwork/linalg/__init__.py +9 -0
- sknetwork/linalg/basics.py +37 -0
- sknetwork/linalg/diteration.cpp +27397 -0
- sknetwork/linalg/diteration.cpython-312-darwin.so +0 -0
- sknetwork/linalg/diteration.pyx +47 -0
- sknetwork/linalg/eig_solver.py +93 -0
- sknetwork/linalg/laplacian.py +15 -0
- sknetwork/linalg/normalizer.py +86 -0
- sknetwork/linalg/operators.py +225 -0
- sknetwork/linalg/polynome.py +76 -0
- sknetwork/linalg/ppr_solver.py +170 -0
- sknetwork/linalg/push.cpp +31069 -0
- sknetwork/linalg/push.cpython-312-darwin.so +0 -0
- sknetwork/linalg/push.pyx +71 -0
- sknetwork/linalg/sparse_lowrank.py +142 -0
- sknetwork/linalg/svd_solver.py +91 -0
- sknetwork/linalg/tests/__init__.py +1 -0
- sknetwork/linalg/tests/test_eig.py +44 -0
- sknetwork/linalg/tests/test_laplacian.py +18 -0
- sknetwork/linalg/tests/test_normalization.py +34 -0
- sknetwork/linalg/tests/test_operators.py +66 -0
- sknetwork/linalg/tests/test_polynome.py +38 -0
- sknetwork/linalg/tests/test_ppr.py +50 -0
- sknetwork/linalg/tests/test_sparse_lowrank.py +61 -0
- sknetwork/linalg/tests/test_svd.py +38 -0
- sknetwork/linkpred/__init__.py +2 -0
- sknetwork/linkpred/base.py +46 -0
- sknetwork/linkpred/nn.py +126 -0
- sknetwork/linkpred/tests/__init__.py +1 -0
- sknetwork/linkpred/tests/test_nn.py +27 -0
- sknetwork/log.py +19 -0
- sknetwork/path/__init__.py +5 -0
- sknetwork/path/dag.py +54 -0
- sknetwork/path/distances.py +98 -0
- sknetwork/path/search.py +31 -0
- sknetwork/path/shortest_path.py +61 -0
- sknetwork/path/tests/__init__.py +1 -0
- sknetwork/path/tests/test_dag.py +37 -0
- sknetwork/path/tests/test_distances.py +62 -0
- sknetwork/path/tests/test_search.py +40 -0
- sknetwork/path/tests/test_shortest_path.py +40 -0
- sknetwork/ranking/__init__.py +8 -0
- sknetwork/ranking/base.py +61 -0
- sknetwork/ranking/betweenness.cpp +9704 -0
- sknetwork/ranking/betweenness.cpython-312-darwin.so +0 -0
- sknetwork/ranking/betweenness.pyx +97 -0
- sknetwork/ranking/closeness.py +92 -0
- sknetwork/ranking/hits.py +94 -0
- sknetwork/ranking/katz.py +83 -0
- sknetwork/ranking/pagerank.py +110 -0
- sknetwork/ranking/postprocess.py +37 -0
- sknetwork/ranking/tests/__init__.py +1 -0
- sknetwork/ranking/tests/test_API.py +32 -0
- sknetwork/ranking/tests/test_betweenness.py +38 -0
- sknetwork/ranking/tests/test_closeness.py +30 -0
- sknetwork/ranking/tests/test_hits.py +20 -0
- sknetwork/ranking/tests/test_pagerank.py +62 -0
- sknetwork/ranking/tests/test_postprocess.py +26 -0
- sknetwork/regression/__init__.py +4 -0
- sknetwork/regression/base.py +61 -0
- sknetwork/regression/diffusion.py +210 -0
- sknetwork/regression/tests/__init__.py +1 -0
- sknetwork/regression/tests/test_API.py +32 -0
- sknetwork/regression/tests/test_diffusion.py +56 -0
- sknetwork/sknetwork.py +3 -0
- sknetwork/test_base.py +35 -0
- sknetwork/test_log.py +15 -0
- sknetwork/topology/__init__.py +8 -0
- sknetwork/topology/cliques.cpp +32562 -0
- sknetwork/topology/cliques.cpython-312-darwin.so +0 -0
- sknetwork/topology/cliques.pyx +149 -0
- sknetwork/topology/core.cpp +30648 -0
- sknetwork/topology/core.cpython-312-darwin.so +0 -0
- sknetwork/topology/core.pyx +90 -0
- sknetwork/topology/cycles.py +243 -0
- sknetwork/topology/minheap.cpp +27329 -0
- sknetwork/topology/minheap.cpython-312-darwin.so +0 -0
- sknetwork/topology/minheap.pxd +20 -0
- sknetwork/topology/minheap.pyx +109 -0
- sknetwork/topology/structure.py +194 -0
- sknetwork/topology/tests/__init__.py +1 -0
- sknetwork/topology/tests/test_cliques.py +28 -0
- sknetwork/topology/tests/test_core.py +19 -0
- sknetwork/topology/tests/test_cycles.py +65 -0
- sknetwork/topology/tests/test_structure.py +85 -0
- sknetwork/topology/tests/test_triangles.py +38 -0
- sknetwork/topology/tests/test_wl.py +72 -0
- sknetwork/topology/triangles.cpp +8891 -0
- sknetwork/topology/triangles.cpython-312-darwin.so +0 -0
- sknetwork/topology/triangles.pyx +151 -0
- sknetwork/topology/weisfeiler_lehman.py +133 -0
- sknetwork/topology/weisfeiler_lehman_core.cpp +27632 -0
- sknetwork/topology/weisfeiler_lehman_core.cpython-312-darwin.so +0 -0
- sknetwork/topology/weisfeiler_lehman_core.pyx +114 -0
- sknetwork/utils/__init__.py +7 -0
- sknetwork/utils/check.py +355 -0
- sknetwork/utils/format.py +221 -0
- sknetwork/utils/membership.py +82 -0
- sknetwork/utils/neighbors.py +115 -0
- sknetwork/utils/tests/__init__.py +1 -0
- sknetwork/utils/tests/test_check.py +190 -0
- sknetwork/utils/tests/test_format.py +63 -0
- sknetwork/utils/tests/test_membership.py +24 -0
- sknetwork/utils/tests/test_neighbors.py +41 -0
- sknetwork/utils/tests/test_tfidf.py +18 -0
- sknetwork/utils/tests/test_values.py +66 -0
- sknetwork/utils/tfidf.py +37 -0
- sknetwork/utils/values.py +76 -0
- sknetwork/visualization/__init__.py +4 -0
- sknetwork/visualization/colors.py +34 -0
- sknetwork/visualization/dendrograms.py +277 -0
- sknetwork/visualization/graphs.py +1039 -0
- sknetwork/visualization/tests/__init__.py +1 -0
- sknetwork/visualization/tests/test_dendrograms.py +53 -0
- sknetwork/visualization/tests/test_graphs.py +176 -0
|
@@ -0,0 +1,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,96 @@
|
|
|
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
|
+
dendrogram_row_ :
|
|
22
|
+
Dendrogram for the rows, for bipartite graphs.
|
|
23
|
+
dendrogram_col_ :
|
|
24
|
+
Dendrogram for the columns, for bipartite graphs.
|
|
25
|
+
dendrogram_full_ :
|
|
26
|
+
Dendrogram for both rows and columns, indexed in this order, for bipartite graphs.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self._init_vars()
|
|
31
|
+
|
|
32
|
+
def predict(self, columns: bool = False) -> np.ndarray:
|
|
33
|
+
"""Return the dendrogram predicted by the algorithm.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
columns : bool
|
|
38
|
+
If ``True``, return the prediction for columns.
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
dendrogram : np.ndarray
|
|
43
|
+
Dendrogram.
|
|
44
|
+
"""
|
|
45
|
+
if columns:
|
|
46
|
+
return self.dendrogram_col_
|
|
47
|
+
return self.dendrogram_
|
|
48
|
+
|
|
49
|
+
def transform(self) -> np.ndarray:
|
|
50
|
+
"""Return the dendrogram predicted by the algorithm.
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
dendrogram : np.ndarray
|
|
55
|
+
Dendrogram.
|
|
56
|
+
"""
|
|
57
|
+
return self.dendrogram_
|
|
58
|
+
|
|
59
|
+
def fit_predict(self, *args, **kwargs) -> np.ndarray:
|
|
60
|
+
"""Fit algorithm to data and return the dendrogram. Same parameters as the ``fit`` method.
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
dendrogram : np.ndarray
|
|
65
|
+
Dendrogram.
|
|
66
|
+
"""
|
|
67
|
+
self.fit(*args, **kwargs)
|
|
68
|
+
return self.dendrogram_
|
|
69
|
+
|
|
70
|
+
def fit_transform(self, *args, **kwargs) -> np.ndarray:
|
|
71
|
+
"""Fit algorithm to data and return the dendrogram. Alias for ``fit_predict``.
|
|
72
|
+
Same parameters as the ``fit`` method.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
dendrogram : np.ndarray
|
|
77
|
+
Dendrogram.
|
|
78
|
+
"""
|
|
79
|
+
self.fit(*args, **kwargs)
|
|
80
|
+
return self.dendrogram_
|
|
81
|
+
|
|
82
|
+
def _init_vars(self):
|
|
83
|
+
"""Init variables."""
|
|
84
|
+
self.dendrogram_ = None
|
|
85
|
+
self.dendrogram_row_ = None
|
|
86
|
+
self.dendrogram_col_ = None
|
|
87
|
+
self.dendrogram_full_ = None
|
|
88
|
+
|
|
89
|
+
def _split_vars(self, shape):
|
|
90
|
+
"""Split variables."""
|
|
91
|
+
dendrogram_row, dendrogram_col = split_dendrogram(self.dendrogram_, shape)
|
|
92
|
+
self.dendrogram_full_ = self.dendrogram_
|
|
93
|
+
self.dendrogram_ = dendrogram_row
|
|
94
|
+
self.dendrogram_row_ = dendrogram_row
|
|
95
|
+
self.dendrogram_col_ = dendrogram_col
|
|
96
|
+
return self
|