Naive Bayes is a supervised classifier for text classification, and it takes a bag of words view of a document. Word order and grammar are thrown away, so a document is only a set of word counts.

It is called naive because it assumes every word probability is independent of every other word. That assumption is false for real language, and the classifier still works well in practice.

Introduction

For a document and a class :

where:

  • is the probability of given is true
  • it means what is the probability that the class , given that we are looking at document
  • means how well class explain these words in document
  • represents how common class is without reading any documents
  • is how liekely is this document overall, across all classes
StepRuleWhy
1the most likely class is the one with the highest probability given document
2rewrite using Bayes theorem
3drop the denominator
4a document is its features
5the naive assumption, so 1 joint probability becomes a product of separate ones
  • is a variable that stands for a single class label
  • is dropped because it is the same value for every class, so it cannot change which class wins
  • step 5 is the only step that is an approximation, and the 4 earlier steps are exact
  • argmax returns the class that scores highest, not the score itself

Training

Training is pure counting, and there is no iteration or gradient. Only 2 quantities come out of the training corpus, both by maximum likelihood estimation.

Class prior probability, which is how common the class is:

Conditional probability, which is how strongly a word points at that class:

Zero probability problem

Take a review classifier with classes positive and negative. The word "excellent" appears in review1 but in no other positive review, so:

  • the class score is a product, so a single zero factor drives the whole product to zero
  • this as zero probabilities that cannot be conditioned away no matter the evidence, meaning that no amount of other strong evidence can rescue the score
  • the trigger is any word unseen for that class in training, which is common because training corpora are finite

Laplace (add-one) smoothing

The fix is to add 1 to every word count, so that no count is ever 0. There are 2 forms:

  • the 2 forms are the same expression, because adding 1 once for each of the vocabulary terms adds in total
  • is the vocabulary, so is the number of distinct words across the whole corpus and not just the words of 1 class
  • the denominator must grow with , otherwise the probabilities for a class stop summing to 1

Worked example

Training data and the test document:

DataDocWordsClass
Training1Hive, Arc, HiveNTU
Training2Hive, Hive, SpineNTU
Training3Hive, TamarindNTU
Training4Eusoff, Temasek, HiveNUS
Test5Hive, Hive, Hive, Eusoff, Temasek?

The counts behind the numbers:

  • vocabulary = {Hive, Arc, Spine, Tamarind, Eusoff, Temasek}, so
  • NTU holds 8 word tokens in total, of which Hive accounts for 5
  • NUS holds 3 word tokens in total, of which each word accounts for 1

Class priors, counted over documents:

Smoothed conditionals, counted over word tokens:

WordGiven NTUGiven NUS
Hive
Eusoff
Temasek

Choosing a class for document 5, where Hive appears 3 times so its probability is raised to the power of 3:

NTU scores higher, so document 5 is predicted as NTU.

Note

The words unseen in a class still get a non-zero probability, which is here, and that is exactly what smoothing bought. A repeated word contributes its probability once per occurrence, which is why Hive is cubed.

Preprocessing before the classifier

This is the pipeline before fitting the model:

  1. use RegEx to strip punctuation and special characters, and lowercase everything
  2. apply POS tagging and lemmatise the input words
  3. remove stop words
  4. apply TF-IDF vectorisation

Code

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB, GaussianNB, CategoricalNB
 
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(corpus).toarray()
X_train, X_test, y_train, y_test = train_test_split(X, labels, test_size=0.2)
 
nb = MultinomialNB()
nb.fit(X_train, y_train)
 
predictions = nb.predict(X_test)
ClassExpectsTypical use
MultinomialNBcounts or TF-IDF weightstext classification, and the default choice here
GaussianNBcontinuous featuresfeatures assumed to follow a normal distribution
CategoricalNBdiscrete category codesfeatures that are labels rather than counts
  • fit performs the counting described above, so training is fast
  • smoothing is already built in through the alpha parameter, which defaults to 1.0 and gives Laplace add-one
  • .toarray() is called because the notebook passes a dense array, although MultinomialNB accepts a sparse matrix as well

Naive Bayes counts words and never draws a boundary, and the model that does draw one is Support Vector Machine (SVM).