Concepts / Text Classification with Bag-of-Words Features

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.

  • Programming

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.

free1meeting0offer1report1
The vocabulary has four words in fixed positions. The sentence free offer report contains three of them, so each of those positions holds 1 and the absent word holds 0.

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.

python
Output (expected)
A [2, 1, 1, 0]
B [0, 1, 2, 2]
Notes on the code
  • 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.
Worked example

Adding up class A by hand

Using the four rows above, find the count of each feature in class A, the class labeled A.

  1. 1
    Select the rows

    Class A contains the first two rows: [1, 0, 1, 0] and [1, 1, 0, 0].

  2. 2
    Add column by column

    Feature 0: 1 + 1 = 2. Feature 1: 0 + 1 = 1. Feature 2: 1 + 0 = 1. Feature 3: 0 + 0 = 0.

  3. 3
    Collect the totals

    The per-class count vector for A is [2, 1, 1, 0].

Result

Class A has counts [2, 1, 1, 0], which matches the program output.

feature 0A: 2, B: 0feature 1A: 1, B: 1feature 2A: 1, B: 2feature 3A: 0, B: 2
Each feature position maps to its count of 1s in class A and in class B. Feature 3 never appears in class A, so its count there is 0.

What do you think happens?

Look at the per-class counts. For which class is the count of feature 3 equal to zero?

  • Class A

  • Class B

  • Both classes

  • Neither class

Reveal answer
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.

Continuous valuesTypical use: veryhigh-dimensional dataBinary present orabsentTypical use: sparse textfeaturesInteger countsTypical use: word counts indocuments
Each Naive Bayes variant is paired with the kind of feature value it assumes, along with the typical use the source associates with it.

The three variants are compared side by side below in terms of the data each one expects.

VariantAssumed feature typeTypical use
GaussianNBContinuous data of any kindMostly very high-dimensional data
BernoulliNBBinary: each feature is present or absentClassifying text, especially with presence or absence signals
MultinomialNBInteger counts, such as how often a word appearsClassifying 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 like

    Choosing GaussianNB or any Naive Bayes variant for a task only because it trains in seconds.

    Why it fails

    Naive Bayes models are often slightly less accurate at generalizing than linear classifiers. Speed is the main advantage, not guaranteed accuracy.

    Do this instead

    Use 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 like

    Feeding integer word counts from documents into GaussianNB because the name sounds like a general default.

    Why it fails

    GaussianNB is meant for continuous data of any kind. Word counts are integer counts, which is the type MultinomialNB assumes.

    Do this instead

    Use 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 like

    Spending a long time searching for the exact best alpha before fitting any model at all.

    Why it fails

    The source reports that performance is relatively robust to alpha, so tuning usually improves accuracy only somewhat.

    Do this instead

    Start 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 fails

    Counts are computed per feature within each class. If the labels and features are confused, the per-class totals become meaningless.

    Do this instead

    Keep 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

EASY

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.

Related concepts

More concepts starting with T