civisanalytics / python-glmnet

A python port of the glmnet package for fitting generalized linear models via penalized maximum likelihood.
Other
262 stars 59 forks source link

ElasticNet.fit raises ValueError when not converging instead of just issuing a warning #74

Open mathurinm opened 2 years ago

mathurinm commented 2 years ago

Thank you for your package, and for making it available on conda.

If I set a max_iter which is too low, instead of getting a convergence warning as in sklearn behavior, it simply fails with an error. Can this be fixed easily? I'm trying to get the solution for a single lambda (and from what I understood, if I use a default apth, I have no guarantee that glmnet will go to the end of it, it may early stop, which I don't want). Reproduce with:

from celer.datasets import make_correlated_data
from sklearn.linear_model import ElasticNet
import glmnet
from numpy.linalg import norm
import numpy as np

np.random.seed(0)
X = np.random.randn(100, 200)
X = np.asfortranarray(X)
y = np.random.randn(100)
alpha_max = norm(X.T @ y, ord=np.inf) / len(y)

clf2 = glmnet.ElasticNet(alpha=1, lambda_path=[
                         alpha_max, alpha_max/100], standardize=False, fit_intercept=False, tol=1e-10, max_iter=1).fit(X, y)

output:

/home/mathurin/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/errors.py:66: RuntimeWarning: Model did not converge for smaller values of lambda, returning solution for the largest 3 values.
  warnings.warn("Model did not converge for smaller values of lambda, "
/home/mathurin/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/errors.py:66: RuntimeWarning: Model did not converge for smaller values of lambda, returning solution for the largest 3 values.
  warnings.warn("Model did not converge for smaller values of lambda, "
/home/mathurin/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/util.py:202: RuntimeWarning: lambda_path has a single value, this may be an intercept-only model.
  warnings.warn("lambda_path has a single value, this may be an "
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-1-97963ff96fcf> in <module>
     10 
     11 
---> 12 clf2 = glmnet.ElasticNet(alpha=1, lambda_path=[
     13                          alpha_max, alpha_max/100], standardize=False, fit_intercept=False, tol=1e-10, max_iter=1).fit(X, y)

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/linear.py in fit(self, X, y, sample_weight, relative_penalties, groups)
    236                 self._cv = GroupKFold(n_splits=self.n_splits)
    237 
--> 238             cv_scores = _score_lambda_path(self, X, y, groups,
    239                                            sample_weight,
    240                                            relative_penalties,

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/util.py in _score_lambda_path(est, X, y, groups, sample_weight, relative_penalties, scoring, n_jobs, verbose)
     64         warnings.simplefilter(action, UndefinedMetricWarning)
     65 
---> 66         scores = Parallel(n_jobs=n_jobs, verbose=verbose, backend='threading')(
     67             delayed(_fit_and_score)(est, scorer, X, y, sample_weight, relative_penalties,
     68                                     est.lambda_path_, train_idx, test_idx)

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in __call__(self, iterable)
   1041             # remaining jobs.
   1042             self._iterating = False
-> 1043             if self.dispatch_one_batch(iterator):
   1044                 self._iterating = self._original_iterator is not None
   1045 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in dispatch_one_batch(self, iterator)
    859                 return False
    860             else:
--> 861                 self._dispatch(tasks)
    862                 return True
    863 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in _dispatch(self, batch)
    777         with self._lock:
    778             job_idx = len(self._jobs)
--> 779             job = self._backend.apply_async(batch, callback=cb)
    780             # A job can complete so quickly than its callback is
    781             # called before we get here, causing self._jobs to

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/_parallel_backends.py in apply_async(self, func, callback)
    206     def apply_async(self, func, callback=None):
    207         """Schedule a func to be run"""
--> 208         result = ImmediateResult(func)
    209         if callback:
    210             callback(result)

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/_parallel_backends.py in __init__(self, batch)
    570         # Don't delay the application, to avoid keeping the input
    571         # arguments in memory
--> 572         self.results = batch()
    573 
    574     def get(self):

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in __call__(self)
    260         # change the default number of processes to -1
    261         with parallel_backend(self._backend, n_jobs=self._n_jobs):
--> 262             return [func(*args, **kwargs)
    263                     for func, args, kwargs in self.items]
    264 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in <listcomp>(.0)
    260         # change the default number of processes to -1
    261         with parallel_backend(self._backend, n_jobs=self._n_jobs):
--> 262             return [func(*args, **kwargs)
    263                     for func, args, kwargs in self.items]
    264 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/util.py in _fit_and_score(est, scorer, X, y, sample_weight, relative_penalties, score_lambda_path, train_inx, test_inx)
    117 
    118     lamb = np.clip(score_lambda_path, m.lambda_path_[-1], m.lambda_path_[0])
--> 119     return scorer(m, X[test_inx, :], y[test_inx], lamb=lamb)
    120 
    121 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/scorer.py in _passthrough_scorer(estimator, *args, **kwargs)
    187 def _passthrough_scorer(estimator, *args, **kwargs):
    188     """Function that wraps estimator.score"""
--> 189     return estimator.score(*args, **kwargs)
    190 
    191 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/linear.py in score(self, X, y, lamb)
    437 
    438         # pred will have shape (n_samples, n_lambda)
--> 439         pred = self.predict(X, lamb=lamb)
    440 
    441         # Reverse the args of the r2_score function from scikit-learn. The

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/linear.py in predict(self, X, lamb)
    414             Predicted response value for each sample given each value of lambda
    415         """
--> 416         return self.decision_function(X, lamb)
    417 
    418     def score(self, X, y, lamb=None):

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/linear.py in decision_function(self, X, lamb)
    392         # single value of lambda
    393         if lamb.shape[0] == 1:
--> 394             z = z.squeeze(axis=-1)
    395         return z
    396 

ValueError: cannot select an axis to squeeze out which has size not equal to one

ping @agramfort

mathurinm commented 2 years ago

Changing the seed in the above script to 1 gives me a different error:

ValueError                                Traceback (most recent call last)
ValueError: unexpected array size: new_size=1, got array with arr_size=0

The above exception was the direct cause of the following exception:

ValueError                                Traceback (most recent call last)
<ipython-input-4-49f76e8921b5> in <module>
     10 
     11 
---> 12 clf2 = glmnet.ElasticNet(alpha=1, lambda_path=[
     13                          alpha_max, alpha_max/100], standardize=False, fit_intercept=False, tol=1e-10, max_iter=1).fit(X, y)

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/linear.py in fit(self, X, y, sample_weight, relative_penalties, groups)
    236                 self._cv = GroupKFold(n_splits=self.n_splits)
    237 
--> 238             cv_scores = _score_lambda_path(self, X, y, groups,
    239                                            sample_weight,
    240                                            relative_penalties,

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/util.py in _score_lambda_path(est, X, y, groups, sample_weight, relative_penalties, scoring, n_jobs, verbose)
     64         warnings.simplefilter(action, UndefinedMetricWarning)
     65 
---> 66         scores = Parallel(n_jobs=n_jobs, verbose=verbose, backend='threading')(
     67             delayed(_fit_and_score)(est, scorer, X, y, sample_weight, relative_penalties,
     68                                     est.lambda_path_, train_idx, test_idx)

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in __call__(self, iterable)
   1041             # remaining jobs.
   1042             self._iterating = False
-> 1043             if self.dispatch_one_batch(iterator):
   1044                 self._iterating = self._original_iterator is not None
   1045 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in dispatch_one_batch(self, iterator)
    859                 return False
    860             else:
--> 861                 self._dispatch(tasks)
    862                 return True
    863 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in _dispatch(self, batch)
    777         with self._lock:
    778             job_idx = len(self._jobs)
--> 779             job = self._backend.apply_async(batch, callback=cb)
    780             # A job can complete so quickly than its callback is
    781             # called before we get here, causing self._jobs to

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/_parallel_backends.py in apply_async(self, func, callback)
    206     def apply_async(self, func, callback=None):
    207         """Schedule a func to be run"""
--> 208         result = ImmediateResult(func)
    209         if callback:
    210             callback(result)

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/_parallel_backends.py in __init__(self, batch)
    570         # Don't delay the application, to avoid keeping the input
    571         # arguments in memory
--> 572         self.results = batch()
    573 
    574     def get(self):

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in __call__(self)
    260         # change the default number of processes to -1
    261         with parallel_backend(self._backend, n_jobs=self._n_jobs):
--> 262             return [func(*args, **kwargs)
    263                     for func, args, kwargs in self.items]
    264 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in <listcomp>(.0)
    260         # change the default number of processes to -1
    261         with parallel_backend(self._backend, n_jobs=self._n_jobs):
--> 262             return [func(*args, **kwargs)
    263                     for func, args, kwargs in self.items]
    264 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/util.py in _fit_and_score(est, scorer, X, y, sample_weight, relative_penalties, score_lambda_path, train_inx, test_inx)
    114     """
    115     m = clone(est)
--> 116     m = m._fit(X[train_inx, :], y[train_inx], sample_weight[train_inx], relative_penalties)
    117 
    118     lamb = np.clip(score_lambda_path, m.lambda_path_[-1], m.lambda_path_[0])

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/linear.py in _fit(self, X, y, sample_weight, relative_penalties)
    372         ca = ca[:, :self.n_lambda_]
    373         nin = nin[:self.n_lambda_]
--> 374         self.coef_path_ = solns(_x.shape[1], ca, ia, nin)
    375 
    376         return self

ValueError: failed in converting 4th argument `nin' of _glmnet.solns to C/Fortran array