Dependency parsing

spaCy · Practical · 12 min

Dependency relations let you extract subjects, objects and modifiers, which flat tokens and entities cannot express.

The dependency parser links every token to a head token with a labelled relation, forming a tree whose root is usually the main verb.

  • token.dep_: the relation to its head, such as nsubj (nominal subject), dobj (direct object), amod (adjective modifier), prep/pobj (preposition and its object), ROOT
  • token.head: the parent token (the root is its own head)
  • token.children, token.lefts, token.rights: direct dependents
  • token.subtree: the token and everything below it, in order; token.ancestors walks up

This structure answers questions regular expressions can't. To find who did what, look for verbs and collect their nsubj and dobj children. To get the full phrase a word heads, take its subtree: doc[t.left_edge.i : t.right_edge.i + 1].

Labels follow the ClearNLP scheme in the English models (spacy.explain describes each). Passive sentences use nsubjpass and agent, so handle both voices when extracting relations.

displacy.render(doc, style="dep") draws the tree in a notebook or browser, and displacy.serve runs a small web server; looking at real parses is the fastest way to write correct extraction rules.

Parses are predictions and degrade on long, informal or ungrammatical text. Write extraction rules defensively and test them on real data; the DependencyMatcher lets you express tree patterns declaratively.

Example

import spacy

nlp = spacy.load("en_core_web_sm")
doc = nlp("The new manager quickly approved the large budget.")

for t in doc:
    print(f"{t.text:9} {t.dep_:7} <- {t.head.text}")
# The       det     <- manager
# new       amod    <- manager
# manager   nsubj   <- approved
# quickly   advmod  <- approved
# approved  ROOT    <- approved
# ...       budget is dobj <- approved

def svo(doc):
    for verb in (t for t in doc if t.pos_ == "VERB"):
        subj = [c for c in verb.children if c.dep_ in ("nsubj", "nsubjpass")]
        obj = [c for c in verb.children if c.dep_ in ("dobj", "attr")]
        if subj and obj:
            phrase = lambda t: doc[t.left_edge.i : t.right_edge.i + 1].text
            yield phrase(subj[0]), verb.lemma_, phrase(obj[0])

print(list(svo(doc)))
# [('The new manager', 'approve', 'the large budget')]

Remember

  • Every token has a head and a dep_ label; the root heads the sentence
  • children, subtree, left_edge/right_edge navigate the tree
  • Combine nsubj/dobj children of verbs to extract relations
  • Visualise with displacy to design rules

Watch out for

  • Extracting only nsubj and missing passive sentences, which use nsubjpass.

Check yourself

  1. In 'The dog chased the cat', what is the head of 'cat'?
  2. How do you get the whole phrase headed by a token t?

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

Official docs: spacy.io/usage/linguistic-features#dependency-parse