Could anyone suggest an elegant way to eliminate the redundant reruns in a pipeline. The example below shows that MyLogistic.fit() is called 37 times (36 for the CV + 1 on the whole data set). For instance, if we take the hyperparameters: (0.2, 1, 0.01) (0.2, 1, 0.5) then it's not necessary to rerun the first step, MyLogistic, with C = 0.2, because its result will be the same (let's ignore the random seed issue). The same reasoning applies to any combination of the first k hyperparameters: once we got the result from the first k steps, we can recycle it.
Thanks to Ben's suggestion, I used the "memory" argument in Pipeline and the count of calls got reduced to 12 (13). Apparently that's because there are 4 combinations of select_k* parameters, times 3 folds. But, for C = 0.02 there is no need to call MyLogistic() twice for max_features 1 and 2. Is there a more or less elegant way to avoid those redundant calls also?
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import (GridSearchCV, StratifiedKFold)
from sklearn.feature_selection import SelectFromModel
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_iris
import numpy as np
class MyLogistic(LogisticRegression):
__call_counter = 0
def fit(self, X, y, sample_weight=None):
print("MyLogistic fit is called.")
MyLogistic._MyLogistic__call_counter += 1
# fit() returns self.
return super().fit(X, y, sample_weight)
# If I use this "extension", everything works fine.
#class MyLogistic(LogisticRegression):
# pass
initial_logistic = MyLogistic(solver="liblinear", random_state = np.random.RandomState(18))
final_logistic = LogisticRegression(solver="liblinear", random_state = np.random.RandomState(20))
# prefit = False by default
select_best = SelectFromModel(estimator = initial_logistic, threshold = -np.inf)
select_k_best_pipeline = Pipeline(steps=[
('first_scaler', StandardScaler(with_mean = False)),
# initial_logistic will be called from select_best, prefit = false by default.
('select_k_best', select_best),
('final_logit', final_logistic)
])
select_best_grid = {'select_k_best__estimator__C' : [0.02, 0.03],
'select_k_best__max_features': [1, 2],
'final_logit__C' : [0.01, 0.5, 1.0]}
skf = StratifiedKFold(n_splits = 3, shuffle = True, random_state = 17)
# Use n_jobs = 1 to get the correct count of calls
logit_best_searcher = GridSearchCV(estimator = select_k_best_pipeline, param_grid = select_best_grid, cv = skf,
scoring = "roc_auc", n_jobs = 1, verbose = 4)
X, y = load_iris(return_X_y=True)
logit_best_searcher.fit(X, y > 0)
print("Best hyperparams: ", logit_best_searcher.best_params_)
print("The count of calls is: ", MyLogistic._MyLogistic__call_counter)