|
28 | 28 | from ..utils import check_consistent_length
|
29 | 29 | from ..utils import compute_sample_weight
|
30 | 30 | from ..utils import column_or_1d
|
| 31 | +from ..utils.validation import check_is_fitted |
31 | 32 | from ..utils.validation import _check_sample_weight
|
32 | 33 | from ..preprocessing import LabelBinarizer
|
33 | 34 | from ..model_selection import GridSearchCV
|
@@ -1010,7 +1011,93 @@ def fit(self, X, y, sample_weight=None):
|
1010 | 1011 | return super().fit(X, y, sample_weight=sample_weight)
|
1011 | 1012 |
|
1012 | 1013 |
|
1013 |
| -class RidgeClassifier(LinearClassifierMixin, _BaseRidge): |
| 1014 | +class _RidgeClassifierMixin(LinearClassifierMixin): |
| 1015 | + def _prepare_data(self, X, y, sample_weight, solver): |
| 1016 | + """Validate `X` and `y` and binarize `y`. |
| 1017 | +
|
| 1018 | + Parameters |
| 1019 | + ---------- |
| 1020 | + X : {ndarray, sparse matrix} of shape (n_samples, n_features) |
| 1021 | + Training data. |
| 1022 | +
|
| 1023 | + y : ndarray of shape (n_samples,) |
| 1024 | + Target values. |
| 1025 | +
|
| 1026 | + sample_weight : float or ndarray of shape (n_samples,), default=None |
| 1027 | + Individual weights for each sample. If given a float, every sample |
| 1028 | + will have the same weight. |
| 1029 | +
|
| 1030 | + solver : str |
| 1031 | + The solver used in `Ridge` to know which sparse format to support. |
| 1032 | +
|
| 1033 | + Returns |
| 1034 | + ------- |
| 1035 | + X : {ndarray, sparse matrix} of shape (n_samples, n_features) |
| 1036 | + Validated training data. |
| 1037 | +
|
| 1038 | + y : ndarray of shape (n_samples,) |
| 1039 | + Validated target values. |
| 1040 | +
|
| 1041 | + sample_weight : ndarray of shape (n_samples,) |
| 1042 | + Validated sample weights. |
| 1043 | +
|
| 1044 | + Y : ndarray of shape (n_samples, n_classes) |
| 1045 | + The binarized version of `y`. |
| 1046 | + """ |
| 1047 | + accept_sparse = _get_valid_accept_sparse(sparse.issparse(X), solver) |
| 1048 | + X, y = self._validate_data( |
| 1049 | + X, |
| 1050 | + y, |
| 1051 | + accept_sparse=accept_sparse, |
| 1052 | + multi_output=True, |
| 1053 | + y_numeric=False, |
| 1054 | + ) |
| 1055 | + |
| 1056 | + self._label_binarizer = LabelBinarizer(pos_label=1, neg_label=-1) |
| 1057 | + Y = self._label_binarizer.fit_transform(y) |
| 1058 | + if not self._label_binarizer.y_type_.startswith("multilabel"): |
| 1059 | + y = column_or_1d(y, warn=True) |
| 1060 | + |
| 1061 | + sample_weight = _check_sample_weight(sample_weight, X, dtype=X.dtype) |
| 1062 | + if self.class_weight: |
| 1063 | + sample_weight = sample_weight * compute_sample_weight(self.class_weight, y) |
| 1064 | + return X, y, sample_weight, Y |
| 1065 | + |
| 1066 | + def predict(self, X): |
| 1067 | + """Predict class labels for samples in `X`. |
| 1068 | +
|
| 1069 | + Parameters |
| 1070 | + ---------- |
| 1071 | + X : {array-like, spare matrix} of shape (n_samples, n_features) |
| 1072 | + The data matrix for which we want to predict the targets. |
| 1073 | +
|
| 1074 | + Returns |
| 1075 | + ------- |
| 1076 | + y_pred : ndarray of shape (n_samples,) or (n_samples, n_outputs) |
| 1077 | + Vector or matrix containing the predictions. In binary and |
| 1078 | + multiclass problems, this is a vector containing `n_samples`. In |
| 1079 | + a multilabel problem, it returns a matrix of shape |
| 1080 | + `(n_samples, n_outputs)`. |
| 1081 | + """ |
| 1082 | + check_is_fitted(self, attributes=["_label_binarizer"]) |
| 1083 | + if self._label_binarizer.y_type_.startswith("multilabel"): |
| 1084 | + # Threshold such that the negative label is -1 and positive label |
| 1085 | + # is 1 to use the inverse transform of the label binarizer fitted |
| 1086 | + # during fit. |
| 1087 | + scores = 2 * (self.decision_function(X) > 0) - 1 |
| 1088 | + return self._label_binarizer.inverse_transform(scores) |
| 1089 | + return super().predict(X) |
| 1090 | + |
| 1091 | + @property |
| 1092 | + def classes_(self): |
| 1093 | + """Classes labels.""" |
| 1094 | + return self._label_binarizer.classes_ |
| 1095 | + |
| 1096 | + def _more_tags(self): |
| 1097 | + return {"multilabel": True} |
| 1098 | + |
| 1099 | + |
| 1100 | +class RidgeClassifier(_RidgeClassifierMixin, _BaseRidge): |
1014 | 1101 | """Classifier using Ridge regression.
|
1015 | 1102 |
|
1016 | 1103 | This classifier first converts the target values into ``{-1, 1}`` and
|
@@ -1096,7 +1183,7 @@ class RidgeClassifier(LinearClassifierMixin, _BaseRidge):
|
1096 | 1183 | .. versionadded:: 0.17
|
1097 | 1184 | Stochastic Average Gradient descent solver.
|
1098 | 1185 | .. versionadded:: 0.19
|
1099 |
| - SAGA solver. |
| 1186 | + SAGA solver. |
1100 | 1187 |
|
1101 | 1188 | - 'lbfgs' uses L-BFGS-B algorithm implemented in
|
1102 | 1189 | `scipy.optimize.minimize`. It can be used only when `positive`
|
@@ -1203,42 +1290,18 @@ def fit(self, X, y, sample_weight=None):
|
1203 | 1290 | will have the same weight.
|
1204 | 1291 |
|
1205 | 1292 | .. versionadded:: 0.17
|
1206 |
| - *sample_weight* support to Classifier. |
| 1293 | + *sample_weight* support to RidgeClassifier. |
1207 | 1294 |
|
1208 | 1295 | Returns
|
1209 | 1296 | -------
|
1210 | 1297 | self : object
|
1211 | 1298 | Instance of the estimator.
|
1212 | 1299 | """
|
1213 |
| - _accept_sparse = _get_valid_accept_sparse(sparse.issparse(X), self.solver) |
1214 |
| - X, y = self._validate_data( |
1215 |
| - X, y, accept_sparse=_accept_sparse, multi_output=True, y_numeric=False |
1216 |
| - ) |
1217 |
| - sample_weight = _check_sample_weight(sample_weight, X, dtype=X.dtype) |
1218 |
| - |
1219 |
| - self._label_binarizer = LabelBinarizer(pos_label=1, neg_label=-1) |
1220 |
| - Y = self._label_binarizer.fit_transform(y) |
1221 |
| - if not self._label_binarizer.y_type_.startswith("multilabel"): |
1222 |
| - y = column_or_1d(y, warn=True) |
1223 |
| - else: |
1224 |
| - # we don't (yet) support multi-label classification in Ridge |
1225 |
| - raise ValueError( |
1226 |
| - "%s doesn't support multi-label classification" |
1227 |
| - % (self.__class__.__name__) |
1228 |
| - ) |
1229 |
| - |
1230 |
| - if self.class_weight: |
1231 |
| - # modify the sample weights with the corresponding class weight |
1232 |
| - sample_weight = sample_weight * compute_sample_weight(self.class_weight, y) |
| 1300 | + X, y, sample_weight, Y = self._prepare_data(X, y, sample_weight, self.solver) |
1233 | 1301 |
|
1234 | 1302 | super().fit(X, Y, sample_weight=sample_weight)
|
1235 | 1303 | return self
|
1236 | 1304 |
|
1237 |
| - @property |
1238 |
| - def classes_(self): |
1239 |
| - """Classes labels.""" |
1240 |
| - return self._label_binarizer.classes_ |
1241 |
| - |
1242 | 1305 |
|
1243 | 1306 | def _check_gcv_mode(X, gcv_mode):
|
1244 | 1307 | possible_gcv_modes = [None, "auto", "svd", "eigen"]
|
@@ -2145,7 +2208,7 @@ class RidgeCV(MultiOutputMixin, RegressorMixin, _BaseRidgeCV):
|
2145 | 2208 | """
|
2146 | 2209 |
|
2147 | 2210 |
|
2148 |
| -class RidgeClassifierCV(LinearClassifierMixin, _BaseRidgeCV): |
| 2211 | +class RidgeClassifierCV(_RidgeClassifierMixin, _BaseRidgeCV): |
2149 | 2212 | """Ridge classifier with built-in cross-validation.
|
2150 | 2213 |
|
2151 | 2214 | See glossary entry for :term:`cross-validation estimator`.
|
@@ -2318,46 +2381,26 @@ def fit(self, X, y, sample_weight=None):
|
2318 | 2381 | self : object
|
2319 | 2382 | Fitted estimator.
|
2320 | 2383 | """
|
2321 |
| - X, y = self._validate_data( |
2322 |
| - X, |
2323 |
| - y, |
2324 |
| - accept_sparse=["csr", "csc", "coo"], |
2325 |
| - multi_output=True, |
2326 |
| - y_numeric=False, |
2327 |
| - ) |
2328 |
| - sample_weight = _check_sample_weight(sample_weight, X, dtype=X.dtype) |
2329 |
| - |
2330 |
| - self._label_binarizer = LabelBinarizer(pos_label=1, neg_label=-1) |
2331 |
| - Y = self._label_binarizer.fit_transform(y) |
2332 |
| - if not self._label_binarizer.y_type_.startswith("multilabel"): |
2333 |
| - y = column_or_1d(y, warn=True) |
2334 |
| - |
2335 |
| - if self.class_weight: |
2336 |
| - # modify the sample weights with the corresponding class weight |
2337 |
| - sample_weight = sample_weight * compute_sample_weight(self.class_weight, y) |
2338 |
| - |
| 2384 | + # `RidgeClassifier` does not accept "sag" or "saga" solver and thus support |
| 2385 | + # csr, csc, and coo sparse matrices. By using solver="eigen" we force to accept |
| 2386 | + # all sparse format. |
| 2387 | + X, y, sample_weight, Y = self._prepare_data(X, y, sample_weight, solver="eigen") |
| 2388 | + |
| 2389 | + # If cv is None, gcv mode will be used and we used the binarized Y |
| 2390 | + # since y will not be binarized in _RidgeGCV estimator. |
| 2391 | + # If cv is not None, a GridSearchCV with some RidgeClassifier |
| 2392 | + # estimators are used where y will be binarized. Thus, we pass y |
| 2393 | + # instead of the binarized Y. |
2339 | 2394 | target = Y if self.cv is None else y
|
2340 |
| - _BaseRidgeCV.fit(self, X, target, sample_weight=sample_weight) |
| 2395 | + super().fit(X, target, sample_weight=sample_weight) |
2341 | 2396 | return self
|
2342 | 2397 |
|
2343 |
| - @property |
2344 |
| - def classes_(self): |
2345 |
| - """Classes labels.""" |
2346 |
| - return self._label_binarizer.classes_ |
2347 |
| - |
2348 | 2398 | def _more_tags(self):
|
2349 | 2399 | return {
|
2350 | 2400 | "multilabel": True,
|
2351 | 2401 | "_xfail_checks": {
|
2352 | 2402 | "check_sample_weights_invariance": (
|
2353 | 2403 | "zero sample_weight is not equivalent to removing samples"
|
2354 | 2404 | ),
|
2355 |
| - # FIXME: see |
2356 |
| - # https://github.com/scikit-learn/scikit-learn/issues/19858 |
2357 |
| - # to track progress to resolve this issue |
2358 |
| - "check_classifiers_multilabel_output_format_predict": ( |
2359 |
| - "RidgeClassifierCV.predict outputs an array of shape (25,) " |
2360 |
| - "instead of (25, 5)" |
2361 |
| - ), |
2362 | 2405 | },
|
2363 | 2406 | }
|
0 commit comments