27
$\begingroup$

I am trying to solve the regression task. I found out that 3 models are working nicely for different subsets of data: LassoLARS, SVR and Gradient Tree Boosting. I noticed that when I make predictions using all these 3 models and then make a table of 'true output' and outputs of my 3 models I see that each time at least one of the models is really close to the true output, though 2 others could be relatively far away.

When I compute minimal possible error (if I take prediction from 'best' predictor for each test example) I get a error which is much smaller than error of any model alone. So I thought about trying to combine predictions from these 3 diffent models into some kind of ensemble. Question is, how to do this properly? All my 3 models are build and tuned using scikit-learn, does it provide some kind of a method which could be used to pack models into ensemble? The problem here is that I don't want to just average predictions from all three models, I want to do this with weighting, where weighting should be determined based on properties of specific example.

Even if scikit-learn not provides such functionality, it would be nice if someone knows how to property address this task - of figuring out the weighting of each model for each example in data. I think that it might be done by a separate regressor built on top of all these 3 models, which will try output optimal weights for each of 3 models, but I am not sure if this is the best way of doing this.

$\endgroup$
32
$\begingroup$

Actually, scikit-learn does provide such a functionality, though it might be a bit tricky to implement. Here is a complete working example of such an average regressor built on top of three models. First of all, let's import all the required packages:

from sklearn.base import TransformerMixin
from sklearn.datasets import make_regression
from sklearn.pipeline import Pipeline, FeatureUnion
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.neighbors import KNeighborsRegressor
from sklearn.preprocessing import StandardScaler, PolynomialFeatures
from sklearn.linear_model import LinearRegression, Ridge

Then, we need to convert our three regressor models into transformers. This will allow us to merge their predicions into a single feature vector using FeatureUnion:

class RidgeTransformer(Ridge, TransformerMixin):

    def transform(self, X, *_):
        return self.predict(X)


class RandomForestTransformer(RandomForestRegressor, TransformerMixin):

    def transform(self, X, *_):
        return self.predict(X)


class KNeighborsTransformer(KNeighborsRegressor, TransformerMixin):

    def transform(self, X, *_):
        return self.predict(X)

Now, let's define a builder function for our frankenstein model:

def build_model():
    ridge_transformer = Pipeline(steps=[
        ('scaler', StandardScaler()),
        ('poly_feats', PolynomialFeatures()),
        ('ridge', RidgeTransformer())
    ])

    pred_union = FeatureUnion(
        transformer_list=[
            ('ridge', ridge_transformer),
            ('rand_forest', RandomForestTransformer()),
            ('knn', KNeighborsTransformer())
        ],
        n_jobs=2
    )

    model = Pipeline(steps=[
        ('pred_union', pred_union),
        ('lin_regr', LinearRegression())
    ])

    return model

Finally, let's fit the model:

print('Build and fit a model...')

model = build_model()

X, y = make_regression(n_features=10, n_targets=2)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

model.fit(X_train, y_train)
score = model.score(X_test, y_test)

print('Done. Score:', score)

Output:

Build and fit a model...
Done. Score: 0.9600413867438636

Why bother complicating things in such a way? Well, this approach allows us to optimize model hyperparameters using standard scikit-learn modules such as GridSearchCV or RandomizedSearchCV. Also, now it is possible to easily save and load from disk a pre-trained model.

$\endgroup$
  • $\begingroup$ When using this approach, is there a simple way to extract which algo is being used when/what fraction of each algo? $\endgroup$ – David Hagan Nov 6 '17 at 14:47
  • $\begingroup$ Perhaps looking at coefficients of the resulting linear model (model.named_steps['lin_regr'].coef_) will give you some insights into how much each model in an ensemble contributes to the final solution. $\endgroup$ – constt Nov 7 '17 at 12:00
  • $\begingroup$ @constt Wouldn't you need to use cross_val_predict in your base models? It seems like your top-level model would get an overoptimistic signal from your base models as this is currently implemented. $\endgroup$ – Brian Bien May 16 '18 at 1:04
  • 1
    $\begingroup$ This is just kinda proof-of-concept example, I didn't address a model selection here. I think such models should be optimized as a whole, i.e., optimizing hyper-parameters of all the built-in models simultaneously using the cross-validation approach. $\endgroup$ – constt May 16 '18 at 4:57
  • $\begingroup$ if we put n_targets=1 X, y = make_regression(n_features=10, n_targets=1) it gives dimension error. can anyone please explain what to do? $\endgroup$ – Mohit Yadav Apr 12 '19 at 21:36
9
$\begingroup$

Ok, after spending some time on googling I found out how I could do the weighting in python even with scikit-learn. Consider the below:

I train a set of my regression models (as mentioned SVR, LassoLars and GradientBoostingRegressor). Then I run all of them on training data (same data which was used for training of each of these 3 regressors). I get predictions for examples with each of my algorithms and save these 3 results into pandas dataframe with columns 'predictedSVR', 'predictedLASSO' and 'predictedGBR'. And I add the final column into this datafrane which I call 'predicted' which is a real prediction value.

Then I just train a linear regression on this new dataframe:

 #df - dataframe with results of 3 regressors and true output

 from sklearn linear_model
 stacker= linear_model.LinearRegression()
 stacker.fit(df[['predictedSVR', 'predictedLASSO', 'predictedGBR']], df['predicted'])

So when I want to make a prediction for new example I just run each of my 3 regressors separately and then I do:

 stacker.predict() 

on outputs of my 3 regressors. And get a result.

The problem here is that I am finding optimal weights for regressors 'on average, the weights will be same for each example on which I will try to make prediction.

If anyone has any ideas on how to do stacking (weighting) using the features of current example it would be nice to hear them.

$\endgroup$
  • $\begingroup$ Wow, I like very much this approach! But why did you used LinearRegression() instead of LogisticRegression() model? $\endgroup$ – harrison4 Mar 15 '17 at 23:27
  • 1
    $\begingroup$ @harrison4 because I was doing regression, not classification task? So I wanted to 'weight' output from each model. Anyway, this is a bad approach, good one is described here: stackoverflow.com/a/35170149/3633250 $\endgroup$ – Maksim Khaitovich Mar 15 '17 at 23:30
  • $\begingroup$ Yeah, sorry you're right! thanks for sharing the link! $\endgroup$ – harrison4 Mar 15 '17 at 23:35
5
$\begingroup$

If your data has obvious subsets you could run a clustering algorithm like k-means and then associate each classifier with the clusters it performs well on. When a new data point arrives, then determine what cluster it's in and run the associated classifier.

You could also use the inverse distances from the centroids to get a set of weights for each classifier and predict using a linear combination of all of the classifiers.

$\endgroup$
  • $\begingroup$ I found a paper tested this stategy (along with a comparison of some similar ideas): paper $\endgroup$ – anthonybell Mar 24 '15 at 14:37
  • $\begingroup$ Interesting idea, though requires lots of work in order to appy it. Thanks for paper! $\endgroup$ – Maksim Khaitovich Mar 26 '15 at 11:49
1
$\begingroup$

I accomplish a type of weighting by doing the following, once all your models are fully trained up and performing well:

  1. Run all your models on a large set of unseen testing data
  2. Store the f1 scores on the test set for each class, for each model
  3. When you predict with the ensemble, each model will give you the most likely class, so weight the confidence or probability by the f1 score for that model on that class. If you're dealing with distance (as in SVM, for example), just normalize the distances to get a general confidence, and then proceed with the per class f1 weighting.

You can further tune your ensemble by taking measure of percent correct over some time. Once you have a significantly large, new data set scored, you can plot threshold in steps of 0.1, for instance, against percent correct if using that threshold to score, to get an idea of what threshold will give you, say, 95% correct for class 1, and so on. You can keep updating the test set and f1 scores as new data come in and keep track of drift, rebuilding the models when thresholds or accuracy fall.

$\endgroup$
  • 1
    $\begingroup$ That's interesting, but it works only for classification tasks, as far as I see, while I am trying to solve regression task. Thus I can't compute F1 score. $\endgroup$ – Maksim Khaitovich Feb 28 '15 at 17:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.