Text Classification with Bag-of-Words Features
Many text problems look like this: the data is large, the features are numerous, and speed matters as much as the last fraction of accuracy.
Why a Fast Classifier Matters
Imagine you need to sort thousands of short messages into two groups, such as spam and not spam, and you need the result to come back almost immediately. Many text problems look like this: the data is large, the features are numerous, and speed matters as much as the last fraction of accuracy. Naive Bayes classifiers are a family of methods built for this situation. They resemble the linear models you have already studied, and their decision rules take a similar shape, but they are built around a different idea of how to learn.
The trade-off is worth stating up front. Naive Bayes models are often slightly less accurate at generalizing than linear classifiers such as LogisticRegression and LinearSVC. What they give up in accuracy, they recover in speed: they are noticeably faster to train than those linear methods and very fast to make predictions. In this article you will follow one small dataset through the whole process, watching the model's internal state change at each step, and then decide which Naive Bayes variant fits a given kind of input.
Turning a Sentence into a Row of Numbers
A classifier cannot read words directly; it needs numbers. The bag-of-words idea solves this by fixing a vocabulary, a list of words the model has agreed to track, and then describing each sentence by which vocabulary words it contains. Every vocabulary word occupies a fixed column position. Position 0 always refers to the same word, position 1 always refers to another word, and so on. The order of words in the sentence is thrown away, which is why the representation is called a bag: only the contents matter, not the arrangement.
Read the diagram as a lookup table with fixed slots. The word free always lives in slot 0, so a value of 1 there means free appeared in the sentence and a value of 0 means it did not. Because every sentence is described against the same vocabulary in the same order, every row in a dataset has the same length and each column can be compared across rows. This is what allows a learning algorithm to ask, for example, how often column 2 is 1 among messages of one class.
Depending on the variant you use, a slot can hold a binary value (the word is present or absent) or an integer count (how many times the word appears). The difference matters later, when you choose a Naive Bayes model, because each variant is designed around one of these kinds of values.
Tracing Counts in a Tiny Dataset
Before any theory, start with a small program. The dataset below has four data points and four binary features each. Each data point belongs to one of two classes, A or B. The program counts how many times each feature is set to 1 within each class. Those counts are the raw material a Naive Bayes model works with. Read the code once and notice which variable holds the features and which holds the labels.
A [2, 1, 1, 0]
B [0, 1, 2, 2]- X holds the features: one row per data point, one column per binary feature.
- y holds the labels: one class name per row, in the same order as X.
- counts maps each class to a list of four totals, one total per feature position.
- The inner loop adds each row's 0s and 1s into the total for that row's class.
Adding up class A by hand
Using the four rows above, find the count of each feature in class A, the class labeled A.
- 1Select the rows
Class A contains the first two rows: [1, 0, 1, 0] and [1, 1, 0, 0].
- 2Add column by column
Feature 0: 1 + 1 = 2. Feature 1: 0 + 1 = 1. Feature 2: 1 + 0 = 1. Feature 3: 0 + 0 = 0.
- 3Collect the totals
The per-class count vector for A is [2, 1, 1, 0].
Class A has counts [2, 1, 1, 0], which matches the program output.
What do you think happens?
Look at the per-class counts. For which class is the count of feature 3 equal to zero?
Reveal answer
Class A
Only the first two rows belong to class A, and both have 0 in the fourth position. Class B has feature 3 set to 1 in both of its rows, giving a count of 2.
Notice what happened to the labels. They were never used to change the features; they only decided which running total each row was added to. This is the essence of the counting step. The model reads each feature separately and gathers, for every class, how often that feature was on. Nothing about the features is adjusted together with the others.
What Naive Bayes Actually Learns
A Naive Bayes model learns simple per-class statistics for each feature. It looks at one feature at a time and records, for every class, how that feature behaves. The per-class counts you traced above are exactly this kind of statistic. When the model is asked to classify something new, it combines the per-feature evidence from each class to decide which class fits best.
The word naive refers to this simplifying choice: the model treats features separately rather than modeling how they relate to each other. A linear model, by contrast, optimizes all of its feature weights together. Because each feature in a Naive Bayes model is handled independently and the work per feature is cheap, the whole training process stays short.
Prediction is also quick because the model has already stored its statistics. Classifying a new row means looking up the stored evidence for each feature that the row contains and combining them. No large search or joint optimization is needed at prediction time, which is why Naive Bayes models are described as very fast to predict as well as to train.
Three Variants and the Data They Assume
scikit-learn implements three Naive Bayes classifiers. They share the same basic idea and differ mainly in the type of data they assume each feature contains. Choosing the right one starts with asking what a single feature value means in your data.
The three variants are compared side by side below in terms of the data each one expects.
| Variant | Assumed feature type | Typical use |
|---|---|---|
| GaussianNB | Continuous data of any kind | Mostly very high-dimensional data |
| BernoulliNB | Binary: each feature is present or absent | Classifying text, especially with presence or absence signals |
| MultinomialNB | Integer counts, such as how often a word appears | Classifying text, especially sparse count data |
Source-stated assumptions for each Naive Bayes variant.
GaussianNB is meant for continuous data of any kind. BernoulliNB assumes each feature is binary, meaning it is either present or absent. MultinomialNB assumes count data, where each feature is an integer count such as how often a particular word appears in a sentence. Both BernoulliNB and MultinomialNB are used most often for classifying text. Applied to the tiny dataset from earlier, the 0s and 1s are binary, so BernoulliNB matches that data directly; if the same slots instead held how many times each word occurred, MultinomialNB would be the matching choice.
When two text variants both seem plausible, the source notes that MultinomialNB usually performs better than BernoulliNB, particularly on datasets with a relatively large number of nonzero features, such as large documents.
The Role of Alpha Smoothing
MultinomialNB and BernoulliNB each have one main tuning parameter, called alpha, which controls model complexity. The mechanism is smoothing. The algorithm adds alpha virtual data points that have positive values for all features, which softens the statistics it collected from the real data. A larger alpha means more smoothing and a less complex model.
Smoothing matters most for features that never occur with a class. In the tiny dataset, feature 3 has a count of 0 in class A. Without smoothing, the statistics for that feature would claim that feature 3 is impossible in class A, which is a very strong conclusion from only two rows. Adding alpha virtual points gives the zero count a small positive value, so one missing example does not make the model treat the feature as ruled out.
Strengths and Weaknesses in Practice
Naive Bayes models share many strengths and weaknesses with linear models. They train and predict very quickly, their training procedure is easy to follow, and they handle high-dimensional sparse data well. They are also relatively robust to their parameters. Because of this, they serve as strong baseline models and are often used on very large datasets where training even a linear model might take too long.
- Strength: very fast training and prediction, even on large datasets.
- Strength: handles high-dimensional sparse data such as word counts well.
- Strength: relatively robust to parameter choices, so it is easy to get reasonable results.
- Weakness: generalization accuracy is often slightly worse than that of linear classifiers such as LogisticRegression and LinearSVC.
- Weakness: because each feature is treated separately, it does not model how features relate to one another.
Mistakes Beginners Make with Naive Bayes
Naive Bayes models are simple to run, which makes it easy to skip the questions that matter most. The mistakes below are tied directly to the details covered above.
Common mistake Assuming Naive Bayes is always the most accurate choice
What it looks likeChoosing GaussianNB or any Naive Bayes variant for a task only because it trains in seconds.
Why it failsNaive Bayes models are often slightly less accurate at generalizing than linear classifiers. Speed is the main advantage, not guaranteed accuracy.
Do this insteadUse Naive Bayes as a fast baseline, then compare it against LogisticRegression or LinearSVC on the same data.
Common mistake Using GaussianNB for word counts
What it looks likeFeeding integer word counts from documents into GaussianNB because the name sounds like a general default.
Why it failsGaussianNB is meant for continuous data of any kind. Word counts are integer counts, which is the type MultinomialNB assumes.
Do this insteadUse MultinomialNB for count data and BernoulliNB when the features are only present or absent.
Common mistake Treating alpha as a value that must be perfectly tuned
What it looks likeSpending a long time searching for the exact best alpha before fitting any model at all.
Why it failsThe source reports that performance is relatively robust to alpha, so tuning usually improves accuracy only somewhat.
Do this insteadStart with the default alpha, and tune it only when you have time to check whether it helps.
Common mistake Mixing up features and labels in the data
Why it failsCounts are computed per feature within each class. If the labels and features are confused, the per-class totals become meaningless.
Do this insteadKeep features in one structure, one row per data point, and labels in a separate sequence aligned to those rows, as in the X and y example above.
Practice: Update the Counts
Using the same four rows and labels, suppose a fifth data point is added: features [1, 0, 0, 1] with label B. Before running anything, predict the new per-class counts for class A and class B. Then check your answer by extending the earlier program.
Hints
- Class A does not change, because the new row belongs to class B.
- For class B, add the new row's values to the existing totals [0, 1, 2, 2] column by column.
- Feature 0 goes from 0 to 1, feature 3 goes from 2 to 3, and the other two columns each gain 0.
Key Takeaways
- A Naive Bayes classifier learns simple per-class statistics by examining each feature separately; its training is fast because this work is independent and cheap.
- GaussianNB assumes continuous data, BernoulliNB assumes binary present-or-absent features, and MultinomialNB assumes integer count data such as word counts; the latter two are the usual choices for text.
- Bag-of-words turns each sentence into a row with one fixed slot per vocabulary word, which is what lets per-class counts be computed column by column.
- Alpha controls smoothing by adding virtual data points, which keeps zero counts from ruling a feature out; performance is relatively robust to its value.
- Naive Bayes models are very fast and robust baselines, but their generalization is often slightly worse than linear classifiers such as LogisticRegression and LinearSVC.