Bayesian Learning and Naive Bayes
No gradient descent, no margin, no splits — just Bayes' theorem, a base rate, and a deliberately naive assumption.
Advanced
P(fail | features)?"
directly — logistic regression fits weights so that a
squashed weighted sum approximates it. Naive Bayes answers the exact same
question a completely different way: instead of fitting that probability directly, it uses
Bayes' theorem to flip the question around, compute the easier, flipped
version from simple counts, and flip it back:
P(fail | features) = P(features | fail) * P(fail) / P(features)
P(fail) is the
prior — the base rate of failure before looking at any features at all
(the same 5%-fail majority-class baseline introduced in
The ML Pipeline). P(features | fail) is the
likelihood — given that a run truly did fail, how likely were these
particular feature values? P(features) is the evidence — how
common these feature values are overall, regardless of outcome; since it's the same number
for every class being compared, it can be dropped entirely when the only goal is to rank
classes against each other. What's left, P(fail | features), is the
posterior — the updated belief about failure after actually seeing the
features.
P(features | fail) is still hard to estimate directly the moment there's more
than one feature — it would require enough historical data to have seen every specific
combination of touches_slow_suite, lines_changed, and every other
feature, conditioned on failure. Naive Bayes sidesteps this with the same independence
assumption already used once before in this track, in
Anomaly Detection's Gaussian model: treat every feature
as conditionally independent given the class, so the joint likelihood becomes a simple
product of per-feature likelihoods:
P(features | fail) = P(touches_slow_suite | fail) * P(large_diff | fail) * ...
touches_slow_suite and
large_diff are probably correlated in real CI data, not independent. The
assumption is made anyway because it turns an intractable estimation problem into counting,
and in practice the resulting classifier tends to work well even when the independence
assumption clearly doesn't hold, in much the same way the anomaly detection model tolerated
the same approximation.
P(fail) = 0.1, P(pass) = 0.9), and two binary features with these
observed rates within each class:
P(touches_slow_suite=yes | fail) = 0.8 P(touches_slow_suite=yes | pass) = 0.2
P(large_diff=yes | fail) = 0.7 P(large_diff=yes | pass) = 0.3
# a new run comes in with touches_slow_suite=yes, large_diff=yes:
score(fail) = P(fail) * P(touches_slow_suite=yes|fail) * P(large_diff=yes|fail)
= 0.1 * 0.8 * 0.7 = 0.056
score(pass) = P(pass) * P(touches_slow_suite=yes|pass) * P(large_diff=yes|pass)
= 0.9 * 0.2 * 0.3 = 0.054
# normalize the two scores so they sum to 1 (this recovers the P(features) denominator):
P(fail | features) = 0.056 / (0.056 + 0.054) = 0.509
P(pass | features) = 0.054 / (0.056 + 0.054) = 0.491
| Variant | Assumes each feature is | Typical use |
|---|---|---|
| Gaussian NB | normally distributed within each class | continuous features, e.g. lines_changed or duration — the same per-class Gaussian fitting from Anomaly Detection, applied per-class instead of just to "normal" |
| Multinomial NB | a count | word counts in text classification (spam filtering, topic classification) |
| Bernoulli NB | a binary presence/absence flag | binary features like touches_slow_suite, or word-present/absent text features |