scikit-network 0.31.0__cp311-cp311-win_amd64.whl → 0.33.0__cp311-cp311-win_amd64.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.31.0.dist-info → scikit_network-0.33.0.dist-info}/AUTHORS.rst +3 -1
- {scikit_network-0.31.0.dist-info → scikit_network-0.33.0.dist-info}/METADATA +27 -5
- scikit_network-0.33.0.dist-info/RECORD +228 -0
- {scikit_network-0.31.0.dist-info → scikit_network-0.33.0.dist-info}/WHEEL +1 -1
- sknetwork/__init__.py +1 -1
- sknetwork/classification/base.py +1 -1
- sknetwork/classification/base_rank.py +3 -3
- sknetwork/classification/diffusion.py +25 -16
- sknetwork/classification/knn.py +23 -16
- sknetwork/classification/metrics.py +4 -4
- sknetwork/classification/pagerank.py +12 -8
- sknetwork/classification/propagation.py +25 -17
- sknetwork/classification/tests/test_diffusion.py +10 -0
- sknetwork/classification/vote.cp311-win_amd64.pyd +0 -0
- sknetwork/classification/vote.cpp +14549 -8668
- sknetwork/clustering/__init__.py +3 -1
- sknetwork/clustering/base.py +1 -1
- sknetwork/clustering/kcenters.py +253 -0
- sknetwork/clustering/leiden.py +242 -0
- sknetwork/clustering/leiden_core.cp311-win_amd64.pyd +0 -0
- sknetwork/clustering/leiden_core.cpp +31564 -0
- sknetwork/clustering/leiden_core.pyx +124 -0
- sknetwork/clustering/louvain.py +118 -83
- sknetwork/clustering/louvain_core.cp311-win_amd64.pyd +0 -0
- sknetwork/clustering/louvain_core.cpp +21876 -16332
- sknetwork/clustering/louvain_core.pyx +86 -94
- sknetwork/clustering/postprocess.py +2 -2
- sknetwork/clustering/propagation_clustering.py +4 -4
- sknetwork/clustering/tests/test_API.py +7 -3
- sknetwork/clustering/tests/test_kcenters.py +60 -0
- sknetwork/clustering/tests/test_leiden.py +34 -0
- sknetwork/clustering/tests/test_louvain.py +2 -3
- sknetwork/data/__init__.py +1 -1
- sknetwork/data/base.py +7 -2
- sknetwork/data/load.py +20 -25
- sknetwork/data/models.py +15 -15
- sknetwork/data/parse.py +57 -34
- sknetwork/data/tests/test_API.py +3 -3
- sknetwork/data/tests/test_base.py +2 -2
- sknetwork/data/tests/test_parse.py +9 -12
- sknetwork/data/tests/test_toy_graphs.py +33 -33
- sknetwork/data/toy_graphs.py +35 -43
- sknetwork/embedding/__init__.py +0 -1
- sknetwork/embedding/base.py +23 -19
- sknetwork/embedding/force_atlas.py +3 -2
- sknetwork/embedding/louvain_embedding.py +1 -27
- sknetwork/embedding/random_projection.py +5 -3
- sknetwork/embedding/spectral.py +0 -73
- sknetwork/embedding/svd.py +0 -4
- sknetwork/embedding/tests/test_API.py +4 -28
- sknetwork/embedding/tests/test_louvain_embedding.py +13 -13
- sknetwork/embedding/tests/test_spectral.py +2 -5
- sknetwork/embedding/tests/test_svd.py +7 -1
- sknetwork/gnn/base_layer.py +3 -3
- sknetwork/gnn/gnn_classifier.py +41 -87
- sknetwork/gnn/layer.py +1 -1
- sknetwork/gnn/loss.py +1 -1
- sknetwork/gnn/optimizer.py +4 -3
- sknetwork/gnn/tests/test_base_layer.py +4 -4
- sknetwork/gnn/tests/test_gnn_classifier.py +12 -39
- sknetwork/gnn/utils.py +8 -8
- sknetwork/hierarchy/base.py +27 -0
- sknetwork/hierarchy/louvain_hierarchy.py +55 -47
- sknetwork/hierarchy/paris.cp311-win_amd64.pyd +0 -0
- sknetwork/hierarchy/paris.cpp +27667 -20915
- sknetwork/hierarchy/paris.pyx +11 -10
- sknetwork/hierarchy/postprocess.py +16 -16
- sknetwork/hierarchy/tests/test_algos.py +5 -0
- sknetwork/hierarchy/tests/test_metrics.py +4 -4
- sknetwork/linalg/__init__.py +1 -1
- sknetwork/linalg/diteration.cp311-win_amd64.pyd +0 -0
- sknetwork/linalg/diteration.cpp +13916 -8050
- sknetwork/linalg/{normalization.py → normalizer.py} +17 -14
- sknetwork/linalg/operators.py +1 -1
- sknetwork/linalg/ppr_solver.py +1 -1
- sknetwork/linalg/push.cp311-win_amd64.pyd +0 -0
- sknetwork/linalg/push.cpp +23187 -16973
- sknetwork/linalg/tests/test_normalization.py +3 -7
- sknetwork/linalg/tests/test_operators.py +2 -6
- sknetwork/linalg/tests/test_ppr.py +1 -1
- sknetwork/linkpred/base.py +12 -1
- sknetwork/linkpred/nn.py +6 -6
- sknetwork/path/distances.py +11 -4
- sknetwork/path/shortest_path.py +1 -1
- sknetwork/path/tests/test_distances.py +7 -0
- sknetwork/path/tests/test_search.py +2 -2
- sknetwork/ranking/base.py +11 -6
- sknetwork/ranking/betweenness.cp311-win_amd64.pyd +0 -0
- sknetwork/ranking/betweenness.cpp +5256 -2190
- sknetwork/ranking/pagerank.py +13 -12
- sknetwork/ranking/tests/test_API.py +0 -2
- sknetwork/ranking/tests/test_betweenness.py +1 -1
- sknetwork/ranking/tests/test_pagerank.py +11 -5
- sknetwork/regression/base.py +18 -1
- sknetwork/regression/diffusion.py +30 -14
- sknetwork/regression/tests/test_diffusion.py +8 -0
- sknetwork/topology/__init__.py +3 -1
- sknetwork/topology/cliques.cp311-win_amd64.pyd +0 -0
- sknetwork/topology/cliques.cpp +23528 -16848
- sknetwork/topology/core.cp311-win_amd64.pyd +0 -0
- sknetwork/topology/core.cpp +22849 -16581
- sknetwork/topology/cycles.py +243 -0
- sknetwork/topology/minheap.cp311-win_amd64.pyd +0 -0
- sknetwork/topology/minheap.cpp +19495 -13469
- sknetwork/topology/structure.py +2 -42
- sknetwork/topology/tests/test_cycles.py +65 -0
- sknetwork/topology/tests/test_structure.py +2 -16
- sknetwork/topology/triangles.cp311-win_amd64.pyd +0 -0
- sknetwork/topology/triangles.cpp +5283 -1397
- sknetwork/topology/triangles.pyx +7 -4
- sknetwork/topology/weisfeiler_lehman_core.cp311-win_amd64.pyd +0 -0
- sknetwork/topology/weisfeiler_lehman_core.cpp +14781 -8915
- sknetwork/utils/__init__.py +1 -1
- sknetwork/utils/format.py +1 -1
- sknetwork/utils/membership.py +2 -2
- sknetwork/utils/values.py +5 -3
- sknetwork/visualization/__init__.py +2 -2
- sknetwork/visualization/dendrograms.py +55 -7
- sknetwork/visualization/graphs.py +261 -44
- sknetwork/visualization/tests/test_dendrograms.py +9 -9
- sknetwork/visualization/tests/test_graphs.py +63 -57
- scikit_network-0.31.0.dist-info/RECORD +0 -221
- sknetwork/embedding/louvain_hierarchy.py +0 -142
- sknetwork/embedding/tests/test_louvain_hierarchy.py +0 -19
- {scikit_network-0.31.0.dist-info → scikit_network-0.33.0.dist-info}/LICENSE +0 -0
- {scikit_network-0.31.0.dist-info → scikit_network-0.33.0.dist-info}/top_level.txt +0 -0
sknetwork/gnn/base_layer.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""
|
|
4
|
-
Created
|
|
4
|
+
Created in July 2022
|
|
5
5
|
@author: Simon Delarue <sdelarue@enst.fr>
|
|
6
6
|
"""
|
|
7
7
|
from typing import Optional, Union
|
|
@@ -73,10 +73,10 @@ class BaseLayer:
|
|
|
73
73
|
in_channels: int
|
|
74
74
|
Number of input channels.
|
|
75
75
|
"""
|
|
76
|
-
#
|
|
76
|
+
# He initialization
|
|
77
77
|
self.weight = np.random.randn(in_channels, self.out_channels) * np.sqrt(2 / self.out_channels)
|
|
78
78
|
if self.use_bias:
|
|
79
|
-
self.bias = np.zeros((self.out_channels
|
|
79
|
+
self.bias = np.zeros((1, self.out_channels))
|
|
80
80
|
self.weights_initialized = True
|
|
81
81
|
|
|
82
82
|
def forward(self, *args, **kwargs):
|
sknetwork/gnn/gnn_classifier.py
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
Created in April 2022
|
|
5
5
|
@author: Simon Delarue <sdelarue@enst.fr>
|
|
6
6
|
"""
|
|
7
|
-
from typing import Optional, Union
|
|
7
|
+
from typing import Iterable, Optional, Union
|
|
8
8
|
from collections import defaultdict
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
@@ -26,35 +26,37 @@ class GNNClassifier(BaseGNN):
|
|
|
26
26
|
|
|
27
27
|
Parameters
|
|
28
28
|
----------
|
|
29
|
-
dims :
|
|
30
|
-
|
|
29
|
+
dims : iterable or int
|
|
30
|
+
Dimension of the output of each layer (in forward direction).
|
|
31
31
|
If an integer, dimension of the output layer (no hidden layer).
|
|
32
32
|
Optional if ``layers`` is specified.
|
|
33
|
-
layer_types :
|
|
33
|
+
layer_types : iterable or str
|
|
34
34
|
Layer types (in forward direction).
|
|
35
|
-
If a string,
|
|
35
|
+
If a string, the same type is used at each layer.
|
|
36
36
|
Can be ``'Conv'``, graph convolutional layer (default) or ``'Sage'`` (GraphSage).
|
|
37
|
-
activations :
|
|
37
|
+
activations : iterable or str
|
|
38
38
|
Activation functions (in forward direction).
|
|
39
|
-
If a string,
|
|
39
|
+
If a string, the same activation function is used at each layer.
|
|
40
40
|
Can be either ``'Identity'``, ``'Relu'``, ``'Sigmoid'`` or ``'Softmax'`` (default = ``'Relu'``).
|
|
41
|
-
use_bias :
|
|
42
|
-
Whether to
|
|
43
|
-
If ``True``, use a bias term at
|
|
44
|
-
normalizations :
|
|
45
|
-
|
|
46
|
-
If a string,
|
|
47
|
-
Can be either
|
|
41
|
+
use_bias : iterable or bool
|
|
42
|
+
Whether to add a bias term at each layer (in forward direction).
|
|
43
|
+
If ``True``, use a bias term at each layer.
|
|
44
|
+
normalizations : iterable or str
|
|
45
|
+
Normalizations of the adjacency matrix for message passing (in forward direction).
|
|
46
|
+
If a string, the same type of normalization is used at each layer.
|
|
47
|
+
Can be either ``'left'`` (left normalization by the degrees), ``'right'`` (right normalization by the degrees),
|
|
48
48
|
``'both'`` (symmetric normalization by the square root of degrees, default) or ``None`` (no normalization).
|
|
49
|
-
self_embeddings :
|
|
50
|
-
Whether to add
|
|
51
|
-
If ``True``, add self-
|
|
52
|
-
sample_sizes :
|
|
53
|
-
|
|
49
|
+
self_embeddings : iterable or str
|
|
50
|
+
Whether to add the embedding to each node for message passing (in forward direction).
|
|
51
|
+
If ``True``, add a self-embedding at each layer.
|
|
52
|
+
sample_sizes : iterable or int
|
|
53
|
+
Sizes of neighborhood sampled for each node (in forward direction).
|
|
54
|
+
If an integer, the same sampling size is used at each layer.
|
|
55
|
+
Used only for ``'Sage'`` layer type.
|
|
54
56
|
loss : str (default = ``'CrossEntropy'``) or BaseLoss
|
|
55
|
-
|
|
56
|
-
layers :
|
|
57
|
-
Custom layers. If used, previous parameters are ignored.
|
|
57
|
+
Name of loss function or custom loss function.
|
|
58
|
+
layers : iterable or None
|
|
59
|
+
Custom layers (in forward directions). If used, previous parameters are ignored.
|
|
58
60
|
optimizer : str or optimizer
|
|
59
61
|
* ``'Adam'``, stochastic gradient-based optimizer (default).
|
|
60
62
|
* ``'GD'``, gradient descent.
|
|
@@ -72,7 +74,7 @@ class GNNClassifier(BaseGNN):
|
|
|
72
74
|
----------
|
|
73
75
|
conv2, ..., conv1: :class:'GCNConv'
|
|
74
76
|
Graph convolutional layers.
|
|
75
|
-
output_ :
|
|
77
|
+
output_ : np.ndarray
|
|
76
78
|
Output of the GNN.
|
|
77
79
|
labels_: np.ndarray
|
|
78
80
|
Predicted node labels.
|
|
@@ -91,15 +93,15 @@ class GNNClassifier(BaseGNN):
|
|
|
91
93
|
>>> features = adjacency.copy()
|
|
92
94
|
>>> gnn = GNNClassifier(dims=1, early_stopping=False)
|
|
93
95
|
>>> labels_pred = gnn.fit_predict(adjacency, features, labels, random_state=42)
|
|
94
|
-
>>>
|
|
96
|
+
>>> round(np.mean(labels_pred == labels_true), 2)
|
|
95
97
|
0.88
|
|
96
98
|
"""
|
|
97
99
|
|
|
98
|
-
def __init__(self, dims: Optional[Union[int,
|
|
99
|
-
activations: Union[str,
|
|
100
|
-
normalizations: Union[str,
|
|
100
|
+
def __init__(self, dims: Optional[Union[int, Iterable]] = None, layer_types: Union[str, Iterable] = 'Conv',
|
|
101
|
+
activations: Union[str, Iterable] = 'ReLu', use_bias: Union[bool, list] = True,
|
|
102
|
+
normalizations: Union[str, Iterable] = 'both', self_embeddings: Union[bool, Iterable] = True,
|
|
101
103
|
sample_sizes: Union[int, list] = 25, loss: Union[BaseLoss, str] = 'CrossEntropy',
|
|
102
|
-
layers: Optional[
|
|
104
|
+
layers: Optional[Iterable] = None, optimizer: Union[BaseOptimizer, str] = 'Adam',
|
|
103
105
|
learning_rate: float = 0.01, early_stopping: bool = True, patience: int = 10, verbose: bool = False):
|
|
104
106
|
super(GNNClassifier, self).__init__(loss, optimizer, learning_rate, verbose)
|
|
105
107
|
if layers is not None:
|
|
@@ -159,7 +161,7 @@ class GNNClassifier(BaseGNN):
|
|
|
159
161
|
|
|
160
162
|
def fit(self, adjacency: Union[sparse.csr_matrix, np.ndarray], features: Union[sparse.csr_matrix, np.ndarray],
|
|
161
163
|
labels: np.ndarray, n_epochs: int = 100, validation: float = 0, reinit: bool = False,
|
|
162
|
-
random_state: Optional[int] = None
|
|
164
|
+
random_state: Optional[int] = None) -> 'GNNClassifier':
|
|
163
165
|
""" Fit model to data and store trained parameters.
|
|
164
166
|
|
|
165
167
|
Parameters
|
|
@@ -169,8 +171,8 @@ class GNNClassifier(BaseGNN):
|
|
|
169
171
|
features : sparse.csr_matrix, np.ndarray
|
|
170
172
|
Input feature of shape :math:`(n, d)` with :math:`n` the number of nodes in the graph and :math:`d`
|
|
171
173
|
the size of feature space.
|
|
172
|
-
labels :
|
|
173
|
-
Known labels
|
|
174
|
+
labels : dict, np.ndarray
|
|
175
|
+
Known labels. Negative values ignored.
|
|
174
176
|
n_epochs : int (default = 100)
|
|
175
177
|
Number of epochs (iterations over the whole graph).
|
|
176
178
|
validation : float
|
|
@@ -179,18 +181,17 @@ class GNNClassifier(BaseGNN):
|
|
|
179
181
|
If ``True``, reinit the trainable parameters of the GNN (weights and biases).
|
|
180
182
|
random_state : int
|
|
181
183
|
Random seed, used for reproducible results across multiple runs.
|
|
182
|
-
history : bool (default = ``False``)
|
|
183
|
-
If ``True``, save training history.
|
|
184
184
|
"""
|
|
185
185
|
if reinit:
|
|
186
186
|
for layer in self.layers:
|
|
187
187
|
layer.weights_initialized = False
|
|
188
|
+
self.history_ = defaultdict(list)
|
|
188
189
|
|
|
189
190
|
if random_state is not None:
|
|
190
191
|
np.random.seed(random_state)
|
|
191
192
|
|
|
192
|
-
check_format(adjacency)
|
|
193
|
-
check_format(features)
|
|
193
|
+
check_format(adjacency, allow_empty=True)
|
|
194
|
+
check_format(features, allow_empty=True)
|
|
194
195
|
|
|
195
196
|
labels = get_values(adjacency.shape, labels)
|
|
196
197
|
labels = labels.astype(int)
|
|
@@ -199,7 +200,7 @@ class GNNClassifier(BaseGNN):
|
|
|
199
200
|
check_output(self.layers[-1].out_channels, labels)
|
|
200
201
|
|
|
201
202
|
self.train_mask = labels >= 0
|
|
202
|
-
if 0 < validation < 1:
|
|
203
|
+
if self.val_mask is None and 0 < validation < 1:
|
|
203
204
|
mask = np.random.random(size=len(labels)) < validation
|
|
204
205
|
self.val_mask = self.train_mask & mask
|
|
205
206
|
self.train_mask &= ~mask
|
|
@@ -237,12 +238,10 @@ class GNNClassifier(BaseGNN):
|
|
|
237
238
|
self.optimizer.step(self)
|
|
238
239
|
|
|
239
240
|
# Save results
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
self.history_['
|
|
244
|
-
if val_accuracy is not None:
|
|
245
|
-
self.history_['val_accuracy'].append(val_accuracy)
|
|
241
|
+
self.history_['loss'].append(loss_value)
|
|
242
|
+
self.history_['train_accuracy'].append(train_accuracy)
|
|
243
|
+
if val_accuracy is not None:
|
|
244
|
+
self.history_['val_accuracy'].append(val_accuracy)
|
|
246
245
|
|
|
247
246
|
if n_epochs > 10 and epoch % int(n_epochs / 10) == 0:
|
|
248
247
|
if val_accuracy is not None:
|
|
@@ -304,48 +303,3 @@ class GNNClassifier(BaseGNN):
|
|
|
304
303
|
adjacencies.append(adjacency)
|
|
305
304
|
|
|
306
305
|
return adjacencies
|
|
307
|
-
|
|
308
|
-
def predict(self, adjacency_vectors: Union[sparse.csr_matrix, np.ndarray] = None,
|
|
309
|
-
feature_vectors: Union[sparse.csr_matrix, np.ndarray] = None) -> np.ndarray:
|
|
310
|
-
"""Predict labels for new nodes. If called without parameters, labels are returned for all nodes.
|
|
311
|
-
|
|
312
|
-
Parameters
|
|
313
|
-
----------
|
|
314
|
-
adjacency_vectors : np.ndarray
|
|
315
|
-
Square adjacency matrix. Array of shape (n, n).
|
|
316
|
-
feature_vectors : np.ndarray
|
|
317
|
-
Features row vectors. Array of shape (n, n_feat). The number of features n_feat must match with the one
|
|
318
|
-
used during training.
|
|
319
|
-
|
|
320
|
-
Returns
|
|
321
|
-
-------
|
|
322
|
-
labels : np.ndarray
|
|
323
|
-
Label of each node of the graph.
|
|
324
|
-
"""
|
|
325
|
-
self._check_fitted()
|
|
326
|
-
|
|
327
|
-
if adjacency_vectors is None and feature_vectors is None:
|
|
328
|
-
return self.labels_
|
|
329
|
-
elif adjacency_vectors is not None and feature_vectors is None:
|
|
330
|
-
raise ValueError('Missing value: feature matrix is missing.')
|
|
331
|
-
elif adjacency_vectors is None:
|
|
332
|
-
adjacency_vectors = sparse.identity(feature_vectors.shape[0], format='csr')
|
|
333
|
-
|
|
334
|
-
check_square(adjacency_vectors)
|
|
335
|
-
check_nonnegative(adjacency_vectors)
|
|
336
|
-
feature_vectors = check_format(feature_vectors)
|
|
337
|
-
|
|
338
|
-
n_row, n_col = adjacency_vectors.shape
|
|
339
|
-
feat_row, feat_col = feature_vectors.shape
|
|
340
|
-
|
|
341
|
-
if n_col != feat_row:
|
|
342
|
-
raise ValueError(f'Dimension mismatch: dim0={n_col} != dim1={feat_row}.')
|
|
343
|
-
elif feat_col != self.layers[0].weight.shape[0]:
|
|
344
|
-
raise ValueError(f'Dimension mismatch: current number of features is {feat_col} whereas GNN has been '
|
|
345
|
-
f'trained with '
|
|
346
|
-
f'{self.layers[0].weight.shape[0]} features.')
|
|
347
|
-
|
|
348
|
-
h = self.forward(adjacency_vectors, feature_vectors)
|
|
349
|
-
labels = self._compute_predictions(h)
|
|
350
|
-
|
|
351
|
-
return labels
|
sknetwork/gnn/layer.py
CHANGED
sknetwork/gnn/loss.py
CHANGED
sknetwork/gnn/optimizer.py
CHANGED
|
@@ -130,9 +130,10 @@ class ADAM(BaseOptimizer):
|
|
|
130
130
|
layer.weight = \
|
|
131
131
|
layer.weight - (self.learning_rate * m_derivative_weight_corr) / (np.sqrt(v_derivative_weight_corr)
|
|
132
132
|
+ self.eps)
|
|
133
|
-
layer.
|
|
134
|
-
layer.bias
|
|
135
|
-
|
|
133
|
+
if layer.use_bias:
|
|
134
|
+
layer.bias = \
|
|
135
|
+
layer.bias - (self.learning_rate * m_derivative_bias_corr) / (np.sqrt(v_derivative_bias_corr)
|
|
136
|
+
+ self.eps)
|
|
136
137
|
|
|
137
138
|
|
|
138
139
|
def get_optimizer(optimizer: Union[BaseOptimizer, str] = 'Adam', learning_rate: float = 0.01) -> BaseOptimizer:
|
|
@@ -27,11 +27,11 @@ class TestBaseLayer(unittest.TestCase):
|
|
|
27
27
|
def test_base_layer_initialize_weights(self):
|
|
28
28
|
self.base_layer._initialize_weights(10)
|
|
29
29
|
self.assertTrue(self.base_layer.weight.shape == (10, len(self.labels)))
|
|
30
|
-
self.assertTrue(
|
|
30
|
+
self.assertTrue(self.base_layer.bias.shape == (1, len(self.labels)))
|
|
31
31
|
self.assertTrue(self.base_layer.weights_initialized)
|
|
32
32
|
|
|
33
33
|
def test_base_layer_repr(self):
|
|
34
34
|
self.assertTrue(self.base_layer.__repr__().startswith(" BaseLayer(layer_type: Conv, out_channels: 10"))
|
|
35
|
-
|
|
36
|
-
self.assertTrue('sample_size' in
|
|
37
|
-
self.assertTrue('sageconv' in
|
|
35
|
+
sage_layer = BaseLayer(layer_type='sageconv', out_channels=len(self.labels))
|
|
36
|
+
self.assertTrue('sample_size' in sage_layer.__repr__())
|
|
37
|
+
self.assertTrue('sageconv' in sage_layer.__repr__())
|
|
@@ -44,6 +44,14 @@ class TestGNNClassifier(unittest.TestCase):
|
|
|
44
44
|
self.assertTrue(len(y_pred) == self.n)
|
|
45
45
|
self.assertTrue(embedding.shape == (self.n, 2))
|
|
46
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
|
+
|
|
47
55
|
def test_gnn_classifier_optimizer(self):
|
|
48
56
|
optimizers = ['GD', 'Adam']
|
|
49
57
|
for optimizer in optimizers:
|
|
@@ -88,23 +96,20 @@ class TestGNNClassifier(unittest.TestCase):
|
|
|
88
96
|
def test_gnn_classifier_early_stopping(self):
|
|
89
97
|
gnn = GNNClassifier(2, patience=2)
|
|
90
98
|
labels = {0: 0, 1: 1}
|
|
91
|
-
_ = gnn.fit_predict(self.adjacency, self.features, labels, n_epochs=100,
|
|
99
|
+
_ = gnn.fit_predict(self.adjacency, self.features, labels, n_epochs=100, validation=0.5,
|
|
92
100
|
random_state=42)
|
|
93
101
|
self.assertTrue(len(gnn.history_['val_accuracy']) < 100)
|
|
94
102
|
|
|
95
103
|
gnn = GNNClassifier(2, early_stopping=False)
|
|
96
|
-
_ = gnn.fit_predict(self.adjacency, self.features, labels, n_epochs=100,
|
|
104
|
+
_ = gnn.fit_predict(self.adjacency, self.features, labels, n_epochs=100, validation=0.5,
|
|
97
105
|
random_state=42)
|
|
98
106
|
self.assertTrue(len(gnn.history_['val_accuracy']) == 100)
|
|
99
107
|
|
|
100
108
|
def test_gnn_classifier_reinit(self):
|
|
101
109
|
gnn = GNNClassifier([4, 2])
|
|
102
|
-
gnn.fit(self.adjacency, self.features, self.labels
|
|
103
|
-
weights = [layer.weight for layer in gnn.layers]
|
|
104
|
-
biases = [layer.bias for layer in gnn.layers]
|
|
110
|
+
gnn.fit(self.adjacency, self.features, self.labels)
|
|
105
111
|
gnn.fit(self.adjacency, self.features, self.labels, n_epochs=1, reinit=True)
|
|
106
|
-
self.assertTrue(
|
|
107
|
-
self.assertTrue(all([np.all(bias != layer.bias) for bias, layer in zip(biases, gnn.layers)]))
|
|
112
|
+
self.assertTrue(gnn.embedding_.shape == (self.n, 2))
|
|
108
113
|
|
|
109
114
|
def test_gnn_classifier_sageconv(self):
|
|
110
115
|
gnn = GNNClassifier([4, 2], ['SAGEConv', 'SAGEConv'], sample_sizes=[5, 3])
|
|
@@ -119,38 +124,6 @@ class TestGNNClassifier(unittest.TestCase):
|
|
|
119
124
|
self.assertTrue(all(labels_pred == gnn.labels_))
|
|
120
125
|
self.assertTrue(all(labels_pred == labels_pred_))
|
|
121
126
|
|
|
122
|
-
# Predict same nodes
|
|
123
|
-
labels_pred_ = gnn.predict(self.adjacency, self.features)
|
|
124
|
-
self.assertTrue(all(labels_pred_ == gnn.labels_))
|
|
125
|
-
|
|
126
|
-
# Incorrect shapes
|
|
127
|
-
new_n = sparse.csr_matrix(np.random.randint(2, size=self.features.shape[1]))
|
|
128
|
-
new_feat = sparse.csr_matrix(np.random.randint(3, size=self.features.shape[1]))
|
|
129
|
-
with self.assertRaises(ValueError):
|
|
130
|
-
gnn.predict(new_n, self.features)
|
|
131
|
-
with self.assertRaises(ValueError):
|
|
132
|
-
gnn.predict(self.adjacency, new_feat)
|
|
133
|
-
|
|
134
|
-
new_feat = sparse.csr_matrix(np.random.rand(self.adjacency.shape[0], self.features.shape[1] - 1))
|
|
135
|
-
with self.assertRaises(ValueError):
|
|
136
|
-
gnn.predict(self.adjacency, new_feat)
|
|
137
|
-
|
|
138
|
-
# Predict new graph
|
|
139
|
-
n = 4
|
|
140
|
-
n_feat = self.features.shape[1]
|
|
141
|
-
adjacency = sparse.csr_matrix(np.random.randint(2, size=(n, n)))
|
|
142
|
-
features = sparse.csr_matrix(np.random.randint(2, size=(n, n_feat)))
|
|
143
|
-
labels_pred = gnn.predict(adjacency, features)
|
|
144
|
-
self.assertTrue(len(labels_pred) == n)
|
|
145
|
-
|
|
146
|
-
# No adj matrix
|
|
147
|
-
labels_pred = gnn.predict(None, features)
|
|
148
|
-
self.assertTrue(len(labels_pred) == features.shape[0])
|
|
149
|
-
|
|
150
|
-
# No feature matrix
|
|
151
|
-
with self.assertRaises(ValueError):
|
|
152
|
-
gnn.predict(new_n)
|
|
153
|
-
|
|
154
127
|
def test_gnn_classifier_predict_proba(self):
|
|
155
128
|
gnn = GNNClassifier([4, 2])
|
|
156
129
|
probs = gnn.fit_predict_proba(self.adjacency, self.features, self.labels)
|
sknetwork/gnn/utils.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# coding: utf-8
|
|
3
3
|
"""
|
|
4
|
-
Created
|
|
4
|
+
Created in April 2022
|
|
5
5
|
@author: Simon Delarue <sdelarue@enst.fr>
|
|
6
6
|
"""
|
|
7
|
-
from typing import Union
|
|
7
|
+
from typing import Iterable, Union
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
|
|
@@ -22,7 +22,7 @@ def check_early_stopping(early_stopping: bool, val_mask: np.ndarray, patience: i
|
|
|
22
22
|
return early_stopping
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def check_normalizations(normalizations: Union[str,
|
|
25
|
+
def check_normalizations(normalizations: Union[str, Iterable]):
|
|
26
26
|
"""Check if normalization is known."""
|
|
27
27
|
available_norms = ['left', 'right', 'both']
|
|
28
28
|
if isinstance(normalizations, list):
|
|
@@ -69,10 +69,10 @@ def check_loss(layer: BaseLayer):
|
|
|
69
69
|
return layer.activation
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
def get_layers(dims: Union[int,
|
|
73
|
-
activations: Union[str, BaseActivation, list], use_bias: Union[bool,
|
|
74
|
-
normalizations: Union[str,
|
|
75
|
-
loss: Union[str, BaseLoss]) -> list:
|
|
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
76
|
"""Get the list of layers.
|
|
77
77
|
|
|
78
78
|
Parameters
|
|
@@ -101,7 +101,7 @@ def get_layers(dims: Union[int, list], layer_types: Union[str, BaseLayer, list],
|
|
|
101
101
|
"""
|
|
102
102
|
check_normalizations(normalizations)
|
|
103
103
|
|
|
104
|
-
if
|
|
104
|
+
if isinstance(dims, int):
|
|
105
105
|
dims = [dims]
|
|
106
106
|
n_layers = len(dims)
|
|
107
107
|
|
sknetwork/hierarchy/base.py
CHANGED
|
@@ -29,6 +29,33 @@ class BaseHierarchy(Algorithm, ABC):
|
|
|
29
29
|
def __init__(self):
|
|
30
30
|
self._init_vars()
|
|
31
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
|
+
|
|
32
59
|
def fit_predict(self, *args, **kwargs) -> np.ndarray:
|
|
33
60
|
"""Fit algorithm to data and return the dendrogram. Same parameters as the ``fit`` method.
|
|
34
61
|
|