Learn how a dependency parser links words in workforce text and why attachment scores need limits.
Word labels are ready for the 28 Riverton sentences. The team then faces a harder decision: when a sentence says training is provided, which word is the thing provided, and does the sentence name a provider?
That question matters because labels alone do not say how words fit together. A sentence can contain experience and required while still saying the opposite of a simple keyword match. A passive sentence can also leave the provider unnamed, so the team needs links between words and a habit of checking them by eye.
A dependency parse says which word each token attaches to and what the link is called. The link points from one token to its head, the word it depends on. This lesson uses a local UDPipe model trained on the same 500-sentence English Web Treebank excerpt as the tagger in Lesson 17, with parsing turned on.
Note
The Riverton Workforce Lab, its job board, and its training flyer are fictional and were created for teaching. The parser and scoring files are local artifacts, so the lesson does not download a model while it runs.
TipWhat you will learn
By the end of this lesson, you will be able to:
describe a dependency parse in plain language;
read root, compound, numeric modifier, passive subject, and punctuation links;
compare two parses of the same sentence; and
explain why a parser score from weblog posts needs a baseline and local checks.
Load the local parser inputs
The parser inputs come from local CSV files. dplyr, tibble, and purrr handle the table work; tokenizers and udpipe keep parsing tied to fixed tokens; digest records artifact fingerprints; and spacyr starts a comparison parse. The project helper use_project_spacy() starts the pinned spaCy pipeline.
knitr::kable( spacy_info_table,col.names =c("Pipeline field", "Value"),caption ="Local spaCy pipeline used for the comparison table",row.names =FALSE)
Local spaCy pipeline used for the comparison table
Pipeline field
Value
name
core_web_sm
version
3.8.0
lang
en
license
MIT
spacy
3.8.7
The chunk checks the score file against the held-out token count, recomputes both ratios, and matches the parser and score fingerprints in the metadata. The parser attaches 2,899 of 4,007 held-out tokens to the right head word, about 0.72, and gets both head and relation right for 2,629 tokens, about 0.66.
Parse fixed tokens
The parser receives one token per line because the model has no tokenizer. The vertical text block hands udpipe each sentence as a stack of fixed tokens.
Dependency parse for ‘Paid 12-week training is provided.’
Token
Head word
Relation
Paid
week
compound
12
Paid
flat
-
Paid
punct
week
training
compound
training
provided
nsubj:pass
is
provided
aux:pass
provided
root
root
.
provided
punct
The parse says provided is the root, the central word of this sentence. training attaches to provided as a passive subject, and is attaches as a passive helper verb. Inside Paid 12-week, the parser is wrong. It makes Paid part of week and hangs 12 off Paid. A reader would attach Paid to training and 12 to week.
The parse answers half the team’s question. training is the thing provided. Nothing in the sentence says who provides it, because English lets a passive drop the doer. There is no word for the parser to point at.
These tables use a few relation labels. root marks the central word. compound links words inside a larger noun phrase. flat is for flat name-like material, though it is a bad fit for 12 here. nummod marks a numeric modifier, and nmod marks a nominal modifier. nsubj:pass marks a passive subject, aux:pass marks a passive helper, ccomp marks a clausal complement, and punct marks punctuation.
Compare one sentence with spaCy
The same idea can come from a different tool. spaCy’s English parser does not use the Universal Dependencies label set. It uses another scheme, which is why nsubjpass will not be found in the UD documentation linked below.
spacy_s001 <-spacy_parse(c(s001 ="Paid 12-week training is provided."),pos =TRUE,lemma =TRUE,entity =TRUE,dependency =TRUE,nounphrase =TRUE) |>as_tibble()spacy_paid_parse <- spacy_s001 |>mutate(head_word =if_else( head_token_id == token_id,"ROOT", token[match(as.character(head_token_id), as.character(token_id))] ) ) |>select(token, head_word, dep_rel, lemma, pos)knitr::kable( spacy_paid_parse,col.names =c("Token", "Head word", "spaCy relation", "Lemma", "POS tag"),caption ="spaCy parse for the same sentence",row.names =FALSE)
spaCy parse for the same sentence
Token
Head word
spaCy relation
Lemma
POS tag
Paid
training
nmod
pay
VERB
12
week
nummod
12
NUM
-
week
punct
-
PUNCT
week
training
compound
week
NOUN
training
provided
nsubjpass
training
NOUN
is
provided
auxpass
be
AUX
provided
ROOT
ROOT
provide
VERB
.
provided
punct
.
PUNCT
invisible(suppressMessages(spacy_finalize()))
The two tools produce different structures, not a spelling difference in the labels. spaCy attaches Paid to training and 12 to week; UDPipe attaches Paid to week and 12 to Paid. Both outputs still have tokens, heads, and labels, but they disagree about this sentence.
Read morphology in context
The parser output also includes the feats column. These are morphological features, compact labels for grammar carried by a token in this sentence.
paid_features <- parsed |>filter(doc_id =="s001", token %in%c("is", "provided")) |>select(token, feats)knitr::kable( paid_features,col.names =c("Token", "Morphological features"),caption ="In-context features for two tokens in sentence s001",row.names =FALSE)
In-context features for two tokens in sentence s001
The feature string for is says it is a finite present-tense helper form. The feature string for provided says it is a past participle in passive voice. That is why the relation labels include passive forms.
Compare parsing with tagging
A parser score needs its own baseline. The chunk below uses a deliberately weak one: attach each token to the token before it, with the first token in a sentence attached to the root.
knitr::kable( attachment_scores,col.names =c("System", "Correct tokens", "Tokens scored", "Attachment score"),caption ="Parser attachment scores and a previous-token baseline",row.names =FALSE)
Parser attachment scores and a previous-token baseline
System
Correct tokens
Tokens scored
Attachment score
Previous-token baseline, head only
304
4007
0.0759
500-sentence parser, head only
2899
4007
0.7235
500-sentence parser, head and relation
2629
4007
0.6561
Attaching every token to the word before it gets 304 of 4,007 heads right. The trained parser gets 2,899 heads right, about 0.72. The tagger’s 0.91 belongs to a different task with a different set of choices, so the table keeps it separate. The head-and-relation row is stricter by construction: it can be right only when the head is right. These numbers come from one train-and-test split of thirteen weblog posts, and tokens inside a post are related because vocabulary, topic, and author repeat.
Check Riverton roots
The held-out score is a warning, not a local audit. Because the Riverton set has only 28 sentences, the lesson can compare each parser root with a hand-chosen root for the sentence.
Riverton sentences where the parser root needs review
Sentence ID
Hand root
Parser root
Root matches
s004
need
skills
FALSE
s017
required
valid
FALSE
s021
included
Product
FALSE
s023
HOUSE
RIVERTON
FALSE
s024
CERTIFICATE
DATA
FALSE
s026
classes
Evening
FALSE
s028
Apply
October
FALSE
Of the 28 Riverton sentences, the parser picks the hand-marked root in 21 and misses 7. The misses cluster in noun-heavy lines and flyer headings, where there is little sentence structure for the parser to hold on to.
In Product training is included, the main claim is that training is included. The parser has made Product the head of the sentence and hung included beneath it as a subordinate clause, so the parser has the sentence inside out.
What to remember
A dependency parse links each token to a head word and names the relation.
UDPipe and spaCy disagree on the heads inside Paid 12-week.
A previous-token baseline gets 304 of 4,007 weblog heads right.
The trained parser gets 2,899 of 4,007 heads right, about 0.72.
The parser misses 7 of 28 hand-marked Riverton roots.
Parser links on job-posting text need direct inspection.
Use dependency links as leads for reading, especially around passives. On this corpus the useful warning is concrete: seven root choices need review before any downstream count trusts them.