Text classification with Naive Bayes

NLTK · Advanced · 15 min

Building a classifier end to end shows the core supervised NLP workflow: features, train/test split, evaluation.

NLTK classifiers work on featuresets: each example is a dict of feature names to values, paired with a label. You write a feature extractor function that turns a document into that dict, for example {"contains(great)": True, "contains(awful)": False, ...} over a chosen vocabulary.

The workflow:

  • build (features, label) pairs for every document,
  • shuffle and split into training and test sets (never evaluate on training data),
  • clf = nltk.NaiveBayesClassifier.train(train_set),
  • nltk.classify.accuracy(clf, test_set),
  • clf.classify(features) for one prediction, clf.prob_classify(...) for probabilities,
  • clf.show_most_informative_features(10) to see which features drive decisions.

Naive Bayes assumes features are independent given the label. That's false for language, but it trains instantly, works well with many sparse features and is a strong baseline for sentiment and topic tasks.

The vocabulary is a design choice: the 2000 most frequent words is a common start. With True/False features for every vocabulary word, inputs should resemble the training documents: a five-word snippet is mostly "absent" features and gets classified poorly. Restrict it to training data only; picking features using the test set leaks information.

nltk.classify.SklearnClassifier wraps any scikit-learn estimator (such as LogisticRegression or LinearSVC) so it accepts the same featuresets, which usually beats Naive Bayes. For real projects with more data, scikit-learn's vectorizers or transformer models are the usual next step.

Example

import random
import nltk
from nltk.corpus import movie_reviews as mr

docs = [(list(mr.words(f)), cat) for cat in mr.categories() for f in mr.fileids(cat)]
random.seed(0)
random.shuffle(docs)
train_docs, test_docs = docs[:1600], docs[1600:]

fd = nltk.FreqDist(w.lower() for words, _ in train_docs for w in words if w.isalpha())
vocab = [w for w, _ in fd.most_common(2000)]          # from training data only

def features(words):
    present = set(w.lower() for w in words)
    return {f"has({w})": (w in present) for w in vocab}

train_set = [(features(d), c) for d, c in train_docs]
test_set = [(features(d), c) for d, c in test_docs]
clf = nltk.NaiveBayesClassifier.train(train_set)
print(round(nltk.classify.accuracy(clf, test_set), 2))   # about 0.8
print(clf.classify(test_set[0][0]), test_set[0][1])  # predicted vs gold label
clf.show_most_informative_features(3)
# has(outstanding) = True   pos : neg = 22.2 : 1.0 ...

Remember

  • Features are dicts produced by your own extractor function
  • Train on one split, measure accuracy on a held-out split
  • NaiveBayesClassifier is fast and interpretable via most_informative_features
  • SklearnClassifier plugs scikit-learn models into the same workflow

Watch out for

  • Building the vocabulary from all documents, including the test set, which inflates accuracy.

Check yourself

  1. What is the input format NLTK's NaiveBayesClassifier.train expects?
  2. Why is Naive Bayes called 'naive'?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.nltk.org/book/ch06.html