How to train an anomaly detector in scikit-learn

Operational datasets often contain rare rows that do not follow the pattern learned from the rest of the sample. scikit-learn can train an unsupervised anomaly detector for that screening job when the rows have numeric features and the workflow needs a repeatable way to mark likely outliers.

The IsolationForest estimator isolates unusual observations through random tree splits instead of requiring class labels. Setting contamination defines the expected outlier share for thresholding, while random_state keeps the small smoke run reproducible.

Start with a controlled dataset before pointing the detector at production rows. The fitted estimator returns 1 for inliers and -1 for outliers, and decision_function() returns negative values for rows on the outlier side of the learned threshold.

Steps to train an anomaly detector with scikit-learn:

  1. Save a controlled anomaly-detection script.
    train_anomaly_detector.py
    import numpy as np
    from sklearn.ensemble import IsolationForest
     
    row_names = np.array(
        [
            "baseline-01",
            "baseline-02",
            "baseline-03",
            "baseline-04",
            "baseline-05",
            "baseline-06",
            "spike-01",
        ]
    )
     
    readings = np.array(
        [
            [10.0, 200.0],
            [10.4, 198.0],
            [9.8, 202.0],
            [10.2, 201.0],
            [10.1, 199.0],
            [10.5, 203.0],
            [42.0, 410.0],
        ]
    )
     
    detector = IsolationForest(contamination=0.15, random_state=42)
    detector.fit(readings)
     
    labels = detector.predict(readings)
    scores = detector.decision_function(readings)
     
    print("Training rows")
    for row_name, label, score in zip(row_names, labels, scores):
        state = "outlier" if label == -1 else "inlier"
        print(f"{row_name:11s} {state:7s} label={label:2d} score={score: .3f}")
     
    outliers = row_names[labels == -1]
    print(f"Detected training outliers: {', '.join(outliers)}")
     
    new_rows = np.array(
        [
            [10.3, 200.5],
            [35.0, 390.0],
        ]
    )
    new_names = np.array(["incoming-normal", "incoming-spike"])
    new_labels = detector.predict(new_rows)
    new_scores = detector.decision_function(new_rows)
     
    print("\nNew rows")
    for row_name, label, score in zip(new_names, new_labels, new_scores):
        state = "outlier" if label == -1 else "inlier"
        print(f"{row_name:15s} {state:7s} label={label:2d} score={score: .3f}")
     
    assert "spike-01" in outliers
    assert "incoming-spike" in new_names[new_labels == -1]
    print("\nSmoke check passed: expected spike rows are outliers")

    Replace the sample array with numeric features from the same measurement window or business process. Encode categorical fields and impute missing numeric values before fitting the detector.

  2. Run the training script.
    $ python3 train_anomaly_detector.py
    Training rows
    baseline-01 inlier  label= 1 score= 0.114
    baseline-02 inlier  label= 1 score= 0.052
    baseline-03 inlier  label= 1 score= 0.053
    baseline-04 inlier  label= 1 score= 0.123
    baseline-05 inlier  label= 1 score= 0.111
    baseline-06 inlier  label= 1 score= 0.031
    spike-01    outlier label=-1 score=-0.280
    Detected training outliers: spike-01
    
    New rows
    incoming-normal inlier  label= 1 score= 0.110
    incoming-spike  outlier label=-1 score=-0.206
    
    Smoke check passed: expected spike rows are outliers

    The training row and incoming row with spike-sized values both return label=-1 and negative scores. Adjust contamination to the review rate the dataset can support instead of tuning it only until a specific row changes labels.