How to Reduce False Positives in Security Alerts with Machine Learning

By Charles Givre · June 3, 2026

machine learningSOCalert triagedata sciencesecurity operationsPython

A SOC does not have an alert problem. It has a false-positive problem. The detections fire, the queue fills, and analysts spend their day closing tickets that were never going to be incidents. Tuning rules helps, but rules are blunt: tighten one and you lose coverage, loosen it and the noise comes back.

Machine learning fits here, but not the way most vendors pitch it. You are not replacing your detection rules. You are adding a layer that ranks what they produce, so the alert most likely to be real sits at the top of the queue and the obvious noise sorts itself to the bottom. Framed correctly, this is a supervised learning problem with training data you already have.

The Data You Already Have

Every alert an analyst closed is a label. A ticket closed as a false positive is a negative example. An escalated or confirmed incident is a positive. Your SIEM, SOAR, or ticketing system has been generating this dataset for years.

The first task is extracting it. Pull historical alerts with their dispositions and build a feature table. Useful features are mostly metadata, not packet contents:

  • Rule identity: which detection fired, and that rule’s historical false-positive rate
  • Asset context: criticality of the destination host, whether the account is privileged
  • Temporal: hour of day, day of week, time since last alert on this entity
  • Correlation: count of related alerts on the same host or user in the last hour
  • Reputation: source IP ASN, whether the domain is newly registered
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.pipeline import Pipeline

# label: 1 = true positive (escalated/confirmed), 0 = false positive (closed benign)
cat = ['rule_name', 'asset_criticality', 'src_asn']
num = ['hour', 'related_alerts_1h', 'rule_historical_fp_rate', 'account_is_priv']

pre = ColumnTransformer([
    ('cat', OneHotEncoder(handle_unknown='ignore'), cat),
    ('num', StandardScaler(), num),
])

clf = Pipeline([
    ('pre', pre),
    ('model', GradientBoostingClassifier(n_estimators=300, max_depth=3,
                                         learning_rate=0.05)),
])
clf.fit(X_train, y_train)

Optimize for the Right Thing

Accuracy is the wrong metric. If 95% of alerts are false positives, a model that calls everything a false positive is 95% accurate and operationally useless, because it closes real attacks. The metric that matters is recall on true positives: of the alerts that were real, how many did the model keep?

Set the decision threshold deliberately. The default 0.5 cutoff is arbitrary. Use the precision-recall curve to find the probability threshold that holds recall at the level you can defend, then accept whatever precision that buys you:

from sklearn.metrics import precision_recall_curve
import numpy as np

probs = clf.predict_proba(X_test)[:, 1]
precision, recall, thresholds = precision_recall_curve(y_test, probs)

# Highest threshold that still keeps 99% of true positives
target_recall = 0.99
ok = recall[:-1] >= target_recall
chosen = thresholds[ok].max()
print(f"threshold={chosen:.3f}, "
      f"precision={precision[:-1][ok][np.argmax(thresholds[ok])]:.3f}")

In practice you do not auto-close anything above the threshold. You rank the queue by probability and auto-close only the lowest-risk band, with every auto-closure logged and a weekly sample audited. The model reorders work; analyst judgment still owns the high-confidence detections.

Collapse the Storms First

Before any classifier, the fastest win is deduplication, and it needs no labels. A vulnerability scanner tripping one rule across 400 hosts is one event presented as 400 tickets. Cluster the alert stream and the storm collapses into a single incident to review.

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import DBSCAN

text = (alerts['rule_name'] + ' ' + alerts['process_cmdline'].fillna('')).str.lower()
X = TfidfVectorizer(min_df=2).fit_transform(text)
alerts['incident'] = DBSCAN(eps=0.4, min_samples=3, metric='cosine').fit_predict(X)

Clustering does not judge true versus false positive. It removes the duplicate volume that drives most of the fatigue, which is why it is worth doing even before you train anything.

Don’t Create Blind Spots

The failure mode of automated suppression is silent loss of coverage. A model that learns to down-rank a noisy rule may be down-ranking the one technique an attacker is about to use. Guard against it by mapping every detection you suppress or de-prioritize back to the MITRE ATT&CK technique it covers (T1110 brute force, T1059 command execution, and so on). If suppressing a rule means a technique now has no high-priority coverage, that is a decision a human makes, not a side effect of a probability score.

Models also drift. New rules, new infrastructure, and new normal behavior shift the input distribution, and precision quietly degrades. Monitor precision and recall on a rolling window of fresh dispositions, watch feature distributions for drift, and retrain on a schedule. Because every new analyst disposition is a new label, the feedback loop that produced your training data keeps producing it.

Where to Learn This

This is applied data science, not a product you buy. The skills are concrete: building feature tables from alert metadata, choosing thresholds from a precision-recall curve, and validating that a model is not hiding an attack technique behind a low score. They transfer across whatever SIEM and SOAR you run.

GTK Cyber’s applied data science and AI training teaches exactly this workflow hands-on, with labs that build alert-triage and clustering models against realistic SOC data, including the threshold tuning and drift monitoring that separate a useful model from one that quietly closes real incidents.

Frequently Asked Questions

What machine learning model works best for triaging security alerts and reducing false positives?
A gradient-boosted tree classifier (GradientBoostingClassifier or XGBoost) is a strong default. It handles the mixed categorical and numeric features typical of alert metadata (rule name, asset criticality, source reputation, time of day, related-alert counts) without heavy preprocessing, captures non-linear interactions, and produces a probability you can use to rank the queue. Train it on historical analyst dispositions (true positive vs. false positive close codes from your SIEM or SOAR), set class_weight or scale_pos_weight to handle the imbalance, and choose a decision threshold from the precision-recall curve rather than using the default 0.5.
How do I make sure an ML alert-triage model never auto-closes a real attack?
Optimize for recall on true positives, not overall accuracy, and pick the probability threshold that achieves your required recall (often 0.98 to 0.99) from the precision-recall curve. Use the model to rank and to auto-close only the lowest-risk band, with every auto-close logged and a sample audited weekly. Never let the model suppress an alert class entirely: map suppressed alerts back to the MITRE ATT&CK techniques they cover so you can see which techniques would go dark before you trust the model with them. The model reorders the analyst queue; it does not get the final word on a high-confidence detection.
Where does the labeled training data for alert triage come from?
From your existing case management. Every alert an analyst dispositioned is a label: a closed-as-false-positive ticket is a negative, an escalated or confirmed-incident ticket is a positive. Mine the close codes from your SIEM, SOAR, or ticketing system to build the training set. The data is usually messy and imbalanced (most alerts are false positives), so normalize close codes into a clean true/false label, drop ambiguous dispositions, and weight the classes. You rarely need to label data from scratch; you need to extract the labels your analysts have already produced.
Can clustering reduce alert volume without a labeled training set?
Yes. Clustering is unsupervised, so it needs no labels. Use DBSCAN on alert features (or on text fields like alert name and command line vectorized with TF-IDF) to collapse an alert storm into a handful of incidents. A scanner tripping one rule across 400 hosts becomes one cluster the analyst reviews once instead of 400 tickets. Clustering does not decide true vs. false positive, but it removes the duplicate volume that drives most alert fatigue, and it is the fastest win because it requires no historical labels.
How do you keep an alert-triage model accurate as the environment changes?
Monitor precision and recall on a rolling window of newly dispositioned alerts and watch for concept drift: new detection rules, infrastructure changes, and new normal behavior all shift the input distribution. Track feature distribution drift (for example with a population stability index) and retrain on a schedule, monthly is a reasonable starting cadence, or trigger retraining when measured precision drops below your floor. Keep the analyst feedback loop closed so every new disposition becomes training data for the next model version.

Want to learn more?

Explore our hands-on AI and cybersecurity training courses.

View Courses