8000 [MRG+2] LogisticRegression convert to float64 (newton-cg) (#8835) · AishwaryaRK/scikit-learn@5ab17a4 · GitHub 8000
[go: up one dir, main page]

Skip to content

Commit 5ab17a4

Browse files
massichAishwaryaRK
authored andcommitted
[MRG+2] LogisticRegression convert to float64 (newton-cg) (scikit-learn#8835)
* Add a test to ensure not changing the input's data type Test that np.float32 input data is not cast to np.float64 when using LR + newton-cg * [WIP] Force X to remain float32. (self.coef_ remains float64 even if X is not) * [WIP] ensure self.coef_ same type as X * keep the np.float32 when multi_class='multinomial' * Avoid hardcoded type for multinomial * pass flake8 * Ensure that the results in 32bits are the same as in 64 * Address Gael's comments for multi_class=='ovr' * Add multi_class=='multinominal' to test * Add support for multi_class=='multinominal' * prefer float64 to float32 * Force X and y to have the same type * Revert "Add support for multi_class=='multinominal'" This reverts commit 4ac33e8. * remvert more stuff * clean up some commmented code * allow class_weight to take advantage of float32 * Add a test where X.dtype is different of y.dtype * Address @raghavrv comments * address the rest of @raghavrv's comments * Revert class_weight * Avoid copying if dtype matches * Address alex comment to the cast from inside _multinomial_loss_grad * address alex comment * add sparsity test * Addressed Tom comment of checking that we keep the 64 aswell
1 parent 3e72a6e commit 5ab17a4

File tree

2 files changed

+44
-8
lines changed

2 files changed

+44
-8
lines changed

sklearn/linear_model/logistic.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,8 @@ def _multinomial_loss_grad(w, X, Y, alpha, sample_weight):
338338
n_classes = Y.shape[1]
339339
n_features = X.shape[1]
340340
fit_intercept = (w.size == n_classes * (n_features + 1))
341-
grad = np.zeros((n_classes, n_features + bool(fit_intercept)))
341+
grad = np.zeros((n_classes, n_features + bool(fit_intercept)),
342+
dtype=X.dtype)
342343
loss, p, w = _multinomial_loss(w, X, Y, alpha, sample_weight)
343344
sample_weight = sample_weight[:, np.newaxis]
344345
diff = sample_weight * (p - Y)
@@ -609,10 +610,10 @@ def logistic_regression_path(X, y, pos_class=None, Cs=10, fit_intercept=True,
609610
# and check length
610611
# Otherwise set them to 1 for all examples
611612
if sample_weight is not None:
612-
sample_weight = np.array(sample_weight, dtype=np.float64, order='C')
613+
sample_weight = np.array(sample_weight, dtype=X.dtype, order='C')
613614
check_consistent_length(y, sample_weight)
614615
else:
615-
sample_weight = np.ones(X.shape[0])
616+
sample_weight = np.ones(X.shape[0], dtype=X.dtype)
616617

617618
# If class_weights is a dict (provided by the user), the weights
618619
# are assigned to the original labels. If it is "balanced", then
@@ -625,10 +626,10 @@ def logistic_regression_path(X, y, pos_class=None, Cs=10, fit_intercept=True,
625626
# For doing a ovr, we need to mask the labels first. for the
626627
# multinomial case this is not necessary.
627628
if multi_class == 'ovr':
628-
w0 = np.zeros(n_features + int(fit_intercept))
629+
w0 = np.zeros(n_features + int(fit_intercept), dtype=X.dtype)
629630
mask_classes = np.array([-1, 1])
630631
mask = (y == pos_class)
631-
y_bin = np.ones(y.shape, dtype=np.float64)
632+
y_bin = np.ones(y.shape, dtype=X.dtype)
632633
y_bin[~mask] = -1.
633634
# for compute_class_weight
634635

@@ -646,10 +647,10 @@ def logistic_regression_path(X, y, pos_class=None, Cs=10, fit_intercept=True,
646647
else:
647648
# SAG multinomial solver needs LabelEncoder, not LabelBinarizer
648649
le = LabelEncoder()
649-
Y_multi = le.fit_transform(y)
650+
Y_multi = le.fit_transform(y).astype(X.dtype, copy=False)
650651

651652
w0 = np.zeros((classes.size, n_features + int(fit_intercept)),
652-
order='F')
653+
order='F', dtype=X.dtype)
653654

654655
if coef is not None:
655656
# it must work both giving the bias term and not
@@ -1204,7 +1205,12 @@ def fit(self, X, y, sample_weight=None):
12041205
raise ValueError("Tolerance for stopping criteria must be "
12051206
"positive; got (tol=%r)" % self.tol)
12061207

1207-
X, y = check_X_y(X, y, accept_sparse='csr', dtype=np.float64,
1208+
if self.solver in ['newton-cg']:
1209+
_dtype = [np.float64, np.float32]
1210+
else:
1211+
_dtype = np.float64
1212+
1213+
X, y = check_X_y(X, y, accept_sparse='csr', dtype=_dtype,
12081214
order="C")
12091215
check_classification_targets(y)
12101216
self.classes_ = np.unique(y)

sklearn/linear_model/tests/test_logistic.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,3 +1129,33 @@ def test_saga_vs_liblinear():
11291129
liblinear.fit(X, y)
11301130
# Convergence for alpha=1e-3 is very slow
11311131
assert_array_almost_equal(saga.coef_, liblinear.coef_, 3)
1132+
1133+
1134+
def test_dtype_match():
1135+
# Test that np.float32 input data is not cast to np.float64 when possible
1136+
1137+
X_32 = np.array(X).astype(np.float32)
1138+
y_32 = np.array(Y1).astype(np.float32)
1139+
X_64 = np.array(X).astype(np.float64)
1140+
y_64 = np.array(Y1).astype(np.float64)
1141+
X_sparse_32 = sp.csr_matrix(X, dtype=np.float32)
1142+
1143+
for solver in ['newton-cg']:
1144+
for multi_class in ['ovr', 'multinomial']:
1145+
1146+
# Check type consistency
1147+
lr_32 = LogisticRegression(solver=solver, multi_class=multi_class)
1148+
lr_32.fit(X_32, y_32)
1149+
assert_equal(lr_32.coef_.dtype, X_32.dtype)
1150+
1151+
# check consistency with sparsity
1152+
lr_32_sparse = LogisticRegression(solver=solver,
1153+
multi_class=multi_class)
1154+
lr_32_sparse.fit(X_sparse_32, y_32)
1155+
assert_equal(lr_32_sparse.coef_.dtype, X_sparse_32.dtype)
1156+
1157+
# Check accuracy consistency
1158+
lr_64 = LogisticRegression(solver=solver, multi_class=multi_class)
1159+
lr_64.fit(X_64, y_64)
1160+
assert_equal(lr_64.coef_.dtype, X_64.dtype)
1161+
assert_almost_equal(lr_32.coef_, lr_64.coef_.astype(np.float32))

0 commit comments

Comments
 (0)
0