Chunking › Regex-Based Chunking with NLTK
Language AI HandbookAll chapters Chunking › Regex-Based Chunking with NLTKOne section · Sections in this chapter7 / 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
NLTK provides a RegexpParser that lets you define chunk patterns using regular expressions over POS tags. This approach is intuitive: you specify what sequences of POS tags should be grouped into a chunk, and the parser applies those rules to POS-tagged input.
The grammar uses angle brackets around POS tags and standard regex quantifiers:
<DT>matches a determiner token<JJ>*matches zero or more adjectives<NN.*>+matches one or more nouns (NN, NNS, NNP, NNPS)<DT|PRP$>matches either a determiner or a possessive pronoun
from nltk import RegexpParser
# Define a grammar for NP, VP, and PP chunking
np_grammar = r"""
NP: {<DT|PRP\$>?<JJ>*<NN.*>+}
VP: {<MD>?<VB.*>+}
PP: {<IN>}
"""
# Create the parser
chunker = RegexpParser(np_grammar)
# A helper to extract chunks from NLTK trees
def tree_to_chunks(tree):
"""Extract (chunk_type, text) pairs from an NLTK parse tree."""
chunks = []
for subtree in tree:
if hasattr(subtree, "label"):
chunk_type = subtree.label()
words = " ".join(word for word, _ in subtree.leaves())
chunks.append((chunk_type, words))
return chunks
# Test on several sentences
test_sentences = [
"The quick brown fox jumps over the lazy dog.",
"A beautiful sunset illuminated the entire valley.",
"My old computer finally crashed yesterday.",
"Scientists discovered a new species in the deep ocean.",
]
regex_results = []
for sent in test_sentences:
tokens = word_tokenize(sent)
tagged = pos_tag(tokens)
tree = chunker.parse(tagged)
chunks = tree_to_chunks(tree)
regex_results.append((sent, tagged, chunks))Regex-Based Chunking Results
=================================================================
Sentence: The quick brown fox jumps over the lazy dog.
POS: The/DT quick/JJ brown/NN fox/NN jumps/VBZ over/IN...
Chunks:
[The quick brown fox] -> NP
[jumps] -> VP
[over] -> PP
[the lazy dog] -> NP
Sentence: A beautiful sunset illuminated the entire valley.
POS: A/DT beautiful/JJ sunset/NN illuminated/VBD the/DT entire/JJ...
Chunks:
[A beautiful sunset] -> NP
[illuminated] -> VP
[the entire valley] -> NP
Sentence: My old computer finally crashed yesterday.
POS: My/PRP$ old/JJ computer/NN finally/RB crashed/VBN yesterday/NN...
Chunks:
[My old computer] -> NP
[crashed] -> VP
[yesterday] -> NP
Sentence: Scientists discovered a new species in the deep ocean.
POS: Scientists/NNS discovered/VBD a/DT new/JJ species/NNS in/IN...
Chunks:
[Scientists] -> NP
[discovered] -> VP
[a new species] -> NP
[in] -> PP
[the deep ocean] -> NPLet's break down exactly what the NP grammar pattern matches:
# Breakdown of the NP pattern components
np_pattern_breakdown = {
"<DT|PRP\\$>?": "Optional: determiner (the, a, an) OR possessive pronoun (my, your, its)",
"<JJ>*": "Zero or more adjectives (modifiers before the noun)",
"<NN.*>+": "One or more nouns: NN (singular), NNS (plural), NNP (proper), NNPS (proper plural)",
}
regex_quantifiers = {
"?": "zero or one (optional element)",
"*": "zero or more (any number)",
"+": "one or more (at least one required)",
"|": "alternation (either/or)",
}NP Pattern Breakdown: {<DT|PRP\$>?<JJ>*<NN.*>+}
============================================================
<DT|PRP\$>?
Optional: determiner (the, a, an) OR possessive pronoun (my, your, its)
<JJ>*
Zero or more adjectives (modifiers before the noun)
<NN.*>+
One or more nouns: NN (singular), NNS (plural), NNP (proper), NNPS (proper plural)
Regex Quantifiers in NLTK Grammar:
----------------------------------------
? zero or one (optional element)
* zero or more (any number)
+ one or more (at least one required)
| alternation (either/or)Chinking: Excluding Tokens from Chunks
Sometimes it's easier to specify what should NOT be in a chunk than what should be. Chinking removes tokens from existing chunks using the }{ syntax. You first chunk everything with {<.*>+}, then cut out the unwanted tokens.
# Chinking grammar:
# Step 1: {<.*>+} chunks every token into one big NP
# Step 2: }<VB.*|IN|CC|\\.>{ removes (chinks) verbs, prepositions,
# conjunctions, and punctuation from chunks
chink_grammar = r"""
NP: {<.*>+}
}<VB.*|IN|CC|\.>{
"""
chink_chunker = RegexpParser(chink_grammar)
chink_sentences = [
"The cat and the dog sat on the mat.",
"Alice ran quickly through the dark forest.",
]
chink_results = []
for sent in chink_sentences:
tokens = word_tokenize(sent)
tagged = pos_tag(tokens)
tree = chink_chunker.parse(tagged)
chunks = tree_to_chunks(tree)
chink_results.append((sent, tagged, chunks))Chinking Examples
============================================================
Strategy: chunk everything, then remove verbs/prepositions/conjunctions
Sentence: The cat and the dog sat on the mat.
POS: The/DT cat/NN and/CC the/DT dog/NN sat/VBD on/IN the/DT mat/NN ./.
Resulting NP chunks:
[The cat] -> NP
[the dog] -> NP
[the mat] -> NP
Sentence: Alice ran quickly through the dark forest.
POS: Alice/NNP ran/VBD quickly/RB through/IN the/DT dark/JJ forest/NN ./.
Resulting NP chunks:
[Alice] -> NP
[quickly] -> NP
[the dark forest] -> NPThe chinking approach inverts the chunking logic. Instead of specifying which POS sequences form a chunk, you specify which POS tags break a chunk. This works well when chunks are mostly contiguous sequences of "content words" interrupted by functional words.
Limitations of Regex Chunking
Regex-based chunking is simple and fast but has structural limitations that matter in practice. The patterns match local POS sequences without access to broader context, making several phenomena difficult:
# Cases where regex chunking struggles
hard_cases = [
{
"sentence": "The man I saw yesterday left.",
"issue": "Relative clause ('I saw yesterday') interrupts the NP",
"expected_np": "The man",
"regex_np": "The man I saw", # incorrectly groups relative clause
},
{
"sentence": "The old man the boats.",
"issue": "Garden path: 'man' is a verb here, not a noun",
"expected_vp": "man",
"regex_problem": "POS tagger likely tags 'man' as NN",
},
{
"sentence": "Flying planes can be dangerous.",
"issue": "Attachment ambiguity: 'flying' modifies 'planes' or starts a VP",
"expected": "Ambiguous without context",
},
]
hard_results = []
for case in hard_cases:
tokens = word_tokenize(case["sentence"])
tagged = pos_tag(tokens)
tree = chunker.parse(tagged)
found_chunks = tree_to_chunks(tree)
hard_results.append((case, tagged, found_chunks))Regex Chunking Limitations
=================================================================
Sentence: The man I saw yesterday left.
Issue: Relative clause ('I saw yesterday') interrupts the NP
POS: The/DT man/NN I/PRP saw/VBD yesterday/NN left/VBD ./.
Chunks found by regex:
[The man] -> NP
[saw] -> VP
[yesterday] -> NP
[left] -> VP
Sentence: The old man the boats.
Issue: Garden path: 'man' is a verb here, not a noun
POS: The/DT old/JJ man/NN the/DT boats/NNS ./.
Chunks found by regex:
[The old man] -> NP
[the boats] -> NP
Sentence: Flying planes can be dangerous.
Issue: Attachment ambiguity: 'flying' modifies 'planes' or starts a VP
POS: Flying/VBG planes/NNS can/MD be/VB dangerous/JJ ./.
Chunks found by regex:
[Flying] -> VP
[planes] -> NP
[can be] -> VPThe fundamental limitation is that regex patterns operate only on local POS sequences. They cannot condition on distant context, handle long-range dependencies, or recover from POS tagging errors. Machine learning approaches address these limitations by learning from data.