Coverage for nilearn/decoding/space_net.py: 12%

365 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-20 10:58 +0200

1"""sklearn-compatible implementation of spatially structured learners. 

2 

3For example: TV-L1, Graph-Net, etc 

4""" 

5 

6import collections 

7import time 

8import warnings 

9from functools import partial 

10from typing import ClassVar 

11 

12import numpy as np 

13from joblib import Memory, Parallel, delayed 

14from scipy import stats 

15from scipy.ndimage import binary_dilation, binary_erosion, gaussian_filter 

16from sklearn.feature_selection import SelectPercentile, f_classif, f_regression 

17from sklearn.linear_model import LinearRegression 

18from sklearn.linear_model._base import _preprocess_data as center_data 

19from sklearn.metrics import accuracy_score 

20from sklearn.model_selection import check_cv 

21from sklearn.preprocessing import LabelBinarizer 

22from sklearn.utils import check_array, check_X_y 

23from sklearn.utils.estimator_checks import check_is_fitted 

24from sklearn.utils.extmath import safe_sparse_dot 

25 

26from nilearn._utils import fill_doc, logger 

27from nilearn._utils.cache_mixin import CacheMixin 

28from nilearn._utils.logger import find_stack_level 

29from nilearn._utils.masker_validation import check_embedded_masker 

30from nilearn._utils.param_validation import ( 

31 adjust_screening_percentile, 

32 check_params, 

33) 

34from nilearn.image import get_data 

35from nilearn.maskers import SurfaceMasker 

36from nilearn.masking import unmask_from_to_3d_array 

37from nilearn.surface import SurfaceImage 

38 

39from .space_net_solvers import ( 

40 graph_net_logistic, 

41 graph_net_squared_loss, 

42 tvl1_solver, 

43) 

44 

45 

46def _crop_mask(mask): 

47 """Crops input mask to produce tighter (i.e smaller) bounding box \ 

48 with the same support (active voxels). 

49 """ 

50 idx = np.where(mask) 

51 if idx[0].size == 0: 

52 raise ValueError( 

53 "Empty mask: if you have given a mask, it is " 

54 "empty, and if you have not given a mask, the " 

55 "mask-extraction routines have failed. Please " 

56 "provide an appropriate mask." 

57 ) 

58 i_min = max(idx[0].min() - 1, 0) 

59 i_max = idx[0].max() 

60 j_min = max(idx[1].min() - 1, 0) 

61 j_max = idx[1].max() 

62 k_min = max(idx[2].min() - 1, 0) 

63 k_max = idx[2].max() 

64 return mask[i_min : i_max + 1, j_min : j_max + 1, k_min : k_max + 1] 

65 

66 

67@fill_doc 

68def _univariate_feature_screening( 

69 X, y, mask, is_classif, screening_percentile, smoothing_fwhm=2.0 

70): 

71 """Select the most import features, via a univariate test. 

72 

73 Parameters 

74 ---------- 

75 X : ndarray, shape (n_samples, n_features) 

76 Design matrix. 

77 

78 y : ndarray, shape (n_samples,) 

79 Response Vector. 

80 

81 mask : ndarray or booleans, shape (nx, ny, nz) 

82 Mask defining brain Rois. 

83 

84 is_classif : bool 

85 Flag telling whether the learning task is classification or regression. 

86 

87 screening_percentile : float in the closed interval [0., 100.] 

88 Only the `screening_percentile * 100" percent most import voxels will 

89 be retained. 

90 %(smoothing_fwhm)s 

91 Default=2. 

92 

93 Returns 

94 ------- 

95 X_ : ndarray, shape (n_samples, n_features_) 

96 Reduced design matrix with only columns corresponding to the voxels 

97 retained after screening. 

98 

99 mask_ : ndarray of booleans, shape (nx, ny, nz) 

100 Mask with support reduced to only contain voxels retained after 

101 screening. 

102 

103 support : ndarray of ints, shape (n_features_,) 

104 Support of the screened mask, as a subset of the support of the 

105 original mask. 

106 """ 

107 # smooth the data (with isotropic Gaussian kernel) before screening 

108 if smoothing_fwhm > 0.0: 

109 sX = np.empty(X.shape) 

110 for sample in range(sX.shape[0]): 

111 sX[sample] = gaussian_filter( 

112 unmask_from_to_3d_array( 

113 X[sample].copy(), # avoid modifying X 

114 mask, 

115 ), 

116 (smoothing_fwhm, smoothing_fwhm, smoothing_fwhm), 

117 )[mask] 

118 else: 

119 sX = X 

120 

121 # do feature screening proper 

122 selector = SelectPercentile( 

123 f_classif if is_classif else f_regression, 

124 percentile=screening_percentile, 

125 ).fit(sX, y) 

126 support = selector.get_support() 

127 

128 # erode and then dilate mask, thus obtaining a "cleaner" version of 

129 # the mask on which a spatial prior actually makes sense 

130 mask_ = mask.copy() 

131 mask_[mask] = support > 0 

132 mask_ = binary_dilation(binary_erosion(mask_)).astype(bool) 

133 mask_[np.logical_not(mask)] = 0 

134 support = mask_[mask] 

135 X = X[:, support] 

136 

137 return X, mask_, support 

138 

139 

140def _space_net_alpha_grid( 

141 X, y, eps=1e-3, n_alphas=10, l1_ratio=1.0, logistic=False 

142): 

143 """Compute the grid of alpha values for TV-L1 and Graph-Net. 

144 

145 Parameters 

146 ---------- 

147 X : ndarray, shape (n_samples, n_features) 

148 Training data (design matrix). 

149 

150 y : ndarray, shape (n_samples,) 

151 Target / response vector. 

152 

153 l1_ratio : float, default=1 

154 The ElasticNet mixing parameter, with ``0 <= l1_ratio <= 1``. 

155 For ``l1_ratio = 0`` the penalty is purely a spatial prior 

156 (Graph-Net, TV, etc.). ``For l1_ratio = 1`` it is an L1 penalty. 

157 For ``0 < l1_ratio < 1``, the penalty is a combination of L1 

158 and a spatial prior. 

159 

160 eps : float, default=1e-3 

161 Length of the path. ``eps=1e-3`` means that 

162 ``alpha_min / alpha_max = 1e-3``. 

163 

164 n_alphas : int, default=10 

165 Number of alphas along the regularization path. 

166 

167 logistic : bool, default=False 

168 Indicates where the underlying loss function is logistic. 

169 

170 """ 

171 if logistic: 

172 # Computes the theoretical upper bound for the overall 

173 # regularization, as derived in "An Interior-Point Method for 

174 # Large-Scale l1-Regularized Logistic Regression", by Koh, Kim, 

175 # Boyd, in Journal of Machine Learning Research, 8:1519-1555, 

176 # July 2007. 

177 # url: https://web.stanford.edu/~boyd/papers/pdf/l1_logistic_reg.pdf 

178 m = float(y.size) 

179 m_plus = float(y[y == 1].size) 

180 m_minus = float(y[y == -1].size) 

181 b = np.zeros_like(y) 

182 b[y == 1] = m_minus / m 

183 b[y == -1] = -m_plus / m 

184 alpha_max = np.max(np.abs(X.T.dot(b))) 

185 

186 # tt may happen that b is in the kernel of X.T! 

187 if alpha_max == 0.0: 

188 alpha_max = np.abs(np.dot(X.T, y)).max() 

189 else: 

190 alpha_max = np.abs(np.dot(X.T, y)).max() 

191 

192 # prevent alpha_max from exploding when l1_ratio = 0 

193 if l1_ratio == 0.0: 

194 l1_ratio = 1e-3 

195 alpha_max /= l1_ratio 

196 

197 if n_alphas == 1: 

198 return np.array([alpha_max]) 

199 

200 alpha_min = alpha_max * eps 

201 return np.logspace(np.log10(alpha_min), np.log10(alpha_max), num=n_alphas)[ 

202 ::-1 

203 ] 

204 

205 

206class _EarlyStoppingCallback: 

207 """Out-of-bag early stopping. 

208 

209 A callable that returns True when the test error starts 

210 rising. We use a Spearman correlation (between X_test.w and y_test) 

211 for scoring. 

212 """ 

213 

214 def __init__(self, X_test, y_test, is_classif, debias=False, verbose=0): 

215 self.X_test = X_test 

216 self.y_test = y_test 

217 self.is_classif = is_classif 

218 self.debias = debias 

219 self.verbose = verbose 

220 self.tol = -1e-4 if self.is_classif else -1e-2 

221 self.test_scores = [] 

222 self.counter = 0.0 

223 

224 def __call__(self, variables): 

225 """Perform callback.""" 

226 # misc 

227 if not isinstance(variables, dict): 

228 variables = {"w": variables} 

229 self.counter += 1 

230 w = variables["w"] 

231 

232 # use Spearman score as stopping criterion 

233 score = self.test_score(w)[0] 

234 

235 self.test_scores.append(score) 

236 if self.counter <= 20 or self.counter % 10 != 2: 

237 return 

238 

239 # check whether score increased on average over last 5 iterations 

240 if ( 

241 len(self.test_scores) > 4 

242 and np.mean(np.diff(self.test_scores[-5:][::-1])) >= self.tol 

243 ): 

244 message = "." 

245 if self.verbose > 1: 

246 message = ( 

247 f"Early stopping.\nTest score: {score:.8f} {40 * '-'}" 

248 ) 

249 logger.log( 

250 message, 

251 verbose=self.verbose, 

252 ) 

253 return True 

254 

255 logger.log( 

256 f"Test score: {score:.8f}", verbose=self.verbose, msg_level=1 

257 ) 

258 return False 

259 

260 def _debias(self, w): 

261 """Debias w by rescaling the coefficients by a fixed factor. 

262 

263 Precisely, the scaling factor is: <y_pred, y_test> / ||y_test||^2. 

264 """ 

265 y_pred = np.dot(self.X_test, w) 

266 scaling = np.dot(y_pred, y_pred) 

267 if scaling > 0.0: 

268 scaling = np.dot(y_pred, self.y_test) / scaling 

269 w *= scaling 

270 return w 

271 

272 def test_score(self, w): 

273 """Compute test score for model, given weights map `w`. 

274 

275 We use correlations between linear prediction and 

276 ground truth (y_test). 

277 

278 We return 2 scores for model selection: one is the Spearman 

279 correlation, which captures ordering between input and 

280 output, but tends to have 'flat' regions. The other 

281 is the Pearson correlation, that we can use to disambiguate 

282 between regions with equivalent Spearman correlation. 

283 

284 """ 

285 if self.is_classif: 

286 w = w[:-1] 

287 if np.ptp(w) == 0: 

288 # constant map, there is nothing 

289 return (-np.inf, -np.inf) 

290 y_pred = np.dot(self.X_test, w) 

291 spearman_score = stats.spearmanr(y_pred, self.y_test)[0] 

292 pearson_score = np.corrcoef(y_pred, self.y_test)[1, 0] 

293 if self.is_classif: 

294 return spearman_score, pearson_score 

295 else: 

296 return pearson_score, spearman_score 

297 

298 

299@fill_doc 

300def path_scores( 

301 solver, 

302 X, 

303 y, 

304 mask, 

305 alphas, 

306 l1_ratios, 

307 train, 

308 test, 

309 solver_params, 

310 is_classif=False, 

311 n_alphas=10, 

312 eps=1e-3, 

313 key=None, 

314 debias=False, 

315 screening_percentile=20.0, 

316 verbose=1, 

317): 

318 """Compute scores of different alphas in regression \ 

319 and classification used by CV objects. 

320 

321 Parameters 

322 ---------- 

323 X : 2D array of shape (n_samples, n_features) 

324 Design matrix, one row per sample point. 

325 

326 y : 1D array of length n_samples 

327 Response vector; one value per sample. 

328 

329 mask : 3D arrays of :obj:`bool` 

330 Mask defining brain regions that we work on. 

331 

332 alphas : :obj:`list` of :obj:`float` 

333 List of regularization parameters being considered. 

334 

335 train : array or :obj:`list` of :obj:`int`: 

336 List of indices for the train samples. 

337 

338 test : array or :obj:`list` of :obj:`int` 

339 List of indices for the test samples. 

340 

341 l1_ratios : :obj:`float` or :obj:`list` of floats in the interval [0, 1] 

342 Constant that mixes L1 and TV (resp. Graph-Net) penalization. 

343 l1_ratios == 0: just smooth. l1_ratios == 1: just lasso. 

344 

345 eps : :obj:`float`, default=1e-3 

346 Length of the path. For example, ``eps=1e-3`` means that 

347 ``alpha_min / alpha_max = 1e-3``. 

348 

349 n_alphas : :obj:`int`, default=10 

350 Generate this number of alphas per regularization path. 

351 This parameter is mutually exclusive with the `alphas` parameter. 

352 

353 solver : function handle 

354 See for example tv.TVl1Classifier documentation. 

355 

356 solver_params : :obj:`dict` 

357 Dictionary of param-value pairs to be passed to solver. 

358 

359 is_classif : :obj:`bool`, default=False 

360 Indicates whether the loss is a classification loss or a 

361 regression loss. 

362 

363 key: ??? TODO: Add description. 

364 

365 debias : :obj:`bool`, default=False 

366 If set, then the estimated weights maps will be debiased. 

367 

368 screening_percentile : :obj:`float` in the interval [0, 100], \ 

369 default=20.0 

370 Percentile value for :term:`ANOVA` univariate feature selection. 

371 A value of 100 means 'keep all features'. 

372 This percentile is expressed 

373 w.r.t the volume of a standard (MNI152) brain, and so is corrected 

374 at runtime to correspond to the volume of the user-supplied mask 

375 (which is typically smaller). If '100' is given, all the features 

376 are used, regardless of the number of voxels. 

377 %(verbose)s 

378 

379 """ 

380 if l1_ratios is None: 

381 raise ValueError("l1_ratios must be specified!") 

382 

383 # misc 

384 _, n_features = X.shape 

385 verbose = int(verbose if verbose is not None else 0) 

386 

387 # Univariate feature screening. Note that if we have only as few as 100 

388 # features in the mask's support, then we should use all of them to 

389 # learn the model i.e disable this screening) 

390 do_screening = (n_features > 100) and screening_percentile < 100.0 

391 if do_screening: 

392 X, mask, support = _univariate_feature_screening( 

393 X, y, mask, is_classif, screening_percentile 

394 ) 

395 

396 # crop the mask to have a tighter bounding box 

397 mask = _crop_mask(mask) 

398 

399 # get train and test data 

400 X_train, y_train = X[train].copy(), y[train].copy() 

401 X_test, y_test = X[test].copy(), y[test].copy() 

402 

403 # it is essential to center the data in regression 

404 X_train, y_train, _, y_train_mean, _ = center_data( 

405 X_train, y_train, fit_intercept=True, copy=False 

406 ) 

407 

408 # misc 

409 if not isinstance(l1_ratios, collections.abc.Iterable): 

410 l1_ratios = [l1_ratios] 

411 l1_ratios = sorted(l1_ratios)[::-1] # from large to small l1_ratios 

412 best_score = -np.inf 

413 best_secondary_score = -np.inf 

414 best_l1_ratio = l1_ratios[0] 

415 best_alpha = None 

416 best_init = None 

417 all_test_scores = [] 

418 if len(test) > 0.0: 

419 # do l1_ratio path 

420 for l1_ratio in l1_ratios: 

421 this_test_scores = [] 

422 

423 # make alpha grid 

424 if alphas is None: 

425 alphas_ = _space_net_alpha_grid( 

426 X_train, 

427 y_train, 

428 l1_ratio=l1_ratio, 

429 eps=eps, 

430 n_alphas=n_alphas, 

431 logistic=is_classif, 

432 ) 

433 else: 

434 alphas_ = alphas 

435 alphas_ = sorted(alphas_)[::-1] # from large to small l1_ratios 

436 

437 # do alpha path 

438 if best_alpha is None: 

439 best_alpha = alphas_[0] 

440 init = None 

441 path_solver_params = solver_params.copy() 

442 # Use a lighter tol during the path 

443 path_solver_params["tol"] = 2 * path_solver_params.get("tol", 1e-4) 

444 for alpha in alphas_: 

445 # setup callback mechanism for early stopping 

446 early_stopper = _EarlyStoppingCallback( 

447 X_test, 

448 y_test, 

449 is_classif=is_classif, 

450 debias=debias, 

451 verbose=verbose, 

452 ) 

453 w, _, init = solver( 

454 X_train, 

455 y_train, 

456 alpha, 

457 l1_ratio, 

458 mask=mask, 

459 init=init, 

460 callback=early_stopper, 

461 verbose=max(verbose - 1, 0.0), 

462 **path_solver_params, 

463 ) 

464 

465 # We use 2 scores for model selection: the second one is to 

466 # disambiguate between regions of equivalent Spearman 

467 # correlations 

468 score, secondary_score = early_stopper.test_score(w) 

469 this_test_scores.append(score) 

470 if np.isfinite(score) and ( 

471 score > best_score 

472 or ( 

473 score == best_score 

474 and secondary_score > best_secondary_score 

475 ) 

476 ): 

477 best_secondary_score = secondary_score 

478 best_score = score 

479 best_l1_ratio = l1_ratio 

480 best_alpha = alpha 

481 best_init = init.copy() 

482 all_test_scores.append(this_test_scores) 

483 else: 

484 if alphas is None: 

485 alphas_ = _space_net_alpha_grid( 

486 X_train, 

487 y_train, 

488 l1_ratio=best_l1_ratio, 

489 eps=eps, 

490 n_alphas=n_alphas, 

491 logistic=is_classif, 

492 ) 

493 else: 

494 alphas_ = alphas 

495 best_alpha = alphas_[0] 

496 

497 # re-fit best model to high precision (i.e without early stopping, etc.) 

498 best_w, _, init = solver( 

499 X_train, 

500 y_train, 

501 best_alpha, 

502 best_l1_ratio, 

503 mask=mask, 

504 init=best_init, 

505 verbose=max(verbose - 1, 0), 

506 **solver_params, 

507 ) 

508 if debias: 

509 best_w = _EarlyStoppingCallback( 

510 X_test, 

511 y_test, 

512 is_classif=is_classif, 

513 debias=debias, 

514 verbose=verbose, 

515 )._debias(best_w) 

516 

517 if len(test) == 0.0: 

518 all_test_scores.append(np.nan) 

519 

520 # unmask univariate screening 

521 if do_screening: 

522 w_ = np.zeros(len(support)) 

523 if is_classif: 

524 w_ = np.append(w_, best_w[-1]) 

525 w_[:-1][support] = best_w[:-1] 

526 else: 

527 w_[support] = best_w 

528 best_w = w_ 

529 

530 if len(best_w) == n_features: 

531 # TODO: implement with Xmean 

532 best_w = np.append(best_w, 0.0) 

533 

534 all_test_scores = np.array(all_test_scores) 

535 return ( 

536 all_test_scores, 

537 best_w, 

538 best_alpha, 

539 best_l1_ratio, 

540 alphas_, 

541 y_train_mean, 

542 key, 

543 ) 

544 

545 

546@fill_doc 

547class BaseSpaceNet(CacheMixin, LinearRegression): 

548 """Regression and classification learners with sparsity and spatial priors. 

549 

550 `SpaceNet` implements Graph-Net and TV-L1 priors / 

551 penalties. Thus, the penalty is a sum of an L1 term and a spatial term. The 

552 aim of such a hybrid prior is to obtain weights maps which are structured 

553 (due to the spatial prior) and sparse (enforced by L1 norm). 

554 

555 Parameters 

556 ---------- 

557 penalty : :obj:`str`, default='graph-net' 

558 Penalty to used in the model. Can be 'graph-net' or 'tv-l1'. 

559 

560 loss : :obj:`str`, default=None 

561 Loss to be used in the model. Must be an one of "mse", or "logistic". 

562 

563 is_classif : :obj:`bool`, default=False 

564 Flag telling whether the learning task is classification or regression. 

565 

566 l1_ratios : :obj:`float` or :obj:`list` of floats in the interval [0, 1]; \ 

567 default=0.5 

568 Constant that mixes L1 and spatial prior terms in penalization. 

569 l1_ratios == 1 corresponds to pure LASSO. The larger the value of this 

570 parameter, the sparser the estimated weights map. If list is provided, 

571 then the best value will be selected by cross-validation. 

572 

573 alphas : :obj:`float` or :obj:`list` of floats, default=None 

574 Choices for the constant that scales the overall regularization term. 

575 This parameter is mutually exclusive with the `n_alphas` parameter. 

576 If None or list of floats is provided, then the best value will be 

577 selected by cross-validation. 

578 

579 n_alphas : :obj:`int`, default=10 

580 Generate this number of alphas per regularization path. 

581 This parameter is mutually exclusive with the `alphas` parameter. 

582 

583 eps : :obj:`float`, default=1e-3 

584 Length of the path. For example, ``eps=1e-3`` means that 

585 ``alpha_min / alpha_max = 1e-3`` 

586 

587 mask : filename, niimg, NiftiMasker instance, default=None 

588 Mask to be used on data. If an instance of masker is passed, 

589 then its mask will be used. If no mask is it will be computed 

590 automatically by a NiftiMasker. 

591 %(target_affine)s 

592 An important use-case of this parameter is for downsampling the 

593 input data to a coarser resolution (to speed of the model fit). 

594 %(target_shape)s 

595 %(low_pass)s 

596 %(high_pass)s 

597 %(t_r)s 

598 screening_percentile : :obj:`float` in the interval [0, 100]; Optional (\ 

599 default 20) 

600 Percentile value for ANOVA univariate feature selection. A value of 

601 100 means 'keep all features'. This percentile is expressed 

602 w.r.t the volume of a standard (MNI152) brain, and so is corrected 

603 at runtime to correspond to the volume of the user-supplied mask 

604 (which is typically smaller). If '100' is given, all the features 

605 are used, regardless of the number of voxels. 

606 

607 standardize : :obj:`bool`, default=True 

608 If set, then the data (X, y) are centered to have mean zero along 

609 axis 0. This is here because nearly all linear models will want 

610 their data to be centered. 

611 

612 fit_intercept : :obj:`bool`, default=True 

613 Fit or not an intercept. 

614 

615 max_iter : :obj:`int`, default=200 

616 Defines the iterations for the solver. 

617 

618 tol : :obj:`float`, default=5e-4 

619 Defines the tolerance for convergence for the backend FISTA solver. 

620 %(verbose)s 

621 %(n_jobs)s 

622 %(memory)s 

623 %(memory_level1)s 

624 cv : :obj:`int`, a cv generator instance, or None, default=8 

625 The input specifying which cross-validation generator to use. 

626 It can be an integer, in which case it is the number of folds in a 

627 KFold, None, in which case 3 fold is used, or another object, that 

628 will then be used as a cv generator. 

629 

630 debias : :obj:`bool`, default=False 

631 If set, then the estimated weights maps will be debiased. 

632 

633 positive : bool, default=False 

634 When set to ``True``, forces the coefficients to be positive. 

635 This option is only supported for dense arrays. 

636 

637 .. versionadded:: 0.11.2dev 

638 

639 Attributes 

640 ---------- 

641 all_coef_ : ndarray, shape (n_l1_ratios, n_folds, n_features) 

642 Coefficients for all folds and features. 

643 

644 alpha_grids_ : ndarray, shape (n_folds, n_alphas) 

645 Alpha values considered for selection of the best ones 

646 (saved in `best_model_params_`) 

647 

648 best_model_params_ : ndarray, shape (n_folds, n_parameter) 

649 Best model parameters (alpha, l1_ratio) saved for the different 

650 cross-validation folds. 

651 

652 classes_ : ndarray of labels (`n_classes_`) 

653 Labels of the classes (for classification problems) 

654 

655 n_classes_ : int 

656 Number of classes (for classification problems) 

657 

658 coef_ : ndarray, shape\ 

659 (1, n_features) for 2 class classification problems\ 

660 (i.e n_classes = 2)\ 

661 (n_classes, n_features) for n_classes > 2 

662 Coefficient of the features in the decision function. 

663 

664 coef_img_ : nifti image 

665 Masked model coefficients 

666 

667 mask_ : ndarray 3D 

668 An array contains values of the mask image. 

669 

670 masker_ : instance of NiftiMasker 

671 The nifti masker used to mask the data. 

672 

673 mask_img_ : Nifti like image 

674 The mask of the data. If no mask was supplied by the user, 

675 this attribute is the mask image computed automatically from the 

676 data `X`. 

677 

678 memory_ : joblib memory cache 

679 

680 intercept_ : narray, shape 

681 (1,) for 2 class classification problems (i.e n_classes = 2) 

682 (n_classes,) for n_classes > 2 

683 Intercept (a.k.a. bias) added to the decision function. 

684 It is available only when parameter intercept is set to True. 

685 

686 cv_ : list of pairs of lists 

687 Each pair is the list of indices for the train and test samples 

688 for the corresponding fold. 

689 

690 cv_scores_ : ndarray, shape (n_folds, n_alphas)\ 

691 or (n_l1_ratios, n_folds, n_alphas) 

692 Scores (misclassification) for each alpha, and on each fold 

693 

694 screening_percentile_ : float 

695 Screening percentile corrected according to volume of mask, 

696 relative to the volume of standard brain. 

697 

698 w_ : ndarray, shape 

699 (1, n_features + 1) for 2 class classification problems 

700 (i.e n_classes = 2) 

701 (n_classes, n_features + 1) for n_classes > 2, and (n_features,) 

702 for regression 

703 Model weights 

704 

705 ymean_ : array, shape (n_samples,) 

706 Mean of prediction targets 

707 

708 Xmean_ : array, shape (n_features,) 

709 Mean of X across samples 

710 

711 Xstd_ : array, shape (n_features,) 

712 Standard deviation of X across samples 

713 """ 

714 

715 SUPPORTED_PENALTIES: ClassVar[tuple[str, ...]] = ("graph-net", "tv-l1") 

716 SUPPORTED_LOSSES: ClassVar[tuple[str, ...]] = ("mse", "logistic") 

717 

718 def __init__( 

719 self, 

720 penalty="graph-net", 

721 is_classif=False, 

722 loss=None, 

723 l1_ratios=0.5, 

724 alphas=None, 

725 n_alphas=10, 

726 mask=None, 

727 target_affine=None, 

728 target_shape=None, 

729 low_pass=None, 

730 high_pass=None, 

731 t_r=None, 

732 max_iter=200, 

733 tol=5e-4, 

734 memory=None, 

735 memory_level=1, 

736 standardize=True, 

737 verbose=1, 

738 mask_args=None, 

739 n_jobs=1, 

740 eps=1e-3, 

741 cv=8, 

742 fit_intercept=True, 

743 screening_percentile=20.0, 

744 debias=False, 

745 positive=False, 

746 ): 

747 self.penalty = penalty 

748 self.is_classif = is_classif 

749 self.loss = loss 

750 self.n_alphas = n_alphas 

751 self.eps = eps 

752 self.l1_ratios = l1_ratios 

753 self.alphas = alphas 

754 self.mask = mask 

755 self.fit_intercept = fit_intercept 

756 self.memory = memory 

757 self.memory_level = memory_level 

758 self.max_iter = max_iter 

759 self.tol = tol 

760 self.verbose = verbose 

761 self.standardize = standardize 

762 self.n_jobs = n_jobs 

763 self.cv = cv 

764 self.screening_percentile = screening_percentile 

765 self.debias = debias 

766 self.low_pass = low_pass 

767 self.high_pass = high_pass 

768 self.t_r = t_r 

769 self.target_affine = target_affine 

770 self.target_shape = target_shape 

771 self.mask_args = mask_args 

772 self.positive = positive 

773 

774 def _check_params(self): 

775 """Make sure parameters are sane.""" 

776 if self.l1_ratios is not None: 

777 l1_ratios = self.l1_ratios 

778 if not isinstance(l1_ratios, collections.abc.Iterable): 

779 l1_ratios = [l1_ratios] 

780 for l1_ratio in l1_ratios: 

781 if not 0 <= l1_ratio <= 1.0: 

782 raise ValueError( 

783 "l1_ratio must be in the interval [0, 1]; " 

784 f" got {l1_ratio:g}" 

785 ) 

786 elif l1_ratio in (0.0, 1.0): 

787 warnings.warn( 

788 f"Specified l1_ratio = {l1_ratio:g}. " 

789 "It's advised to only specify values of l1_ratio " 

790 "strictly between 0 and 1.", 

791 stacklevel=find_stack_level(), 

792 ) 

793 if not (0.0 <= self.screening_percentile <= 100.0): 

794 raise ValueError( 

795 "screening_percentile should be in the interval [0, 100]. " 

796 f"Got {self.screening_percentile:g}." 

797 ) 

798 if self.penalty not in self.SUPPORTED_PENALTIES: 

799 raise ValueError( 

800 "'penalty' parameter must be one of " 

801 f"{self.SUPPORTED_PENALTIES}. " 

802 f"Got {self.penalty}." 

803 ) 

804 if self.loss is not None and self.loss not in self.SUPPORTED_LOSSES: 

805 raise ValueError( 

806 f"'loss' parameter must be one of {self.SUPPORTED_LOSSES}. " 

807 f"Got {self.loss}." 

808 ) 

809 if ( 

810 self.loss is not None 

811 and not self.is_classif 

812 and (self.loss == "logistic") 

813 ): 

814 raise ValueError( 

815 "'logistic' loss is only available for classification " 

816 "problems." 

817 ) 

818 

819 def _set_coef_and_intercept(self, w): 

820 """Set the loadings vector (coef) and the intercept of the fitted \ 

821 model. 

822 """ 

823 self.w_ = np.array(w) 

824 if self.w_.ndim == 1: 

825 self.w_ = self.w_[np.newaxis, :] 

826 self.coef_ = self.w_[:, :-1] 

827 if self.is_classif: 

828 self.intercept_ = self.w_[:, -1] 

829 else: 

830 self._set_intercept(self.Xmean_, self.ymean_, self.Xstd_) 

831 

832 def fit(self, X, y): 

833 """Fit the learner. 

834 

835 Parameters 

836 ---------- 

837 X : :obj:`list` of Niimg-like objects 

838 See :ref:`extracting_data`. 

839 Data on which model is to be fitted. If this is a list, 

840 the affine is considered the same for all. 

841 

842 y : array or :obj:`list` of length n_samples 

843 The dependent variable (age, sex, QI, etc.). 

844 

845 Notes 

846 ----- 

847 self : `SpaceNet` object 

848 Model selection is via cross-validation with bagging. 

849 """ 

850 check_params(self.__dict__) 

851 # sanity check on params 

852 self._check_params() 

853 if isinstance(X, SurfaceImage) or isinstance(self.mask, SurfaceMasker): 

854 raise NotImplementedError( 

855 "Running space net on surface objects is not supported." 

856 ) 

857 

858 # misc 

859 self._check_params() 

860 if self.memory is None or isinstance(self.memory, str): 

861 self.memory_ = Memory( 

862 self.memory, verbose=max(0, self.verbose - 1) 

863 ) 

864 else: 

865 self.memory_ = self.memory 

866 

867 tic = time.time() 

868 

869 self.masker_ = check_embedded_masker(self, masker_type="nii") 

870 X = self.masker_.fit_transform(X) 

871 

872 X, y = check_X_y( 

873 X, 

874 y, 

875 ["csr", "csc", "coo"], 

876 dtype=float, 

877 multi_output=True, 

878 y_numeric=not self.is_classif, 

879 ) 

880 

881 if not self.is_classif and np.all(np.diff(y) == 0.0): 

882 raise ValueError( 

883 "The given input y must have at least 2 targets" 

884 " to do regression analysis. You provided only" 

885 f" one target {np.unique(y)}" 

886 ) 

887 

888 # misc 

889 self.Xmean_ = X.mean(axis=0) 

890 self.Xstd_ = X.std(axis=0) 

891 self.Xstd_[self.Xstd_ < 1e-8] = 1 

892 self.mask_img_ = self.masker_.mask_img_ 

893 self.mask_ = get_data(self.mask_img_).astype(bool) 

894 n_samples, _ = X.shape 

895 y = np.array(y).copy() 

896 l1_ratios = self.l1_ratios 

897 if not isinstance(l1_ratios, collections.abc.Iterable): 

898 l1_ratios = [l1_ratios] 

899 alphas = self.alphas 

900 if alphas is not None and not isinstance( 

901 alphas, collections.abc.Iterable 

902 ): 

903 alphas = [alphas] 

904 if self.loss is not None: 

905 loss = self.loss 

906 elif self.is_classif: 

907 loss = "logistic" 

908 else: 

909 loss = "mse" 

910 

911 # set backend solver 

912 if self.penalty.lower() == "graph-net": 

913 if not self.is_classif or loss == "mse": 

914 solver = graph_net_squared_loss 

915 else: 

916 solver = graph_net_logistic 

917 elif not self.is_classif or loss == "mse": 

918 solver = partial(tvl1_solver, loss="mse") 

919 else: 

920 solver = partial(tvl1_solver, loss="logistic") 

921 

922 # generate fold indices 

923 case1 = (None in [alphas, l1_ratios]) and self.n_alphas > 1 

924 case2 = (alphas is not None) and min(len(l1_ratios), len(alphas)) > 1 

925 if case1 or case2: 

926 self.cv_ = list( 

927 check_cv(self.cv, y=y, classifier=self.is_classif).split(X, y) 

928 ) 

929 else: 

930 # no cross-validation needed, user supplied all params 

931 self.cv_ = [(np.arange(n_samples), [])] 

932 n_folds = len(self.cv_) 

933 

934 # number of problems to solve 

935 y = self._binarize_y(y) if self.is_classif else y[:, np.newaxis] 

936 

937 n_problems = ( 

938 self.n_classes_ if self.is_classif and self.n_classes_ > 2 else 1 

939 ) 

940 

941 # standardize y 

942 self.ymean_ = np.zeros(y.shape[0]) 

943 if n_problems == 1: 

944 y = y[:, 0] 

945 

946 # scores & mean weights map over all folds 

947 self.cv_scores_ = [[] for _ in range(n_problems)] 

948 w = np.zeros((n_problems, X.shape[1] + 1)) 

949 self.all_coef_ = np.ndarray((n_problems, n_folds, X.shape[1])) 

950 

951 self.screening_percentile_ = adjust_screening_percentile( 

952 self.screening_percentile, self.mask_img_, verbose=self.verbose 

953 ) 

954 

955 # main loop: loop on classes and folds 

956 solver_params = {"tol": self.tol, "max_iter": self.max_iter} 

957 self.best_model_params_ = [] 

958 self.alpha_grids_ = [] 

959 for ( 

960 test_scores, 

961 best_w, 

962 best_alpha, 

963 best_l1_ratio, 

964 alphas, 

965 y_train_mean, 

966 (cls, fold), 

967 ) in Parallel(n_jobs=self.n_jobs, verbose=2 * self.verbose)( 

968 delayed(self._cache(path_scores, func_memory_level=2))( 

969 solver, 

970 X, 

971 y[:, cls] if n_problems > 1 else y, 

972 self.mask_, 

973 alphas, 

974 l1_ratios, 

975 self.cv_[fold][0], 

976 self.cv_[fold][1], 

977 solver_params, 

978 n_alphas=self.n_alphas, 

979 eps=self.eps, 

980 is_classif=self.loss == "logistic", 

981 key=(cls, fold), 

982 debias=self.debias, 

983 verbose=self.verbose, 

984 screening_percentile=self.screening_percentile_, 

985 ) 

986 for cls in range(n_problems) 

987 for fold in range(n_folds) 

988 ): 

989 self.best_model_params_.append((best_alpha, best_l1_ratio)) 

990 self.alpha_grids_.append(alphas) 

991 self.ymean_[cls] += y_train_mean 

992 self.all_coef_[cls, fold] = best_w[:-1] 

993 if len(np.atleast_1d(l1_ratios)) == 1: 

994 test_scores = test_scores[0] 

995 self.cv_scores_[cls].append(test_scores) 

996 w[cls] += best_w 

997 

998 # misc 

999 self.cv_scores_ = np.array(self.cv_scores_) 

1000 self.best_model_params_ = np.array(self.best_model_params_) 

1001 self.alpha_grids_ = np.array(self.alpha_grids_) 

1002 self.ymean_ /= n_folds 

1003 if not self.is_classif: 

1004 self.all_coef_ = np.array(self.all_coef_) 

1005 w = w[0] 

1006 self.ymean_ = self.ymean_[0] 

1007 

1008 # bagging: average best weights maps over folds 

1009 w /= n_folds 

1010 

1011 # set coefs and intercepts 

1012 self._set_coef_and_intercept(w) 

1013 

1014 # unmask weights map as a niimg 

1015 self.coef_img_ = self.masker_.inverse_transform(self.coef_) 

1016 

1017 # report time elapsed 

1018 duration = time.time() - tic 

1019 logger.log( 

1020 f"Time Elapsed: {duration} seconds, {duration / 60.0} minutes.", 

1021 self.verbose, 

1022 ) 

1023 

1024 return self 

1025 

1026 def __sklearn_is_fitted__(self): 

1027 return hasattr(self, "masker_") 

1028 

1029 def decision_function(self, X): 

1030 """Predict confidence scores for samples. 

1031 

1032 The confidence score for a sample is the signed distance of that 

1033 sample to the hyperplane. 

1034 

1035 Parameters 

1036 ---------- 

1037 X : {array-like, sparse matrix}, shape = (n_samples, n_features) 

1038 Samples. 

1039 

1040 Returns 

1041 ------- 

1042 array, shape=(n_samples,) if n_classes == 2 else (n_samples, n_classes) 

1043 Confidence scores per (sample, class) combination. In the binary 

1044 case, confidence score for `self.classes_[1]` where >0 means this 

1045 class would be predicted. 

1046 """ 

1047 # handle regression (least-squared loss) 

1048 if not self.is_classif: 

1049 raise ValueError("There is no decision_function in classification") 

1050 

1051 X = check_array(X) 

1052 n_features = self.coef_.shape[1] 

1053 if X.shape[1] != n_features: 

1054 raise ValueError( 

1055 f"X has {X.shape[1]} features per sample; " 

1056 f"expecting {n_features}." 

1057 ) 

1058 

1059 scores = ( 

1060 safe_sparse_dot(X, self.coef_.T, dense_output=True) 

1061 + self.intercept_ 

1062 ) 

1063 return scores.ravel() if scores.shape[1] == 1 else scores 

1064 

1065 def predict(self, X): 

1066 """Predict class labels for samples in X. 

1067 

1068 Parameters 

1069 ---------- 

1070 X : :obj:`list` of Niimg-like objects 

1071 See :ref:`extracting_data`. 

1072 Data on prediction is to be made. If this is a list, 

1073 the affine is considered the same for all. 

1074 

1075 Returns 

1076 ------- 

1077 y_pred : ndarray, shape (n_samples,) 

1078 Predicted class label per sample. 

1079 """ 

1080 # cast X into usual 2D array 

1081 check_is_fitted(self) 

1082 

1083 X = self.masker_.transform(X) 

1084 

1085 # handle regression (least-squared loss) 

1086 if not self.is_classif: 

1087 return LinearRegression.predict(self, X) 

1088 

1089 # prediction proper 

1090 scores = self.decision_function(X) 

1091 if len(scores.shape) == 1: 

1092 indices = (scores > 0).astype(int) 

1093 else: 

1094 indices = scores.argmax(axis=1) 

1095 return self.classes_[indices] 

1096 

1097 

1098@fill_doc 

1099class SpaceNetClassifier(BaseSpaceNet): 

1100 """Classification learners with sparsity and spatial priors. 

1101 

1102 `SpaceNetClassifier` implements Graph-Net and TV-L1 

1103 priors / penalties for classification problems. Thus, the penalty 

1104 is a sum an L1 term and a spatial term. The aim of such a hybrid prior 

1105 is to obtain weights maps which are structured (due to the spatial 

1106 prior) and sparse (enforced by L1 norm). 

1107 

1108 Parameters 

1109 ---------- 

1110 penalty : :obj:`str`, default='graph-net' 

1111 Penalty to used in the model. Can be 'graph-net' or 'tv-l1'. 

1112 

1113 loss : :obj:`str`, default="logistic" 

1114 Loss to be used in the classifier. Must be one of "mse", or "logistic". 

1115 

1116 l1_ratios : :obj:`float` or :obj:`list` of floats in the interval [0, 1]; \ 

1117 default=0.5 

1118 Constant that mixes L1 and spatial prior terms in penalization. 

1119 l1_ratios == 1 corresponds to pure LASSO. The larger the value of this 

1120 parameter, the sparser the estimated weights map. If list is provided, 

1121 then the best value will be selected by cross-validation. 

1122 

1123 alphas : :obj:`float` or :obj:`list` of floats, default=None 

1124 Choices for the constant that scales the overall regularization term. 

1125 This parameter is mutually exclusive with the `n_alphas` parameter. 

1126 If None or list of floats is provided, then the best value will be 

1127 selected by cross-validation. 

1128 

1129 n_alphas : :obj:`int`, default=10 

1130 Generate this number of alphas per regularization path. 

1131 This parameter is mutually exclusive with the `alphas` parameter. 

1132 

1133 eps : :obj:`float`, default=1e-3 

1134 Length of the path. For example, ``eps=1e-3`` means that 

1135 ``alpha_min / alpha_max = 1e-3``. 

1136 

1137 mask : filename, niimg, NiftiMasker instance, default=None 

1138 Mask to be used on data. If an instance of masker is passed, 

1139 then its mask will be used. If no mask is it will be computed 

1140 automatically by a MultiNiftiMasker with default parameters. 

1141 %(target_affine)s 

1142 %(target_shape)s 

1143 %(low_pass)s 

1144 %(high_pass)s 

1145 %(t_r)s 

1146 screening_percentile : :obj:`float` in the interval [0, 100]; \ 

1147 default=20 

1148 Percentile value for ANOVA univariate feature selection. 

1149 A value of 100 means 'keep all features'. 

1150 This percentile is expressed w.r.t the volume 

1151 of a standard (MNI152) brain, and so is corrected 

1152 at runtime by premultiplying it with the ratio of the volume 

1153 of the mask of the data and volume of a standard brain. 

1154 If '100' is given, all the features are used, 

1155 regardless of the number of voxels. 

1156 

1157 standardize : :obj:`bool`, default=True 

1158 If set, then we'll center the data (X, y) have mean zero along axis 0. 

1159 This is here because nearly all linear models will want their data 

1160 to be centered. 

1161 

1162 fit_intercept : :obj:`bool`, default=True 

1163 Fit or not an intercept. 

1164 

1165 max_iter : :obj:`int`, default=200 

1166 Defines the iterations for the solver. 

1167 

1168 tol : :obj:`float`, default=1e-4. 

1169 Defines the tolerance for convergence. 

1170 %(verbose)s 

1171 %(n_jobs)s 

1172 %(memory)s 

1173 %(memory_level1)s 

1174 cv : :obj:`int`, a cv generator instance, or None, default=8 

1175 The input specifying which cross-validation generator to use. 

1176 It can be an integer, in which case it is the number of folds in a 

1177 KFold, None, in which case 3 fold is used, or another object, that 

1178 will then be used as a cv generator. 

1179 

1180 debias : :obj:`bool`, default=False 

1181 If set, then the estimated weights maps will be debiased. 

1182 

1183 Attributes 

1184 ---------- 

1185 all_coef_ : ndarray, shape (n_l1_ratios, n_folds, n_features) 

1186 Coefficients for all folds and features. 

1187 

1188 alpha_grids_ : ndarray, shape (n_folds, n_alphas) 

1189 Alpha values considered for selection of the best ones 

1190 (saved in `best_model_params_`) 

1191 

1192 best_model_params_ : ndarray, shape (n_folds, n_parameter) 

1193 Best model parameters (alpha, l1_ratio) saved for the different 

1194 cross-validation folds. 

1195 

1196 classes_ : ndarray of labels (`n_classes_`) 

1197 Labels of the classes 

1198 

1199 n_classes_ : int 

1200 Number of classes 

1201 

1202 coef_ : ndarray, shape 

1203 (1, n_features) for 2 class classification problems (i.e n_classes = 2) 

1204 (n_classes, n_features) for n_classes > 2 

1205 Coefficient of the features in the decision function. 

1206 

1207 coef_img_ : nifti image 

1208 Masked model coefficients 

1209 

1210 mask_ : ndarray 3D 

1211 An array contains values of the mask image. 

1212 

1213 masker_ : instance of NiftiMasker 

1214 The nifti masker used to mask the data. 

1215 

1216 mask_img_ : Nifti like image 

1217 The mask of the data. If no mask was supplied by the user, 

1218 this attribute is the mask image computed automatically from the 

1219 data `X`. 

1220 

1221 memory_ : joblib memory cache 

1222 

1223 intercept_ : narray, shape 

1224 (1, ) for 2 class classification problems (i.e n_classes = 2) 

1225 (n_classes, ) for n_classes > 2 

1226 Intercept (a.k.a. bias) added to the decision function. 

1227 It is available only when parameter intercept is set to True. 

1228 

1229 cv_ : list of pairs of lists 

1230 Each pair is the list of indices for the train and test 

1231 samples for the corresponding fold. 

1232 

1233 cv_scores_ : ndarray, shape (n_folds, n_alphas)\ 

1234 or (n_l1_ratios, n_folds, n_alphas) 

1235 Scores (misclassification) for each alpha, and on each fold 

1236 

1237 screening_percentile_ : float 

1238 Screening percentile corrected according to volume of mask, 

1239 relative to the volume of standard brain. 

1240 

1241 w_ : ndarray, shape 

1242 (1, n_features + 1) for 2 class classification problems 

1243 (i.e n_classes = 2) 

1244 (n_classes, n_features + 1) for n_classes > 2 

1245 Model weights 

1246 

1247 ymean_ : array, shape (n_samples,) 

1248 Mean of prediction targets 

1249 

1250 Xmean_ : array, shape (n_features,) 

1251 Mean of X across samples 

1252 

1253 Xstd_ : array, shape (n_features,) 

1254 Standard deviation of X across samples 

1255 

1256 See Also 

1257 -------- 

1258 nilearn.decoding.SpaceNetRegressor: Graph-Net and TV-L1 priors/penalties 

1259 

1260 """ 

1261 

1262 def __init__( 

1263 self, 

1264 penalty="graph-net", 

1265 loss="logistic", 

1266 l1_ratios=0.5, 

1267 alphas=None, 

1268 n_alphas=10, 

1269 mask=None, 

1270 target_affine=None, 

1271 target_shape=None, 

1272 low_pass=None, 

1273 high_pass=None, 

1274 t_r=None, 

1275 max_iter=200, 

1276 tol=1e-4, 

1277 memory=None, 

1278 memory_level=1, 

1279 standardize=True, 

1280 verbose=1, 

1281 n_jobs=1, 

1282 eps=1e-3, 

1283 cv=8, 

1284 fit_intercept=True, 

1285 screening_percentile=20.0, 

1286 debias=False, 

1287 ): 

1288 if memory is None: 

1289 memory = Memory(location=None) 

1290 super().__init__( 

1291 penalty=penalty, 

1292 is_classif=True, 

1293 l1_ratios=l1_ratios, 

1294 alphas=alphas, 

1295 n_alphas=n_alphas, 

1296 target_shape=target_shape, 

1297 low_pass=low_pass, 

1298 high_pass=high_pass, 

1299 mask=mask, 

1300 t_r=t_r, 

1301 max_iter=max_iter, 

1302 tol=tol, 

1303 memory=memory, 

1304 memory_level=memory_level, 

1305 n_jobs=n_jobs, 

1306 eps=eps, 

1307 cv=cv, 

1308 debias=debias, 

1309 fit_intercept=fit_intercept, 

1310 standardize=standardize, 

1311 screening_percentile=screening_percentile, 

1312 loss=loss, 

1313 target_affine=target_affine, 

1314 verbose=verbose, 

1315 ) 

1316 

1317 def _binarize_y(self, y): 

1318 """Encode target classes as -1 and 1. 

1319 

1320 Helper function invoked just before fitting a classifier. 

1321 """ 

1322 y = np.array(y) 

1323 

1324 # encode target classes as -1 and 1 

1325 self._enc = LabelBinarizer(pos_label=1, neg_label=-1) 

1326 y = self._enc.fit_transform(y) 

1327 self.classes_ = self._enc.classes_ 

1328 self.n_classes_ = len(self.classes_) 

1329 return y 

1330 

1331 def score(self, X, y): 

1332 """Return the mean accuracy on the given test data and labels. 

1333 

1334 Parameters 

1335 ---------- 

1336 X : :obj:`list` of Niimg-like objects 

1337 See :ref:`extracting_data`. 

1338 Data on which model is to be fitted. If this is a list, 

1339 the affine is considered the same for all. 

1340 

1341 y : array or :obj:`list` of length n_samples. 

1342 Labels. 

1343 

1344 Returns 

1345 ------- 

1346 score : float 

1347 Mean accuracy of self.predict(X) w.r.t y. 

1348 """ 

1349 return accuracy_score(y, self.predict(X)) 

1350 

1351 

1352@fill_doc 

1353class SpaceNetRegressor(BaseSpaceNet): 

1354 """Regression learners with sparsity and spatial priors. 

1355 

1356 `SpaceNetRegressor` implements Graph-Net and TV-L1 priors / penalties 

1357 for regression problems. Thus, the penalty is a sum an L1 term and a 

1358 spatial term. The aim of such a hybrid prior is to obtain weights maps 

1359 which are structured (due to the spatial prior) and sparse (enforced 

1360 by L1 norm). 

1361 

1362 Parameters 

1363 ---------- 

1364 penalty : :obj:`str`, default='graph-net' 

1365 Penalty to used in the model. Can be 'graph-net' or 'tv-l1'. 

1366 

1367 l1_ratios : :obj:`float` or :obj:`list` of floats in the interval [0, 1]; \ 

1368 default=0.5 

1369 Constant that mixes L1 and spatial prior terms in penalization. 

1370 l1_ratios == 1 corresponds to pure LASSO. The larger the value of this 

1371 parameter, the sparser the estimated weights map. If list is provided, 

1372 then the best value will be selected by cross-validation. 

1373 

1374 alphas : :obj:`float` or :obj:`list` of floats or None, default=None 

1375 Choices for the constant that scales the overall regularization term. 

1376 This parameter is mutually exclusive with the `n_alphas` parameter. 

1377 If None or list of floats is provided, then the best value will be 

1378 selected by cross-validation. 

1379 

1380 n_alphas : :obj:`int`, default=10 

1381 Generate this number of alphas per regularization path. 

1382 This parameter is mutually exclusive with the `alphas` parameter. 

1383 

1384 eps : :obj:`float`, default=1e-3 

1385 Length of the path. For example, ``eps=1e-3`` means that 

1386 ``alpha_min / alpha_max = 1e-3`` 

1387 

1388 mask : filename, niimg, NiftiMasker instance, default=None 

1389 Mask to be used on data. If an instance of masker is passed, 

1390 then its mask will be used. If no mask is it will be computed 

1391 automatically by a MultiNiftiMasker with default parameters. 

1392 %(target_affine)s 

1393 %(target_shape)s 

1394 %(low_pass)s 

1395 %(high_pass)s 

1396 %(t_r)s 

1397 screening_percentile : :obj:`float` in the interval [0, 100]; \ 

1398 default=20 

1399 Percentile value for ANOVA univariate feature selection. 

1400 A value of 100 means 'keep all features'. 

1401 This percentile is expressed w.r.t the volume 

1402 of a standard (MNI152) brain, and so is corrected 

1403 at runtime to correspond to the volume of the user-supplied mask 

1404 (which is typically smaller). 

1405 

1406 standardize : :obj:`bool`, default=True 

1407 If set, then we'll center the data (X, y) have mean zero along axis 0. 

1408 This is here because nearly all linear models will want their data 

1409 to be centered. 

1410 

1411 fit_intercept : :obj:`bool`, default=True 

1412 Fit or not an intercept. 

1413 

1414 max_iter : :obj:`int`, default=200 

1415 Defines the iterations for the solver. 

1416 

1417 tol : :obj:`float`, default=1e-4 

1418 Defines the tolerance for convergence. 

1419 %(verbose)s 

1420 %(n_jobs)s 

1421 %(memory)s 

1422 %(memory_level1)s 

1423 cv : :obj:`int`, a cv generator instance, or None, default=8 

1424 The input specifying which cross-validation generator to use. 

1425 It can be an integer, in which case it is the number of folds in a 

1426 KFold, None, in which case 3 fold is used, or another object, that 

1427 will then be used as a cv generator. 

1428 

1429 debias : :obj:`bool`, default=False 

1430 If set, then the estimated weights maps will be debiased. 

1431 

1432 Attributes 

1433 ---------- 

1434 all_coef_ : ndarray, shape (n_l1_ratios, n_folds, n_features) 

1435 Coefficients for all folds and features. 

1436 

1437 alpha_grids_ : ndarray, shape (n_folds, n_alphas) 

1438 Alpha values considered for selection of the best ones 

1439 (saved in `best_model_params_`) 

1440 

1441 best_model_params_ : ndarray, shape (n_folds, n_parameter) 

1442 Best model parameters (alpha, l1_ratio) saved for the different 

1443 cross-validation folds. 

1444 

1445 coef_ : ndarray, shape (n_features,) 

1446 Coefficient of the features in the decision function. 

1447 

1448 coef_img_ : nifti image 

1449 Masked model coefficients 

1450 

1451 mask_ : ndarray 3D 

1452 An array contains values of the mask image. 

1453 

1454 masker_ : instance of NiftiMasker 

1455 The nifti masker used to mask the data. 

1456 

1457 mask_img_ : Nifti like image 

1458 The mask of the data. If no mask was supplied by the user, this 

1459 attribute is the mask image computed automatically from the data `X`. 

1460 

1461 memory_ : joblib memory cache 

1462 

1463 intercept_ : narray, shape (1) 

1464 Intercept (a.k.a. bias) added to the decision function. 

1465 It is available only when parameter intercept is set to True. 

1466 

1467 cv_ : list of pairs of lists 

1468 Each pair is the list of indices for the train and test 

1469 samples for the corresponding fold. 

1470 

1471 cv_scores_ : ndarray, shape (n_folds, n_alphas)\ 

1472 or (n_l1_ratios, n_folds, n_alphas) 

1473 Scores (misclassification) for each alpha, and on each fold 

1474 

1475 screening_percentile_ : :obj:`float` 

1476 Screening percentile corrected according to volume of mask, 

1477 relative to the volume of standard brain. 

1478 

1479 w_ : ndarray, shape (n_features,) 

1480 Model weights 

1481 

1482 ymean_ : array, shape (n_samples,) 

1483 Mean of prediction targets 

1484 

1485 Xmean_ : array, shape (n_features,) 

1486 Mean of X across samples 

1487 

1488 Xstd_ : array, shape (n_features,) 

1489 Standard deviation of X across samples 

1490 

1491 See Also 

1492 -------- 

1493 nilearn.decoding.SpaceNetClassifier: Graph-Net and TV-L1 priors/penalties 

1494 

1495 """ 

1496 

1497 def __init__( 

1498 self, 

1499 penalty="graph-net", 

1500 l1_ratios=0.5, 

1501 alphas=None, 

1502 n_alphas=10, 

1503 mask=None, 

1504 target_affine=None, 

1505 target_shape=None, 

1506 low_pass=None, 

1507 high_pass=None, 

1508 t_r=None, 

1509 max_iter=200, 

1510 tol=1e-4, 

1511 memory=None, 

1512 memory_level=1, 

1513 standardize=True, 

1514 verbose=1, 

1515 n_jobs=1, 

1516 eps=1e-3, 

1517 cv=8, 

1518 fit_intercept=True, 

1519 screening_percentile=20.0, 

1520 debias=False, 

1521 ): 

1522 if memory is None: 

1523 memory = Memory(location=None) 

1524 super().__init__( 

1525 penalty=penalty, 

1526 is_classif=False, 

1527 l1_ratios=l1_ratios, 

1528 alphas=alphas, 

1529 n_alphas=n_alphas, 

1530 target_shape=target_shape, 

1531 low_pass=low_pass, 

1532 high_pass=high_pass, 

1533 mask=mask, 

1534 t_r=t_r, 

1535 max_iter=max_iter, 

1536 tol=tol, 

1537 memory=memory, 

1538 memory_level=memory_level, 

1539 n_jobs=n_jobs, 

1540 eps=eps, 

1541 cv=cv, 

1542 debias=debias, 

1543 fit_intercept=fit_intercept, 

1544 standardize=standardize, 

1545 screening_percentile=screening_percentile, 

1546 target_affine=target_affine, 

1547 verbose=verbose, 

1548 )