Merge pull request #2862 from MechCoder/LogCV
[MRG] Logistic Regression CV
This commit is contained in:
commit
384ff19efc
|
|
@ -639,6 +639,7 @@ From text
|
|||
linear_model.LassoLarsIC
|
||||
linear_model.LinearRegression
|
||||
linear_model.LogisticRegression
|
||||
linear_model.LogisticRegressionCV
|
||||
linear_model.MultiTaskLasso
|
||||
linear_model.MultiTaskElasticNet
|
||||
linear_model.MultiTaskLassoCV
|
||||
|
|
|
|||
|
|
@ -649,10 +649,10 @@ rather than regression. Logistic regression is also known in the literature as
|
|||
logit regression, maximum-entropy classification (MaxEnt)
|
||||
or the log-linear classifier. In this model, the probabilities describing the possible outcomes of a single trial are modeled using a `logistic function <http://en.wikipedia.org/wiki/Logistic_function>`_.
|
||||
|
||||
The implementation of logistic regression in scikit-learn can be accessed from
|
||||
class :class:`LogisticRegression`. This
|
||||
The implementation of logistic regression in scikit-learn can be accessed from
|
||||
class :class:`LogisticRegression`. This
|
||||
implementation can fit a multiclass (one-vs-rest) logistic regression with optional
|
||||
L2 or L1 regularization.
|
||||
L2 or L1 regularization.
|
||||
|
||||
As an optimization problem, binary class L2 penalized logistic regression minimizes
|
||||
the following cost function:
|
||||
|
|
@ -663,7 +663,14 @@ Similarly, L1 regularized logistic regression solves the following optimization
|
|||
|
||||
.. math:: \underset{w, c}{min\,} \|w\|_1 + C \sum_{i=1}^n \log(\exp(- y_i (X_i^T w + c)) + 1) .
|
||||
|
||||
L1 penalization yields sparse predicting weights.
|
||||
The solvers implemented in the class :class:`LogisticRegression`
|
||||
are "liblinear" (which is a wrapper around the C++ library,
|
||||
LIBLINEAR), "newton-cg" and "lbfgs".
|
||||
|
||||
The lbfgs and newton-cg solvers only support L2 penalization and are found
|
||||
to converge faster for some high dimensional data. L1 penalization yields
|
||||
sparse predicting weights.
|
||||
|
||||
For L1 penalization :func:`sklearn.svm.l1_min_c` allows to calculate
|
||||
the lower bound for C in order to get a non "null" (all feature weights to
|
||||
zero) model.
|
||||
|
|
@ -685,6 +692,12 @@ which is shipped with scikit-learn.
|
|||
thus be used to perform feature selection, as detailed in
|
||||
:ref:`l1_feature_selection`.
|
||||
|
||||
:class:`LogisticRegressionCV` implements Logistic Regression with
|
||||
builtin cross-validation to find out the optimal C parameter. In
|
||||
general the "newton-cg" and "lbfgs" solvers are found to be faster
|
||||
due to warm-starting. For the multiclass case, One-vs-All is used
|
||||
and an optimal C is obtained for each class.
|
||||
|
||||
|
||||
Stochastic Gradient Descent - SGD
|
||||
=================================
|
||||
|
|
|
|||
|
|
@ -370,8 +370,8 @@ function or **logistic** function:
|
|||
>>> logistic = linear_model.LogisticRegression(C=1e5)
|
||||
>>> logistic.fit(iris_X_train, iris_y_train)
|
||||
LogisticRegression(C=100000.0, class_weight=None, dual=False,
|
||||
fit_intercept=True, intercept_scaling=1, penalty='l2',
|
||||
random_state=None, tol=0.0001)
|
||||
fit_intercept=True, intercept_scaling=1, max_iter=100,
|
||||
penalty='l2', random_state=None, solver='liblinear', tol=0.0001)
|
||||
|
||||
This is known as :class:`LogisticRegression`.
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ New features
|
|||
- Add the :func:`metrics.label_ranking_average_precision_score` metrics. By
|
||||
`Arnaud Joly`_.
|
||||
|
||||
- Added :class:`linear_model.LogisticRegressionCV`. By
|
||||
`Manoj Kumar`_, `Fabian Pedregosa`_, `Gael Varoquaux`_
|
||||
and `Alexandre Gramfort`_.
|
||||
|
||||
Enhancements
|
||||
............
|
||||
|
|
@ -29,6 +32,9 @@ Enhancements
|
|||
- Add support for sample weights in scorer objects. Metrics with sample
|
||||
weight support will automatically benefit from it.
|
||||
|
||||
- Added ``newton-cg`` and `lbfgs` solver support in
|
||||
:class:`linear_model.LogisticRegression`. By `Manoj Kumar`_.
|
||||
|
||||
|
||||
Documentation improvements
|
||||
..........................
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ from .sgd_fast import Hinge, Log, ModifiedHuber, SquaredLoss, Huber
|
|||
from .stochastic_gradient import SGDClassifier, SGDRegressor
|
||||
from .ridge import (Ridge, RidgeCV, RidgeClassifier, RidgeClassifierCV,
|
||||
ridge_regression)
|
||||
from .logistic import LogisticRegression
|
||||
from .logistic import (LogisticRegression, LogisticRegressionCV,
|
||||
logistic_regression_path)
|
||||
from .omp import (orthogonal_mp, orthogonal_mp_gram, OrthogonalMatchingPursuit,
|
||||
OrthogonalMatchingPursuitCV)
|
||||
from .passive_aggressive import PassiveAggressiveClassifier
|
||||
|
|
@ -48,6 +49,7 @@ __all__ = ['ARDRegression',
|
|||
'LinearRegression',
|
||||
'Log',
|
||||
'LogisticRegression',
|
||||
'LogisticRegressionCV',
|
||||
'ModifiedHuber',
|
||||
'MultiTaskElasticNet',
|
||||
'MultiTaskElasticNetCV',
|
||||
|
|
@ -71,6 +73,7 @@ __all__ = ['ARDRegression',
|
|||
'lars_path',
|
||||
'lasso_path',
|
||||
'lasso_stability_path',
|
||||
'logistic_regression_path',
|
||||
'orthogonal_mp',
|
||||
'orthogonal_mp_gram',
|
||||
'ridge_regression',
|
||||
|
|
|
|||
|
|
@ -1,12 +1,565 @@
|
|||
# Authors: Fabian Pedregosa
|
||||
# Alexandre Gramfort
|
||||
# License: 3-clause BSD
|
||||
"""
|
||||
Logistic Regression
|
||||
"""
|
||||
|
||||
# Author: Gael Varoquaux <gael.varoquaux@normalesup.org>
|
||||
# Fabian Pedregosa <f@bianp.net>
|
||||
# Alexandre Gramfort <alexandre.gramfort@telecom-paristech.fr>
|
||||
# Manoj Kumar <manojkumarsivaraj334@gmail.com>
|
||||
|
||||
import numbers
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
from scipy import optimize, sparse
|
||||
|
||||
from .base import LinearClassifierMixin, SparseCoefMixin
|
||||
from .base import LinearClassifierMixin, SparseCoefMixin, BaseEstimator
|
||||
from ..feature_selection.from_model import _LearntSelectorMixin
|
||||
from ..preprocessing import LabelEncoder
|
||||
from ..svm.base import BaseLibLinear
|
||||
from ..utils import check_array, check_consistent_length, compute_class_weight
|
||||
from ..utils.extmath import log_logistic, safe_sparse_dot
|
||||
from ..utils.optimize import newton_cg
|
||||
from ..utils.validation import as_float_array, DataConversionWarning
|
||||
from ..utils.fixes import expit
|
||||
from ..externals.joblib import Parallel, delayed
|
||||
from ..cross_validation import _check_cv
|
||||
from ..externals import six
|
||||
from ..metrics import SCORERS
|
||||
|
||||
|
||||
# .. some helper functions for logistic_regression_path ..
|
||||
def _intercept_dot(w, X, y):
|
||||
"""Computes y * np.dot(X, w).
|
||||
|
||||
It takes into consideration if the intercept should be fit or not.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
w : ndarray, shape (n_features,) or (n_features + 1,)
|
||||
Coefficient vector.
|
||||
|
||||
X : {array-like, sparse matrix}, shape (n_samples, n_features)
|
||||
Training data.
|
||||
|
||||
y : ndarray, shape (n_samples,)
|
||||
Array of labels.
|
||||
"""
|
||||
c = 0.
|
||||
if w.size == X.shape[1] + 1:
|
||||
c = w[-1]
|
||||
w = w[:-1]
|
||||
|
||||
z = safe_sparse_dot(X, w) + c
|
||||
return w, c, y * z
|
||||
|
||||
|
||||
def _logistic_loss_and_grad(w, X, y, alpha, sample_weight=None):
|
||||
"""Computes the logistic loss and gradient.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
w : ndarray, shape (n_features,) or (n_features + 1,)
|
||||
Coefficient vector.
|
||||
|
||||
X : {array-like, sparse matrix}, shape (n_samples, n_features)
|
||||
Training data.
|
||||
|
||||
y : ndarray, shape (n_samples,)
|
||||
Array of labels.
|
||||
|
||||
alpha : float
|
||||
Regularization parameter. alpha is equal to 1 / C.
|
||||
|
||||
sample_weight : ndarray, shape (n_samples,) optional
|
||||
Array of weights that are assigned to individual samples.
|
||||
If not provided, then each sample is given unit weight.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : float
|
||||
Logistic loss.
|
||||
|
||||
grad : ndarray, shape (n_features,) or (n_features + 1,)
|
||||
Logistic gradient.
|
||||
"""
|
||||
_, n_features = X.shape
|
||||
grad = np.empty_like(w)
|
||||
|
||||
w, c, yz = _intercept_dot(w, X, y)
|
||||
|
||||
if sample_weight is None:
|
||||
sample_weight = np.ones(y.shape[0])
|
||||
|
||||
# Logistic loss is the negative of the log of the logistic function.
|
||||
out = -np.sum(sample_weight * log_logistic(yz)) + .5 * alpha * np.dot(w, w)
|
||||
|
||||
z = expit(yz)
|
||||
z0 = sample_weight * (z - 1) * y
|
||||
|
||||
grad[:n_features] = safe_sparse_dot(X.T, z0) + alpha * w
|
||||
|
||||
# Case where we fit the intercept.
|
||||
if grad.shape[0] > n_features:
|
||||
grad[-1] = z0.sum()
|
||||
return out, grad
|
||||
|
||||
|
||||
def _logistic_loss(w, X, y, alpha, sample_weight=None):
|
||||
"""Computes the logistic loss.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
w : ndarray, shape (n_features,) or (n_features + 1,)
|
||||
Coefficient vector.
|
||||
|
||||
X : {array-like, sparse matrix}, shape (n_samples, n_features)
|
||||
Training data.
|
||||
|
||||
y : ndarray, shape (n_samples,)
|
||||
Array of labels.
|
||||
|
||||
alpha : float
|
||||
Regularization parameter. alpha is equal to 1 / C.
|
||||
|
||||
sample_weight : ndarray, shape (n_samples,) optional
|
||||
Array of weights that are assigned to individual samples.
|
||||
If not provided, then each sample is given unit weight.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : float
|
||||
Logistic loss.
|
||||
"""
|
||||
w, c, yz = _intercept_dot(w, X, y)
|
||||
|
||||
if sample_weight is None:
|
||||
sample_weight = np.ones(y.shape[0])
|
||||
|
||||
# Logistic loss is the negative of the log of the logistic function.
|
||||
out = -np.sum(sample_weight * log_logistic(yz)) + .5 * alpha * np.dot(w, w)
|
||||
return out
|
||||
|
||||
|
||||
def _logistic_loss_grad_hess(w, X, y, alpha, sample_weight=None):
|
||||
"""Computes the logistic loss, gradient and the Hessian.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
w : ndarray, shape (n_features,) or (n_features + 1,)
|
||||
Coefficient vector.
|
||||
|
||||
X : {array-like, sparse matrix}, shape (n_samples, n_features)
|
||||
Training data.
|
||||
|
||||
y : ndarray, shape (n_samples,)
|
||||
Array of labels.
|
||||
|
||||
alpha : float
|
||||
Regularization parameter. alpha is equal to 1 / C.
|
||||
|
||||
sample_weight : ndarray, shape (n_samples,) optional
|
||||
Array of weights that are assigned to individual samples.
|
||||
If not provided, then each sample is given unit weight.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : float
|
||||
Logistic loss.
|
||||
|
||||
grad : ndarray, shape (n_features,) or (n_features + 1,)
|
||||
Logistic gradient.
|
||||
|
||||
Hs : callable
|
||||
Function that takes the gradient as a parameter and returns the
|
||||
matrix product of the Hessian and gradient.
|
||||
"""
|
||||
n_samples, n_features = X.shape
|
||||
grad = np.empty_like(w)
|
||||
fit_intercept = grad.shape[0] > n_features
|
||||
|
||||
w, c, yz = _intercept_dot(w, X, y)
|
||||
|
||||
if sample_weight is None:
|
||||
sample_weight = np.ones(y.shape[0])
|
||||
|
||||
# Logistic loss is the negative of the log of the logistic function.
|
||||
out = -np.sum(sample_weight * log_logistic(yz)) + .5 * alpha * np.dot(w, w)
|
||||
|
||||
z = expit(yz)
|
||||
z0 = sample_weight * (z - 1) * y
|
||||
|
||||
grad[:n_features] = safe_sparse_dot(X.T, z0) + alpha * w
|
||||
|
||||
# Case where we fit the intercept.
|
||||
if fit_intercept:
|
||||
grad[-1] = z0.sum()
|
||||
|
||||
# The mat-vec product of the Hessian
|
||||
d = sample_weight * z * (1 - z)
|
||||
if sparse.issparse(X):
|
||||
dX = safe_sparse_dot(sparse.dia_matrix((d, 0),
|
||||
shape=(n_samples, n_samples)), X)
|
||||
else:
|
||||
# Precompute as much as possible
|
||||
dX = d[:, np.newaxis] * X
|
||||
|
||||
if fit_intercept:
|
||||
# Calculate the double derivative with respect to intercept
|
||||
# In the case of sparse matrices this returns a matrix object.
|
||||
dd_intercept = np.squeeze(np.array(dX.sum(axis=0)))
|
||||
|
||||
def Hs(s):
|
||||
ret = np.empty_like(s)
|
||||
ret[:n_features] = X.T.dot(dX.dot(s[:n_features]))
|
||||
ret[:n_features] += alpha * s[:n_features]
|
||||
|
||||
# For the fit intercept case.
|
||||
if fit_intercept:
|
||||
ret[:n_features] += s[-1] * dd_intercept
|
||||
ret[-1] = dd_intercept.dot(s[:n_features])
|
||||
ret[-1] += d.sum() * s[-1]
|
||||
return ret
|
||||
|
||||
return out, grad, Hs
|
||||
|
||||
|
||||
def logistic_regression_path(X, y, pos_class=None, Cs=10, fit_intercept=True,
|
||||
max_iter=100, tol=1e-4, verbose=0,
|
||||
solver='lbfgs', coef=None, copy=True,
|
||||
class_weight=None, dual=False, penalty='l2',
|
||||
intercept_scaling=1.):
|
||||
"""Compute a Logistic Regression model for a list of regularization
|
||||
parameters.
|
||||
|
||||
This is an implementation that uses the result of the previous model
|
||||
to speed up computations along the set of solutions, making it faster
|
||||
than sequentially calling LogisticRegression for the different parameters.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
X : array-like or sparse matrix, shape (n_samples, n_features)
|
||||
Input data.
|
||||
|
||||
y : array-like, shape (n_samples,)
|
||||
Input data, target values.
|
||||
|
||||
Cs : int | array-like, shape (n_cs,)
|
||||
List of values for the regularization parameter or integer specifying
|
||||
the number of regularization parameters that should be used. In this
|
||||
case, the parameters will be chosen in a logarithmic scale between
|
||||
1e-4 and 1e4.
|
||||
|
||||
pos_class : int, None
|
||||
The class with respect to which we perform a one-vs-all fit.
|
||||
If None, then it is assumed that the given problem is binary.
|
||||
|
||||
fit_intercept : bool
|
||||
Whether to fit an intercept for the model. In this case the shape of
|
||||
the returned array is (n_cs, n_features + 1).
|
||||
|
||||
max_iter : int
|
||||
Maximum number of iterations for the solver.
|
||||
|
||||
tol : float
|
||||
Stopping criterion. For the newton-cg and lbfgs solvers, the iteration
|
||||
will stop when ``max{|g_i | i = 1, ..., n} <= tol``
|
||||
where ``g_i`` is the i-th component of the gradient.
|
||||
|
||||
verbose : int
|
||||
Print convergence message if True.
|
||||
|
||||
solver : {'lbfgs', 'newton-cg', 'liblinear'}
|
||||
Numerical solver to use.
|
||||
|
||||
coef : array-like, shape (n_features,), default None
|
||||
Initialization value for coefficients of logistic regression.
|
||||
|
||||
copy : bool, default True
|
||||
Whether or not to produce a copy of the data. Setting this to
|
||||
True will be useful in cases, when logistic_regression_path
|
||||
is called repeatedly with the same data, as y is modified
|
||||
along the path.
|
||||
|
||||
class_weight : {dict, 'auto'}, optional
|
||||
Over-/undersamples the samples of each class according to the given
|
||||
weights. If not given, all classes are supposed to have weight one.
|
||||
The 'auto' mode selects weights inversely proportional to class
|
||||
frequencies in the training set.
|
||||
|
||||
dual : bool
|
||||
Dual or primal formulation. Dual formulation is only implemented for
|
||||
l2 penalty with liblinear solver. Prefer dual=False when
|
||||
n_samples > n_features.
|
||||
|
||||
penalty : str, 'l1' or 'l2'
|
||||
Used to specify the norm used in the penalization. The newton-cg and
|
||||
lbfgs solvers support only l2 penalties.
|
||||
|
||||
intercept_scaling : float, default 1.
|
||||
This parameter is useful only when the solver 'liblinear' is used
|
||||
and self.fit_intercept is set to True. In this case, x becomes
|
||||
[x, self.intercept_scaling],
|
||||
i.e. a "synthetic" feature with constant value equals to
|
||||
intercept_scaling is appended to the instance vector.
|
||||
The intercept becomes intercept_scaling * synthetic feature weight
|
||||
Note! the synthetic feature weight is subject to l1/l2 regularization
|
||||
as all other features.
|
||||
To lessen the effect of regularization on synthetic feature weight
|
||||
(and therefore on the intercept) intercept_scaling has to be increased.
|
||||
|
||||
Returns
|
||||
-------
|
||||
coefs : ndarray, shape (n_cs, n_features) or (n_cs, n_features + 1)
|
||||
List of coefficients for the Logistic Regression model. If
|
||||
fit_intercept is set to True then the second dimension will be
|
||||
n_features + 1, where the last item represents the intercept.
|
||||
|
||||
Cs : ndarray
|
||||
Grid of Cs used for cross-validation.
|
||||
|
||||
Notes
|
||||
-----
|
||||
You might get slighly different results with the solver liblinear than
|
||||
with the others since this uses LIBLINEAR which penalizes the intercept.
|
||||
"""
|
||||
if isinstance(Cs, numbers.Integral):
|
||||
Cs = np.logspace(-4, 4, Cs)
|
||||
|
||||
X = check_array(X, accept_sparse='csc', dtype=np.float64)
|
||||
y = check_array(y, ensure_2d=False, copy=copy)
|
||||
check_consistent_length(X, y)
|
||||
n_classes = np.unique(y)
|
||||
|
||||
if pos_class is None:
|
||||
if (n_classes.size > 2):
|
||||
raise ValueError('To fit OvA, use the pos_class argument')
|
||||
# np.unique(y) gives labels in sorted order.
|
||||
pos_class = n_classes[1]
|
||||
|
||||
# If class_weights is a dict (provided by the user), the weights
|
||||
# are assigned to the original labels. If it is "auto", then
|
||||
# the class_weights are assigned after masking the labels with a OvA.
|
||||
sample_weight = np.ones(X.shape[0])
|
||||
le = LabelEncoder()
|
||||
|
||||
if isinstance(class_weight, dict):
|
||||
if solver == "liblinear":
|
||||
if n_classes.size == 2:
|
||||
# Reconstruct the weights with keys 1 and -1
|
||||
temp = {}
|
||||
temp[1] = class_weight[pos_class]
|
||||
temp[-1] = class_weight[n_classes[0]]
|
||||
class_weight = temp.copy()
|
||||
else:
|
||||
raise ValueError("In LogisticRegressionCV the liblinear solver "
|
||||
"cannot handle multiclass with class_weight "
|
||||
"of type dict. Use the lbfgs, newton-cg "
|
||||
"solvers or set class_weight='auto'")
|
||||
else:
|
||||
class_weight_ = compute_class_weight(class_weight, n_classes, y)
|
||||
sample_weight = class_weight_[le.fit_transform(y)]
|
||||
|
||||
mask = (y == pos_class)
|
||||
y[mask] = 1
|
||||
y[~mask] = -1
|
||||
|
||||
# To take care of object dtypes
|
||||
y = as_float_array(y, copy=False)
|
||||
if class_weight == "auto":
|
||||
class_weight_ = compute_class_weight(class_weight, [-1, 1], y)
|
||||
sample_weight = class_weight_[le.fit_transform(y)]
|
||||
|
||||
if fit_intercept:
|
||||
w0 = np.zeros(X.shape[1] + 1)
|
||||
else:
|
||||
w0 = np.zeros(X.shape[1])
|
||||
|
||||
if coef is not None:
|
||||
# it must work both giving the bias term and not
|
||||
if not coef.size in (X.shape[1], w0.size):
|
||||
raise ValueError('Initialization coef is not of correct shape')
|
||||
w0[:coef.size] = coef
|
||||
coefs = list()
|
||||
|
||||
for C in Cs:
|
||||
if solver == 'lbfgs':
|
||||
func = _logistic_loss_and_grad
|
||||
try:
|
||||
out = optimize.fmin_l_bfgs_b(
|
||||
func, w0, fprime=None,
|
||||
args=(X, y, 1. / C, sample_weight),
|
||||
iprint=(verbose > 0) - 1, pgtol=tol, maxiter=max_iter)
|
||||
except TypeError:
|
||||
# old scipy doesn't have maxiter
|
||||
out = optimize.fmin_l_bfgs_b(
|
||||
func, w0, fprime=None,
|
||||
args=(X, y, 1. / C, sample_weight),
|
||||
iprint=(verbose > 0) - 1, pgtol=tol)
|
||||
w0 = out[0]
|
||||
if out[2]["warnflag"] == 1:
|
||||
warnings.warn("lbfgs failed to converge. Increase the number "
|
||||
"of iterations.")
|
||||
|
||||
elif solver == 'newton-cg':
|
||||
grad = lambda x, *args: _logistic_loss_and_grad(x, *args)[1]
|
||||
w0 = newton_cg(_logistic_loss_grad_hess, _logistic_loss, grad, w0,
|
||||
args=(X, y, 1. / C, sample_weight),
|
||||
maxiter=max_iter, tol=tol)
|
||||
elif solver == 'liblinear':
|
||||
lr = LogisticRegression(C=C, fit_intercept=fit_intercept, tol=tol,
|
||||
class_weight=class_weight, dual=dual,
|
||||
penalty=penalty,
|
||||
intercept_scaling=intercept_scaling)
|
||||
lr.fit(X, y)
|
||||
if fit_intercept:
|
||||
w0 = np.concatenate([lr.coef_.ravel(), lr.intercept_])
|
||||
else:
|
||||
w0 = lr.coef_.ravel()
|
||||
else:
|
||||
raise ValueError("solver must be one of {'liblinear', 'lbfgs', "
|
||||
"'newton-cg'}, got '%s' instead" % solver)
|
||||
coefs.append(w0)
|
||||
return coefs, np.array(Cs)
|
||||
|
||||
|
||||
# helper function for LogisticCV
|
||||
def _log_reg_scoring_path(X, y, train, test, pos_class=None, Cs=10,
|
||||
scoring=None, fit_intercept=False,
|
||||
max_iter=100, tol=1e-4, class_weight=None,
|
||||
verbose=0, solver='lbfgs', penalty='l2',
|
||||
dual=False, copy=True, intercept_scaling=1.):
|
||||
"""Computes scores across logistic_regression_path
|
||||
|
||||
Parameters
|
||||
----------
|
||||
X : {array-like, sparse matrix}, shape (n_samples, n_features)
|
||||
Training data.
|
||||
|
||||
y : array-like, shape (n_samples,) or (n_samples, n_targets)
|
||||
Target labels.
|
||||
|
||||
train : list of indices
|
||||
The indices of the train set.
|
||||
|
||||
test : list of indices
|
||||
The indices of the test set.
|
||||
|
||||
pos_class : int, None
|
||||
The class with respect to which we perform a one-vs-all fit.
|
||||
If None, then it is assumed that the given problem is binary.
|
||||
|
||||
Cs : list of floats | int
|
||||
Each of the values in Cs describes the inverse of
|
||||
regularization strength. If Cs is as an int, then a grid of Cs
|
||||
values are chosen in a logarithmic scale between 1e-4 and 1e4.
|
||||
If not provided, then a fixed set of values for Cs are used.
|
||||
|
||||
scoring : callable
|
||||
For a list of scoring functions that can be used, look at
|
||||
:mod:`sklearn.metrics`. The default scoring option used is
|
||||
accuracy_score.
|
||||
|
||||
fit_intercept : bool
|
||||
If False, then the bias term is set to zero. Else the last
|
||||
term of each coef_ gives us the intercept.
|
||||
|
||||
max_iter : int
|
||||
Maximum number of iterations for the solver.
|
||||
|
||||
tol : float
|
||||
Tolerance for stopping criteria.
|
||||
|
||||
class_weight : {dict, 'auto'}, optional
|
||||
Over-/undersamples the samples of each class according to the given
|
||||
weights. If not given, all classes are supposed to have weight one.
|
||||
The 'auto' mode selects weights inversely proportional to class
|
||||
frequencies in the training set.
|
||||
|
||||
verbose : int
|
||||
Amount of verbosity.
|
||||
|
||||
solver : {'lbfgs', 'newton-cg', 'liblinear'}
|
||||
Decides which solver to use.
|
||||
|
||||
penalty : str, 'l1' or 'l2'
|
||||
Used to specify the norm used in the penalization. The newton-cg and
|
||||
lbfgs solvers support only l2 penalties.
|
||||
|
||||
dual : bool
|
||||
Dual or primal formulation. Dual formulation is only implemented for
|
||||
l2 penalty with liblinear solver. Prefer dual=False when
|
||||
n_samples > n_features.
|
||||
|
||||
intercept_scaling : float, default 1.
|
||||
This parameter is useful only when the solver 'liblinear' is used
|
||||
and self.fit_intercept is set to True. In this case, x becomes
|
||||
[x, self.intercept_scaling],
|
||||
i.e. a "synthetic" feature with constant value equals to
|
||||
intercept_scaling is appended to the instance vector.
|
||||
The intercept becomes intercept_scaling * synthetic feature weight
|
||||
Note! the synthetic feature weight is subject to l1/l2 regularization
|
||||
as all other features.
|
||||
To lessen the effect of regularization on synthetic feature weight
|
||||
(and therefore on the intercept) intercept_scaling has to be increased.
|
||||
|
||||
Returns
|
||||
-------
|
||||
coefs : ndarray, shape (n_cs, n_features) or (n_cs, n_features + 1)
|
||||
List of coefficients for the Logistic Regression model. If
|
||||
fit_intercept is set to True then the second dimension will be
|
||||
n_features + 1, where the last item represents the intercept.
|
||||
|
||||
Cs : ndarray
|
||||
Grid of Cs used for cross-validation.
|
||||
|
||||
scores : ndarray, shape (n_cs,)
|
||||
Scores obtained for each Cs.
|
||||
"""
|
||||
|
||||
log_reg = LogisticRegression(fit_intercept=fit_intercept)
|
||||
log_reg._enc = LabelEncoder()
|
||||
log_reg._enc.fit_transform([-1, 1])
|
||||
|
||||
X_train = X[train]
|
||||
X_test = X[test]
|
||||
y_train = y[train]
|
||||
y_test = y[test]
|
||||
|
||||
if pos_class is not None:
|
||||
mask = (y_test == pos_class)
|
||||
y_test[mask] = 1
|
||||
y_test[~mask] = -1
|
||||
|
||||
# To deal with object dtypes, we need to convert into an array of floats.
|
||||
y_test = as_float_array(y_test, copy=False)
|
||||
|
||||
coefs, Cs = logistic_regression_path(X_train, y_train, Cs=Cs,
|
||||
fit_intercept=fit_intercept,
|
||||
solver=solver,
|
||||
max_iter=max_iter,
|
||||
class_weight=class_weight,
|
||||
copy=copy, pos_class=pos_class,
|
||||
tol=tol, verbose=verbose,
|
||||
dual=dual, penalty=penalty,
|
||||
intercept_scaling=intercept_scaling)
|
||||
|
||||
scores = list()
|
||||
|
||||
if isinstance(scoring, six.string_types):
|
||||
scoring = SCORERS[scoring]
|
||||
for w in coefs:
|
||||
if fit_intercept:
|
||||
log_reg.coef_ = w[np.newaxis, :-1]
|
||||
log_reg.intercept_ = w[-1]
|
||||
else:
|
||||
log_reg.coef_ = w[np.newaxis, :]
|
||||
log_reg.intercept_ = 0.
|
||||
if scoring is None:
|
||||
scores.append(log_reg.score(X_test, y_test))
|
||||
else:
|
||||
scores.append(scoring(log_reg, X_test, y_test))
|
||||
return coefs, Cs, np.array(scores)
|
||||
|
||||
|
||||
class LogisticRegression(BaseLibLinear, LinearClassifierMixin,
|
||||
|
|
@ -16,19 +569,25 @@ class LogisticRegression(BaseLibLinear, LinearClassifierMixin,
|
|||
In the multiclass case, the training algorithm uses a one-vs.-all (OvA)
|
||||
scheme, rather than the "true" multinomial LR.
|
||||
|
||||
This class implements L1 and L2 regularized logistic regression using the
|
||||
`liblinear` library. It can handle both dense and sparse input. Use
|
||||
C-ordered arrays or CSR matrices containing 64-bit floats for optimal
|
||||
performance; any other input format will be converted (and copied).
|
||||
This class implements regularized logistic regression using the
|
||||
`liblinear` library, newton-cg and lbfgs solvers. It can handle both
|
||||
dense and sparse input. Use C-ordered arrays or CSR matrices containing
|
||||
64-bit floats for optimal performance; any other input format will be
|
||||
converted (and copied).
|
||||
|
||||
The newton-cg and lbfgs solvers support only L2 regularization with primal
|
||||
formulation. The liblinear solver supports both L1 and L2 regularization,
|
||||
with a dual formulation only for the L2 penalty.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
penalty : string, 'l1' or 'l2'
|
||||
Used to specify the norm used in the penalization.
|
||||
penalty : str, 'l1' or 'l2'
|
||||
Used to specify the norm used in the penalization. The newton-cg and
|
||||
lbfgs solvers support only l2 penalties.
|
||||
|
||||
dual : boolean
|
||||
Dual or primal formulation. Dual formulation is only
|
||||
implemented for l2 penalty. Prefer dual=False when
|
||||
dual : bool
|
||||
Dual or primal formulation. Dual formulation is only implemented for
|
||||
l2 penalty with liblinear solver. Prefer dual=False when
|
||||
n_samples > n_features.
|
||||
|
||||
C : float, optional (default=1.0)
|
||||
|
|
@ -49,7 +608,7 @@ class LogisticRegression(BaseLibLinear, LinearClassifierMixin,
|
|||
Note! the synthetic feature weight is subject to l1/l2 regularization
|
||||
as all other features.
|
||||
To lessen the effect of regularization on synthetic feature weight
|
||||
(and therefore on the intercept) intercept_scaling has to be increased
|
||||
(and therefore on the intercept) intercept_scaling has to be increased.
|
||||
|
||||
class_weight : {dict, 'auto'}, optional
|
||||
Over-/undersamples the samples of each class according to the given
|
||||
|
|
@ -57,27 +616,34 @@ class LogisticRegression(BaseLibLinear, LinearClassifierMixin,
|
|||
The 'auto' mode selects weights inversely proportional to class
|
||||
frequencies in the training set.
|
||||
|
||||
random_state: int seed, RandomState instance, or None (default)
|
||||
max_iter : int
|
||||
Useful only for the newton-cg and lbfgs solvers. Maximum number of
|
||||
iterations taken for the solvers to converge.
|
||||
|
||||
random_state : int seed, RandomState instance, or None (default)
|
||||
The seed of the pseudo random number generator to use when
|
||||
shuffling the data.
|
||||
|
||||
tol: float, optional
|
||||
solver : {'newton-cg', 'lbfgs', 'liblinear'}
|
||||
Algorithm to use in the optimization problem.
|
||||
|
||||
tol : float, optional
|
||||
Tolerance for stopping criteria.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
`coef_` : array, shape = [n_classes, n_features]
|
||||
`coef_` : array, shape (n_classes, n_features)
|
||||
Coefficient of the features in the decision function.
|
||||
|
||||
`intercept_` : array, shape = [n_classes]
|
||||
`intercept_` : array, shape (n_classes,)
|
||||
Intercept (a.k.a. bias) added to the decision function.
|
||||
If `fit_intercept` is set to False, the intercept is set to zero.
|
||||
|
||||
See also
|
||||
--------
|
||||
SGDClassifier: incrementally trained logistic regression (when given
|
||||
SGDClassifier : incrementally trained logistic regression (when given
|
||||
the parameter ``loss="log"``).
|
||||
sklearn.svm.LinearSVC: learns SVM models using the same algorithm.
|
||||
sklearn.svm.LinearSVC : learns SVM models using the same algorithm.
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
|
@ -99,12 +665,13 @@ class LogisticRegression(BaseLibLinear, LinearClassifierMixin,
|
|||
|
||||
def __init__(self, penalty='l2', dual=False, tol=1e-4, C=1.0,
|
||||
fit_intercept=True, intercept_scaling=1, class_weight=None,
|
||||
random_state=None):
|
||||
random_state=None, solver='liblinear', max_iter=100):
|
||||
|
||||
super(LogisticRegression, self).__init__(
|
||||
penalty=penalty, dual=dual, loss='lr', tol=tol, C=C,
|
||||
fit_intercept=fit_intercept, intercept_scaling=intercept_scaling,
|
||||
class_weight=class_weight, random_state=random_state)
|
||||
class_weight=class_weight, random_state=random_state,
|
||||
solver=solver, max_iter=max_iter)
|
||||
|
||||
def predict_proba(self, X):
|
||||
"""Probability estimates.
|
||||
|
|
@ -141,3 +708,289 @@ class LogisticRegression(BaseLibLinear, LinearClassifierMixin,
|
|||
model, where classes are ordered as they are in ``self.classes_``.
|
||||
"""
|
||||
return np.log(self.predict_proba(X))
|
||||
|
||||
|
||||
class LogisticRegressionCV(LogisticRegression, BaseEstimator,
|
||||
LinearClassifierMixin, _LearntSelectorMixin):
|
||||
"""Logistic Regression CV (aka logit, MaxEnt) classifier.
|
||||
|
||||
This class implements logistic regression using liblinear, newton-cg or
|
||||
LBFGS optimizer. The newton-cg and lbfgs solvers support only L2
|
||||
regularization with primal formulation. The liblinear solver supports both
|
||||
L1 and L2 regularization, with a dual formulation only for the L2 penalty.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
Cs : list of floats | int
|
||||
Each of the values in Cs describes the inverse of regularization
|
||||
strength. If Cs is as an int, then a grid of Cs values are chosen
|
||||
in a logarithmic scale between 1e-4 and 1e4.
|
||||
Like in support vector machines, smaller values specify stronger
|
||||
regularization.
|
||||
|
||||
fit_intercept : bool, default: True
|
||||
Specifies if a constant (a.k.a. bias or intercept) should be
|
||||
added the decision function.
|
||||
|
||||
class_weight : {dict, 'auto'}, optional
|
||||
Over-/undersamples the samples of each class according to the given
|
||||
weights. If not given, all classes are supposed to have weight one.
|
||||
The 'auto' mode selects weights inversely proportional to class
|
||||
frequencies in the training set.
|
||||
|
||||
cv : integer or cross-validation generator
|
||||
The default cross-validation generator used is Stratified K-Folds.
|
||||
If an integer is provided, then it is the number of folds used.
|
||||
See the module :mod:`sklearn.cross_validation` module for the
|
||||
list of possible cross-validation objects.
|
||||
|
||||
penalty : str, 'l1' or 'l2'
|
||||
Used to specify the norm used in the penalization. The newton-cg and
|
||||
lbfgs solvers support only l2 penalties.
|
||||
|
||||
dual : bool
|
||||
Dual or primal formulation. Dual formulation is only implemented for
|
||||
l2 penalty with liblinear solver. Prefer dual=False when
|
||||
n_samples > n_features.
|
||||
|
||||
scoring : callabale
|
||||
Scoring function to use as cross-validation criteria. For a list of
|
||||
scoring functions that can be used, look at :mod:`sklearn.metrics`.
|
||||
The default scoring option used is accuracy_score.
|
||||
|
||||
solver : {'newton-cg', 'lbfgs', 'liblinear'}
|
||||
Algorithm to use in the optimization problem.
|
||||
|
||||
tol : float, optional
|
||||
Tolerance for stopping criteria.
|
||||
|
||||
max_iter : int, optional
|
||||
Maximum number of iterations of the optimization algorithm.
|
||||
|
||||
class_weight : {dict, 'auto'}, optional
|
||||
Over-/undersamples the samples of each class according to the given
|
||||
weights. If not given, all classes are supposed to have weight one.
|
||||
The 'auto' mode selects weights inversely proportional to class
|
||||
frequencies in the training set.
|
||||
|
||||
n_jobs : int, optional
|
||||
Number of CPU cores used during the cross-validation loop. If given
|
||||
a value of -1, all cores are used.
|
||||
|
||||
verbose : bool | int
|
||||
Amount of verbosity.
|
||||
|
||||
refit : bool
|
||||
If set to True, the scores are averaged across all folds, and the
|
||||
coefs and the C that corresponds to the best score is taken, and a
|
||||
final refit is done using these parameters.
|
||||
Otherwise the coefs, intercepts and C that correspond to the
|
||||
best scores across folds are averaged.
|
||||
|
||||
intercept_scaling : float, default 1.
|
||||
This parameter is useful only when the solver 'liblinear' is used
|
||||
and self.fit_intercept is set to True. In this case, x becomes
|
||||
[x, self.intercept_scaling],
|
||||
i.e. a "synthetic" feature with constant value equals to
|
||||
intercept_scaling is appended to the instance vector.
|
||||
The intercept becomes intercept_scaling * synthetic feature weight
|
||||
Note! the synthetic feature weight is subject to l1/l2 regularization
|
||||
as all other features.
|
||||
To lessen the effect of regularization on synthetic feature weight
|
||||
(and therefore on the intercept) intercept_scaling has to be increased.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
`coef_` : array, shape (1, n_features) or (n_classes, n_features)
|
||||
Coefficient of the features in the decision function.
|
||||
|
||||
`coef_` is of shape (1, n_features) when the given problem
|
||||
is binary.
|
||||
`coef_` is readonly property derived from `raw_coef_` that
|
||||
follows the internal memory layout of liblinear.
|
||||
|
||||
`intercept_` : array, shape (1,) or (n_classes,)
|
||||
Intercept (a.k.a. bias) added to the decision function.
|
||||
It is available only when parameter intercept is set to True
|
||||
and is of shape(1,) when the problem is binary.
|
||||
|
||||
`Cs_` : array
|
||||
Array of C i.e. inverse of regularization parameter values used
|
||||
for cross-validation.
|
||||
|
||||
`coefs_paths_` : array, shape (n_folds, len(Cs_), n_features) or
|
||||
(n_folds, len(Cs_), n_features + 1)
|
||||
dict with classes as the keys, and the path of coefficients obtained
|
||||
during cross-validating across each fold and then across each Cs
|
||||
after doing an OvA for the corresponding class.
|
||||
Each dict value has shape (n_folds, len(Cs_), n_features) or
|
||||
(n_folds, len(Cs_), n_features + 1) depending on whether the
|
||||
intercept is fit or not.
|
||||
|
||||
`scores_` : dict
|
||||
dict with classes as the keys, and the values as the
|
||||
grid of scores obtained during cross-validating each fold, after doing
|
||||
an OvA for the corresponding class.
|
||||
Each dict value has shape (n_folds, len(Cs))
|
||||
|
||||
`C_` : array, shape (n_classes,) or (n_classes - 1,)
|
||||
Array of C that maps to the best scores across every class. If refit is
|
||||
set to False, then for each class, the best C is the average of the
|
||||
C's that correspond to the best scores for each fold.
|
||||
|
||||
See also
|
||||
--------
|
||||
LogisticRegression
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, Cs=10, fit_intercept=True, cv=None, dual=False,
|
||||
penalty='l2', scoring=None, solver='lbfgs', tol=1e-4,
|
||||
max_iter=100, class_weight=None, n_jobs=1, verbose=False,
|
||||
refit=True, intercept_scaling=1.):
|
||||
self.Cs = Cs
|
||||
self.fit_intercept = fit_intercept
|
||||
self.cv = cv
|
||||
self.dual = dual
|
||||
self.penalty = penalty
|
||||
self.scoring = scoring
|
||||
self.tol = tol
|
||||
self.max_iter = max_iter
|
||||
self.class_weight = class_weight
|
||||
self.n_jobs = n_jobs
|
||||
self.verbose = verbose
|
||||
self.solver = solver
|
||||
self.refit = refit
|
||||
self.intercept_scaling = 1.
|
||||
|
||||
def fit(self, X, y):
|
||||
"""Fit the model according to the given training data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
X : {array-like, sparse matrix}, shape (n_samples, n_features)
|
||||
Training vector, where n_samples in the number of samples and
|
||||
n_features is the number of features.
|
||||
|
||||
y : array-like, shape (n_samples,)
|
||||
Target vector relative to X.
|
||||
|
||||
Returns
|
||||
-------
|
||||
self : object
|
||||
Returns self.
|
||||
"""
|
||||
if self.solver != 'liblinear':
|
||||
if self.penalty != 'l2':
|
||||
raise ValueError("newton-cg and lbfgs solvers support only "
|
||||
"l2 penalties.")
|
||||
if self.dual:
|
||||
raise ValueError("newton-cg and lbfgs solvers support only "
|
||||
"the primal form.")
|
||||
|
||||
X = check_array(X, accept_sparse='csc', dtype=np.float64)
|
||||
y = check_array(y, ensure_2d=False)
|
||||
|
||||
if y.ndim == 2 and y.shape[1] == 1:
|
||||
warnings.warn(
|
||||
"A column-vector y was passed when a 1d array was"
|
||||
" expected. Please change the shape of y to "
|
||||
"(n_samples, ), for example using ravel().",
|
||||
DataConversionWarning
|
||||
)
|
||||
y = np.ravel(y)
|
||||
|
||||
check_consistent_length(X, y)
|
||||
|
||||
# init cross-validation generator
|
||||
cv = _check_cv(self.cv, X, y, classifier=True)
|
||||
folds = list(cv)
|
||||
|
||||
self._enc = LabelEncoder()
|
||||
self._enc.fit(y)
|
||||
|
||||
labels = self.classes_
|
||||
n_classes = len(labels)
|
||||
|
||||
if n_classes < 2:
|
||||
raise ValueError("Number of classes have to be greater than one.")
|
||||
|
||||
if n_classes == 2:
|
||||
# OvA in case of binary problems is as good as fitting
|
||||
# the higher label
|
||||
n_classes = 1
|
||||
labels = labels[1:]
|
||||
|
||||
if self.class_weight and not(isinstance(self.class_weight, dict) or
|
||||
self.class_weight == 'auto'):
|
||||
raise ValueError("class_weight provided should be a "
|
||||
"dict or 'auto'")
|
||||
|
||||
fold_coefs_ = Parallel(n_jobs=self.n_jobs, verbose=self.verbose)(
|
||||
delayed(_log_reg_scoring_path)(X, y, train, test,
|
||||
pos_class=label,
|
||||
Cs=self.Cs,
|
||||
fit_intercept=self.fit_intercept,
|
||||
penalty=self.penalty,
|
||||
dual=self.dual,
|
||||
solver=self.solver,
|
||||
max_iter=self.max_iter,
|
||||
tol=self.tol,
|
||||
class_weight=self.class_weight,
|
||||
verbose=max(0, self.verbose - 1),
|
||||
scoring=self.scoring,
|
||||
intercept_scaling=self.intercept_scaling)
|
||||
for label in labels
|
||||
for train, test in folds
|
||||
)
|
||||
coefs_paths, Cs, scores = zip(*fold_coefs_)
|
||||
|
||||
self.Cs_ = Cs[0]
|
||||
coefs_paths = np.reshape(coefs_paths, (n_classes, len(folds),
|
||||
len(self.Cs_), -1))
|
||||
self.coefs_paths_ = dict(zip(labels, coefs_paths))
|
||||
scores = np.reshape(scores, (n_classes, len(folds), -1))
|
||||
self.scores_ = dict(zip(labels, scores))
|
||||
|
||||
self.C_ = list()
|
||||
self.coef_ = list()
|
||||
self.intercept_ = list()
|
||||
|
||||
for label in labels:
|
||||
scores = self.scores_[label]
|
||||
coefs_paths = self.coefs_paths_[label]
|
||||
|
||||
if self.refit:
|
||||
best_index = scores.sum(axis=0).argmax()
|
||||
C_ = self.Cs_[best_index]
|
||||
self.C_.append(C_)
|
||||
coef_init = np.mean(coefs_paths[:, best_index, :], axis=0)
|
||||
|
||||
w, _ = logistic_regression_path(
|
||||
X, y, pos_class=label, Cs=[C_], solver=self.solver,
|
||||
fit_intercept=self.fit_intercept, coef=coef_init,
|
||||
max_iter=self.max_iter, tol=self.tol,
|
||||
class_weight=self.class_weight,
|
||||
verbose=max(0, self.verbose - 1))
|
||||
w = w[0]
|
||||
|
||||
else:
|
||||
# Take the best scores across every fold and the average of all
|
||||
# coefficients corresponding to the best scores.
|
||||
best_indices = np.argmax(scores, axis=1)
|
||||
w = np.mean([
|
||||
coefs_paths[i][best_indices[i]]
|
||||
for i in range(len(folds))
|
||||
], axis=0)
|
||||
self.C_.append(np.mean(self.Cs_[best_indices]))
|
||||
|
||||
if self.fit_intercept:
|
||||
self.coef_.append(w[:-1])
|
||||
self.intercept_.append(w[-1])
|
||||
else:
|
||||
self.coef_.append(w)
|
||||
self.intercept_.append(0.)
|
||||
self.C_ = np.asarray(self.C_)
|
||||
self.coef_ = np.asarray(self.coef_)
|
||||
self.intercept_ = np.asarray(self.intercept_)
|
||||
return self
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import numpy as np
|
||||
import scipy.sparse as sp
|
||||
from scipy import linalg, optimize
|
||||
|
||||
from sklearn.utils.testing import assert_almost_equal
|
||||
from sklearn.utils.testing import assert_array_equal
|
||||
from sklearn.utils.testing import assert_array_almost_equal
|
||||
from sklearn.utils.testing import assert_equal
|
||||
|
|
@ -8,15 +10,21 @@ from sklearn.utils.testing import assert_greater
|
|||
from sklearn.utils.testing import assert_raises
|
||||
from sklearn.utils.testing import assert_true
|
||||
from sklearn.utils.testing import raises
|
||||
from sklearn.utils.testing import ignore_warnings
|
||||
|
||||
from sklearn.linear_model import logistic
|
||||
from sklearn import datasets
|
||||
from sklearn.linear_model.logistic import (
|
||||
LogisticRegression,
|
||||
logistic_regression_path, LogisticRegressionCV,
|
||||
_logistic_loss_and_grad, _logistic_loss_grad_hess
|
||||
)
|
||||
from sklearn.cross_validation import StratifiedKFold
|
||||
from sklearn.datasets import load_iris, make_classification
|
||||
|
||||
X = [[-1, 0], [0, 1], [1, 1]]
|
||||
X_sp = sp.csr_matrix(X)
|
||||
Y1 = [0, 1, 1]
|
||||
Y2 = [2, 1, 0]
|
||||
iris = datasets.load_iris()
|
||||
iris = load_iris()
|
||||
|
||||
|
||||
def check_predictions(clf, X, y):
|
||||
|
|
@ -42,28 +50,26 @@ def test_predict_2_classes():
|
|||
|
||||
Make sure it predicts the correct result on simple datasets.
|
||||
"""
|
||||
check_predictions(logistic.LogisticRegression(random_state=0), X, Y1)
|
||||
check_predictions(logistic.LogisticRegression(random_state=0), X_sp, Y1)
|
||||
check_predictions(LogisticRegression(random_state=0), X, Y1)
|
||||
check_predictions(LogisticRegression(random_state=0), X_sp, Y1)
|
||||
|
||||
check_predictions(logistic.LogisticRegression(C=100, random_state=0),
|
||||
X, Y1)
|
||||
check_predictions(logistic.LogisticRegression(C=100, random_state=0),
|
||||
X_sp, Y1)
|
||||
check_predictions(LogisticRegression(C=100, random_state=0), X, Y1)
|
||||
check_predictions(LogisticRegression(C=100, random_state=0), X_sp, Y1)
|
||||
|
||||
check_predictions(logistic.LogisticRegression(fit_intercept=False,
|
||||
random_state=0), X, Y1)
|
||||
check_predictions(logistic.LogisticRegression(fit_intercept=False,
|
||||
random_state=0), X_sp, Y1)
|
||||
check_predictions(LogisticRegression(fit_intercept=False,
|
||||
random_state=0), X, Y1)
|
||||
check_predictions(LogisticRegression(fit_intercept=False,
|
||||
random_state=0), X_sp, Y1)
|
||||
|
||||
|
||||
def test_error():
|
||||
"""Test for appropriate exception on errors"""
|
||||
assert_raises(ValueError, logistic.LogisticRegression(C=-1).fit, X, Y1)
|
||||
assert_raises(ValueError, LogisticRegression(C=-1).fit, X, Y1)
|
||||
|
||||
|
||||
def test_predict_3_classes():
|
||||
check_predictions(logistic.LogisticRegression(C=10), X, Y2)
|
||||
check_predictions(logistic.LogisticRegression(C=10), X_sp, Y2)
|
||||
check_predictions(LogisticRegression(C=10), X, Y2)
|
||||
check_predictions(LogisticRegression(C=10), X_sp, Y2)
|
||||
|
||||
|
||||
def test_predict_iris():
|
||||
|
|
@ -71,7 +77,7 @@ def test_predict_iris():
|
|||
n_samples, n_features = iris.data.shape
|
||||
|
||||
target = iris.target_names[iris.target]
|
||||
clf = logistic.LogisticRegression(C=len(iris.data)).fit(iris.data, target)
|
||||
clf = LogisticRegression(C=len(iris.data)).fit(iris.data, target)
|
||||
assert_array_equal(np.unique(target), clf.classes_)
|
||||
|
||||
pred = clf.predict(iris.data)
|
||||
|
|
@ -88,7 +94,7 @@ def test_sparsify():
|
|||
"""Test sparsify and densify members."""
|
||||
n_samples, n_features = iris.data.shape
|
||||
target = iris.target_names[iris.target]
|
||||
clf = logistic.LogisticRegression(random_state=0).fit(iris.data, target)
|
||||
clf = LogisticRegression(random_state=0).fit(iris.data, target)
|
||||
|
||||
pred_d_d = clf.decision_function(iris.data)
|
||||
|
||||
|
|
@ -114,7 +120,7 @@ def test_inconsistent_input():
|
|||
y_ = np.ones(X_.shape[0])
|
||||
y_[0] = 0
|
||||
|
||||
clf = logistic.LogisticRegression(random_state=0)
|
||||
clf = LogisticRegression(random_state=0)
|
||||
|
||||
# Wrong dimensions for training data
|
||||
y_wrong = y_[:-1]
|
||||
|
|
@ -127,10 +133,7 @@ def test_inconsistent_input():
|
|||
|
||||
def test_write_parameters():
|
||||
"""Test that we can write to coef_ and intercept_"""
|
||||
#rng = np.random.RandomState(0)
|
||||
#X = rng.random_sample((5, 10))
|
||||
#y = np.ones(X.shape[0])
|
||||
clf = logistic.LogisticRegression(random_state=0)
|
||||
clf = LogisticRegression(random_state=0)
|
||||
clf.fit(X, Y1)
|
||||
clf.coef_[:] = 0
|
||||
clf.intercept_[:] = 0
|
||||
|
|
@ -145,13 +148,277 @@ def test_nan():
|
|||
"""
|
||||
Xnan = np.array(X, dtype=np.float64)
|
||||
Xnan[0, 1] = np.nan
|
||||
logistic.LogisticRegression(random_state=0).fit(Xnan, Y1)
|
||||
LogisticRegression(random_state=0).fit(Xnan, Y1)
|
||||
|
||||
|
||||
def test_consistency_path():
|
||||
"""Test that the path algorithm is consistent"""
|
||||
rng = np.random.RandomState(0)
|
||||
X = np.concatenate((rng.randn(100, 2) + [1, 1], rng.randn(100, 2)))
|
||||
y = [1] * 100 + [-1] * 100
|
||||
Cs = np.logspace(0, 4, 10)
|
||||
|
||||
f = ignore_warnings
|
||||
# can't test with fit_intercept=True since LIBLINEAR
|
||||
# penalizes the intercept
|
||||
for method in ('lbfgs', 'newton-cg', 'liblinear'):
|
||||
coefs, Cs = f(logistic_regression_path)(
|
||||
X, y, Cs=Cs, fit_intercept=False, tol=1e-16, solver=method)
|
||||
for i, C in enumerate(Cs):
|
||||
lr = LogisticRegression(C=C, fit_intercept=False, tol=1e-16)
|
||||
lr.fit(X, y)
|
||||
lr_coef = lr.coef_.ravel()
|
||||
assert_array_almost_equal(lr_coef, coefs[i], decimal=4)
|
||||
|
||||
# test for fit_intercept=True
|
||||
for method in ('lbfgs', 'newton-cg', 'liblinear'):
|
||||
Cs = [1e3]
|
||||
coefs, Cs = f(logistic_regression_path)(
|
||||
X, y, Cs=Cs, fit_intercept=True, tol=1e-4, solver=method)
|
||||
lr = LogisticRegression(C=Cs[0], fit_intercept=True, tol=1e-4,
|
||||
intercept_scaling=10000)
|
||||
lr.fit(X, y)
|
||||
lr_coef = np.concatenate([lr.coef_.ravel(), lr.intercept_])
|
||||
assert_array_almost_equal(lr_coef, coefs[0], decimal=4)
|
||||
|
||||
|
||||
def test_liblinear_random_state():
|
||||
X, y = datasets.make_classification(n_samples=20)
|
||||
lr1 = logistic.LogisticRegression(random_state=0)
|
||||
X, y = make_classification(n_samples=20)
|
||||
lr1 = LogisticRegression(random_state=0)
|
||||
lr1.fit(X, y)
|
||||
lr2 = logistic.LogisticRegression(random_state=0)
|
||||
lr2 = LogisticRegression(random_state=0)
|
||||
lr2.fit(X, y)
|
||||
assert_array_almost_equal(lr1.coef_, lr2.coef_)
|
||||
|
||||
|
||||
def test_logistic_loss_and_grad():
|
||||
X_ref, y = make_classification(n_samples=20)
|
||||
n_features = X_ref.shape[1]
|
||||
|
||||
X_sp = X_ref.copy()
|
||||
X_sp[X_sp < .1] = 0
|
||||
X_sp = sp.csr_matrix(X_sp)
|
||||
for X in (X_ref, X_sp):
|
||||
w = np.zeros(n_features)
|
||||
|
||||
# First check that our derivation of the grad is correct
|
||||
loss, grad = _logistic_loss_and_grad(w, X, y, alpha=1.)
|
||||
approx_grad = optimize.approx_fprime(
|
||||
w, lambda w: _logistic_loss_and_grad(w, X, y, alpha=1.)[0], 1e-3
|
||||
)
|
||||
assert_array_almost_equal(grad, approx_grad, decimal=2)
|
||||
|
||||
# Second check that our intercept implementation is good
|
||||
w = np.zeros(n_features + 1)
|
||||
loss_interp, grad_interp = _logistic_loss_and_grad(
|
||||
w, X, y, alpha=1.
|
||||
)
|
||||
assert_array_almost_equal(loss, loss_interp)
|
||||
|
||||
approx_grad = optimize.approx_fprime(
|
||||
w, lambda w: _logistic_loss_and_grad(w, X, y, alpha=1.)[0], 1e-3
|
||||
)
|
||||
assert_array_almost_equal(grad_interp, approx_grad, decimal=2)
|
||||
|
||||
|
||||
def test_logistic_loss_grad_hess():
|
||||
rng = np.random.RandomState(0)
|
||||
n_samples, n_features = 50, 5
|
||||
X_ref = rng.randn(n_samples, n_features)
|
||||
y = np.sign(X_ref.dot(5 * rng.randn(n_features)))
|
||||
X_ref -= X_ref.mean()
|
||||
X_ref /= X_ref.std()
|
||||
X_sp = X_ref.copy()
|
||||
X_sp[X_sp < .1] = 0
|
||||
X_sp = sp.csr_matrix(X_sp)
|
||||
for X in (X_ref, X_sp):
|
||||
w = .1 * np.ones(n_features)
|
||||
|
||||
# First check that _logistic_loss_grad_hess is consistent
|
||||
# with _logistic_loss_and_grad
|
||||
loss, grad = _logistic_loss_and_grad(w, X, y, alpha=1.)
|
||||
loss_2, grad_2, hess = _logistic_loss_grad_hess(w, X, y, alpha=1.)
|
||||
assert_array_almost_equal(grad, grad_2)
|
||||
|
||||
# Now check our hessian along the second direction of the grad
|
||||
vector = np.zeros_like(grad)
|
||||
vector[1] = 1
|
||||
hess_col = hess(vector)
|
||||
|
||||
# Computation of the Hessian is particularly fragile to numerical
|
||||
# errors when doing simple finite differences. Here we compute the
|
||||
# grad along a path in the direction of the vector and then use a
|
||||
# least-square regression to estimate the slope
|
||||
e = 1e-3
|
||||
d_x = np.linspace(-e, e, 30)
|
||||
d_grad = np.array([
|
||||
_logistic_loss_and_grad(w + t * vector, X, y, alpha=1.)[1]
|
||||
for t in d_x
|
||||
])
|
||||
|
||||
d_grad -= d_grad.mean(axis=0)
|
||||
approx_hess_col = linalg.lstsq(d_x[:, np.newaxis], d_grad)[0].ravel()
|
||||
|
||||
assert_array_almost_equal(approx_hess_col, hess_col, decimal=3)
|
||||
|
||||
# Second check that our intercept implementation is good
|
||||
w = np.zeros(n_features + 1)
|
||||
loss_interp, grad_interp = _logistic_loss_and_grad(
|
||||
w, X, y, alpha=1.
|
||||
)
|
||||
loss_interp_2, grad_interp_2, hess = \
|
||||
_logistic_loss_grad_hess(w, X, y, alpha=1.)
|
||||
assert_array_almost_equal(loss_interp, loss_interp_2)
|
||||
assert_array_almost_equal(grad_interp, grad_interp_2)
|
||||
|
||||
|
||||
def test_logistic_cv():
|
||||
"""test for LogisticRegressionCV object"""
|
||||
n_samples, n_features = 50, 5
|
||||
rng = np.random.RandomState(0)
|
||||
X_ref = rng.randn(n_samples, n_features)
|
||||
y = np.sign(X_ref.dot(5 * rng.randn(n_features)))
|
||||
X_ref -= X_ref.mean()
|
||||
X_ref /= X_ref.std()
|
||||
lr_cv = LogisticRegressionCV(Cs=[1.], fit_intercept=False,
|
||||
solver='liblinear')
|
||||
lr_cv.fit(X_ref, y)
|
||||
lr = LogisticRegression(C=1., fit_intercept=False)
|
||||
lr.fit(X_ref, y)
|
||||
assert_array_almost_equal(lr.coef_, lr_cv.coef_)
|
||||
|
||||
assert_array_equal(lr_cv.coef_.shape, (1, n_features))
|
||||
assert_array_equal(lr_cv.classes_, [-1, 1])
|
||||
assert_equal(len(lr_cv.classes_), 2)
|
||||
|
||||
coefs_paths = np.asarray(list(lr_cv.coefs_paths_.values()))
|
||||
assert_array_equal(coefs_paths.shape, (1, 3, 1, n_features))
|
||||
assert_array_equal(lr_cv.Cs_.shape, (1, ))
|
||||
scores = np.asarray(list(lr_cv.scores_.values()))
|
||||
assert_array_equal(scores.shape, (1, 3, 1))
|
||||
|
||||
|
||||
def test_logistic_cv_sparse():
|
||||
X, y = make_classification(n_samples=50, n_features=5,
|
||||
random_state=0)
|
||||
X[X < 1.0] = 0.0
|
||||
csr = sp.csr_matrix(X)
|
||||
|
||||
clf = LogisticRegressionCV(fit_intercept=True)
|
||||
clf.fit(X, y)
|
||||
clfs = LogisticRegressionCV(fit_intercept=True)
|
||||
clfs.fit(csr, y)
|
||||
assert_array_almost_equal(clfs.coef_, clf.coef_)
|
||||
assert_array_almost_equal(clfs.intercept_, clf.intercept_)
|
||||
assert_equal(clfs.C_, clf.C_)
|
||||
|
||||
|
||||
def test_intercept_logistic_helper():
|
||||
n_samples, n_features = 10, 5
|
||||
X, y = make_classification(n_samples=n_samples, n_features=n_features,
|
||||
random_state=0)
|
||||
|
||||
# Fit intercept case.
|
||||
alpha = 1.
|
||||
w = np.ones(n_features + 1)
|
||||
loss_interp, grad_interp, hess_interp = _logistic_loss_grad_hess(
|
||||
w, X, y, alpha)
|
||||
|
||||
# Do not fit intercept. This can be considered equivalent to adding
|
||||
# a feature vector of ones, i.e column of one vectors.
|
||||
X_ = np.hstack((X, np.ones(10)[:, np.newaxis]))
|
||||
loss, grad, hess = _logistic_loss_grad_hess(w, X_, y, alpha)
|
||||
|
||||
# In the fit_intercept=False case, the feature vector of ones is
|
||||
# penalized. This should be taken care of.
|
||||
assert_almost_equal(loss_interp + 0.5 * (w[-1] ** 2), loss)
|
||||
|
||||
# Check gradient.
|
||||
assert_array_almost_equal(grad_interp[:n_features], grad[:n_features])
|
||||
assert_almost_equal(grad_interp[-1] + alpha * w[-1], grad[-1])
|
||||
|
||||
rng = np.random.RandomState(0)
|
||||
grad = rng.rand(n_features + 1)
|
||||
hess_interp = hess_interp(grad)
|
||||
hess = hess(grad)
|
||||
assert_array_almost_equal(hess_interp[:n_features], hess[:n_features])
|
||||
assert_almost_equal(hess_interp[-1] + alpha * grad[-1], hess[-1])
|
||||
|
||||
|
||||
def test_ova_iris():
|
||||
"""Test that our OvA implementation is correct using the iris dataset."""
|
||||
train, target = iris.data, iris.target
|
||||
n_samples, n_features = train.shape
|
||||
|
||||
# Use pre-defined fold as folds generated for different y
|
||||
cv = StratifiedKFold(target, 3)
|
||||
clf = LogisticRegressionCV(cv=cv)
|
||||
clf.fit(train, target)
|
||||
|
||||
clf1 = LogisticRegressionCV(cv=cv)
|
||||
target[target == 0] = 1
|
||||
clf1.fit(train, target)
|
||||
|
||||
assert_array_equal(clf.scores_[2], clf1.scores_[2])
|
||||
assert_array_equal(clf.intercept_[2:], clf1.intercept_)
|
||||
assert_array_equal(clf.coef_[2][np.newaxis, :], clf1.coef_)
|
||||
|
||||
# Test the shape of various attributes.
|
||||
assert_array_equal(clf.coef_.shape, (3, n_features))
|
||||
assert_array_equal(clf.classes_, [0, 1, 2])
|
||||
assert_equal(len(clf.classes_), 3)
|
||||
|
||||
coefs_paths = np.asarray(list(clf.coefs_paths_.values()))
|
||||
assert_array_equal(coefs_paths.shape, (3, 3, 10, n_features + 1))
|
||||
assert_array_equal(clf.Cs_.shape, (10, ))
|
||||
scores = np.asarray(list(clf.scores_.values()))
|
||||
assert_array_equal(scores.shape, (3, 3, 10))
|
||||
|
||||
|
||||
def test_logistic_regression_solvers():
|
||||
X, y = make_classification(n_features=10, n_informative=5, random_state=0)
|
||||
clf_n = LogisticRegression(solver='newton-cg', fit_intercept=False)
|
||||
clf_n.fit(X, y)
|
||||
clf_lbf = LogisticRegression(solver='lbfgs', fit_intercept=False)
|
||||
clf_lbf.fit(X, y)
|
||||
clf_lib = LogisticRegression(fit_intercept=False)
|
||||
clf_lib.fit(X, y)
|
||||
assert_array_almost_equal(clf_n.coef_, clf_lib.coef_, decimal=3)
|
||||
assert_array_almost_equal(clf_lib.coef_, clf_lbf.coef_, decimal=3)
|
||||
assert_array_almost_equal(clf_n.coef_, clf_lbf.coef_, decimal=3)
|
||||
|
||||
|
||||
def test_logistic_regression_solvers_multiclass():
|
||||
X, y = make_classification(n_samples=20, n_features=20, n_informative=10,
|
||||
n_classes=3, random_state=0)
|
||||
clf_n = LogisticRegression(solver='newton-cg', fit_intercept=False)
|
||||
clf_n.fit(X, y)
|
||||
clf_lbf = LogisticRegression(solver='lbfgs', fit_intercept=False)
|
||||
clf_lbf.fit(X, y)
|
||||
clf_lib = LogisticRegression(fit_intercept=False)
|
||||
clf_lib.fit(X, y)
|
||||
assert_array_almost_equal(clf_n.coef_, clf_lib.coef_, decimal=4)
|
||||
assert_array_almost_equal(clf_lib.coef_, clf_lbf.coef_, decimal=4)
|
||||
assert_array_almost_equal(clf_n.coef_, clf_lbf.coef_, decimal=4)
|
||||
|
||||
|
||||
def test_logistic_regressioncv_class_weights():
|
||||
X, y = make_classification(n_samples=20, n_features=20, n_informative=10,
|
||||
n_classes=3, random_state=0)
|
||||
|
||||
# Test the liblinear fails when class_weight of type dict is
|
||||
# provided, when it is multiclass
|
||||
clf_lib = LogisticRegressionCV(class_weight={0: 0.1, 1: 0.2},
|
||||
solver='liblinear')
|
||||
assert_raises(ValueError, clf_lib.fit, X, y)
|
||||
|
||||
# Test for class_weight=auto
|
||||
X, y = make_classification(n_samples=20, n_features=20, n_informative=10,
|
||||
random_state=0)
|
||||
clf_lbf = LogisticRegressionCV(solver='lbfgs', fit_intercept=False,
|
||||
class_weight='auto')
|
||||
clf_lbf.fit(X, y)
|
||||
clf_lib = LogisticRegressionCV(solver='liblinear', fit_intercept=False,
|
||||
class_weight='auto')
|
||||
clf_lib.fit(X, y)
|
||||
assert_array_almost_equal(clf_lib.coef_, clf_lbf.coef_, decimal=4)
|
||||
|
|
|
|||
|
|
@ -601,7 +601,8 @@ class BaseLibLinear(six.with_metaclass(ABCMeta, BaseEstimator)):
|
|||
@abstractmethod
|
||||
def __init__(self, penalty='l2', loss='l2', dual=True, tol=1e-4, C=1.0,
|
||||
multi_class='ovr', fit_intercept=True, intercept_scaling=1,
|
||||
class_weight=None, verbose=0, random_state=None):
|
||||
class_weight=None, verbose=0, random_state=None, max_iter=100,
|
||||
solver='liblinear'):
|
||||
|
||||
self.penalty = penalty
|
||||
self.loss = loss
|
||||
|
|
@ -614,6 +615,8 @@ class BaseLibLinear(six.with_metaclass(ABCMeta, BaseEstimator)):
|
|||
self.class_weight = class_weight
|
||||
self.verbose = verbose
|
||||
self.random_state = random_state
|
||||
self.solver = solver
|
||||
self.max_iter = max_iter
|
||||
|
||||
# Check that the arguments given are valid:
|
||||
self._get_solver_type()
|
||||
|
|
@ -670,6 +673,19 @@ class BaseLibLinear(six.with_metaclass(ABCMeta, BaseEstimator)):
|
|||
self : object
|
||||
Returns self.
|
||||
"""
|
||||
|
||||
# Circular import, logistic_regression_path depends on LogisticRegression
|
||||
# and hence BaseLibLinear.
|
||||
from ..linear_model import logistic_regression_path
|
||||
|
||||
if self.solver != 'liblinear':
|
||||
if self.penalty != 'l2':
|
||||
raise ValueError("newton-cg and lbfgs solvers support only "
|
||||
"l2 penalties.")
|
||||
if self.dual:
|
||||
raise ValueError("newton-cg and lbfgs solvers support only "
|
||||
"the primal form.")
|
||||
|
||||
self._enc = LabelEncoder()
|
||||
y_ind = self._enc.fit_transform(y)
|
||||
if len(self.classes_) < 2:
|
||||
|
|
@ -678,6 +694,7 @@ class BaseLibLinear(six.with_metaclass(ABCMeta, BaseEstimator)):
|
|||
|
||||
X = check_array(X, accept_sparse='csr', dtype=np.float64, order="C")
|
||||
|
||||
# Used in the liblinear solver.
|
||||
self.class_weight_ = compute_class_weight(self.class_weight,
|
||||
self.classes_, y)
|
||||
|
||||
|
|
@ -686,31 +703,67 @@ class BaseLibLinear(six.with_metaclass(ABCMeta, BaseEstimator)):
|
|||
"X has %s samples, but y has %s." %
|
||||
(X.shape[0], y_ind.shape[0]))
|
||||
|
||||
liblinear.set_verbosity_wrap(self.verbose)
|
||||
if self.solver not in ['liblinear', 'newton-cg', 'lbfgs']:
|
||||
raise ValueError("Logistic Regression supports only liblinear,"
|
||||
" newton-cg and lbfgs solvers.")
|
||||
|
||||
rnd = check_random_state(self.random_state)
|
||||
if self.verbose:
|
||||
print('[LibLinear]', end='')
|
||||
if self.solver == 'liblinear':
|
||||
liblinear.set_verbosity_wrap(self.verbose)
|
||||
|
||||
# LibLinear wants targets as doubles, even for classification
|
||||
y_ind = np.asarray(y_ind, dtype=np.float64).ravel()
|
||||
raw_coef_ = liblinear.train_wrap(X, y_ind,
|
||||
sp.isspmatrix(X),
|
||||
self._get_solver_type(),
|
||||
self.tol, self._get_bias(),
|
||||
self.C, self.class_weight_,
|
||||
rnd.randint(np.iinfo('i').max))
|
||||
# Regarding rnd.randint(..) in the above signature:
|
||||
# seed for srand in range [0..INT_MAX); due to limitations in Numpy
|
||||
# on 32-bit platforms, we can't get to the UINT_MAX limit that
|
||||
# srand supports
|
||||
rnd = check_random_state(self.random_state)
|
||||
if self.verbose:
|
||||
print('[LibLinear]', end='')
|
||||
|
||||
# LibLinear wants targets as doubles, even for classification
|
||||
y_ind = np.asarray(y_ind, dtype=np.float64).ravel()
|
||||
raw_coef_ = liblinear.train_wrap(X, y_ind,
|
||||
sp.isspmatrix(X),
|
||||
self._get_solver_type(),
|
||||
self.tol, self._get_bias(),
|
||||
self.C, self.class_weight_,
|
||||
rnd.randint(np.iinfo('i').max))
|
||||
# Regarding rnd.randint(..) in the above signature:
|
||||
# seed for srand in range [0..INT_MAX); due to limitations in Numpy
|
||||
# on 32-bit platforms, we can't get to the UINT_MAX limit that
|
||||
# srand supports
|
||||
|
||||
if self.fit_intercept:
|
||||
self.coef_ = raw_coef_[:, :-1]
|
||||
self.intercept_ = self.intercept_scaling * raw_coef_[:, -1]
|
||||
else:
|
||||
self.coef_ = raw_coef_
|
||||
self.intercept_ = 0.
|
||||
|
||||
if self.fit_intercept:
|
||||
self.coef_ = raw_coef_[:, :-1]
|
||||
self.intercept_ = self.intercept_scaling * raw_coef_[:, -1]
|
||||
else:
|
||||
self.coef_ = raw_coef_
|
||||
self.intercept_ = 0.
|
||||
if self.penalty != 'l2':
|
||||
raise ValueError("newton-cg and lbfgs solvers support only "
|
||||
"l2 penalties.")
|
||||
|
||||
n_tasks = len(self.classes_)
|
||||
classes_ = self.classes_
|
||||
|
||||
if len(self.classes_) == 2:
|
||||
n_tasks = 1
|
||||
classes_ = classes_[1:]
|
||||
|
||||
self.coef_ = np.empty((n_tasks, X.shape[1]))
|
||||
self.intercept_ = np.zeros(n_tasks)
|
||||
|
||||
for ind, class_ in enumerate(classes_):
|
||||
coef_, _ = logistic_regression_path(
|
||||
X, y, pos_class=class_, Cs=[self.C],
|
||||
fit_intercept=self.fit_intercept,
|
||||
tol=self.tol, verbose=self.verbose,
|
||||
solver=self.solver, copy=True,
|
||||
max_iter=self.max_iter, class_weight=self.class_weight)
|
||||
|
||||
coef_ = coef_[0]
|
||||
if self.fit_intercept:
|
||||
self.coef_[ind] = coef_[:-1]
|
||||
self.intercept_[ind] = coef_[-1]
|
||||
|
||||
else:
|
||||
self.coef_[ind] = coef_
|
||||
|
||||
if self.multi_class == "crammer_singer" and len(self.classes_) == 2:
|
||||
self.coef_ = (self.coef_[1] - self.coef_[0]).reshape(1, -1)
|
||||
|
|
|
|||
|
|
@ -319,7 +319,9 @@ def test_non_transformer_estimators_n_iter():
|
|||
# These models are dependent on external solvers like
|
||||
# libsvm and accessing the iter parameter is non-trivial.
|
||||
if name in (['Ridge', 'SVR', 'NuSVR', 'NuSVC',
|
||||
'RidgeClassifier', 'SVC', 'RandomizedLasso']):
|
||||
'RidgeClassifier', 'SVC', 'RandomizedLasso',
|
||||
'LogisticRegressionCV', 'LogisticRegression',
|
||||
'LinearSVC']):
|
||||
continue
|
||||
|
||||
# Tested in test_transformer_n_iter below
|
||||
|
|
@ -340,6 +342,7 @@ def test_transformer_n_iter():
|
|||
# Dependent on external solvers and hence accessing the iter
|
||||
# param is non-trivial.
|
||||
external_solver = ['Isomap', 'KernelPCA', 'LocallyLinearEmbedding',
|
||||
'RandomizedLasso']
|
||||
'RandomizedLasso', 'LogisticRegression',
|
||||
'LogisticRegressionCV', 'LinearSVC']
|
||||
if hasattr(estimator, "max_iter") and name not in external_solver:
|
||||
yield check_transformer_n_iter, name, estimator
|
||||
|
|
|
|||
|
|
@ -771,8 +771,9 @@ def check_cluster_overwrite_params(name, Clustering):
|
|||
|
||||
|
||||
def check_sparsify_multiclass_classifier(name, Classifier):
|
||||
X = np.array([[-2, -1], [-1, -1], [-1, -2], [1, 1], [1, 2], [2, 1]])
|
||||
y = [1, 1, 1, 2, 2, 3]
|
||||
X = np.array([[-2, -1], [-1, -1], [-1, -2], [1, 1], [1, 2], [2, 1],
|
||||
[-1, -2], [2, 2], [-2, -2]])
|
||||
y = [1, 1, 1, 2, 2, 2, 3, 3, 3]
|
||||
est = Classifier()
|
||||
|
||||
est.fit(X, y)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,176 @@
|
|||
"""
|
||||
Our own implementation of the Newton algorithm
|
||||
|
||||
Unlike the scipy.optimize version, this version of the Newton conjugate
|
||||
gradient solver uses only one function call to retrieve the
|
||||
func value, the gradient value and a callable for the Hessian matvec
|
||||
product. If the function call is very expensive (e.g. for logistic
|
||||
regression with large design matrix), this approach gives very
|
||||
significant speedups.
|
||||
"""
|
||||
# This is a modified file from scipy.optimize
|
||||
# Original authors: Travis Oliphant, Eric Jones
|
||||
# Modifications by Gael Varoquaux
|
||||
# License: BSD
|
||||
|
||||
import numpy as np
|
||||
import warnings
|
||||
from scipy.optimize.linesearch import line_search_wolfe2, line_search_wolfe1
|
||||
|
||||
|
||||
class _LineSearchError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def _line_search_wolfe12(f, fprime, xk, pk, gfk, old_fval, old_old_fval,
|
||||
**kwargs):
|
||||
"""
|
||||
Same as line_search_wolfe1, but fall back to line_search_wolfe2 if
|
||||
suitable step length is not found, and raise an exception if a
|
||||
suitable step length is not found.
|
||||
|
||||
Raises
|
||||
------
|
||||
_LineSearchError
|
||||
If no suitable step size is found
|
||||
|
||||
"""
|
||||
ret = line_search_wolfe1(f, fprime, xk, pk, gfk,
|
||||
old_fval, old_old_fval,
|
||||
**kwargs)
|
||||
|
||||
if ret[0] is None:
|
||||
# line search failed: try different one.
|
||||
ret = line_search_wolfe2(f, fprime, xk, pk, gfk,
|
||||
old_fval, old_old_fval, **kwargs)
|
||||
|
||||
if ret[0] is None:
|
||||
raise _LineSearchError()
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def newton_cg(func_grad_hess, func, grad, x0, args=(), eps=1e-4, tol=1e-4,
|
||||
maxiter=100):
|
||||
"""
|
||||
Minimization of scalar function of one or more variables using the
|
||||
Newton-CG algorithm.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func_grad_hess : callable
|
||||
Should return the value of the function, the gradient, and a
|
||||
callable returning the matvec product of the Hessian.
|
||||
|
||||
func : callable
|
||||
Should return the value of the function.
|
||||
|
||||
grad : callable
|
||||
Should return the function value and the gradient. This is used
|
||||
by the linesearch functions.
|
||||
|
||||
x0 : float
|
||||
Initial guess.
|
||||
|
||||
args: tuple, optional
|
||||
Arguments passed to func_grad_hess, func and grad.
|
||||
|
||||
tol : float
|
||||
Stopping criterion. The iteration will stop when
|
||||
``max{|g_i | i = 1, ..., n} <= tol``
|
||||
where ``g_i`` is the i-th component of the gradient.
|
||||
|
||||
eps : float, optional
|
||||
If fhess is approximated, use this value for the step size.
|
||||
|
||||
maxiter : int
|
||||
Number of iterations.
|
||||
|
||||
Returns
|
||||
-------
|
||||
xk : float
|
||||
Estimated minimum.
|
||||
"""
|
||||
x0 = np.asarray(x0).flatten()
|
||||
xk = x0
|
||||
k = 1
|
||||
old_fval = func(x0, *args)
|
||||
old_old_fval = None
|
||||
|
||||
# Outer loop: our Newton iteration
|
||||
while k <= maxiter:
|
||||
|
||||
# Compute a search direction pk by applying the CG method to
|
||||
# del2 f(xk) p = - fgrad f(xk) starting from 0.
|
||||
fval, fgrad, fhess_p = func_grad_hess(xk, *args)
|
||||
|
||||
absgrad = np.abs(fgrad)
|
||||
if np.max(absgrad) < tol:
|
||||
break
|
||||
|
||||
maggrad = np.sum(absgrad)
|
||||
eta = min([0.5, np.sqrt(maggrad)])
|
||||
termcond = eta * maggrad
|
||||
xsupi = np.zeros(len(x0), dtype=x0.dtype)
|
||||
ri = fgrad
|
||||
psupi = -ri
|
||||
i = 0
|
||||
dri0 = np.dot(ri, ri)
|
||||
|
||||
# Inner loop: solve the Newton update by conjugate gradient, to
|
||||
# avoid inverting the Hessian
|
||||
while np.sum(np.abs(ri)) > termcond:
|
||||
Ap = fhess_p(psupi)
|
||||
# check curvature
|
||||
curv = np.dot(psupi, Ap)
|
||||
if 0 <= curv <= 3*np.finfo(np.float64).eps:
|
||||
break
|
||||
elif curv < 0:
|
||||
if (i > 0):
|
||||
break
|
||||
else:
|
||||
# fall back to steepest descent direction
|
||||
xsupi = xsupi + dri0 / curv * psupi
|
||||
break
|
||||
alphai = dri0 / curv
|
||||
xsupi = xsupi + alphai * psupi
|
||||
ri = ri + alphai * Ap
|
||||
dri1 = np.dot(ri, ri)
|
||||
betai = dri1 / dri0
|
||||
psupi = -ri + betai * psupi
|
||||
i = i + 1
|
||||
dri0 = dri1 # update np.dot(ri,ri) for next time.
|
||||
|
||||
try:
|
||||
alphak, fc, gc, old_fval, old_old_fval, gfkp1 = \
|
||||
_line_search_wolfe12(func, grad, xk, xsupi, fgrad,
|
||||
old_fval, old_old_fval, args=args)
|
||||
except _LineSearchError:
|
||||
warnings.warn('Line Search failed')
|
||||
break
|
||||
|
||||
update = alphak * xsupi
|
||||
xk = xk + update # upcast if necessary
|
||||
k += 1
|
||||
|
||||
if k > maxiter:
|
||||
warnings.warn("newton-cg failed to converge. Increase the "
|
||||
"number of iterations.")
|
||||
return xk
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Tests
|
||||
|
||||
if __name__ == "__main__":
|
||||
A = np.random.normal(size=(10, 10))
|
||||
|
||||
def func(x):
|
||||
Ax = A.dot(x)
|
||||
return .5*(Ax).dot(Ax)
|
||||
|
||||
def func_grad_hess(x):
|
||||
return func(x), A.T.dot(A.dot(x)), lambda x: A.T.dot(A.dot(x))
|
||||
|
||||
x0 = np.ones(10)
|
||||
out = newton_cg(func_grad_hess, func, x0)
|
||||
Loading…
Reference in New Issue