Naive Bayes › Example: A Two-Feature Gaussian Calculation

Michael BrenndoerferOctober 26, 20269 min read
Machine Learning from ScratchAll chapters
Part 7 of 15Supervised Learning Beyond Linear Models
Chapter 54Naive Bayes › Example: A Two-Feature Gaussian Calculation
Reading section 6 of 10 · Example: A Two-Feature Gaussian Calculation · 9 minSection 6 of 10 · 9 min

Section 6 of 10 · 9 min read

Naive Bayes › Example: A Two-Feature Gaussian CalculationSection view · Sections in this chapter6 / 10 ↓
Reading tools Definitions, links & notes

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Notes & comments

The formulas are compact, but compact formulas hide their arithmetic. Before writing any code, we work one small Gaussian naive Bayes problem by hand, in full, so that every symbol in the score has a concrete number attached to it. For the Gaussian parameter estimates in this worked example, subscripts give the class first and the feature second; the general Gaussian formulas above use feature first and class second. The training data have two features and two classes, with five observations per class:

Training observations for the two-feature Gaussian example.
ClassFeature 1Feature 2
012
023
032
011
022
165
176
187
176
185

The first thing to check is whether anything needs smoothing. Each class has five observations, no feature is constant within a class, and no variance is zero, so the unsmoothed maximum likelihood estimates are safe here and we can use them directly.

The priors are equal because each class has five observations: π0=π1=0.5\pi_0 = \pi_1 = 0.5. We want to classify the new point x=(4.5,4.0)\mathbf{x} = (4.5, 4.0), which sits in the gap between the two clusters but is closer to class 1.

We begin with the sufficient statistics. For class 0, the feature means are

μ^0,1=1+2+3+1+25=1.8,μ^0,2=2+3+2+1+25=2.0.\hat{\mu}_{0,1} = \frac{1+2+3+1+2}{5} = 1.8, \qquad \hat{\mu}_{0,2} = \frac{2+3+2+1+2}{5} = 2.0.

The variances use division by nk=5n_k=5:

σ^0,12=(1−1.8)2+(2−1.8)2+(3−1.8)2+(1−1.8)2+(2−1.8)25=2.85=0.56,\hat{\sigma}_{0,1}^2 = \frac{(1-1.8)^2 + (2-1.8)^2 + (3-1.8)^2 + (1-1.8)^2 + (2-1.8)^2}{5} = \frac{2.8}{5} = 0.56, σ^0,22=(2−2)2+(3−2)2+(2−2)2+(1−2)2+(2−2)25=2.05=0.40.\hat{\sigma}_{0,2}^2 = \frac{(2-2)^2 + (3-2)^2 + (2-2)^2 + (1-2)^2 + (2-2)^2}{5} = \frac{2.0}{5} = 0.40.

For class 1, the means are

μ^1,1=6+7+8+7+85=365=7.2,μ^1,2=5+6+7+6+55=295=5.8.\begin{aligned} \hat{\mu}_{1,1} &= \frac{6+7+8+7+8}{5} = \frac{36}{5} = 7.2, \\ \hat{\mu}_{1,2} &= \frac{5+6+7+6+5}{5} = \frac{29}{5} = 5.8. \end{aligned}

The variances use division by nk=5n_k=5 in the same way:

σ^1,12=(6−7.2)2+(7−7.2)2+(8−7.2)2+(7−7.2)2+(8−7.2)25=(−1.2)2+(−0.2)2+(0.8)2+(−0.2)2+(0.8)25=1.44+0.04+0.64+0.04+0.645=2.85=0.56,\begin{aligned} \hat{\sigma}_{1,1}^2 &= \frac{(6-7.2)^2 + (7-7.2)^2 + (8-7.2)^2 + (7-7.2)^2 + (8-7.2)^2}{5} \\ &= \frac{(-1.2)^2 + (-0.2)^2 + (0.8)^2 + (-0.2)^2 + (0.8)^2}{5} \\ &= \frac{1.44 + 0.04 + 0.64 + 0.04 + 0.64}{5} \\ &= \frac{2.8}{5} = 0.56, \end{aligned} σ^1,22=(5−5.8)2+(6−5.8)2+(7−5.8)2+(6−5.8)2+(5−5.8)25=(−0.8)2+(0.2)2+(1.2)2+(0.2)2+(−0.8)25=0.64+0.04+1.44+0.04+0.645=2.85=0.56.\begin{aligned} \hat{\sigma}_{1,2}^2 &= \frac{(5-5.8)^2 + (6-5.8)^2 + (7-5.8)^2 + (6-5.8)^2 + (5-5.8)^2}{5} \\ &= \frac{(-0.8)^2 + (0.2)^2 + (1.2)^2 + (0.2)^2 + (-0.8)^2}{5} \\ &= \frac{0.64 + 0.04 + 1.44 + 0.04 + 0.64}{5} \\ &= \frac{2.8}{5} = 0.56. \end{aligned}

where:

  • 6,7,8,7,86, 7, 8, 7, 8: the class 1 values of feature 1 used in the mean
  • 5,6,7,6,55, 6, 7, 6, 5: the class 1 values of feature 2 used in the mean
  • 3636: the sum of the class 1 values of feature 1
  • 2929: the sum of the class 1 values of feature 2
  • 7.27.2: the class 1 mean for feature 1, μ^1,1\hat{\mu}_{1,1}
  • 5.85.8: the class 1 mean for feature 2, μ^1,2\hat{\mu}_{1,2}
  • 2.82.8: the sum of squared deviations for feature 1 and, separately, for feature 2
  • 55: the number of class 1 observations, nkn_k

With the statistics in hand, we evaluate one log density at a time. Each density has the same two-part structure: a normalizing constant that depends only on the variance, and a negative penalty that grows with the squared distance from the mean. For class 0 and feature 1, with x1=4.5x_1=4.5:

log⁡p(4.5∣y=0)=−12log⁡(2π⋅0.56)−(4.5−1.8)22⋅0.56.\log p(4.5 \mid y=0) = -\frac{1}{2}\log(2\pi \cdot 0.56) - \frac{(4.5-1.8)^2}{2 \cdot 0.56}.

where:

  • 4.54.5: the value of feature 1 for the new point x=(4.5,4.0)\mathbf{x}=(4.5, 4.0)
  • 1.81.8: the class 0 mean for feature 1, μ^0,1\hat{\mu}_{0,1}
  • 0.560.56: the class 0 variance for feature 1, σ^0,12\hat{\sigma}_{0,1}^2
  • −log⁡(2π⋅0.56)/2-\log(2\pi \cdot 0.56)/2: the log-density normalizing contribution from the variance
  • (4.5−1.8)2/(2⋅0.56)(4.5-1.8)^2/(2 \cdot 0.56): the squared distance from the mean measured in units of the variance

The first term is −12log⁡(2π⋅0.56)≈−0.6290-\frac{1}{2}\log(2\pi \cdot 0.56) \approx -0.6290. The squared distance is (2.7)2=7.29(2.7)^2 = 7.29, and the denominator is 1.121.12, so the second term is −7.29/1.12≈−6.5089-7.29/1.12 \approx -6.5089. The squared-distance term dominates the normalizing constant here. The new point is far from the class 0 mean, and in this calculation the distance penalty (6.50896.5089) is much larger than the variance normalizing constant (0.62900.6290). Thus

log⁡p(4.5∣y=0)≈−0.6290−6.5089=−7.1379.\log p(4.5 \mid y=0) \approx -0.6290 - 6.5089 = -7.1379.

For class 0 and feature 2, with x2=4.0x_2=4.0:

log⁡p(4.0∣y=0)=−12log⁡(2π⋅0.40)−(4.0−2.0)22⋅0.40.\log p(4.0 \mid y=0) = -\frac{1}{2}\log(2\pi \cdot 0.40) - \frac{(4.0-2.0)^2}{2 \cdot 0.40}.

where:

  • 4.04.0: the value of feature 2 for the new point
  • 2.02.0: the class 0 mean for feature 2, μ^0,2\hat{\mu}_{0,2}
  • 0.400.40: the class 0 variance for feature 2, σ^0,22\hat{\sigma}_{0,2}^2

The first term is −12log⁡(2π⋅0.40)≈−0.4608-\frac{1}{2}\log(2\pi \cdot 0.40) \approx -0.4608. The squared distance is (4.0−2.0)2=4.0(4.0-2.0)^2 = 4.0, and the denominator is 2⋅0.40=0.82 \cdot 0.40 = 0.8, so the second term is −4.0/0.8=−5.0-4.0/0.8 = -5.0. Thus

log⁡p(4.0∣y=0)≈−0.4608−5.0=−5.4608.\log p(4.0 \mid y=0) \approx -0.4608 - 5.0 = -5.4608.

The total log score for class 0 is the log prior plus the two log densities, evaluated before rounding:

s0(x)=log⁡(0.5)+log⁡p(4.5∣y=0)+log⁡p(4.0∣y=0)≈−13.2919.s_0(\mathbf{x}) = \log(0.5) + \log p(4.5 \mid y=0) + \log p(4.0 \mid y=0) \approx -13.2919.

Now we repeat the calculation for class 1. For feature 1 the arithmetic is symmetric, because the distance from 4.54.5 to 7.27.2 is also 2.72.7 and the variance is the same:

log⁡p(4.5∣y=1)≈−7.1379.\log p(4.5 \mid y=1) \approx -7.1379.

For class 1 and feature 2, with x2=4.0x_2=4.0 and mean 5.85.8:

log⁡p(4.0∣y=1)=−12log⁡(2π⋅0.56)−(4.0−5.8)22⋅0.56.\log p(4.0 \mid y=1) = -\frac{1}{2}\log(2\pi \cdot 0.56) - \frac{(4.0-5.8)^2}{2 \cdot 0.56}.

where:

  • 5.85.8: the class 1 mean for feature 2, μ^1,2\hat{\mu}_{1,2}
  • 0.560.56: the class 1 variance for feature 2, σ^1,22\hat{\sigma}_{1,2}^2

The squared distance is (−1.8)2=3.24(-1.8)^2 = 3.24, so the second term is −3.24/1.12≈−2.8929-3.24/1.12 \approx -2.8929. The first term is −12log⁡(2π⋅0.56)≈−0.6290-\frac{1}{2}\log(2\pi \cdot 0.56) \approx -0.6290. Thus

log⁡p(4.0∣y=1)≈−0.6290−2.8929=−3.5219.\log p(4.0 \mid y=1) \approx -0.6290 - 2.8929 = -3.5219.

This is the decisive term. Feature 1 contributes identically to both classes, so the entire difference between the two scores comes from feature 2, where the new point is much closer to the class 1 mean. The total log score for class 1, again evaluated before rounding, is

s1(x)=log⁡(0.5)+log⁡p(4.5∣y=1)+log⁡p(4.0∣y=1)≈−11.3530.s_1(\mathbf{x}) = \log(0.5) + \log p(4.5 \mid y=1) + \log p(4.0 \mid y=1) \approx -11.3530.

Class 1 has the larger score by s1−s0=−11.3530−(−13.2919)=1.9389s_1 - s_0 = -11.3530 - (-13.2919) = 1.9389. To convert the scores into a posterior probability, we use the logistic form for two classes:

p(y=1∣x)=exp⁡(s1)exp⁡(s0)+exp⁡(s1)=11+exp⁡(s0−s1).p(y=1 \mid \mathbf{x}) = \frac{\exp(s_1)}{\exp(s_0)+\exp(s_1)} = \frac{1}{1+\exp(s_0-s_1)}.

where:

  • s0s_0: the unnormalized log score for class 0, computed above as s0(x)≈−13.2919s_0(\mathbf{x}) \approx -13.2919
  • s1s_1: the unnormalized log score for class 1, computed above as s1(x)≈−11.3530s_1(\mathbf{x}) \approx -11.3530
  • s0−s1s_0-s_1: the score difference, which is the only quantity that matters after normalization

Substituting s0−s1=−13.2919−(−11.3530)=−1.9389s_0-s_1 = -13.2919 - (-11.3530) = -1.9389 gives

p(y=1∣x)=11+exp⁡(−1.9389)≈0.874.p(y=1 \mid \mathbf{x}) = \frac{1}{1+\exp(-1.9389)} \approx 0.874.

So we classify the point as class 1 with a predicted probability of about 87.4%87.4\%. Notice how the log-sum-exp normalization works here in a two-class setting: the difference of scores is the only quantity that matters, and the sigmoid turns that difference into a probability without ever computing the raw likelihoods.

Within class 0, the sample correlation between the two features is about 0.420.42. Within class 1, it is about 0.430.43. These sample correlations are a diagnostic to investigate; five observations per class cannot establish population conditional dependence. The fitted model nevertheless multiplies the two marginal likelihoods. It favors class 1, while the calibration of its probability remains a separate question.

We can verify the arithmetic with a short computation:

PythonIn [10]
CollapseExpand
X_ex = np.array(
    [
        [1, 2],
        [2, 3],
        [3, 2],
        [1, 1],
        [2, 2],
        [6, 5],
        [7, 6],
        [8, 7],
        [7, 6],
        [8, 5],
    ],
    dtype=float,
)
y_ex = np.array([0] * 5 + [1] * 5)
x_new = np.array([4.5, 4.0])


def normal_log_pdf(x, mu, var):
    return -0.5 * np.log(2 * np.pi * var) - 0.5 * (x - mu) ** 2 / var


example_rows = []
for k in [0, 1]:
    Xk = X_ex[y_ex == k]
    mu = Xk.mean(axis=0)
    var = Xk.var(axis=0)
    log_score = np.log(0.5)
    for j in range(2):
        log_score += normal_log_pdf(x_new[j], mu[j], var[j])
    example_rows.append(
        {
            "class": k,
            "mean feature 1": mu[0],
            "mean feature 2": mu[1],
            "variance feature 1": var[0],
            "variance feature 2": var[1],
            "log score": log_score,
        }
    )

example_table = pd.DataFrame(example_rows)
ConsoleOut [11]
CollapseExpand
 class  mean feature 1  mean feature 2  variance feature 1  variance feature 2  log score
     0             1.8             2.0                0.56                0.40   -13.2919
     1             7.2             5.8                0.56                0.56   -11.3530

Posterior probability of class 1: 0.8742
Within-class correlation for class 0: 0.4226
Within-class correlation for class 1: 0.4286

The printed means, variances, and scores match the hand calculation at the displayed precision. The posterior from the printed scores comes out at approximately 0.8740.874, matching the earlier sigmoid calculation. The arithmetic agrees with the formulas. The sample correlations motivate checking the class-specific dependence and calibration on more data; they do not establish population dependence from five observations per class.

The printed scores come from a product of one-dimensional densities. The next figure shows those densities directly for feature 2, with the new observation marked.

PythonIn [12]
CollapseExpand
# Compute per-feature Gaussian densities for feature 2 from the worked example.
feature_index = 1
x_grid_density = np.linspace(
    X_ex[:, feature_index].min() - 1.0,
    X_ex[:, feature_index].max() + 1.0,
    300,
)
density_by_class = {}
for k in [0, 1]:
    Xk = X_ex[y_ex == k]
    mu = Xk[:, feature_index].mean()
    var = Xk[:, feature_index].var()
    density_by_class[k] = np.exp(normal_log_pdf(x_grid_density, mu, var))
VisualizationOut [13]
CollapseExpand
Two bell curves for feature 2, one per class, and a vertical line at the new point.
Class-conditional Gaussian densities for feature 2 in the worked example. Each class has its own mean and variance, and the vertical line marks the feature-2 value of the new observation. With the fitted means and variances, the class 1 density at the new point is higher. This feature therefore contributes a log-likelihood ratio in favor of class 1.

Comments

Add to the conversation

A question, a correction, or an example from your own work.

No comments yet. Be the first to share your thoughts!

Newsletter

Stay up to date

Get new articles, book updates, and notes on AI and technology.

Unsubscribe at any time. Privacy policy