Skip to content

Commit ad5773c

Browse files
MechCoderogrisel
authored andcommitted
TST: Added test to check ConvergenceWarning
1 parent c8c72fd commit ad5773c

File tree

8 files changed

+49
-30
lines changed

8 files changed

+49
-30
lines changed

sklearn/linear_model/logistic.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -639,9 +639,9 @@ class LogisticRegression(BaseLibLinear, LinearClassifierMixin,
639639
Intercept (a.k.a. bias) added to the decision function.
640640
If `fit_intercept` is set to False, the intercept is set to zero.
641641
642-
`n_iter_` : int | array, shape (n_classes,)
643-
Number of iterations run per class. Valid only for the liblinear
644-
solver.
642+
`n_iter_` : int
643+
Maximum of the actual number of iterations across all classes.
644+
Valid only for the liblinear solver.
645645
646646
See also
647647
--------

sklearn/linear_model/tests/test_logistic.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
from sklearn.utils.testing import assert_greater
1010
from sklearn.utils.testing import assert_raises
1111
from sklearn.utils.testing import assert_true
12+
from sklearn.utils.testing import assert_warns
1213
from sklearn.utils.testing import raises
1314
from sklearn.utils.testing import ignore_warnings
15+
from sklearn.utils import ConvergenceWarning
1416

1517
from sklearn.linear_model.logistic import (
1618
LogisticRegression,
@@ -422,3 +424,12 @@ def test_logistic_regressioncv_class_weights():
422424
class_weight='auto')
423425
clf_lib.fit(X, y)
424426
assert_array_almost_equal(clf_lib.coef_, clf_lbf.coef_, decimal=4)
427+
428+
429+
def test_logistic_regression_convergence_warnings():
430+
"""Test that warnings are raised if model does not converge"""
431+
432+
X, y = make_classification(n_samples=20, n_features=20)
433+
clf_lib = LogisticRegression(solver='liblinear', max_iter=2)
434+
assert_warns(ConvergenceWarning, clf_lib.fit, X, y)
435+
assert_equal(clf_lib.n_iter_, 2)

sklearn/svm/base.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,7 @@ def fit(self, X, y):
716716

717717
# LibLinear wants targets as doubles, even for classification
718718
y_ind = np.asarray(y_ind, dtype=np.float64).ravel()
719-
raw_coef_, self.n_iter_ = liblinear.train_wrap(
719+
raw_coef_, n_iter_ = liblinear.train_wrap(
720720
X, y_ind, sp.isspmatrix(X), self._get_solver_type(),
721721
self.tol, self._get_bias(), self.C, self.class_weight_,
722722
self.max_iter, rnd.randint(np.iinfo('i').max)
@@ -725,12 +725,10 @@ def fit(self, X, y):
725725
# seed for srand in range [0..INT_MAX); due to limitations in Numpy
726726
# on 32-bit platforms, we can't get to the UINT_MAX limit that
727727
# srand supports
728-
for n_iter in self.n_iter_:
729-
if n_iter >= self.max_iter:
730-
warnings.warn("Liblinear failed to converge, increase "
731-
"the number of iterations.", ConvergenceWarning)
732-
if len(self.classes_) == 2:
733-
self.n_iter_ = self.n_iter_[0]
728+
self.n_iter_ = max(n_iter_)
729+
if self.n_iter_ >= self.max_iter:
730+
warnings.warn("Liblinear failed to converge, increase "
731+
"the number of iterations.", ConvergenceWarning)
734732

735733
if self.fit_intercept:
736734
self.coef_ = raw_coef_[:, :-1]

sklearn/svm/liblinear.c

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sklearn/svm/liblinear.pyx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def train_wrap(X, np.ndarray[np.float64_t, ndim=1, mode='c'] Y,
3737
bias)
3838

3939
cdef np.ndarray[np.int32_t, ndim=1, mode='c'] \
40-
class_weight_label = np.arange(class_weight.shape[0], dtype=np.int32)
40+
class_weight_label = np.arange(class_weight.shape[0], dtype=np.intc)
4141
param = set_parameter(solver_type, eps, C, class_weight.shape[0],
4242
class_weight_label.data, class_weight.data,
4343
max_iter, random_seed)
@@ -59,7 +59,7 @@ def train_wrap(X, np.ndarray[np.float64_t, ndim=1, mode='c'] Y,
5959
cdef int labels_ = nr_class
6060
if nr_class == 2:
6161
labels_ = 1
62-
cdef np.ndarray[np.int32_t, ndim=1, mode='c'] n_iter = np.zeros(labels_, dtype=np.int32)
62+
cdef np.ndarray[np.int32_t, ndim=1, mode='c'] n_iter = np.zeros(labels_, dtype=np.intc)
6363
get_n_iter(model, <int *>n_iter.data)
6464

6565
cdef int nr_feature = get_nr_feature(model)

sklearn/svm/src/liblinear/linear.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- Remove the hard-coded value of max_iter (1000), that allows max_iter
1616
to be passed as a parameter from the classes LogisticRegression and
1717
LinearSVC, Manoj Kumar
18+
- Added function get_n_iter that exposes the number of iterations.
1819
See issue 3499: https://github.com/scikit-learn/scikit-learn/issues/3499
1920
See pull 3501: https://github.com/scikit-learn/scikit-learn/pull/3501
2021
@@ -2809,11 +2810,12 @@ void get_labels(const model *model_, int* label)
28092810

28102811
void get_n_iter(const model *model_, int* n_iter)
28112812
{
2812-
int labels;
2813-
labels = model_->nr_class;
2814-
if (labels == 2)
2815-
labels = 1;
2816-
if (model_->n_iter != NULL)
2813+
int labels;
2814+
labels = model_->nr_class;
2815+
if (labels == 2)
2816+
labels = 1;
2817+
2818+
if (model_->n_iter != NULL)
28172819
for(int i=0;i<labels;i++)
28182820
n_iter[i] = model_->n_iter[i];
28192821
}

sklearn/svm/tests/test_svm.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,14 @@ def test_consistent_proba():
674674
assert_array_almost_equal(proba_1, proba_2)
675675

676676

677+
def test_linear_svc_convergence_warnings():
678+
"""Test that warnings are raised if model does not converge"""
679+
680+
lsvc = svm.LinearSVC(max_iter=2)
681+
assert_warns(ConvergenceWarning, lsvc.fit, X, Y)
682+
assert_equal(lsvc.n_iter_, 2)
683+
684+
677685
if __name__ == '__main__':
678686
import nose
679687
nose.runmodule()

sklearn/tests/test_common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,6 @@ def test_transformer_n_iter():
343343
# Dependent on external solvers and hence accessing the iter
344344
# param is non-trivial.
345345
external_solver = ['Isomap', 'KernelPCA', 'LocallyLinearEmbedding',
346-
'RandomizedLasso','LogisticRegressionCV']
346+
'RandomizedLasso', 'LogisticRegressionCV']
347347
if hasattr(estimator, "max_iter") and name not in external_solver:
348348
yield check_transformer_n_iter, name, estimator

0 commit comments

Comments
 (0)