|
11 | 11 | from copy import deepcopy |
12 | 12 | from scipy.special import expit |
13 | 13 | from sklearn.base import ClassifierMixin |
| 14 | +from sklearn.multioutput import MultiOutputRegressor |
14 | 15 | from sklearn.preprocessing import StandardScaler |
15 | 16 |
|
16 | 17 |
|
@@ -81,6 +82,7 @@ def __init__( |
81 | 82 | self.type_fit = "classification" |
82 | 83 | self.obj = obj |
83 | 84 | self.fit_objs_ = {} |
| 85 | + self.multioutput_model_ = None |
84 | 86 | self.X_scaler_ = StandardScaler() |
85 | 87 | self.scaled_X_ = None |
86 | 88 |
|
@@ -115,21 +117,37 @@ def fit(self, X, y, sample_weight=None, **kwargs): |
115 | 117 | # multitask response |
116 | 118 | Y = mo.one_hot_encode2(y, self.n_classes_) |
117 | 119 |
|
| 120 | + # Try MultiOutputRegressor first (more efficient) |
118 | 121 | try: |
119 | | - for i in range(self.n_classes_): |
120 | | - self.fit_objs_[i] = deepcopy( |
121 | | - self.obj.fit( |
122 | | - self.scaled_X_, |
123 | | - Y[:, i], |
124 | | - sample_weight=sample_weight, |
125 | | - **kwargs |
126 | | - ) |
127 | | - ) |
128 | | - except Exception as e: |
129 | | - for i in range(self.n_classes_): |
130 | | - self.fit_objs_[i] = deepcopy( |
131 | | - self.obj.fit(self.scaled_X_, Y[:, i], **kwargs) |
| 122 | + self.multioutput_model_ = MultiOutputRegressor(deepcopy(self.obj)) |
| 123 | + try: |
| 124 | + self.multioutput_model_.fit( |
| 125 | + self.scaled_X_, |
| 126 | + Y, |
| 127 | + sample_weight=sample_weight, |
| 128 | + **kwargs |
132 | 129 | ) |
| 130 | + except TypeError: |
| 131 | + # If sample_weight not supported, try without it |
| 132 | + self.multioutput_model_.fit(self.scaled_X_, Y, **kwargs) |
| 133 | + except Exception: |
| 134 | + # Fallback: fit separate models for each class |
| 135 | + self.multioutput_model_ = None |
| 136 | + try: |
| 137 | + for i in range(self.n_classes_): |
| 138 | + self.fit_objs_[i] = deepcopy( |
| 139 | + self.obj.fit( |
| 140 | + self.scaled_X_, |
| 141 | + Y[:, i], |
| 142 | + sample_weight=sample_weight, |
| 143 | + **kwargs |
| 144 | + ) |
| 145 | + ) |
| 146 | + except TypeError: |
| 147 | + for i in range(self.n_classes_): |
| 148 | + self.fit_objs_[i] = deepcopy( |
| 149 | + self.obj.fit(self.scaled_X_, Y[:, i], **kwargs) |
| 150 | + ) |
133 | 151 | return self |
134 | 152 |
|
135 | 153 | def predict(self, X, **kwargs): |
@@ -169,28 +187,43 @@ def predict_proba(self, X, **kwargs): |
169 | 187 |
|
170 | 188 | shape_X = X.shape |
171 | 189 |
|
172 | | - probs = np.zeros((shape_X[0], self.n_classes_)) |
173 | | - |
174 | | - if len(shape_X) == 1: # one example |
175 | | - n_features = shape_X[0] |
176 | | - |
177 | | - new_X = mo.rbind( |
178 | | - X.reshape(1, n_features), |
179 | | - np.ones(n_features).reshape(1, n_features), |
180 | | - ) |
| 190 | + if self.multioutput_model_ is not None: |
| 191 | + # Use MultiOutputRegressor for prediction |
| 192 | + if len(shape_X) == 1: # one example |
| 193 | + n_features = shape_X[0] |
| 194 | + new_X = mo.rbind( |
| 195 | + X.reshape(1, n_features), |
| 196 | + np.ones(n_features).reshape(1, n_features), |
| 197 | + ) |
| 198 | + Z = self.X_scaler_.transform(new_X, **kwargs) |
| 199 | + probs = self.multioutput_model_.predict(Z, **kwargs)[:1, :] |
| 200 | + else: # multiple rows |
| 201 | + Z = self.X_scaler_.transform(X, **kwargs) |
| 202 | + probs = self.multioutput_model_.predict(Z, **kwargs) |
| 203 | + else: |
| 204 | + # Use separate models for each class |
| 205 | + probs = np.zeros((shape_X[0], self.n_classes_)) |
| 206 | + |
| 207 | + if len(shape_X) == 1: # one example |
| 208 | + n_features = shape_X[0] |
| 209 | + |
| 210 | + new_X = mo.rbind( |
| 211 | + X.reshape(1, n_features), |
| 212 | + np.ones(n_features).reshape(1, n_features), |
| 213 | + ) |
181 | 214 |
|
182 | | - Z = self.X_scaler_.transform(new_X, **kwargs) |
| 215 | + Z = self.X_scaler_.transform(new_X, **kwargs) |
183 | 216 |
|
184 | | - # Fallback to standard model |
185 | | - for i in range(self.n_classes_): |
186 | | - probs[:, i] = self.fit_objs_[i].predict(Z, **kwargs)[0] |
| 217 | + # Fallback to standard model |
| 218 | + for i in range(self.n_classes_): |
| 219 | + probs[:, i] = self.fit_objs_[i].predict(Z, **kwargs)[0] |
187 | 220 |
|
188 | | - else: # multiple rows |
189 | | - Z = self.X_scaler_.transform(X, **kwargs) |
| 221 | + else: # multiple rows |
| 222 | + Z = self.X_scaler_.transform(X, **kwargs) |
190 | 223 |
|
191 | | - # Fallback to standard model |
192 | | - for i in range(self.n_classes_): |
193 | | - probs[:, i] = self.fit_objs_[i].predict(Z, **kwargs) |
| 224 | + # Fallback to standard model |
| 225 | + for i in range(self.n_classes_): |
| 226 | + probs[:, i] = self.fit_objs_[i].predict(Z, **kwargs) |
194 | 227 |
|
195 | 228 | expit_raw_probs = expit(probs) |
196 | 229 |
|
|
0 commit comments