Source code for hetero.fitting
"""
This module defines the linear readout used to decode the target functions from the networks' state. This module is based on scikit-learn's linear model module and can be extended to other (possibly non-linear) regression methods as needed.
"""
import os
osjoin = os.path.join
from pdb import set_trace
import numpy as np
from sklearn import linear_model
from sklearn.metrics import confusion_matrix, r2_score
from hetero.utils import timer
[docs]
class Decoder(object):
"""
A decoder class for fitting and predicting using various regression algorithms.
Parameters
----------
optim_params : dict
Dictionary containing optimization parameters:
- 'alg': str, regression algorithm ('ridge', 'lasso', 'linreg', 'logreg')
- 'fit_bias': bool, whether to include bias term
- 'reg': float, regularization parameter (for ridge and lasso)
ncpu : int, optional
Number of CPU cores to use, by default -1 (all cores)
.. warning:: The optimization algorithm must be compatible with the problem at hand.
No sanity checks are performed to assess that is the case or not.
"""
def __init__(self, optim_params, ncpu=-1, **kwargs):
self.alg = optim_params['alg']
self.fit_bias = optim_params['fit_bias']
self.alpha = optim_params['reg']
self.n_jobs = ncpu
self.score_fun = det_coeff
self.coef_ = None
# Standard ridge regression
if self.alg == 'ridge':
# if sag/saga solver is chosen we need to have
# a fixed random state
self.model = linear_model.Ridge(alpha=self.alpha, fit_intercept=self.fit_bias,
random_state=0 )
# Standard lasso regression
elif self.alg == 'lasso':
self.model = linear_model.Lasso(alpha=self.alpha, fit_intercept=self.fit_bias)
# Standard linear regression
elif self.alg == 'linreg':
self.model = linear_model.LinearRegression(fit_intercept=self.fit_bias,
n_jobs=self.n_jobs)
# Standard logistic regression
elif self.alg == 'logreg':
# if sag/saga solver is chosen we need to have
# a fixed random state
self.score_fun = conf_mat
self.model = linear_model.LogisticRegression(fit_intercept=self.fit_bias,
n_jobs=self.n_jobs, random_state=0)
# Removed in favor of Scikit-learn that handles the intercept much nicer.
## direct least squares (scipy)
# elif self.alg == 'direct':
# self.model = lstsq
else:
raise ValueError(f'Fitting algorithm {self.alg} is invalid.')
[docs]
def fit(self, X, y):
"""Fit the model using features X and target y."""
self.model.fit(X.T, y.T)
[docs]
def predict(self, X):
"""Predict using fitted model on features X."""
return self.model.predict(X.T).T
[docs]
def score(self, yhat, y):
"""Compute score between predictions yhat and true values y."""
return self.score_fun(yhat, y)
def __get__(self):
return self.model
@property
def coeff(self):
"""Get the model coefficients."""
return self.model.coef_
[docs]
def det_coeff(yhat, y, eps=1e-9):
"""Calculate the coefficient of determination (R-squared) score.
Handles both single and multi-dimensional targets, with built-in outlier handling.
For multi-dimensional data, computes R-squared separately for each dimension.
Relevant from regression problems.
Parameters
----------
yhat : array-like
Predicted values
y : array-like
True values
eps : float, optional
Small constant to avoid division by zero, default 1e-9
Returns
-------
float or array-like
R-squared score(s). Returns array for multi-dimensional targets.
"""
if y.ndim==2:
v = np.sum((y - y.mean(axis=1, keepdims=True))**2, axis=1) + eps
u = np.sum((y - yhat)**2, axis=1)
return 1 - u/v
else:
return r2_score(y, yhat)
[docs]
def conf_mat(yhat, y):
"""Compute confusion matrix between predicted and true labels. Relevant from classification
problems.
Parameters
----------
yhat : array-like
Predicted values
y : array-like
True values
Returns
-------
2d array
Confusion matrix
"""
return confusion_matrix(y, yhat)