Skip to content
5 changes: 5 additions & 0 deletions doc/whats_new/v0.20.rst
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ Classifiers and regressors
error for prior list which summed to 1.
:issue:`10005` by :user:`Gaurav Dhingra <gxyd>`.

- Fixed a bug in :class:`linear_model.LogisticRegression` where when using the
parameter ``multi_class='multinomial'``, the ``predict_proba`` method was
returning incorrect probabilities in the case of binary outcomes.
:issue:`9939` by :user:`Roger Westover <rwolst>`.

- Fixed a bug in :class:`linear_model.OrthogonalMatchingPursuit` that was
broken when setting ``normalize=False``.
:issue:`10071` by `Alexandre Gramfort`_.
Expand Down
22 changes: 16 additions & 6 deletions sklearn/linear_model/logistic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1101,14 +1101,18 @@ class LogisticRegression(BaseEstimator, LinearClassifierMixin,
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 of shape (1, n_features) when the given problem is binary.
In particular, when `multi_class='multinomial'`, `coef_` corresponds
to outcome 1 (True) and `-coef_` corresponds to outcome 0 (False).

intercept_ : array, shape (1,) or (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.
`intercept_` is of shape(1,) when the problem is binary.
`intercept_` is of shape (1,) when the given problem is binary.
In particular, when `multi_class='multinomial'`, `intercept_`
corresponds to outcome 1 (True) and `-intercept_` corresponds to
outcome 0 (False).

n_iter_ : array, shape (n_classes,) or (1, )
Actual number of iterations for all classes. If binary or multinomial,
Expand Down Expand Up @@ -1332,11 +1336,17 @@ def predict_proba(self, X):
"""
if not hasattr(self, "coef_"):
raise NotFittedError("Call fit before prediction")
calculate_ovr = self.coef_.shape[0] == 1 or self.multi_class == "ovr"
if calculate_ovr:
if self.multi_class == "ovr":
return super(LogisticRegression, self)._predict_proba_lr(X)
else:
return softmax(self.decision_function(X), copy=False)
decision = self.decision_function(X)
if decision.ndim == 1:
# Workaround for multi_class="multinomial" and binary outcomes
# which requires softmax prediction with only a 1D decision.
decision_2d = np.c_[-decision, decision]
else:
decision_2d = decision
return softmax(decision_2d, copy=False)

def predict_log_proba(self, X):
"""Log of probability estimates.
Expand Down
17 changes: 17 additions & 0 deletions sklearn/linear_model/tests/test_logistic.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,23 @@ def test_multinomial_binary():
assert_greater(np.mean(pred == target), .9)


def test_multinomial_binary_probabilities():
# Test multinomial LR gives expected probabilities based on the
# decision function, for a binary problem.
X, y = make_classification()
clf = LogisticRegression(multi_class='multinomial', solver='saga')
clf.fit(X, y)

decision = clf.decision_function(X)
proba = clf.predict_proba(X)

expected_proba_class_1 = (np.exp(decision) /
(np.exp(decision) + np.exp(-decision)))
expected_proba = np.c_[1-expected_proba_class_1, expected_proba_class_1]

assert_almost_equal(proba, expected_proba)


def test_sparsify():
# Test sparsify and densify members.
n_samples, n_features = iris.data.shape
Expand Down