Chunking › Training a Statistical Chunker

Michael BrenndoerferUpdated March 21, 20264 min read
Language AI HandbookAll chapters
Part 6 of 67Sequence Labeling
Chapter 39Chunking › Training a Statistical Chunker
Reading section 10 of 15 · Training a Statistical Chunker · 4 minSection 10 of 15 · 4 min

Book contents

Section 10 of 15 · 4 min read

Chunking › Training a Statistical ChunkerOne section · Sections in this chapter10 / 15 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Machine learning chunkers learn which IOB tags to assign from annotated examples. Even simple statistical approaches that model the probability of an IOB tag given the current POS tag outperform hand-crafted regex rules.

The simplest statistical model is a unigram tagger: for each POS tag, predict the IOB label that most often followed it in training data.

In[38]:
Code
from nltk.chunk import ChunkParserI
from nltk.tag import BigramTagger, UnigramTagger


class UnigramChunker(ChunkParserI):
    """
    Chunker using a unigram model over (POS -> IOB) mappings.
    For each POS tag, predicts the most frequent IOB label seen in training.
    """

    def __init__(self, train_sents):
        # Convert each tree to (pos, iob) pairs for tagger training
        train_data = []
        for tree in train_sents:
            triples = tree_to_iob_triples(tree)
            train_data.append([(pos, iob) for _, pos, iob in triples])
        self.tagger = UnigramTagger(train_data)

    def parse(self, tagged_sent):
        """Assign IOB labels to a POS-tagged sentence."""
        pos_tags = [pos for _, pos in tagged_sent]
        iob_preds = [self.tagger.tag([pos])[0][1] or "O" for pos in pos_tags]
        iob_triples = [
            (w, p, iob) for (w, p), iob in zip(tagged_sent, iob_preds)
        ]
        return nltk.chunk.conlltags2tree(iob_triples)


class BigramChunker(ChunkParserI):
    """
    Chunker using a bigram model that conditions on the previous POS tag.
    Falls back to unigram for unseen bigrams.
    """

    def __init__(self, train_sents):
        train_data = []
        for tree in train_sents:
            triples = tree_to_iob_triples(tree)
            train_data.append([(pos, iob) for _, pos, iob in triples])
        unigram = UnigramTagger(train_data)
        self.tagger = BigramTagger(train_data, backoff=unigram)

    def parse(self, tagged_sent):
        pos_tags = [pos for _, pos in tagged_sent]
        tagged_preds = self.tagger.tag(pos_tags)
        iob_preds = [iob if iob else "O" for _, iob in tagged_preds]
        iob_triples = [
            (w, p, iob) for (w, p), iob in zip(tagged_sent, iob_preds)
        ]
        return nltk.chunk.conlltags2tree(iob_triples)


# Train both models
unigram_chunker = UnigramChunker(train_sents)
bigram_chunker = BigramChunker(train_sents)

# Evaluate
unigram_eval = evaluate_chunker(unigram_chunker, test_sents)
bigram_eval = evaluate_chunker(bigram_chunker, test_sents)
Out[39]:
Console
Statistical Chunker Evaluation Comparison
=======================================================

  Model      Precision    Recall     F1      
  --------------------------------------------
  Regex          75.75%     72.29%   73.98%
  Unigram        76.78%     87.77%   81.91%
  Bigram         81.62%     86.97%   84.21%
Out[40]:
Visualization
Grouped bar chart comparing precision, recall, and F1 for regex, unigram, and bigram chunkers.
Precision, recall, and F1 scores for three chunking approaches on the CoNLL-2000 test set. The regex chunker uses hand-crafted POS patterns. The unigram and bigram statistical chunkers learn IOB label distributions from training data. Both statistical models improve substantially over the hand-crafted baseline, with the bigram model gaining additional accuracy by conditioning on neighboring POS context.

The unigram model's improvement over regex is substantial. By learning from thousands of annotated examples, it discovers patterns that hand-crafted rules miss. The bigram model improves further by conditioning on the previous POS tag, capturing sequential patterns like "after a determiner, a noun is likely inside an NP."

Production systems typically use CRF (Conditional Random Field) models, which we cover in the Conditional Random Fields chapter. CRFs can condition on arbitrary features and optimize global sequence consistency, pushing CoNLL-2000 F1 to the low 90s.