Search code examples
pythonmachine-learningscikit-learndata-processing

Scikit-learn fine-tuning: Postprocess predicted labels before evaluation


I was wondering whether there is a way to post-process predicted labels in sklearn. My training data has ground truth labels in the form 0, 1

However, the problem is I am currently using Isolation Forest, which predicts:

  • -1 for outliers, equivalent to ground-truth label 1
  • 1 for normal data, equivalent to ground-truth label 0

If I were to write a function to post-process the prediction, it would be very simple:

def process_anomaly_labels(raw_y_pred):
    y_pred = raw_y_pred.copy()
    y_pred[raw_y_pred == 1] = 0
    y_pred[raw_y_pred == -1] = 1
    return y_pred

But I don't know how to postprocess prediction labels when I finetune the model using RandomSearchCV:

from sklearn.model_selection import RandomizedSearchCV
# fine tuning
forest_params = {
    "n_estimators": [50, 200, 800],
    "max_samples": [1000, 4000, 16000, 64000, 120000],
    "max_features": [1, 5, 15, 30],
    "contamination": [0.001, 0.1, 0.2, 0.5]
}
forest_grid_search = RandomizedSearchCV(
    IsolationForest(),
    param_distributions=forest_params,
    scoring="f1",
    n_jobs=8,
    n_iter=50,
    cv=3,
    verbose=2
)
forest_grid_search.fit(X_train_trans, y_train)

I cannot convert the ground truth labels to match with predicted labels, because I would like to use binary F1 score when evaluating.


Solution

  • As suggested in the comments, write a custom scorer that performs the desired mapping.

    Sample code

    from sklearn.metrics import make_scorer, f1_score
    from sklearn.ensemble import IsolationForest
    from sklearn.datasets import make_blobs
    from sklearn.model_selection import RandomizedSearchCV
    import numpy as np
    
    def relabeled_f1_score(y_true, y_pred):
        y_pred_c = y_pred.copy()
        y_pred_c[y_pred_c == 1] = 0
        y_pred_c[y_pred_c == -1] = 1
        return f1_score(y_true=y_true, y_pred=y_pred_c)
    
    n_samples = 1000
    n_features = 40
    
    X, _ = make_blobs(n_samples=n_samples, n_features=n_features)
    y = np.random.choice([0, 1], n_samples)  # 1 = outlier, 0 = inliner
    
    param_grid = {
        "n_estimators": [50, 200, 800],
        "max_samples": [1000, 4000, 16000, 64000, 120000],
        "max_features": [1, 5, 15, 30],
        "contamination": [0.001, 0.1, 0.2, 0.5]
    }
    
    custom_scorer = make_scorer(score_func=relabeled_f1_score, greater_is_better=True)
    my_rs = RandomizedSearchCV(IsolationForest(), param_distributions=param_grid, scoring=custom_scorer, verbose=3)
    
    my_rs.fit(X, y)