GroupMultiNeSS 0.0.1__py3-none-any.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.
- GroupMultiNeSS/MASE.py +72 -0
- GroupMultiNeSS/__init__.py +7 -0
- GroupMultiNeSS/base.py +337 -0
- GroupMultiNeSS/data_generation.py +205 -0
- GroupMultiNeSS/group_multiness.py +524 -0
- GroupMultiNeSS/multiness.py +1307 -0
- GroupMultiNeSS/shared_space_hunting.py +315 -0
- GroupMultiNeSS/utils.py +379 -0
- groupmultiness-0.0.1.dist-info/METADATA +102 -0
- groupmultiness-0.0.1.dist-info/RECORD +13 -0
- groupmultiness-0.0.1.dist-info/WHEEL +5 -0
- groupmultiness-0.0.1.dist-info/licenses/LICENSE +22 -0
- groupmultiness-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from typing import List, Dict, Union, Tuple
|
|
3
|
+
from collections import Counter
|
|
4
|
+
from functools import partial
|
|
5
|
+
from joblib import Parallel, delayed
|
|
6
|
+
from warnings import warn
|
|
7
|
+
|
|
8
|
+
from .multiness import BaseMultiNeSS, BaseMultiNeSSRefined, BaseMultiNeSSRefinedCV, BaseOracleMultiNeSS
|
|
9
|
+
from .base import BaseRefitting
|
|
10
|
+
from .utils import if_scalar_or_given_length_array, fill_diagonals, \
|
|
11
|
+
soft_thresholding_operator
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseGroupMultiNeSS(BaseMultiNeSS):
|
|
16
|
+
"""
|
|
17
|
+
Base class for group-structured MultiNeSS models.
|
|
18
|
+
|
|
19
|
+
Handles shared, group-specific, and individual latent spaces for network layers.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
group_indices : List[int]
|
|
24
|
+
List of group assignments for each network layer.
|
|
25
|
+
edge_distrib : str, default='normal'
|
|
26
|
+
Distribution of edges ('normal' or 'bernoulli').
|
|
27
|
+
loops_allowed : bool, default=True
|
|
28
|
+
Whether self-loops are allowed in the network.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, group_indices: List[int],
|
|
32
|
+
edge_distrib: str = "normal",
|
|
33
|
+
loops_allowed: bool = True):
|
|
34
|
+
"""
|
|
35
|
+
Initialize BaseGroupMultiNeSS with group assignments.
|
|
36
|
+
"""
|
|
37
|
+
super().__init__(edge_distrib=edge_distrib, loops_allowed=loops_allowed)
|
|
38
|
+
self.group_indices = group_indices
|
|
39
|
+
|
|
40
|
+
def compute_latent_spaces(self):
|
|
41
|
+
"""
|
|
42
|
+
Compute the full latent space by summing shared, group-specific, and individual components.
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
np.ndarray
|
|
47
|
+
The combined latent space matrix.
|
|
48
|
+
"""
|
|
49
|
+
ls = self.get_shared_latent_space() + self.get_group_latent_spaces()[self.group_indices] + \
|
|
50
|
+
self.get_individual_latent_spaces()
|
|
51
|
+
if not self.loops_allowed:
|
|
52
|
+
fill_diagonals(ls, val=0., inplace=True)
|
|
53
|
+
return ls
|
|
54
|
+
|
|
55
|
+
def get_shared_latent_space(self):
|
|
56
|
+
"""
|
|
57
|
+
Get the shared latent space.
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
np.ndarray
|
|
62
|
+
Shared component matrix.
|
|
63
|
+
"""
|
|
64
|
+
return self.get_all_fitted_matrices()[0]
|
|
65
|
+
|
|
66
|
+
def get_group_latent_spaces(self):
|
|
67
|
+
"""
|
|
68
|
+
Get the group-specific latent spaces.
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
np.ndarray
|
|
73
|
+
Group component matrices.
|
|
74
|
+
"""
|
|
75
|
+
return self.get_all_fitted_matrices()[1: self.n_groups_ + 1]
|
|
76
|
+
|
|
77
|
+
def get_individual_latent_spaces(self):
|
|
78
|
+
"""
|
|
79
|
+
Get the individual latent spaces.
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
np.ndarray
|
|
84
|
+
Individual component matrices.
|
|
85
|
+
"""
|
|
86
|
+
return self.get_all_fitted_matrices()[self.n_groups_ + 1:]
|
|
87
|
+
|
|
88
|
+
def get_all_fitted_matrices_by_type(self):
|
|
89
|
+
"""
|
|
90
|
+
Return all fitted matrices separated by type: shared, group, individual.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
list
|
|
95
|
+
List of matrices by type.
|
|
96
|
+
"""
|
|
97
|
+
return [self.get_shared_latent_space(), self.get_group_latent_spaces(), self.get_individual_latent_spaces()]
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def _get_all_fitted_parameter_names():
|
|
101
|
+
"""
|
|
102
|
+
Names of the fitted parameter groups.
|
|
103
|
+
|
|
104
|
+
Returns
|
|
105
|
+
-------
|
|
106
|
+
list
|
|
107
|
+
List of strings representing component types.
|
|
108
|
+
"""
|
|
109
|
+
return ["Shared component", "Group components", "Individual components"]
|
|
110
|
+
|
|
111
|
+
def _validate_input(self, As: List[np.array]):
|
|
112
|
+
"""
|
|
113
|
+
Validate input data and group assignments.
|
|
114
|
+
|
|
115
|
+
Parameters
|
|
116
|
+
----------
|
|
117
|
+
As : List[np.ndarray]
|
|
118
|
+
List of network adjacency matrices.
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
np.ndarray
|
|
123
|
+
Validated adjacency matrices.
|
|
124
|
+
"""
|
|
125
|
+
if np.any([group_size == 1 for group_size in Counter(self.group_indices).values()]):
|
|
126
|
+
warn("Group latent space is unidentifiable for groups of size 1.")
|
|
127
|
+
self.unique_groups_ = np.sort(np.unique(self.group_indices))
|
|
128
|
+
self.group_sizes_ = np.array([(self.group_indices == group).sum() for group in self.unique_groups_])
|
|
129
|
+
self.n_groups_ = len(self.unique_groups_)
|
|
130
|
+
assert np.all(np.sort(self.unique_groups_) == np.arange(len(self.unique_groups_))), \
|
|
131
|
+
"Groups should be named from 0 to K-1, where K is the number of unique groups."
|
|
132
|
+
As = super()._validate_input(As)
|
|
133
|
+
assert len(self.group_indices) == len(As), "Length of group_indices should be equal to the number of layers"
|
|
134
|
+
return As
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class BaseGroupMultiNeSSRefined(BaseGroupMultiNeSS, BaseMultiNeSSRefined):
|
|
138
|
+
"""
|
|
139
|
+
Refined group-structured MultiNeSS model.
|
|
140
|
+
|
|
141
|
+
Inherits from BaseGroupMultiNeSS and BaseMultiNeSSRefined.
|
|
142
|
+
Handles optimization over shared, group-specific, and individual components.
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
group_indices : List[int]
|
|
147
|
+
List of group assignments for each network layer.
|
|
148
|
+
edge_distrib : str, default='normal'
|
|
149
|
+
Edge distribution.
|
|
150
|
+
max_rank : int, optional
|
|
151
|
+
Maximum rank for low-rank components.
|
|
152
|
+
init_rank : int, optional
|
|
153
|
+
Initial rank for low-rank components.
|
|
154
|
+
sigmas : float, list, or np.ndarray, optional
|
|
155
|
+
Noise standard deviation(s).
|
|
156
|
+
loops_allowed : bool, default=True
|
|
157
|
+
Whether self-loops are allowed.
|
|
158
|
+
n_jobs : int, optional
|
|
159
|
+
Number of parallel jobs for optimization.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
def __init__(self, group_indices: List[int],
|
|
163
|
+
edge_distrib: str = "normal",
|
|
164
|
+
max_rank: int = None, init_rank: int = None,
|
|
165
|
+
sigmas: Union[float, List, np.ndarray] = None,
|
|
166
|
+
loops_allowed: bool = True,
|
|
167
|
+
n_jobs: int = None):
|
|
168
|
+
"""
|
|
169
|
+
Initialize BaseGroupMultiNeSSRefined.
|
|
170
|
+
"""
|
|
171
|
+
BaseMultiNeSSRefined.__init__(self, edge_distrib=edge_distrib, loops_allowed=loops_allowed,
|
|
172
|
+
max_rank=max_rank, init_rank=init_rank, sigmas=sigmas)
|
|
173
|
+
self.group_indices = group_indices
|
|
174
|
+
self.n_jobs = n_jobs
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def n_fitted_matrices(self) -> int:
|
|
178
|
+
"""
|
|
179
|
+
Total number of fitted matrices (shared + group + individual).
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
int
|
|
184
|
+
Number of fitted matrices.
|
|
185
|
+
"""
|
|
186
|
+
return 1 + self.n_layers_ + self.n_groups_
|
|
187
|
+
|
|
188
|
+
def _get_group_dependent_matrix_indices(self, group) -> List:
|
|
189
|
+
"""
|
|
190
|
+
Get indices of matrices dependent on a specific group.
|
|
191
|
+
|
|
192
|
+
Parameters
|
|
193
|
+
----------
|
|
194
|
+
group : int
|
|
195
|
+
Group index.
|
|
196
|
+
|
|
197
|
+
Returns
|
|
198
|
+
-------
|
|
199
|
+
list
|
|
200
|
+
List of matrix indices for the group.
|
|
201
|
+
"""
|
|
202
|
+
individs_within_group_indices = 1 + self.n_groups_ + np.arange(self.n_layers_)[self.group_indices == group]
|
|
203
|
+
return [1 + group, *individs_within_group_indices]
|
|
204
|
+
|
|
205
|
+
def _get_param_participance_masks(self):
|
|
206
|
+
"""
|
|
207
|
+
Get boolean masks indicating parameter participation for each component.
|
|
208
|
+
|
|
209
|
+
Returns
|
|
210
|
+
-------
|
|
211
|
+
np.ndarray
|
|
212
|
+
Masks array of shape (n_params, n_layers).
|
|
213
|
+
"""
|
|
214
|
+
group_masks = [self.group_indices == group for group in self.unique_groups_]
|
|
215
|
+
return np.stack([np.ones(self.n_layers_, dtype=bool),
|
|
216
|
+
*group_masks,
|
|
217
|
+
*np.eye(self.n_layers_, dtype=bool)])
|
|
218
|
+
|
|
219
|
+
def _make_key_2_history_key_dict(self):
|
|
220
|
+
"""
|
|
221
|
+
Map optimization keys to human-readable history keys.
|
|
222
|
+
"""
|
|
223
|
+
self._key_2_history_key = {**{(0, group): f"Stage 1, Group {group}" for group in self.unique_groups_},
|
|
224
|
+
**{(1, 0): "Stage 2"}}
|
|
225
|
+
|
|
226
|
+
def _make_key_2_param_indices_dict(self):
|
|
227
|
+
"""
|
|
228
|
+
Map optimization keys to parameter indices.
|
|
229
|
+
"""
|
|
230
|
+
self._key_2_param_indices = {**{(0, group): self._get_group_dependent_matrix_indices(group)
|
|
231
|
+
for group in self.unique_groups_},
|
|
232
|
+
**{(1, 0): list(range(1 + self.n_groups_))}}
|
|
233
|
+
|
|
234
|
+
def _update_latent_history(self, key: Tuple[int, int]) -> None:
|
|
235
|
+
"""
|
|
236
|
+
Update stored latent history for a given key.
|
|
237
|
+
|
|
238
|
+
Parameters
|
|
239
|
+
----------
|
|
240
|
+
key : tuple
|
|
241
|
+
Optimization stage and group key.
|
|
242
|
+
"""
|
|
243
|
+
stage, group = key
|
|
244
|
+
history_key = self._key_2_history_key[key]
|
|
245
|
+
if stage == 0:
|
|
246
|
+
self.latent_history_[history_key].append(
|
|
247
|
+
(self.get_group_latent_spaces()[group].copy(),
|
|
248
|
+
self.get_individual_latent_spaces()[self.group_indices == group].copy()))
|
|
249
|
+
else:
|
|
250
|
+
self.latent_history_[history_key].append((self.get_shared_latent_space(), self.get_group_latent_spaces()))
|
|
251
|
+
|
|
252
|
+
def _fit(self, As, lr: Union[float, Tuple[float, float]] = 1., **optim_kwargs):
|
|
253
|
+
"""
|
|
254
|
+
Fit the model using two-stage optimization for groups and shared components.
|
|
255
|
+
|
|
256
|
+
Parameters
|
|
257
|
+
----------
|
|
258
|
+
As : np.ndarray
|
|
259
|
+
Input adjacency matrices.
|
|
260
|
+
lr : float or tuple of float, default=1.
|
|
261
|
+
Learning rates for stage 1 and 2.
|
|
262
|
+
"""
|
|
263
|
+
self._pre_fit()
|
|
264
|
+
lr = if_scalar_or_given_length_array(lr, length=2, name="lr")
|
|
265
|
+
Parallel(n_jobs=self.n_jobs)(
|
|
266
|
+
[delayed(self._optimize_params)(key=(0, group),
|
|
267
|
+
As=As, lr=lr[0],
|
|
268
|
+
**optim_kwargs)
|
|
269
|
+
for group in self.unique_groups_])
|
|
270
|
+
self._optimize_params(key=(1, 0), As=As, lr=lr[1], **optim_kwargs)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class GroupMultiNeSS(BaseGroupMultiNeSSRefined, BaseRefitting):
|
|
274
|
+
"""
|
|
275
|
+
Group-structured MultiNeSS model with refitting capability.
|
|
276
|
+
|
|
277
|
+
Parameters
|
|
278
|
+
----------
|
|
279
|
+
group_indices : List[int]
|
|
280
|
+
List of group assignments for each network layer.
|
|
281
|
+
lmbda1, lmbda2 : float or array-like
|
|
282
|
+
Regularization parameters for stage 1 (group) and stage 2 (shared) components.
|
|
283
|
+
alpha1, alpha2 : float or array-like
|
|
284
|
+
Weighting parameters for stage 1 (group) and stage 2 (shared) components.
|
|
285
|
+
edge_distrib : str, default='normal'
|
|
286
|
+
Edge distribution.
|
|
287
|
+
max_rank, init_rank : int, optional
|
|
288
|
+
Maximum and initial ranks for low-rank components.
|
|
289
|
+
sigmas : float, list, or np.ndarray, optional
|
|
290
|
+
Noise standard deviation(s).
|
|
291
|
+
loops_allowed : bool, default=True
|
|
292
|
+
Whether self-loops are allowed.
|
|
293
|
+
n_jobs : int, optional
|
|
294
|
+
Number of parallel jobs for optimization.
|
|
295
|
+
refit_threshold : float, default=1e-8
|
|
296
|
+
Threshold for refitting components.
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
def __init__(self, group_indices: List[int],
|
|
300
|
+
lmbda1: Union[float, np.ndarray, list] = None,
|
|
301
|
+
lmbda2: float = None,
|
|
302
|
+
alpha1: Union[float, np.ndarray, list] = None,
|
|
303
|
+
alpha2: Union[float, np.ndarray, list] = None,
|
|
304
|
+
edge_distrib: str = "normal",
|
|
305
|
+
max_rank: int = None, init_rank: int = None,
|
|
306
|
+
sigmas: Union[float, List, np.ndarray] = None,
|
|
307
|
+
loops_allowed: bool = True,
|
|
308
|
+
n_jobs: int = None,
|
|
309
|
+
refit_threshold: float = 1e-8):
|
|
310
|
+
"""
|
|
311
|
+
Initialize GroupMultiNeSS model.
|
|
312
|
+
"""
|
|
313
|
+
BaseGroupMultiNeSSRefined.__init__(self, group_indices=group_indices, edge_distrib=edge_distrib,
|
|
314
|
+
max_rank=max_rank, init_rank=init_rank, sigmas=sigmas,
|
|
315
|
+
loops_allowed=loops_allowed, n_jobs=n_jobs)
|
|
316
|
+
BaseRefitting.__init__(self, edge_distrib=edge_distrib, max_rank=max_rank, loops_allowed=loops_allowed,
|
|
317
|
+
refit_threshold=refit_threshold)
|
|
318
|
+
self.lmbda1 = lmbda1
|
|
319
|
+
self.lmbda2 = lmbda2
|
|
320
|
+
self.alpha1 = alpha1
|
|
321
|
+
self.alpha2 = alpha2
|
|
322
|
+
|
|
323
|
+
def _compute_projected_map(self, idx: int, lr, key: Tuple[int, int]):
|
|
324
|
+
"""
|
|
325
|
+
Compute projected map for soft-thresholding operator.
|
|
326
|
+
|
|
327
|
+
Parameters
|
|
328
|
+
----------
|
|
329
|
+
idx : int
|
|
330
|
+
Index of the parameter.
|
|
331
|
+
lr : float
|
|
332
|
+
Learning rate.
|
|
333
|
+
key : tuple
|
|
334
|
+
Optimization stage and group key.
|
|
335
|
+
|
|
336
|
+
Returns
|
|
337
|
+
-------
|
|
338
|
+
partial
|
|
339
|
+
Soft-thresholding operator with scaled threshold.
|
|
340
|
+
"""
|
|
341
|
+
mask = self._param_participance_masks[idx]
|
|
342
|
+
penalty = self._compute_penalty_coef(idx=idx, key=key)
|
|
343
|
+
return partial(soft_thresholding_operator, threshold=penalty * lr / mask.sum(), max_rank=self.max_rank)
|
|
344
|
+
|
|
345
|
+
def _compute_penalty_coef(self, idx: int, key: Tuple[int, int]) -> float:
|
|
346
|
+
"""
|
|
347
|
+
Compute penalty coefficient for a given parameter and stage.
|
|
348
|
+
|
|
349
|
+
Parameters
|
|
350
|
+
----------
|
|
351
|
+
idx : int
|
|
352
|
+
Parameter index.
|
|
353
|
+
key : tuple
|
|
354
|
+
Stage and group key.
|
|
355
|
+
|
|
356
|
+
Returns
|
|
357
|
+
-------
|
|
358
|
+
float
|
|
359
|
+
Penalty coefficient.
|
|
360
|
+
"""
|
|
361
|
+
hyperparam_dict = self._key_2_hyperparams_dict[key]
|
|
362
|
+
stage, group = key
|
|
363
|
+
lmbda, alpha = hyperparam_dict[f"lmbda{stage + 1}"], hyperparam_dict[f"alpha{stage + 1}"]
|
|
364
|
+
if stage == 0:
|
|
365
|
+
if idx == group + 1:
|
|
366
|
+
return lmbda
|
|
367
|
+
else:
|
|
368
|
+
idx_in_group = np.sum(self.group_indices[:idx - self.n_groups_ - 1] == group)
|
|
369
|
+
return lmbda * alpha[idx_in_group]
|
|
370
|
+
else:
|
|
371
|
+
return lmbda if idx == 0 else lmbda * alpha[idx - 1]
|
|
372
|
+
|
|
373
|
+
def _make_key_2_hyperparams_dict(self):
|
|
374
|
+
"""
|
|
375
|
+
Create dictionary mapping optimization keys to hyperparameters.
|
|
376
|
+
"""
|
|
377
|
+
if self.lmbda1 is None:
|
|
378
|
+
lmbda1 = 2. * np.sqrt(self.group_sizes_ * self.n_nodes_)
|
|
379
|
+
else:
|
|
380
|
+
lmbda1 = if_scalar_or_given_length_array(self.lmbda1, length=self.n_groups_, name="lmbda1")
|
|
381
|
+
lmbda2 = 2. * self.n_layers_ * np.sqrt(self.n_nodes_ / self.n_groups_) if self.lmbda2 is None else self.lmbda2
|
|
382
|
+
if self.alpha1 is None:
|
|
383
|
+
alpha1 = np.ones(self.n_layers_) / np.sqrt(self.group_sizes_[self.group_indices])
|
|
384
|
+
else:
|
|
385
|
+
alpha1 = if_scalar_or_given_length_array(self.alpha1, length=self.n_layers_, name="alpha1")
|
|
386
|
+
if self.alpha2 is None:
|
|
387
|
+
alpha2 = self.group_sizes_ / self.n_layers_ * np.sqrt(self.n_groups_)
|
|
388
|
+
else:
|
|
389
|
+
alpha2 = if_scalar_or_given_length_array(self.alpha2, length=self.n_groups_, name="alpha2")
|
|
390
|
+
|
|
391
|
+
self._key_2_hyperparams_dict = \
|
|
392
|
+
{**{(0, group): {"lmbda1": lmbda1[group], "alpha1": alpha1[self.group_indices == group]}
|
|
393
|
+
for group in self.unique_groups_},
|
|
394
|
+
**{(1, 0): {"lmbda2": lmbda2, "alpha2": alpha2}}}
|
|
395
|
+
|
|
396
|
+
def fit(self, As: List[np.array],
|
|
397
|
+
lr: Union[float, Tuple[float, float]] = 1e-1,
|
|
398
|
+
max_iter: int = 100, tol=1e-5, patience=10,
|
|
399
|
+
refit=True, verbose: bool = False, verbose_interval=1,
|
|
400
|
+
save_latent_history=False, save_nll_history=True):
|
|
401
|
+
"""
|
|
402
|
+
Fit the GroupMultiNeSS model.
|
|
403
|
+
|
|
404
|
+
Parameters
|
|
405
|
+
----------
|
|
406
|
+
As : np.ndarray
|
|
407
|
+
Input adjacency matrices.
|
|
408
|
+
lr : float or tuple, default=0.1
|
|
409
|
+
Learning rate(s) for stages.
|
|
410
|
+
max_iter : int, default=100
|
|
411
|
+
Maximum number of iterations.
|
|
412
|
+
tol : float, default=1e-5
|
|
413
|
+
Convergence tolerance.
|
|
414
|
+
patience : int, default=10
|
|
415
|
+
Early stopping patience.
|
|
416
|
+
refit : bool, default=True
|
|
417
|
+
Whether to refit components.
|
|
418
|
+
verbose : bool, default=False
|
|
419
|
+
Verbosity flag.
|
|
420
|
+
verbose_interval : int, default=1
|
|
421
|
+
Interval for printing verbose messages.
|
|
422
|
+
save_latent_history : bool, default=False
|
|
423
|
+
Whether to save latent history.
|
|
424
|
+
save_nll_history : bool, default=True
|
|
425
|
+
Whether to save NLL history.
|
|
426
|
+
|
|
427
|
+
Returns
|
|
428
|
+
-------
|
|
429
|
+
self
|
|
430
|
+
Fitted model instance.
|
|
431
|
+
"""
|
|
432
|
+
As = self._validate_input(As=As)
|
|
433
|
+
self._fit(As, lr=lr,
|
|
434
|
+
max_iter=max_iter, tol=tol, patience=patience,
|
|
435
|
+
refit=refit,
|
|
436
|
+
verbose=verbose, verbose_interval=verbose_interval,
|
|
437
|
+
save_latent_history=save_latent_history, save_nll_history=save_nll_history)
|
|
438
|
+
return self
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
class GroupMultiNeSSCV(GroupMultiNeSS, BaseMultiNeSSRefinedCV):
|
|
442
|
+
def __init__(self, group_indices: List[int],
|
|
443
|
+
param_grid: Dict[str, List] = None,
|
|
444
|
+
edge_distrib: str = "normal",
|
|
445
|
+
lmbda1: Union[float, np.ndarray, list] = None,
|
|
446
|
+
lmbda2: float = None,
|
|
447
|
+
alpha1: Union[float, np.ndarray, list] = None,
|
|
448
|
+
alpha2: Union[float, np.ndarray, list] = None,
|
|
449
|
+
loops_allowed: bool = True,
|
|
450
|
+
cv_folds=3, test_prop=0.1,
|
|
451
|
+
max_rank: int = None, init_rank: int = None,
|
|
452
|
+
sigmas: Union[float, List, np.ndarray] = None,
|
|
453
|
+
n_jobs: int = None,
|
|
454
|
+
refit_threshold=1e-8,
|
|
455
|
+
fix_layer_split: bool = False,
|
|
456
|
+
use_1se_rule: bool = False,
|
|
457
|
+
random_seed: int = None):
|
|
458
|
+
|
|
459
|
+
BaseMultiNeSSRefinedCV.__init__(self, param_grid=param_grid, cv_folds=cv_folds, test_prop=test_prop,
|
|
460
|
+
loops_allowed=loops_allowed,
|
|
461
|
+
fix_layer_split=fix_layer_split,
|
|
462
|
+
use_1se_rule=use_1se_rule,
|
|
463
|
+
random_seed=random_seed)
|
|
464
|
+
|
|
465
|
+
GroupMultiNeSS.__init__(self, group_indices=group_indices,
|
|
466
|
+
lmbda1=lmbda1, lmbda2=lmbda2, alpha1=alpha1, alpha2=alpha2, edge_distrib=edge_distrib,
|
|
467
|
+
max_rank=max_rank, init_rank=init_rank, sigmas=sigmas,
|
|
468
|
+
loops_allowed=loops_allowed, n_jobs=n_jobs,
|
|
469
|
+
refit_threshold=refit_threshold)
|
|
470
|
+
|
|
471
|
+
def _make_key_2_hyperparams_grid_dict(self) -> None:
|
|
472
|
+
const_range = np.array([0.03, 0.1, 0.3, 1, 3, 10])
|
|
473
|
+
if self.param_grid is None:
|
|
474
|
+
self._key_2_hyperparams_grid = {
|
|
475
|
+
**{(1, 0): {"lmbda2": self.n_layers_ * np.sqrt(self.n_nodes_ / self.n_groups_) * const_range}},
|
|
476
|
+
**{(0, group): {"lmbda1": np.sqrt(self.n_nodes_ * self.group_sizes_[group]) * const_range}
|
|
477
|
+
for group in self.unique_groups_}}
|
|
478
|
+
else:
|
|
479
|
+
|
|
480
|
+
stage_2_grid = {stage: {key: grid for key, grid in self.param_grid.items() if key.endswith(str(stage + 1))}
|
|
481
|
+
for stage in range(2)}
|
|
482
|
+
self._key_2_hyperparams_grid = {**{(0, group): stage_2_grid[0] for group in self.unique_groups_},
|
|
483
|
+
**{(1, 0): stage_2_grid[1]}}
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
class OracleGroupMultiNeSS(BaseGroupMultiNeSSRefined, BaseOracleMultiNeSS):
|
|
487
|
+
def __init__(self, group_indices: List[int],
|
|
488
|
+
d_shared: int, d_groups: List[int], d_individs: List[int],
|
|
489
|
+
edge_distrib: str = "normal",
|
|
490
|
+
sigmas: Union[float, List, np.ndarray] = None,
|
|
491
|
+
loops_allowed: bool = True,
|
|
492
|
+
n_jobs: int = None):
|
|
493
|
+
|
|
494
|
+
BaseGroupMultiNeSSRefined.__init__(self, group_indices=group_indices,
|
|
495
|
+
edge_distrib=edge_distrib, sigmas=sigmas,
|
|
496
|
+
loops_allowed=loops_allowed, n_jobs=n_jobs)
|
|
497
|
+
self.d_shared = d_shared
|
|
498
|
+
self.d_groups = d_groups
|
|
499
|
+
self.d_individs = d_individs
|
|
500
|
+
|
|
501
|
+
def _make_key_2_hyperparams_dict(self):
|
|
502
|
+
d_shared = if_scalar_or_given_length_array(self.d_shared, length=1, name="d_shared")[0]
|
|
503
|
+
d_groups = if_scalar_or_given_length_array(self.d_groups, length=self.n_groups_, name="d_groups")
|
|
504
|
+
d_individs = if_scalar_or_given_length_array(self.d_individs, length=self.n_layers_, name="d_individs")
|
|
505
|
+
self._key_2_hyperparams_dict = {**{(0, group): {"d_shared": d_shared + d_groups[group],
|
|
506
|
+
"d_individs": d_individs[self.group_indices == group]}
|
|
507
|
+
for group in self.unique_groups_},
|
|
508
|
+
**{(1, 0): {"d_shared": d_shared, "d_individs": d_groups}}}
|
|
509
|
+
|
|
510
|
+
def _compute_threshold_rank(self, idx: int, key: Tuple[int, int]) -> int:
|
|
511
|
+
hyperparam_dict = self._key_2_hyperparams_dict[key]
|
|
512
|
+
d_shared, d_individs = hyperparam_dict["d_shared"], hyperparam_dict["d_individs"]
|
|
513
|
+
stage, group = key
|
|
514
|
+
if idx == 0:
|
|
515
|
+
max_rank = d_shared
|
|
516
|
+
elif 1 <= idx < self.n_groups_ + 1:
|
|
517
|
+
if stage == 0:
|
|
518
|
+
max_rank = d_shared
|
|
519
|
+
else:
|
|
520
|
+
max_rank = d_individs[idx - 1]
|
|
521
|
+
else:
|
|
522
|
+
idx_in_group = np.sum(self.group_indices[:idx - self.n_groups_ - 1] == group)
|
|
523
|
+
max_rank = d_individs[idx_in_group]
|
|
524
|
+
return max_rank
|