Semantic Roles and Argument Structure › Semantic Role Labeling

Michael BrenndoerferMay 10, 202536 min read
Language AI HandbookAll chapters
Part 8 of 67Semantics and Information Extraction
Chapter 52Semantic Roles and Argument Structure › Semantic Role Labeling
Reading section 5 of 8 · Semantic Role Labeling · 36 minSection 5 of 8 · 36 min

Section 5 of 8 · 36 min read

Semantic Roles and Argument Structure › Semantic Role LabelingSection view · Sections in this chapter5 / 8 ↓
Reading tools Definitions, links & notes

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

Notes & comments

Start with the selected-predicate task for contiguous argument spans.

Given tokens x=(x0,…,xn−1)x = (x_0, \dots, x_{n-1}) and a selected predicate at position pp with 0≤p<n0 \leq p < n, predict a set of labeled spans

A^={(i1,j1,r1),…,(ik,jk,rk)}\hat{A} = \{(i_1, j_1, r_1), \dots, (i_k, j_k, r_k)\}

where each (i,j)(i, j) denotes the half-open range from token ii through token j−1j-1, with 0≤i<j≤n0 \leq i < j \leq n, and rr is a role drawn from an inventory R\mathcal{R} determined by the benchmark. The toy’s gold spans are nonoverlapping and exclude the selected predicate token, as required by its BIO encoding. The set lists which text spans fill which roles for the selected predicate; it does not by itself encode every aspect of sentence meaning. The CoNLL-2012 organizer paper's sample marks the selected predicate with V in its argument column, serialized as (V*) (Table 12 and Figure 3). The toy tag sequence below uses bare V. The toy code below takes the selected token index and uses the lowercased form of that token as a feature.

The inventory R\mathcal{R} can be coarse or fine, global or frame-specific. A task that permits discontinuous arguments also needs a way to link separated spans as parts of one argument, such as the continuation convention below. Other protocols predict the predicate instead of supplying it. These decisions specify the output target and evaluation protocol. Their effect on empirical prediction difficulty must be measured for the selected setting.

The problem is usually decomposed into four subtasks:

  1. Predicate identification. Which tokens are predicates? An evaluation can supply gold predicates instead of scoring their identification.
  2. Frame or sense identification. Which frameset or frame does this predicate instance evoke? For verbal predicates, this includes word sense disambiguation; the selected frameset or frame determines which role definitions apply.
  3. Argument identification. Which spans realize roles for this selected predicate, including any adjunct-like modifiers included by the annotation scheme?
  4. Argument classification. Which role does each span receive?

The four-way decomposition separates different outputs to inspect. If gold predicates are supplied, predicate identification is not a model decision in that evaluation. Predicting them introduces another possible source of error. A wrong sense or frame can also change which role definitions should apply; no universal severity ranking follows from the decomposition. Argument identification errors determine whether a span is considered at all. Argument classification errors determine which label it receives. A system that gets identification right but classification wrong will produce spans with plausible boundaries and incorrect labels, which is a different failure mode from a system that misses spans entirely.

In a pipeline these decisions run in sequence, so an upstream error can affect downstream labels. He et al. (2018) use shared contextual features to score word-indexed predicate candidates and span-based argument candidates and predict their relations. Their end-to-end setting predicts predicates and argument spans without gold predicates. Given the input, the baseline predicts a role or no relation separately for each pair that survives pruning. Across examples, errors on different pairs can still co-vary when the model relies on the same misleading features. Pruning a candidate rules out all of its pairs.

If a pipeline retains intermediate predictions and reference annotations for its stages, we can compare them to locate predicate, sense, boundary, or role-label disagreements. Such a comparison does not by itself establish which earlier decision caused a later error. A joint model can use evidence from several decisions together. Gold-predicate tagging and full predicate-argument prediction both remain useful experimental settings, so the term end-to-end should be accompanied by an explicit description of which labels were supplied and which were predicted.

Predicate conditioning

In SRL, the labels depend on which predicate you are asking about. In "The chef carved the roast", the span "the roast" is A1 of carved. If the sentence also contained a nominalization, the same span could be an argument of two different predicates with two different roles.

Predicate conditioning identifies which predicate-argument relations the output describes. The same token can receive different labels depending on which predicate it is associated with. A sentence with multiple predicates therefore needs an output convention that preserves predicate identity, for example separate tagging passes or labeled predicate–argument edges.

For predicate-conditioned token tagging, the input or output convention must identify the selected predicate. The tagger below encodes each token's position relative to the supplied predicate, together with its surface form, as features. This convention identifies which predicate-argument relations the tags describe; it does not require every SRL architecture to use an externally supplied predicate index.

Relative position identifies whether a token precedes or follows the selected predicate and records its distance, clipped to six tokens in either direction. The predicate form adds lexical context. These inputs can distinguish the same token under different supplied predicates; they do not prove a general distance ordering between arguments and adjuncts or guarantee that the correct role is recoverable. The later position plot describes only the hand-built corpus.

Span representation and BIO

One encoding converts labeled spans into a per-token tag sequence using the BIO convention we saw in BIO Tagging. In the toy's gold encoding, argument spans exclude the selected predicate token. A span of role A0 starting at token ii and ending before token jj becomes:

  • B-A0 at position ii,
  • I-A0 at positions i+1i+1 through j−1j-1.

In this gold encoding, the predicate token is V and every remaining token outside the arguments is O. The example uses the tutorial's AM-MNR label for the instrument. Released PropBank carve.01 lists the instrument as ARG2.

BIO tags for a sample sentence with predicate and arguments.
TokenThechefcarvedtheroastwithaknife.
TagB-A0I-A0VB-A1I-A1B-AM-MNRI-AM-MNRI-AM-MNRO

The plain BIO scheme used here cannot connect separated pieces as one argument. PropBank-style data can include continuation segments, which need an additional convention such as C-A1 to associate them with the argument's first segment. A list of unlinked B-A1 spans does not express that relationship. CoNLL-2005 explicitly describes noncontiguous arguments (Carreras and Màrquez, 2005, Section 2); the miniature corpus below avoids them.

For one predicate, this BIO scheme can encode a flat set of nonoverlapping contiguous argument spans that exclude the selected predicate token. Discontinuous arguments need a way to link their separated pieces, such as the continuation convention above. The toy converter handles only contiguous spans.

Evaluation

The standard metric is span-level precision, recall, and F1, where a predicted span counts as correct only if both boundaries and the label match the gold annotation exactly:

P=∣A^∩A∣∣A^∣,R=∣A^∩A∣∣A∣,F1=2PRP+R.P = \frac{|\hat{A} \cap A|}{|\hat{A}|}, \qquad R = \frac{|\hat{A} \cap A|}{|A|}, \qquad F_1 = \frac{2PR}{P + R}.

Here AA is the set of gold arguments and A^\hat{A} the predicted argument set for the same selected predicate. The notation ∣S∣|S| counts elements of set SS, and the intersection keeps arguments with matching start, end, and role. PP is precision, RR is recall, and F1F_1 is their harmonic mean. For corpus aggregation, the keys also retain sentence and predicate identity. The formulas assume nonzero denominators. The code returns zero for precision when the prediction set is empty, for recall when the gold set is empty, and for F1F_1 when P+R=0P+R=0.

The formula is the familiar one, but the unit of evaluation is the span, not the token. A prediction is correct only if the start boundary, the end boundary, and the label all match. This is a strict criterion, and it is the right one for applications that need exact argument spans.

Three details make published numbers hard to compare:

  • Gold versus predicted predicates. An evaluation can supply gold predicates or require their prediction. He et al. (2018) report these as separate settings; a score from one setting cannot by itself establish performance in the other.
  • Head-word versus full-span scoring. Span SRL and dependency SRL use different units: complete argument spans versus argument heads. CoNLL-2005 required both the words spanning an argument and its role to be correct (Carreras and Màrquez, 2005, Section 2.1). Do not compare its span scores directly with dependency SRL scores.
  • Annotation inventory and tokenization. PropBank and FrameNet define different semantic labels. OntoNotes is a corpus project that includes PropBank-style annotations, rather than a third independent role theory. Compare the actual resource release and evaluation token boundaries. Two encoders can use different subword tokenizers while their predictions are evaluated against the same original word spans.

These settings change what is evaluated. Reading SRL results requires checking the predicate-identification setting, the annotation unit, the label inventory, and alignment to the benchmark tokenization. Subword tokenization inside an encoder does not itself change the benchmark evaluation unit when outputs are correctly realigned.

The distribution of labels matters too. In CoNLL-2005's training data, A0 and A1 are much more frequent than A4 and A5; Table 1 reports 61,440 A0 instances, 84,917 A1 instances, 2,703 A4 instances, and 68 A5 instances. AM-PNC has 2,289 training instances, so calling all less common roles "a few dozen" would be inaccurate. The counts and their split must be stated when interpreting a per-role score (shared-task paper, Table 1).

Pool exact matches across roles to compute micro-averaged scores. Precision divides the match count by the total predicted count when that count is positive. Recall divides it by the total gold count when that count is positive. Micro F1 is twice the match count divided by the sum of those two totals when that sum is positive. An aggregate does not specify the error rate for a rare role. If an application requires that role, report its support and per-role precision and recall as well as the aggregate. The role’s value depends on the application.

A short history of SRL systems

The field's trajectory is a useful case study in how NLP moved from feature engineering to pretraining.

Timeline of representative approaches to semantic role labeling.
EraRepresentative approachWhat mattered
2002 to 2005Parse-tree path features with statistical classifiers (Gildea and Jurafsky, 2002), then CRFs and memory-based learners in the CoNLL-2005 shared taskHand-designed features: path from predicate to constituent in the parse tree, head word, voice, predicate lemma, POS
2005 to 2013Dependency-based SRL in CoNLL-2008 and 2009, system ensembles in CoNLL-2005Argument heads as a scoring unit; multilingual PropBank-style annotation in OntoNotes 5.0
2015 to 2018BiLSTM taggers (He et al., 2017), dependency path LSTMs (Roth and Lapata, 2016), joint span prediction (He et al., 2018), graph convolutional networks over syntax (Marcheggiani and Titov, 2017)Learned contextual representations and different uses of syntactic information
2019 to 2021BERT-based models evaluated on span and dependency SRL (Shi and Lin, 2019); seq2seq reformulations that cast SRL as translation into an augmented target string (Paolini et al., 2021)Pretrained models for token tagging or augmented-text generation

These examples illustrate different modeling choices rather than a sequence in which each newer approach replaces all earlier ones. Parse features expose explicit structural information; recurrent and pretrained encoders learn contextual features; text-to-text approaches serialize annotations into an output string. The representation and decoding choices affect which outputs a model can express and how its errors must be scored.

For a concrete example of pretrained encoders in SRL, Shi and Lin (2019) present BERT-based models for span and dependency tasks. Their evaluation settings and comparisons belong to that paper; they do not establish a timeless performance level for every OntoNotes experiment. To attribute a gain to pretraining rather than syntax features or decoding, compare systems that control those other choices or inspect a relevant ablation.

A generated annotation must satisfy the same structural requirements as any other prediction: spans must align to the input, labels must belong to the chosen inventory, and the selected predicate must be identifiable. Set an invalid-output policy before scoring, check the predictions against these requirements, and apply that policy consistently when computing F1. For example, an output phrase absent from the input cannot be accepted as an exact input span, even if it is a plausible paraphrase of an argument.

Prompted and task-trained systems should be compared on a named dataset, output protocol, and metric, rather than assigned a universal accuracy ranking. The text-to-text formulation itself is compatible with supervised training: Paolini et al. (2021) include SRL among the structured tasks in their Translation between Augmented Natural Languages framework. Serialization is a modeling choice, not evidence that exact structural prediction is impossible.

A worked example

Take the sentence:

The committee awarded the prize to the young researcher last year.

With awarded as the predicate, compare the following participant analyses. The frame-based description needs a qualification about the resource target.

Thematic roles. The committee is Agent, the prize is Theme, to the young researcher is Recipient, and last year is Time.

PropBank. In award.01, the official descriptors are giver for A0, award for A1, and beneficiary for A2. In this sentence, those labels go on the committee, the prize, and to the young researcher, respectively. Last year is AM-TMP, an adjunct.

Frame semantics. Giving provides a useful comparison: its core FEs are Donor, Theme, and Recipient, and Time is peripheral. Those names describe the transfer participants in a paraphrase with gave. FrameNet 1.7 lists give.v in Giving but does not list award.v there or in its LU index. The transfer analogy is therefore not a released FrameNet annotation of awarded: a corpus analysis must consult the actual lexical unit and release rather than assign a frame from intuition alone.

Proto-roles. On an intentional-awarding reading, the committee has agent-like properties such as volition and causation. The prize is the transferred participant, and the researcher is the recipient. Calling the prize Theme does not establish Dowty's incremental-theme property: that property concerns a separate relation between parts of a participant and parts of an event. A proto-role analysis must justify the predicate entailments rather than derive a score from the names Agent, Theme, or Recipient.

The four analyses are not competing answers to the same question. They address different questions. Thematic roles answer "what kind of participant is this?" PropBank answers "which role does the selected roleset define for this argument?" FrameNet answers "which frame element does this fill in this scene?" Proto-roles answer "how agent-like or patient-like is this participant?" The thematic-role, PropBank, and proto-role analyses use the award sentence. The FrameNet comparison uses a paraphrase with gave. Choose the analysis that fits the application.

Now passivize:

The prize was awarded to the young researcher by the committee last year.

The same participant roles are expressed differently in the passive. A1 covers the prize, A2 covers to the young researcher, and A0 covers by the committee, including by in this worked annotation.

A subject-to-A0 shortcut would mislabel the prize. The passive construction identifies it as the passive subject, and the agentive phrase by the committee points to the awarder. The award.01 role definitions determine the numbers. Compare by noon, which gives a time limit. Gildea and Jurafsky use a voice feature and prepositions as PP heads. The toy tagger below uses local token features, including an auxiliary flag for the current word.

Finally, nominalize:

The committee's award of the prize to the young researcher last year.

The predicate is now the noun award. In this event reading the same participants can be identified. A verb-only target excludes this nominal relation; the PropBank frame files and FrameNet include nominal predicates.

The noun award in this event reading relates an awarder, an award, and an awardee. A verb-only target excludes this nominal relation. Whether a system must support it depends on the extraction task; the example does not measure a corpus-wide coverage gain.

Building an SRL tagger step by step

The rest of the chapter implements a small PropBank-style tagger. The corpus is deliberately tiny (31 sentences) so every step is visible. The point is the pipeline, not the accuracy.

The toy system trains on 25 sentences, uses hand-designed features, and predicts BIO tags with logistic regression. Larger systems make different choices about representations and decoding, as the cited examples show. This exercise exposes feature extraction, fitting, decoding, and evaluation; scaling its data alone would not turn it into every model in the timeline.

A miniature annotated corpus

Each entry stores the token list, the predicate index, and a list of (start, end, role) spans using half-open indexing.

PythonIn [5]
CollapseExpand
def sent(text, pred_word, args):
    tokens = text.split()
    return {"tokens": tokens, "pred": tokens.index(pred_word), "args": args}


CORPUS = [
    sent("The chef carved the roast .", "carved", [(0, 2, "A0"), (3, 5, "A1")]),
    sent("The chef sliced the bread .", "sliced", [(0, 2, "A0"), (3, 5, "A1")]),
    sent(
        "The gardener planted the roses .",
        "planted",
        [(0, 2, "A0"), (3, 5, "A1")],
    ),
    sent(
        "The student answered the question .",
        "answered",
        [(0, 2, "A0"), (3, 5, "A1")],
    ),
    sent(
        "The mechanic repaired the engine .",
        "repaired",
        [(0, 2, "A0"), (3, 5, "A1")],
    ),
    sent(
        "The nurse examined the patient .",
        "examined",
        [(0, 2, "A0"), (3, 5, "A1")],
    ),
    sent(
        "The company sold the building .", "sold", [(0, 2, "A0"), (3, 5, "A1")]
    ),
    sent(
        "The jury rejected the appeal .",
        "rejected",
        [(0, 2, "A0"), (3, 5, "A1")],
    ),
    sent(
        "The chef carved the roast with a knife .",
        "carved",
        [(0, 2, "A0"), (3, 5, "A1"), (5, 8, "AM-MNR")],
    ),
    sent(
        "The gardener dug the hole with a spade .",
        "dug",
        [(0, 2, "A0"), (3, 5, "A1"), (5, 8, "AM-MNR")],
    ),
    sent(
        "The mechanic opened the crate with a crowbar .",
        "opened",
        [(0, 2, "A0"), (3, 5, "A1"), (5, 8, "AM-MNR")],
    ),
    sent(
        "The student wrote the essay with a pencil .",
        "wrote",
        [(0, 2, "A0"), (3, 5, "A1"), (5, 8, "AM-MNR")],
    ),
    sent(
        "The chef cooked the meal in the kitchen .",
        "cooked",
        [(0, 2, "A0"), (3, 5, "A1"), (5, 8, "AM-LOC")],
    ),
    sent(
        "The gardener planted the roses in the garden .",
        "planted",
        [(0, 2, "A0"), (3, 5, "A1"), (5, 8, "AM-LOC")],
    ),
    sent(
        "The student read the book in the library .",
        "read",
        [(0, 2, "A0"), (3, 5, "A1"), (5, 8, "AM-LOC")],
    ),
    sent(
        "The mechanic parked the truck on the street .",
        "parked",
        [(0, 2, "A0"), (3, 5, "A1"), (5, 8, "AM-LOC")],
    ),
    sent(
        "The chef gave a plate to the guest .",
        "gave",
        [(0, 2, "A0"), (3, 5, "A1"), (5, 8, "A2")],
    ),
    sent(
        "The teacher handed a book to the student .",
        "handed",
        [(0, 2, "A0"), (3, 5, "A1"), (5, 8, "A2")],
    ),
    sent(
        "The company sent a contract to the client .",
        "sent",
        [(0, 2, "A0"), (3, 5, "A1"), (5, 8, "A2")],
    ),
    sent(
        "The nurse brought a file to the doctor .",
        "brought",
        [(0, 2, "A0"), (3, 5, "A1"), (5, 8, "A2")],
    ),
    sent(
        "The roast was carved by the chef .",
        "carved",
        [(0, 2, "A1"), (5, 7, "A0")],
    ),
    sent(
        "The bread was sliced by the chef .",
        "sliced",
        [(0, 2, "A1"), (5, 7, "A0")],
    ),
    sent(
        "The engine was repaired by the mechanic .",
        "repaired",
        [(0, 2, "A1"), (5, 7, "A0")],
    ),
    sent(
        "The essay was written by the student .",
        "written",
        [(0, 2, "A1"), (5, 7, "A0")],
    ),
    sent("The vase broke .", "broke", [(0, 2, "A1")]),
    sent("The window cracked .", "cracked", [(0, 2, "A1")]),
    sent("The ice melted .", "melted", [(0, 2, "A1")]),
    sent("The child laughed .", "laughed", [(0, 2, "A0")]),
    sent("The audience applauded .", "applauded", [(0, 2, "A0")]),
    sent(
        "The chef carved the roast yesterday .",
        "carved",
        [(0, 2, "A0"), (3, 5, "A1"), (5, 6, "AM-TMP")],
    ),
    sent(
        "The student read the book yesterday .",
        "read",
        [(0, 2, "A0"), (3, 5, "A1"), (5, 6, "AM-TMP")],
    ),
]

The span (5, 8) in the instrument sentence covers "with a knife". The span (5, 7) in the passive covers "the chef" without by. The toy annotations are specified inline for this exercise. The exercise groups its instrument phrases under AM-MNR and omits by from passive A0 spans for simplicity; an official frameset may instead define a numbered instrument role, and a benchmark's boundary conventions must be followed when evaluating against that benchmark. The toy labels in the garden AM-LOC in its planting example. The released plant.01 lists the selected planting location as ARG2 (frame file).

These toy boundaries differ from the PropBank paper's passive example, which includes by in the Arg0 span (Palmer et al., 2005, Section 1). Training predictions and evaluation references need compatible span boundaries, either directly or through an explicit conversion. The training labels define the behavior being learned, but successful imitation is an empirical question. Evaluate predictions against that convention rather than assume the model has learned it.

Out[6]:
Sentences: 31   Annotated arguments: 71
sent_id predicate role argument sentence
0 0 carved A0 The chef The chef carved the roast .
1 0 carved A1 the roast The chef carved the roast .
2 1 sliced A0 The chef The chef sliced the bread .
3 1 sliced A1 the bread The chef sliced the bread .
4 2 planted A0 The gardener The gardener planted the roses .
5 2 planted A1 the roses The gardener planted the roses .

Thirty-one sentences yield 71 annotated arguments. Each table row records one predicate-argument instance; the span metric below scores held-out argument instances rather than tokens.

The preview shows each argument’s text and role alongside its sentence ID, predicate text, and full sentence. These fields let you trace an annotation back to its sentence. The exact held-out score below uses sentence ID, start and end offsets, and role, so identical offsets in different sentences identify different arguments.

VisualizationOut [7]
CollapseExpand
Horizontal bar chart showing counts of semantic role labels in a small corpus.
Role label frequencies in the 31 hand-written examples. A0 and A1 account for most annotated arguments; the other labels occur in fewer examples by construction. The chart describes this toy dataset, not an estimate of a language-wide role distribution.

A0 and A1 together account for more than half the annotations. That frequency statement does not specify the accuracy of a particular restricted classifier. The later evaluation compares token accuracy with exact spans on actual predictions.

The same qualitative frequency difference appears in the CoNLL-2005 counts discussed earlier. The counts here come from the inline tutorial examples above. Use the actual support counts when reading the scores below. A role with many gold arguments contributes more to the recall denominator, and a role with many predictions contributes more to the precision denominator. A single error can dominate a category with only one held-out argument.

The next heatmap shows where roles start relative to the predicate in all 31 authored sentences, including the six held-out examples. It shows the templates in the corpus. A controlled feature comparison on held-out data would be needed to measure predictive value. A span is counted as before if it starts to the left of the predicate (offset < 0), near after if it starts at the predicate or up to two tokens after it (offset 0 to 2), and far after if it starts more than two tokens to the right (offset > 2). The code includes zero in the middle bucket. None of the supplied gold spans starts at the predicate.

PythonIn [8]
CollapseExpand
import numpy as np
import pandas as pd

position_order = ["before", "near after", "far after"]
position_rows = []
for item in CORPUS:
    pred = item["pred"]
    for s, e, r in item["args"]:
        offset = s - pred
        if offset > 2:
            bucket = "far after"
        elif offset >= 0:
            bucket = "near after"
        else:
            bucket = "before"
        position_rows.append({"role": r, "bucket": bucket})

position_df = pd.DataFrame(position_rows)
position_matrix = (
    position_df.groupby(["role", "bucket"])
    .size()
    .unstack(fill_value=0)
    .reindex(columns=position_order, fill_value=0)
)
role_position_order = position_matrix.index.tolist()
position_ticks = np.arange(len(role_position_order))
bucket_ticks = np.arange(len(position_order))
VisualizationOut [9]
CollapseExpand
Heatmap of role start positions relative to the predicate in the toy corpus.
Role start positions relative to the predicate in the toy corpus. A0 starts mostly before the predicate, A1 occurs on both sides, and the toy AM modifiers begin more than two tokens to the right. The pattern follows this hand-built corpus and is not a universal syntactic rule.

Argument structure patterns

Grouping the annotations by role signature counts the recurrence of each supplied role signature.

Out[10]:
role_signature sentences
0 A0 + A1 12
1 A0 + A1 + AM-MNR 4
2 A0 + A1 + AM-LOC 4
3 A0 + A1 + A2 4
4 A1 3
5 A0 2
6 A0 + A1 + AM-TMP 2
Out[11]:
sentence roles
0 The chef carved the roast . A0, A1
1 The chef carved the roast with a knife . A0, A1, AM-MNR
2 The roast was carved by the chef . A0, A1
3 The chef carved the roast yesterday . A0, A1, AM-TMP

The four carve sentences instantiate three distinct role signatures: plain transitive (A0 + A1), transitive with an instrument (A0 + A1 + AM-MNR), and transitive with a temporal adjunct (A0 + A1 + AM-TMP). The passive sentence has the same role set as the plain transitive, with A1 in subject position and A0 in a by-phrase. The selected participant inventory and its syntactic realizations must be distinguished. In the temporal example, adding yesterday changes the supplied role signature while leaving the selected carver and carved-thing participants unchanged. The instrument example uses a tutorial AM-MNR label; the official PropBank carve.01 frameset includes an ARG2 instrument role.

Across the 31 sentences, the co-occurrence matrix counts how often each pair of labels appears in the same annotation, with roles ordered alphabetically. Its diagonal counts sentences containing a label; an off-diagonal cell counts sentences containing both labels. These descriptive counts do not by themselves estimate grammatical licensing or establish statistical dependence in a population.

PythonIn [12]
CollapseExpand
import numpy as np
import pandas as pd

role_order = sorted({r for item in CORPUS for _, _, r in item["args"]})
role_pos = {r: i for i, r in enumerate(role_order)}
cooc = np.zeros((len(role_order), len(role_order)), dtype=int)
for item in CORPUS:
    present = sorted({r for _, _, r in item["args"]})
    for r in present:
        for c in present:
            cooc[role_pos[r], role_pos[c]] += 1
cooc_df = pd.DataFrame(cooc, index=role_order, columns=role_order)
role_ticks = np.arange(len(role_order))
VisualizationOut [13]
CollapseExpand
Heatmap showing how often semantic role labels co-occur in the toy corpus.
Counts of sentences containing each pair of role labels in the toy corpus. A diagonal cell counts sentences with that label; an off-diagonal cell counts sentences with both. Large values reflect repeated tutorial templates rather than a test of grammatical licensing.

The carve example makes the point concretely. The verb has a stable core meaning, but it appears in several syntactic frames. The passive rearranges their expression, while the instrument and time phrases add annotations in this toy scheme. VerbNet records class frames and semantic roles; PropBank records predicate-sense role definitions and examples. Neither statement guarantees an exhaustive list of every acceptable carve construction.

Scoring proto-roles by hand

The following table illustrates property-counting with hand-assigned binary values. It is a coding exercise inspired by proto-roles, not an annotation from Dowty or a validated lexical analysis. Dowty's properties concern what a predicate entails about an argument; an imaginable event detail such as whether a door moves is not automatically an entailment of the predicate.

PythonIn [14]
CollapseExpand
AGENT_PROPS = [
    "volitional",
    "sentient",
    "causes_change",
    "moves",
    "exists_independently",
]
PATIENT_PROPS = [
    "changes_state",
    "incremental_theme",
    "causally_affected",
    "stationary",
    "existence_dependent",
]

PROTO_DATA = [
    # (argument, event, agent-property bits, patient-property bits)
    ("the chef", "carve the roast", (1, 1, 1, 1, 1), (0, 0, 0, 0, 0)),
    ("the roast", "carve the roast", (0, 0, 0, 0, 1), (1, 1, 1, 1, 0)),
    ("the key", "open the door", (0, 0, 1, 1, 1), (0, 0, 0, 0, 0)),
    ("the door", "open the door", (0, 0, 0, 0, 1), (1, 1, 1, 1, 0)),
    ("the storm", "destroy the house", (0, 0, 1, 1, 1), (0, 0, 0, 0, 0)),
    ("the house", "destroy the house", (0, 0, 0, 0, 1), (1, 1, 1, 1, 0)),
]

proto_df = pd.DataFrame(
    [
        {
            "argument": arg,
            "event": ev,
            "agent_score": sum(a),
            "patient_score": sum(p),
            "agent_properties": ", ".join(
                k for k, v in zip(AGENT_PROPS, a) if v
            )
            or "none",
        }
        for arg, ev, a, p in PROTO_DATA
    ]
)
proto_x = np.arange(len(proto_df))
proto_width = 0.38
Out[15]:
argument agent_score patient_score agent_properties
0 the chef 5 0 volitional, sentient, causes_change, moves, exists_independently
1 the roast 1 4 exists_independently
2 the key 3 0 causes_change, moves, exists_independently
3 the door 1 4 exists_independently
4 the storm 3 0 causes_change, moves, exists_independently
5 the house 1 4 exists_independently

The preview lists the Agent properties and shows totals for both groups. To see which Patient properties were assigned, find the matching row in PROTO_DATA. Match the 1s in its final tuple to PATIENT_PROPS in order. The chef has five agent-property indicators and zero patient-property indicators; the roast has one and four. The storm has three agent-property indicators. These sums demonstrate how a participant can have several agent-like properties without volition. They do not establish that motion or incrementality is entailed by every use of the displayed predicate, and they are not a fitted predictor of subject position.

The numbers below follow directly from the supplied vectors. The vectors record the interpretation chosen for this exercise. The four Patient indicators assigned to the door are change of state, incrementality, causal affectedness and relative stationarity. The example does not specify a motion path for the door or the key. A prototype-like cluster does not mean every property must be a continuously graded quantity. The table demonstrates arithmetic over the chosen judgments; it does not establish those judgments as necessary lexical entailments or prove a subject-selection rule.

VisualizationOut [16]
CollapseExpand
Grouped bar chart of proto-agent and proto-patient scores for six noun phrases.
Sums of hand-assigned proto-role property indicators for six illustrative arguments. The plotted values describe the supplied coding exercise, not measured annotations or an independent prediction of syntax.

Numbered roles in buy.01 and sell.01

The following manually specified table compares selected roles from the PropBank senses buy.01 and sell.01. It uses scene descriptions for the column values; these descriptions are not a claim that FrameNet itself assigns A0 through A3. The official frame files assign buyer to buy.01 A0 and seller to sell.01 A0 (buy, sell).

PythonIn [17]
CollapseExpand
FRAMES = {
    "buy.01": {
        "A0": "Buyer",
        "A1": "Goods",
        "A2": "Seller",
        "A3": "Price paid",
    },
    "sell.01": {
        "A0": "Seller",
        "A1": "Goods",
        "A2": "Buyer",
        "A3": "Price paid",
    },
}

frame_table = pd.DataFrame({frame: spec for frame, spec in FRAMES.items()}).T
frame_table
Out[18]:
A0 A1 A2 A3
buy.01 Buyer Goods Seller Price paid
sell.01 Seller Goods Buyer Price paid

A0 is the buyer in one roleset and the seller in the other. Numbered role meanings are predicate-specific; their identity can remain stable while syntactic position changes, as the passive examples showed. The table lists only the four roles relevant to the commercial-transaction example. It is not the complete inventory of every buying or selling sense, and its mnemonic descriptions do not replace the definitions in the frame files.

Choose a resource by its annotation target, available data, definitions, and license. A need for a particular corpus or role distinction does not establish a general PropBank-versus-FrameNet ranking. If an application combines their labels, use an explicit sense/frame mapping and record the resource versions.

Converting spans to BIO tags

The tagger operates on per-token tags, so the first step is a converter.

PythonIn [19]
CollapseExpand
def to_bio(tokens, pred, args):
    tags = ["O"] * len(tokens)
    tags[pred] = "V"
    for start, end, role in args:
        tags[start] = f"B-{role}"
        for i in range(start + 1, end):
            tags[i] = f"I-{role}"
    return tags
ConsoleOut [20]
CollapseExpand
       The  B-A0
      chef  I-A0
    carved  V
       the  B-A1
     roast  I-A1
      with  B-AM-MNR
         a  I-AM-MNR
     knife  I-AM-MNR
         .  O

The predicate token gets V and is excluded from the argument spans. The instrument PP receives a clean B-/I-/I- sequence.

The converter assumes valid, contiguous, nonoverlapping spans within the token list, with no argument covering the supplied predicate. It does not validate these preconditions: overlapping spans can overwrite earlier tags, including V. If an argument were discontinuous, the converter would need a more complex scheme. This converter implements a restricted contiguous-span subset, rather than all PropBank continuation and reference conventions.

Features that condition on the predicate

Every feature is computed with the predicate index available, because the same word can be an argument of different predicates with different roles.

PythonIn [21]
CollapseExpand
PREPOSITIONS = {"by", "with", "in", "on", "to", "at", "from", "for"}
DETERMINERS = {"the", "a", "an"}


def token_features(tokens, i, pred):
    w = tokens[i].lower()
    rel = i - pred
    return {
        "bias": 1.0,
        "w": w,
        "prev": tokens[i - 1].lower() if i > 0 else "<s>",
        "next": tokens[i + 1].lower() if i + 1 < len(tokens) else "</s>",
        "rel": str(max(-6, min(6, rel))),
        "side": "before" if rel < 0 else ("at" if rel == 0 else "after"),
        "is_det": w in DETERMINERS,
        "is_prep": w in PREPOSITIONS,
        "is_aux": w in {"was", "were", "is", "are", "be"},
        "suffix": w[-3:],
        "pred_form": tokens[pred].lower(),
    }

The rel feature encodes distance and direction from the predicate. The side feature coarse-grains it. The prev and next features expose local word context, including nearby prepositions. They do not supply a syntactic parse or guarantee where a phrase ends; the held-out errors let us inspect that limitation.

The feature set is small enough to inspect. Word identity and neighboring words supply lexical context; relative position records distance from the selected predicate. The preposition and determiner flags are simple lexical indicators. The auxiliary flag records whether the current token is one of a few listed auxiliary forms; it does not explicitly label the clause voice. The suffix keeps the final three characters of the lowercased token, or the whole token when it is shorter. The pred_form feature is a lowercased surface form, not a lemma produced by morphological analysis. These distinctions matter when interpreting what the classifier could have learned.

A held-out split with unseen vocabulary

Six sentences are held out. Each test role-label signature also occurs in training, while several test sentences contain verb forms and nouns absent from training. Here a signature is the sorted multiset of observed role labels, not a FrameNet frame or a complete lexical entry.

PythonIn [22]
CollapseExpand
from sklearn.feature_extraction import DictVectorizer
from sklearn.linear_model import LogisticRegression

TEST_IDS = [5, 11, 15, 19, 23, 28]


def build_examples(ids):
    X, y = [], []
    for sid in ids:
        item = CORPUS[sid]
        tokens, pred = item["tokens"], item["pred"]
        tags = to_bio(tokens, pred, item["args"])
        for i, tag in enumerate(tags):
            X.append(token_features(tokens, i, pred))
            y.append(tag)
    return X, y


train_ids = [i for i in range(len(CORPUS)) if i not in TEST_IDS]
X_train, y_train = build_examples(train_ids)
X_test, y_test = build_examples(TEST_IDS)

vectorizer = DictVectorizer(sparse=True)
X_train_vec = vectorizer.fit_transform(X_train)
X_test_vec = vectorizer.transform(X_test)

tagger = LogisticRegression(max_iter=2000, C=1.0)
tagger.fit(X_train_vec, y_train)

With the book’s specified scikit-learn 1.8.0 and default lbfgs solver, this multiclass fit uses multinomial logistic regression over BIO tags (API reference). Its 25 training sentences are intended to make feature extraction and evaluation inspectable. This experiment does not compare the classifier with a neural system, and its accuracy cannot establish a general model ranking.

Several held-out sentences contain verb forms and nouns absent from training. These full-feature predictions do not measure a word-only tagger's accuracy or isolate the contribution of positional features. The classifier also has local context and lexical-class indicators; a controlled ablation would be needed to separate their contributions.

Token accuracy is not the metric

ConsoleOut [23]
CollapseExpand
Token accuracy:  0.933
All-O baseline:  0.178

The all-O baseline is low in this argument-dense toy corpus: most tokens belong to an annotated argument. Token accuracy also scores the V tag at each supplied predicate position. Predicate identification is a separate task. Token accuracy still hides boundary errors, because getting most tokens right does not guarantee an exact argument span.

In data where O is frequent, an all-O system can obtain high token accuracy while retrieving no arguments. That is not the class distribution of this toy split. We report exact labeled span F1 so each argument must have both the correct boundaries and the correct role. The scoring keys retain sentence identity; spans with the same offsets in different sentences must remain distinct. This toy corpus selects one predicate per sentence, so its sentence ID also identifies the selected predicate instance. A corpus with several predicates per sentence must add predicate identity to the scoring key. The decoder below starts spans at B tags and skips I tags outside a matching run. It receives only the tags, so a predicted role span can cover the selected predicate. The toy scores the raw spans. A span covering the predicate cannot match a gold argument here, so it counts as a false positive.

PythonIn [24]
CollapseExpand
def bio_to_spans(tags):
    spans = []
    i = 0
    while i < len(tags):
        if tags[i].startswith("B-"):
            role = tags[i][2:]
            j = i + 1
            while j < len(tags) and tags[j] == f"I-{role}":
                j += 1
            spans.append((i, j, role))
            i = j
        else:
            i += 1
    return spans
Out[25]:
sentence gold predicted
sent_id
5 The nurse examined the patient . A0[0, 2): The nurse | A1[3, 5): the patient A0[0, 2): The nurse | A1[3, 5): the patient
11 The student wrote the essay with a pencil . A0[0, 2): The student | A1[3, 5): the essay | AM-MNR[5, 8): with a pencil A0[0, 2): The student | A1[3, 5): the essay | AM-MNR[5, 8): with a pencil
15 The mechanic parked the truck on the street . A0[0, 2): The mechanic | A1[3, 5): the truck | AM-LOC[5, 8): on the street A0[0, 2): The mechanic | A1[3, 5): the truck | A1[6, 7): the
19 The nurse brought a file to the doctor . A0[0, 2): The nurse | A1[3, 5): a file | A2[5, 8): to the doctor A0[0, 2): The nurse | A1[3, 5): a file | A2[5, 7): to the
23 The essay was written by the student . A1[0, 2): The essay | A0[5, 7): the student A1[0, 2): The essay | A0[5, 7): the student
28 The audience applauded . A0[0, 2): The audience A0[0, 2): The audience
Out[26]:
Span precision: 0.857
Span recall:    0.857
Span F1:        0.857
Gold spans:     14
role gold precision recall f1
0 A0 6 1.000 1.0 1.000
1 A1 5 0.833 1.0 0.909
2 A2 1 0.000 0.0 0.000
3 AM-LOC 1 0.000 0.0 0.000
4 AM-MNR 1 1.000 1.0 1.000
VisualizationOut [27]
CollapseExpand
Horizontal bars show exact span F1 for five roles in held-out gold or predictions.
Exact labeled span F1 for labels found in the gold annotations or predictions on six fixed held-out sentences. Several roles have only one gold test argument, so their scores are especially sensitive to a single error. These illustrative results are not a benchmark comparison.

Exact-span scores vary by role on this held-out split. Compare the half-open token offsets in the prediction table with per-role precision and recall: a correct label on an incomplete span still fails exact matching. There is only one held-out instance of each of A2, AM-LOC, and AM-MNR, so a single boundary mistake can move a role score from one to zero. These six sentences do not establish how a model generalizes at scale.

Per-role analysis localizes the observed errors, but does not by itself distinguish learning from memorization or show which feature caused a prediction. Use controlled feature ablations to test a feature’s contribution, then check whether the pattern holds on additional held-out examples. Here it is a tool for inspecting errors, not a causal account of why the classifier made them.

Running the tagger on a new sentence

ConsoleOut [28]
CollapseExpand
         The  B-A0
   committee  I-A0
     awarded  V
         the  B-A1
       prize  I-A1
          to  B-A2
         the  I-A2
       young  I-A1
  researcher  O
           .  O

Extracted spans: [(0, 2, 'A0'), (3, 5, 'A1'), (5, 7, 'A2')]

The sentence contains the unseen verb awarded and unseen nouns. The decoder returns A2 over "to the", at [5, 7), while the intended recipient span "to the young researcher" is [5, 9). The label agrees, but the end boundary fails exact matching. The tagger gives young I-A1 outside an A1 run, so the decoder skips that tag. This single example is an error-analysis exercise, not evidence of general accuracy on unseen verbs.

The toy training examples deliberately reuse subject-before-verb and transfer-to-recipient patterns. This makes positional and lexical-context features candidates for explaining the predictions, but the experiment has not isolated their contributions. Changing the voice, adding a longer argument, or replacing a familiar phrase provides a useful next test. To claim that one feature or model improves transfer, evaluate a controlled comparison on additional held-out examples.

Comments

Add to the conversation

A question, a correction, or an example from your own work.

No comments yet. Be the first to share your thoughts!

Newsletter

Stay up to date

Get new articles, book updates, and notes on AI and technology.

Unsubscribe at any time. Privacy policy