@@ -36,6 +36,10 @@ class SequentialFeatureSelector(SelectorMixin, MetaEstimatorMixin,
36
36
to select. If float between 0 and 1, it is the fraction of features to
37
37
select.
38
38
39
+ aborted_rate : float, default=None
40
+ The rate of increase score until the fitting is aborted. If `None`,
41
+ fitting is NOT aborted.
42
+
39
43
direction : {'forward', 'backward'}, default='forward'
40
44
Whether to perform forward selection or backward selection.
41
45
@@ -106,11 +110,12 @@ class SequentialFeatureSelector(SelectorMixin, MetaEstimatorMixin,
106
110
>>> sfs.transform(X).shape
107
111
(150, 3)
108
112
"""
109
- def __init__ (self , estimator , * , n_features_to_select = None ,
113
+ def __init__ (self , estimator , * , n_features_to_select = None , censored_rate = None ,
110
114
direction = 'forward' , scoring = None , cv = 5 , n_jobs = None ):
111
115
112
116
self .estimator = estimator
113
117
self .n_features_to_select = n_features_to_select
118
+ self .censored_rate = censored_rate
114
119
self .direction = direction
115
120
self .scoring = scoring
116
121
self .cv = cv
@@ -175,18 +180,34 @@ def fit(self, X, y):
175
180
self .n_features_to_select_ if self .direction == 'forward'
176
181
else n_features - self .n_features_to_select_
177
182
)
178
- for _ in range (n_iterations ):
179
- new_feature_idx = self ._get_best_new_feature (cloned_estimator , X ,
180
- y , current_mask )
181
- current_mask [new_feature_idx ] = True
182
183
183
- if self .direction == 'backward' :
184
- current_mask = ~ current_mask
184
+ if self .censored_rate is None :
185
+ for _ in range (n_iterations ):
186
+ new_feature_idx , new_score = self ._get_best_new_feature_score (cloned_estimator , X ,
187
+ y , current_mask )
188
+ current_mask [new_feature_idx ] = True
189
+
190
+ if self .direction == 'backward' :
191
+ current_mask = ~ current_mask
192
+ else :
193
+ old_score = 0
194
+ for _ in range (n_iterations ):
195
+ new_feature_idx , new_score = self ._get_best_new_feature_score (cloned_estimator , X ,
196
+ y , current_mask )
197
+ if new_score < old_score * (1 + self .censored_rate ):
198
+ break
199
+
200
+ old_score = new_score
201
+ current_mask [new_f
BF44
eature_idx ] = True
202
+
203
+ if self .direction == 'backward' :
204
+ current_mask = ~ current_mask
205
+
185
206
self .support_ = current_mask
186
207
187
208
return self
188
209
189
- def _get_best_new_feature (self , estimator , X , y , current_mask ):
210
+ def _get_best_new_feature_score (self , estimator , X , y , current_mask ):
190
211
# Return the best new feature to add to the current_mask, i.e. return
191
212
# the best new feature to add (resp. remove) when doing forward
192
213
# selection (resp. backward selection)
@@ -201,7 +222,9 @@ def _get_best_new_feature(self, estimator, X, y, current_mask):
201
222
scores [feature_idx ] = cross_val_score (
202
223
estimator , X_new , y , cv = self .cv , scoring = self .scoring ,
203
224
n_jobs = self .n_jobs ).mean ()
204
- return max (scores , key = lambda feature_idx : scores [feature_idx ])
225
+
226
+ new_feature_idx = max (scores , key = lambda feature_idx : scores [feature_idx ])
227
+ return new_feature_idx , scores [new_feature_idx ]
205
228
206
229
def _get_support_mask (self ):
207
230
check_is_fitted (self )
0 commit comments