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.

Files changed (126) hide show
  1. {scikit_network-0.31.0.dist-info → scikit_network-0.33.0.dist-info}/AUTHORS.rst +3 -1
  2. {scikit_network-0.31.0.dist-info → scikit_network-0.33.0.dist-info}/METADATA +27 -5
  3. scikit_network-0.33.0.dist-info/RECORD +228 -0
  4. {scikit_network-0.31.0.dist-info → scikit_network-0.33.0.dist-info}/WHEEL +1 -1
  5. sknetwork/__init__.py +1 -1
  6. sknetwork/classification/base.py +1 -1
  7. sknetwork/classification/base_rank.py +3 -3
  8. sknetwork/classification/diffusion.py +25 -16
  9. sknetwork/classification/knn.py +23 -16
  10. sknetwork/classification/metrics.py +4 -4
  11. sknetwork/classification/pagerank.py +12 -8
  12. sknetwork/classification/propagation.py +25 -17
  13. sknetwork/classification/tests/test_diffusion.py +10 -0
  14. sknetwork/classification/vote.cp311-win_amd64.pyd +0 -0
  15. sknetwork/classification/vote.cpp +14549 -8668
  16. sknetwork/clustering/__init__.py +3 -1
  17. sknetwork/clustering/base.py +1 -1
  18. sknetwork/clustering/kcenters.py +253 -0
  19. sknetwork/clustering/leiden.py +242 -0
  20. sknetwork/clustering/leiden_core.cp311-win_amd64.pyd +0 -0
  21. sknetwork/clustering/leiden_core.cpp +31564 -0
  22. sknetwork/clustering/leiden_core.pyx +124 -0
  23. sknetwork/clustering/louvain.py +118 -83
  24. sknetwork/clustering/louvain_core.cp311-win_amd64.pyd +0 -0
  25. sknetwork/clustering/louvain_core.cpp +21876 -16332
  26. sknetwork/clustering/louvain_core.pyx +86 -94
  27. sknetwork/clustering/postprocess.py +2 -2
  28. sknetwork/clustering/propagation_clustering.py +4 -4
  29. sknetwork/clustering/tests/test_API.py +7 -3
  30. sknetwork/clustering/tests/test_kcenters.py +60 -0
  31. sknetwork/clustering/tests/test_leiden.py +34 -0
  32. sknetwork/clustering/tests/test_louvain.py +2 -3
  33. sknetwork/data/__init__.py +1 -1
  34. sknetwork/data/base.py +7 -2
  35. sknetwork/data/load.py +20 -25
  36. sknetwork/data/models.py +15 -15
  37. sknetwork/data/parse.py +57 -34
  38. sknetwork/data/tests/test_API.py +3 -3
  39. sknetwork/data/tests/test_base.py +2 -2
  40. sknetwork/data/tests/test_parse.py +9 -12
  41. sknetwork/data/tests/test_toy_graphs.py +33 -33
  42. sknetwork/data/toy_graphs.py +35 -43
  43. sknetwork/embedding/__init__.py +0 -1
  44. sknetwork/embedding/base.py +23 -19
  45. sknetwork/embedding/force_atlas.py +3 -2
  46. sknetwork/embedding/louvain_embedding.py +1 -27
  47. sknetwork/embedding/random_projection.py +5 -3
  48. sknetwork/embedding/spectral.py +0 -73
  49. sknetwork/embedding/svd.py +0 -4
  50. sknetwork/embedding/tests/test_API.py +4 -28
  51. sknetwork/embedding/tests/test_louvain_embedding.py +13 -13
  52. sknetwork/embedding/tests/test_spectral.py +2 -5
  53. sknetwork/embedding/tests/test_svd.py +7 -1
  54. sknetwork/gnn/base_layer.py +3 -3
  55. sknetwork/gnn/gnn_classifier.py +41 -87
  56. sknetwork/gnn/layer.py +1 -1
  57. sknetwork/gnn/loss.py +1 -1
  58. sknetwork/gnn/optimizer.py +4 -3
  59. sknetwork/gnn/tests/test_base_layer.py +4 -4
  60. sknetwork/gnn/tests/test_gnn_classifier.py +12 -39
  61. sknetwork/gnn/utils.py +8 -8
  62. sknetwork/hierarchy/base.py +27 -0
  63. sknetwork/hierarchy/louvain_hierarchy.py +55 -47
  64. sknetwork/hierarchy/paris.cp311-win_amd64.pyd +0 -0
  65. sknetwork/hierarchy/paris.cpp +27667 -20915
  66. sknetwork/hierarchy/paris.pyx +11 -10
  67. sknetwork/hierarchy/postprocess.py +16 -16
  68. sknetwork/hierarchy/tests/test_algos.py +5 -0
  69. sknetwork/hierarchy/tests/test_metrics.py +4 -4
  70. sknetwork/linalg/__init__.py +1 -1
  71. sknetwork/linalg/diteration.cp311-win_amd64.pyd +0 -0
  72. sknetwork/linalg/diteration.cpp +13916 -8050
  73. sknetwork/linalg/{normalization.py → normalizer.py} +17 -14
  74. sknetwork/linalg/operators.py +1 -1
  75. sknetwork/linalg/ppr_solver.py +1 -1
  76. sknetwork/linalg/push.cp311-win_amd64.pyd +0 -0
  77. sknetwork/linalg/push.cpp +23187 -16973
  78. sknetwork/linalg/tests/test_normalization.py +3 -7
  79. sknetwork/linalg/tests/test_operators.py +2 -6
  80. sknetwork/linalg/tests/test_ppr.py +1 -1
  81. sknetwork/linkpred/base.py +12 -1
  82. sknetwork/linkpred/nn.py +6 -6
  83. sknetwork/path/distances.py +11 -4
  84. sknetwork/path/shortest_path.py +1 -1
  85. sknetwork/path/tests/test_distances.py +7 -0
  86. sknetwork/path/tests/test_search.py +2 -2
  87. sknetwork/ranking/base.py +11 -6
  88. sknetwork/ranking/betweenness.cp311-win_amd64.pyd +0 -0
  89. sknetwork/ranking/betweenness.cpp +5256 -2190
  90. sknetwork/ranking/pagerank.py +13 -12
  91. sknetwork/ranking/tests/test_API.py +0 -2
  92. sknetwork/ranking/tests/test_betweenness.py +1 -1
  93. sknetwork/ranking/tests/test_pagerank.py +11 -5
  94. sknetwork/regression/base.py +18 -1
  95. sknetwork/regression/diffusion.py +30 -14
  96. sknetwork/regression/tests/test_diffusion.py +8 -0
  97. sknetwork/topology/__init__.py +3 -1
  98. sknetwork/topology/cliques.cp311-win_amd64.pyd +0 -0
  99. sknetwork/topology/cliques.cpp +23528 -16848
  100. sknetwork/topology/core.cp311-win_amd64.pyd +0 -0
  101. sknetwork/topology/core.cpp +22849 -16581
  102. sknetwork/topology/cycles.py +243 -0
  103. sknetwork/topology/minheap.cp311-win_amd64.pyd +0 -0
  104. sknetwork/topology/minheap.cpp +19495 -13469
  105. sknetwork/topology/structure.py +2 -42
  106. sknetwork/topology/tests/test_cycles.py +65 -0
  107. sknetwork/topology/tests/test_structure.py +2 -16
  108. sknetwork/topology/triangles.cp311-win_amd64.pyd +0 -0
  109. sknetwork/topology/triangles.cpp +5283 -1397
  110. sknetwork/topology/triangles.pyx +7 -4
  111. sknetwork/topology/weisfeiler_lehman_core.cp311-win_amd64.pyd +0 -0
  112. sknetwork/topology/weisfeiler_lehman_core.cpp +14781 -8915
  113. sknetwork/utils/__init__.py +1 -1
  114. sknetwork/utils/format.py +1 -1
  115. sknetwork/utils/membership.py +2 -2
  116. sknetwork/utils/values.py +5 -3
  117. sknetwork/visualization/__init__.py +2 -2
  118. sknetwork/visualization/dendrograms.py +55 -7
  119. sknetwork/visualization/graphs.py +261 -44
  120. sknetwork/visualization/tests/test_dendrograms.py +9 -9
  121. sknetwork/visualization/tests/test_graphs.py +63 -57
  122. scikit_network-0.31.0.dist-info/RECORD +0 -221
  123. sknetwork/embedding/louvain_hierarchy.py +0 -142
  124. sknetwork/embedding/tests/test_louvain_hierarchy.py +0 -19
  125. {scikit_network-0.31.0.dist-info → scikit_network-0.33.0.dist-info}/LICENSE +0 -0
  126. {scikit_network-0.31.0.dist-info → scikit_network-0.33.0.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
3
  """
4
- Created on July 2022
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
- # Trainable parameters with He initialization
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, 1)).T
79
+ self.bias = np.zeros((1, self.out_channels))
80
80
  self.weights_initialized = True
81
81
 
82
82
  def forward(self, *args, **kwargs):
@@ -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 : list or int
30
- Dimensions of the output of each layer (in forward direction).
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 : list or str
33
+ layer_types : iterable or str
34
34
  Layer types (in forward direction).
35
- If a string, use the same type of layer for all layers.
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 : list or str
37
+ activations : iterable or str
38
38
  Activation functions (in forward direction).
39
- If a string, use the same activation function for all layers.
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 : list or bool
42
- Whether to use a bias term at each layer.
43
- If ``True``, use a bias term at all layers.
44
- normalizations : list or str
45
- Normalization of the adjacency matrix for message passing.
46
- If a string, use the same normalization for all layers.
47
- Can be either `'left'`` (left normalization by the degrees), ``'right'`` (right normalization by the degrees),
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 : list or str
50
- Whether to add a self embeddings to each node of the graph for message passing.
51
- If ``True``, add self-embeddings at all layers.
52
- sample_sizes : list or int
53
- Size of neighborhood sampled for each node. Used only for ``'Sage'`` layer type.
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
- Loss function name or custom loss.
56
- layers : list or None
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_ : array
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
- >>> np.round(np.mean(labels_pred == labels_true), 2)
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, list]] = None, layer_types: Union[str, list] = 'Conv',
99
- activations: Union[str, list] = 'ReLu', use_bias: Union[bool, list] = True,
100
- normalizations: Union[str, list] = 'both', self_embeddings: Union[bool, list] = True,
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[list] = None, optimizer: Union[BaseOptimizer, str] = 'Adam',
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, history: bool = False) -> 'GNNClassifier':
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 (dictionary or vector of int). Negative values ignored.
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
- if history:
241
- self.history_['embedding'].append(self.layers[-1].embedding)
242
- self.history_['loss'].append(loss_value)
243
- self.history_['train_accuracy'].append(train_accuracy)
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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  # coding: utf-8
3
3
  """
4
- Created on Thu Apr 21 2022
4
+ Created in April 2022
5
5
  @author: Simon Delarue <sdelarue@enst.fr>
6
6
  """
7
7
  from typing import Optional, Union
sknetwork/gnn/loss.py CHANGED
@@ -53,7 +53,7 @@ class CrossEntropy(BaseLoss, Softmax):
53
53
  probs = Softmax.output(signal)
54
54
 
55
55
  # for numerical stability
56
- eps = 1e-15
56
+ eps = 1e-10
57
57
  probs = np.clip(probs, eps, 1 - eps)
58
58
 
59
59
  value = -np.log(probs[np.arange(n), labels]).sum()
@@ -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.bias = \
134
- layer.bias - (self.learning_rate * m_derivative_bias_corr) / (np.sqrt(v_derivative_bias_corr)
135
- + self.eps)
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(all(self.base_layer.bias[0] == np.zeros((len(self.labels), 1)).T[0]))
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
- sagelayer = BaseLayer(layer_type='sageconv', out_channels=len(self.labels))
36
- self.assertTrue('sample_size' in sagelayer.__repr__())
37
- self.assertTrue('sageconv' in sagelayer.__repr__())
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, history=True, validation=0.5,
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, history=True, validation=0.5,
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, reinit=False)
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(all([np.all(weight != layer.weight) for weight, layer in zip(weights, gnn.layers)]))
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 on Thu Apr 21 2022
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, list]):
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, list], layer_types: Union[str, BaseLayer, list],
73
- activations: Union[str, BaseActivation, list], use_bias: Union[bool, list],
74
- normalizations: Union[str, list], self_embeddings: Union[bool, list], sample_sizes: Union[int, list],
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 not isinstance(dims, list):
104
+ if isinstance(dims, int):
105
105
  dims = [dims]
106
106
  n_layers = len(dims)
107
107
 
@@ -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