Skip to content

Naive Bayes

Algorithm

Naive Bayes combines a class prior with a likelihood for each feature, assuming features are conditionally independent given the class. ToyML provides Gaussian likelihoods for continuous features, multinomial likelihoods for count features, and categorical likelihoods for discrete feature values.

Inputs and limitations

  • Pass a non-empty rectangular list of features and an equally long list of integer labels.
  • Gaussian training needs at least two observations per class with the default unbiased variance estimate. Class priors are estimated from the training label frequencies.
  • Multinomial inputs should be non-negative counts. Categorical inputs should use the same category encoding at training and prediction; unseen values currently contribute zero log-likelihood, so keep prediction categories within the training vocabulary.
  • predict, predict_proba, and predict_log_proba each accept one sample.

Minimal example

from toyml.classification.naive_bayes import GaussianNaiveBayes

samples = [[0.0], [0.2], [4.0], [4.2]]
model = GaussianNaiveBayes().fit(samples, [0, 0, 1, 1])
print(model.predict([0.1]))
print(model.predict_proba([0.1]))

API

toyml.classification.naive_bayes.BaseNaiveBayes dataclass

BaseNaiveBayes()

Bases: ABC

class_prior_ class-attribute instance-attribute

class_prior_: dict[int, float] = field(default_factory=dict, init=False)

The prior probability of each class in training dataset

predict

predict(sample: list[float]) -> int

Predict the class label for a given sample.

PARAMETER DESCRIPTION
sample

A single sample to predict, represented as a list of feature values.

TYPE: list[float]

RETURNS DESCRIPTION
int

Predicted class label.

TYPE: int

Source code in src/toyml/classification/naive_bayes.py
17
18
19
20
21
22
23
24
25
26
27
28
def predict(self, sample: list[float]) -> int:
    """Predict the class label for a given sample.

    Args:
        sample: A single sample to predict, represented as a list of feature values.

    Returns:
        int: Predicted class label.
    """
    class_posteriors = self.predict_proba(sample)
    label = max(class_posteriors, key=lambda label: class_posteriors[label])
    return label

predict_proba

predict_proba(sample: list[float], normalization: bool = True) -> dict[int, float]

Predict class probabilities for a given sample.

PARAMETER DESCRIPTION
sample

A single sample to predict, represented as a list of feature values.

TYPE: list[float]

normalization

Whether to normalize the probabilities. If False, return joint probabilities (or densities for Gaussian features). Default is True.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict[int, float]

dict[int, float]: Posterior probabilities by class label when normalized; otherwise, unnormalized joint probabilities or densities.

Source code in src/toyml/classification/naive_bayes.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def predict_proba(self, sample: list[float], normalization: bool = True) -> dict[int, float]:
    """Predict class probabilities for a given sample.

    Args:
        sample: A single sample to predict, represented as a list of feature values.
        normalization: Whether to normalize the probabilities. If False, return
            joint probabilities (or densities for Gaussian features). Default is True.

    Returns:
        dict[int, float]: Posterior probabilities by class label when normalized;
            otherwise, unnormalized joint probabilities or densities.
    """
    class_log_probs = self.predict_log_proba(sample, normalization)
    return {label: math.exp(log_prob) for label, log_prob in class_log_probs.items()}

predict_log_proba

predict_log_proba(sample: list[float], normalization: bool = True) -> dict[int, float]

Predict log probabilities for a given sample.

PARAMETER DESCRIPTION
sample

A single sample to predict, represented as a list of feature values.

TYPE: list[float]

normalization

Whether to normalize the log probabilities. If False, return joint log probabilities (or log densities for Gaussian features). Default is True.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
dict[int, float]

dict[int, float]: Log posterior probabilities by class label when normalized; otherwise, unnormalized joint log probabilities or log densities.

Source code in src/toyml/classification/naive_bayes.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def predict_log_proba(self, sample: list[float], normalization: bool = True) -> dict[int, float]:
    """Predict log probabilities for a given sample.

    Args:
        sample: A single sample to predict, represented as a list of feature values.
        normalization: Whether to normalize the log probabilities. If False, return
            joint log probabilities (or log densities for Gaussian features). Default is True.

    Returns:
        dict[int, float]: Log posterior probabilities by class label when normalized;
            otherwise, unnormalized joint log probabilities or log densities.
    """
    if not self.class_prior_:
        msg = "The model is not fitted yet!"
        raise ValueError(msg)
    class_log_likelihoods = self._log_likelihood(sample)
    class_log_joint_probs: dict[int, float] = {}
    for label, log_likelihood in class_log_likelihoods.items():
        class_log_joint_probs[label] = log_likelihood + math.log(self.class_prior_[label])
    if normalization is False:
        return class_log_joint_probs
    # ref: https://github.com/scikit-learn/scikit-learn/blob/2beed55847ee70d363bdbfe14ee4401438fba057/sklearn/naive_bayes.py#L97
    max_log_joint_prob = max(class_log_joint_probs.values())
    log_evidence = max_log_joint_prob + math.log(
        sum(math.exp(log_joint_prob - max_log_joint_prob) for log_joint_prob in class_log_joint_probs.values()),
    )
    class_log_posteriors = {
        label: log_joint_prob - log_evidence for label, log_joint_prob in class_log_joint_probs.items()
    }
    return class_log_posteriors

toyml.classification.naive_bayes.GaussianNaiveBayes dataclass

GaussianNaiveBayes(unbiased_variance: bool = True, var_smoothing: float = 1e-09)

Bases: BaseNaiveBayes

Gaussian naive bayes classification algorithm implementation.

Examples:

>>> label = [0, 0, 0, 0, 1, 1, 1, 1]
>>> dataset = [
...     [6.00, 180, 12],
...     [5.92, 190, 11],
...     [5.58, 170, 12],
...     [5.92, 165, 10],
...     [5.00, 100, 6],
...     [5.50, 150, 8],
...     [5.42, 130, 7],
...     [5.75, 150, 9],
... ]
>>> clf = GaussianNaiveBayes().fit(dataset, label)
>>> clf.predict([6.00, 130, 8])
1

unbiased_variance class-attribute instance-attribute

unbiased_variance: bool = True

Use the unbiased variance estimation or not. Default is True.

var_smoothing class-attribute instance-attribute

var_smoothing: float = 1e-09

Portion of the largest variance of all features that is added to variances for calculation stability.

labels_ class-attribute instance-attribute

labels_: list[int] = field(default_factory=list, init=False)

The labels in training dataset

class_count_ class-attribute instance-attribute

class_count_: int = field(default=0, init=False)

The number of classes in training dataset

class_prior_ class-attribute instance-attribute

class_prior_: dict[int, float] = field(default_factory=dict, init=False)

Class prior probabilities estimated from training label frequencies.

means_ class-attribute instance-attribute

means_: dict[int, list[float]] = field(default_factory=dict, init=False)

The means of each class in training dataset

variances_ class-attribute instance-attribute

variances_: dict[int, list[float]] = field(default_factory=dict, init=False)

The variance of each class in training dataset

epsilon_ class-attribute instance-attribute

epsilon_: float = field(default=0, init=False)

The absolute additive value to variances.

fit

fit(dataset: list[list[float]], labels: list[int]) -> GaussianNaiveBayes

Fit the Gaussian Naive Bayes classifier.

PARAMETER DESCRIPTION
dataset

Training data, where each row is a sample and each column is a feature.

TYPE: list[list[float]]

labels

Target labels for training data.

TYPE: list[int]

RETURNS DESCRIPTION
self

Returns the instance itself.

TYPE: GaussianNaiveBayes

Source code in src/toyml/classification/naive_bayes.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
def fit(self, dataset: list[list[float]], labels: list[int]) -> GaussianNaiveBayes:
    """Fit the Gaussian Naive Bayes classifier.

    Args:
        dataset: Training data, where each row is a sample and each column is a feature.
        labels: Target labels for training data.

    Returns:
        self: Returns the instance itself.
    """
    self.labels_ = sorted(set(labels))
    self.class_count_ = len(set(labels))
    self.class_prior_ = {label: count / len(dataset) for label, count in Counter(labels).items()}
    self.epsilon_ = self.var_smoothing * max(self._variance(col) for col in zip(*dataset, strict=False))
    self.means_, self.variances_ = self._get_classes_means_variances(dataset, labels)
    return self

toyml.classification.naive_bayes.MultinomialNaiveBayes dataclass

MultinomialNaiveBayes(alpha: float = 1.0)

Bases: BaseNaiveBayes

Multinomial Naive Bayes classifier.

Examples:

>>> import random
>>> rng = random.Random(0)
>>> dataset = [[rng.randint(0, 5) for _ in range(100)] for _ in range(6)]
>>> label = [1, 2, 3, 4, 5, 6]
>>> clf = MultinomialNaiveBayes().fit(dataset, label)
>>> clf.predict(dataset[2])
3

alpha class-attribute instance-attribute

alpha: float = 1.0

Additive (Laplace/Lidstone) smoothing parameter

labels_ class-attribute instance-attribute

labels_: list[int] = field(default_factory=list, init=False)

The labels in training dataset

class_count_ class-attribute instance-attribute

class_count_: int = field(default=0, init=False)

The number of classes in training dataset

class_prior_ class-attribute instance-attribute

class_prior_: dict[int, float] = field(default_factory=dict, init=False)

The prior probability of each class in training dataset

class_feature_count_ class-attribute instance-attribute

class_feature_count_: dict[int, list[float]] = field(default_factory=dict, init=False)

Feature counts with additive smoothing for each class.

class_feature_log_prob_ class-attribute instance-attribute

class_feature_log_prob_: dict[int, list[float]] = field(default_factory=dict, init=False)

Feature conditional log probabilities for each class.

fit

fit(dataset: list[list[float]], labels: list[int]) -> MultinomialNaiveBayes

Fit the Multinomial Naive Bayes classifier.

PARAMETER DESCRIPTION
dataset

Training data, where each row is a sample and each column is a feature. Features should be represented as counts (non-negative integers).

TYPE: list[list[float]]

labels

Target labels for training data.

TYPE: list[int]

RETURNS DESCRIPTION
self

Returns the instance itself.

TYPE: MultinomialNaiveBayes

Source code in src/toyml/classification/naive_bayes.py
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
def fit(self, dataset: list[list[float]], labels: list[int]) -> MultinomialNaiveBayes:
    """Fit the Multinomial Naive Bayes classifier.

    Args:
        dataset: Training data, where each row is a sample and each column is a feature.
                 Features should be represented as counts (non-negative integers).
        labels: Target labels for training data.

    Returns:
        self: Returns the instance itself.
    """
    self.labels_ = sorted(set(labels))
    self.class_count_ = len(set(labels))
    # get the prior from training dataset labels
    self.class_prior_ = {label: count / len(dataset) for label, count in Counter(labels).items()}
    self.class_feature_count_, self.class_feature_log_prob_ = self._get_classes_feature_counts_log_probs(
        dataset, labels
    )
    return self

toyml.classification.naive_bayes.CategoricalNaiveBayes dataclass

CategoricalNaiveBayes(alpha: float = 1.0)

Bases: BaseNaiveBayes

Categorical Naive Bayes classifier.

Examples:

>>> import random
>>> rng = random.Random(0)
>>> dataset = [[rng.randint(0, 5) for _ in range(100)] for _ in range(6)]
>>> label = [1, 2, 3, 4, 5, 6]
>>> clf = CategoricalNaiveBayes().fit(dataset, label)
>>> clf.predict(dataset[2])
3

alpha class-attribute instance-attribute

alpha: float = 1.0

Additive (Laplace/Lidstone) smoothing parameter

labels_ class-attribute instance-attribute

labels_: list[int] = field(default_factory=list, init=False)

The labels in training dataset

class_count_ class-attribute instance-attribute

class_count_: int = field(default=0, init=False)

The number of classes in training dataset

class_prior_ class-attribute instance-attribute

class_prior_: dict[int, float] = field(default_factory=dict, init=False)

The prior probability of each class in training dataset

class_feature_count_ class-attribute instance-attribute

class_feature_count_: dict[int, dict[int, dict[float, float]]] = field(default_factory=dict, init=False)

Smoothed counts indexed by class label, feature index, then feature value.

class_feature_log_prob_ class-attribute instance-attribute

class_feature_log_prob_: dict[int, dict[int, dict[float, float]]] = field(default_factory=dict, init=False)

Conditional log probabilities indexed by class label, feature index, then feature value.

fit

fit(dataset: list[list[float]], labels: list[int]) -> CategoricalNaiveBayes

Fit the Categorical Naive Bayes classifier.

PARAMETER DESCRIPTION
dataset

Training data, where each row is a sample and each column is a feature.

TYPE: list[list[float]]

labels

Target labels for training data.

TYPE: list[int]

RETURNS DESCRIPTION
self

Returns the instance itself.

TYPE: CategoricalNaiveBayes

Source code in src/toyml/classification/naive_bayes.py
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
def fit(self, dataset: list[list[float]], labels: list[int]) -> CategoricalNaiveBayes:
    """Fit the Categorical Naive Bayes classifier.

    Args:
        dataset: Training data, where each row is a sample and each column is a feature.
        labels: Target labels for training data.

    Returns:
        self: Returns the instance itself.
    """
    self.labels_ = sorted(set(labels))
    self.class_count_ = len(set(labels))
    # get the prior from training dataset labels
    self.class_prior_ = {label: count / len(dataset) for label, count in Counter(labels).items()}
    self.class_feature_count_, self.class_feature_log_prob_ = self._get_classes_feature_counts_log_probs(
        dataset, labels
    )
    return self