HardClassic ML

Naive Bayes Log-Likelihood (Bernoulli)

Classic ML

Hard

Problem

Train a Bernoulli Naive Bayes model on binary features and return the unnormalized log posterior for every test sample and class.

\theta_{jc}=\frac{N_{jc}+1}{N_c+2}

\log \widetilde{P}(c\mid\mathbf{x})=\log P(c)+\sum_{j=1}^{D}\left[x_j\log\theta_{jc}+(1-x_j)\log(1-\theta_{jc})\right]

Here, N_c is the number of training samples in class c, N_{jc} counts class-c samples whose feature j is one, and D is the feature count. The added one and two implement Laplace smoothing. Order output columns by ascending class label, round values to four decimals, and return a NumPy array of shape (n_test, n_classes).

Theory

Bernoulli Naive Bayes is a classification algorithm for binary features. Each feature is either present (1) or absent (0). The model assumes features are independent given the class label and follow a Bernoulli distribution.

Common applications include:


The Probabilistic Model

For a sample x = (x_1, x_2, ..., x_d) where each x_i \in \{0, 1\}:

P(x | y = c) = \prod_{i=1}^{d} P(x_i | y = c)

Each feature follows a Bernoulli distribution:

P(x_i | y = c) = p_{ic}^{x_i} (1 - p_{ic})^{1 - x_i}

where p_{ic} = P(x_i = 1 | y = c) is the probability that feature i is present given class c.


The Classification Rule

Using Bayes' theorem, we classify based on the posterior probability:

P(y = c | x) \propto P(y = c) \prod_{i=1}^{d} P(x_i | y = c)

The predicted class is:

\hat{y} = \arg\max_c P(y = c) \prod_{i=1}^{d} P(x_i | y = c)


Expanding the Likelihood

For Bernoulli features:

P(x | y = c) = \prod_{i=1}^{d} p_{ic}^{x_i} (1 - p_{ic})^{1 - x_i}

This means:

Important: Unlike Multinomial Naive Bayes, Bernoulli considers the absence of features as informative.


Log-Probability Form

To avoid numerical underflow with many features, use log probabilities:

\log P(y = c | x) = \log P(y = c) + \sum_{i=1}^{d} \left[ x_i \log p_{ic} + (1 - x_i) \log(1 - p_{ic}) \right]

Simplifying:

= \log P(y = c) + \sum_{i=1}^{d} \left[ x_i \log \frac{p_{ic}}{1 - p_{ic}} + \log(1 - p_{ic}) \right]


Parameter Estimation

Class prior:

P(y = c) = \frac{N_c}{N}

where N_c is the number of samples in class c and N is total samples.

Feature probability:

p_{ic} = P(x_i = 1 | y = c) = \frac{\text{count}(x_i = 1 \text{ in class } c)}{N_c}

This is the fraction of class c samples where feature i is present.


Laplace Smoothing

To avoid zero probabilities when a feature never appears in a class:

p_{ic} = \frac{\text{count}(x_i = 1 \text{ in class } c) + \alpha}{N_c + 2\alpha}

where \alpha is the smoothing parameter (commonly \alpha = 1).

The denominator uses 2\alpha because there are 2 possible values (0 or 1) for each feature.


Worked Example

Training data: 4 documents, 2 classes (spam/not spam), 3 binary features

Step 1: Class priors

P(\text{spam}) = 2/4 = 0.5

P(\text{not spam}) = 2/4 = 0.5

Step 2: Feature probabilities (with Laplace smoothing, \alpha = 1)

For spam (2 samples):

For not spam (2 samples):


Step 3: Classify a new document

New document: x = [1, 0, 1]

For spam:

P(\text{spam} | x) \propto 0.5 \times 0.75^1 \times (1-0.5)^1 \times 0.5^1

= 0.5 \times 0.75 \times 0.5 \times 0.5 = 0.09375

For not spam:

P(\text{not spam} | x) \propto 0.5 \times 0.25^1 \times (1-0.5)^1 \times 0.5^1

= 0.5 \times 0.25 \times 0.5 \times 0.5 = 0.03125

Normalization:

P(\text{spam} | x) = 0.09375 / (0.09375 + 0.03125) = 0.75

P(\text{not spam} | x) = 0.03125 / (0.09375 + 0.03125) = 0.25

Prediction: spam (75% probability)


Why Feature Absence Matters

In Bernoulli Naive Bayes, the absence of a feature provides information.

Example: Classifying emails

If the word "free" appears in 80% of spam but only 10% of legitimate emails:

The term (1 - p_{ic}) captures this information.


Bernoulli vs Multinomial Naive Bayes

Bernoulli:

Multinomial:


The Naive Bayes Assumption

"Naive" refers to the assumption that features are conditionally independent given the class:

P(x_1, x_2, ..., x_d | y) = \prod_{i=1}^{d} P(x_i | y)

This assumption is often violated in practice (words are correlated), but Naive Bayes still works well empirically.


Decision Boundary

Bernoulli Naive Bayes defines a linear decision boundary in the binary feature space.

The log-odds ratio between two classes:

\log \frac{P(y=1|x)}{P(y=0|x)} = \log \frac{P(y=1)}{P(y=0)} + \sum_{i=1}^{d} x_i \log \frac{p_{i1}(1-p_{i0})}{p_{i0}(1-p_{i1})} + \text{const}

This is linear in x, making Bernoulli Naive Bayes a linear classifier.


Handling Non-Binary Features

If features are not naturally binary:

Thresholding: Convert numeric features to binary using a threshold

Binarization: For text, use presence/absence instead of counts

Multiple thresholds: Create multiple binary features from one numeric feature


Computational Complexity

Training:

Prediction:

Very efficient for high-dimensional sparse data.


Advantages and Limitations

Advantages:

Limitations:

Examples

Example 1

Input
X_train = [[1, 0], [0, 1]], y_train = [1, 0], X_test = [[1, 0]]
Output
[[-2.8904, -1.5041]]
Explanation
The columns correspond to classes 0 and 1, and the observed feature pattern is more likely under class 1.

Example 2

Input
X_train = [[1, 0], [1, 1], [0, 0], [0, 1]], y_train = [0, 0, 1, 1], X_test = [[1, 0], [0, 1]]
Output
[[-1.674, -2.7726], [-2.7726, -1.674]]

Hints

  1. Use np.unique(y_train, return_counts=True) to obtain sorted classes and priors.
  2. For one class, X_train[y_train == label].sum(axis=0) gives all feature-one counts.
  3. Evaluate the Bernoulli terms with matrix multiplication against np.log(theta) and np.log1p(-theta).

Requirements

Constraints

Starter Code

import numpy as np

def naive_bayes_bernoulli(X_train: list, y_train: list, X_test: list) -> np.ndarray:
    """
    Returns a NumPy array of log posteriors.
    """
    # Write code here
    pass

Test Cases

CaseMatches
Basic 2 samplespublic
Symmetric classespublic