Rule-based matching: Matcher and PhraseMatcher

spaCy · Practical · 15 min

Rules give precise, explainable extraction for things models miss, and are often all you need.

The `Matcher` finds sequences of tokens described by patterns: a list of dicts, one per token, each mapping attributes to required values.

[{"LOWER": "machine"}, {"LOWER": "learning"}]
[{"POS": "ADJ", "OP": "*"}, {"POS": "NOUN"}]
[{"LIKE_NUM": True}, {"LOWER": {"IN": ["kg", "g"]}}]

Attributes include TEXT, LOWER, LEMMA, POS, TAG, DEP, ENT_TYPE, IS_PUNCT, LIKE_NUM, SHAPE, LENGTH. Values can be exact or use IN, NOT_IN, REGEX, and comparisons such as {">=": 3}. "OP" adds quantifiers: ? optional, * zero or more, + one or more, ! negation, {n,m} ranges.

matcher = Matcher(nlp.vocab), matcher.add("LABEL", [pattern1, pattern2]), then matcher(doc) returns (match_id, start, end) tuples; nlp.vocab.strings[match_id] gives the label. With operators, overlapping matches appear; greedy="LONGEST" when adding, or spacy.util.filter_spans, keeps the longest.

The `PhraseMatcher` is built for long term lists such as product catalogues or gazetteers. Patterns are Docs: matcher.add("DRUG", [nlp.make_doc(t) for t in terms]). It scales to hundreds of thousands of terms. Pass attr="LOWER" for case-insensitive matching.

Patterns relying on POS, LEMMA or DEP need the corresponding trained components; LOWER and TEXT work even with spacy.blank. as_spans=True returns Span objects directly.

Example

import spacy
from spacy.matcher import Matcher, PhraseMatcher

nlp = spacy.load("en_core_web_sm")
doc = nlp("Order 3 kg of Green Tea and 250 g of green tea leaves. Ship 2 boxes.")

matcher = Matcher(nlp.vocab)
matcher.add("WEIGHT", [[{"LIKE_NUM": True}, {"LOWER": {"IN": ["kg", "g"]}}]])
matcher.add("ADJ_NOUN", [[{"POS": "ADJ", "OP": "+"}, {"POS": "NOUN"}]], greedy="LONGEST")
for match_id, start, end in matcher(doc):
    print(nlp.vocab.strings[match_id], doc[start:end].text)
# WEIGHT 3 kg
# WEIGHT 250 g
# ADJ_NOUN green tea   (relies on the tagger's POS predictions)

terms = ["green tea", "black tea", "oolong"]
pm = PhraseMatcher(nlp.vocab, attr="LOWER")         # case-insensitive
pm.add("TEA", [nlp.make_doc(t) for t in terms])
print([s.text for s in pm(doc, as_spans=True)])
# ['Green Tea', 'green tea']

Remember

  • Matcher patterns are lists of per-token attribute dicts
  • OP quantifiers (?, *, +, !) and IN/REGEX/comparisons add flexibility
  • PhraseMatcher matches large term lists efficiently; attr='LOWER' ignores case
  • Use greedy='LONGEST' or filter_spans to drop overlapping matches

Watch out for

  • Writing POS- or LEMMA-based patterns and running them with spacy.blank, where those attributes are empty.

Check yourself

  1. Which pattern matches 'very big' and 'big' before 'house' (tagged ADV ADJ NOUN / ADJ NOUN)?
  2. You need to tag 200,000 product names in text. Which tool fits?

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

Official docs: spacy.io/usage/rule-based-matching