Skip to content

Commit 42072f4

Browse files
Add spancat pipeline in spacy debug data (#10070)
* Setup debug data for spancat * Add check for missing labels * Add low-level data warning error * Improve logic when compiling the gold train data * Implement check for negative examples * Remove breakpoint * Remove ws_ents and missing entity checks * Fix mypy errors * Make variable name spans_key consistent * Rename pipeline -> component for consistency * Account for missing labels per spans_key * Cleanup variable names for consistency * Improve brevity of conditional statements * Remove unused variables * Include spans_key as an argument for _get_examples * Add a conditional check for spans_key * Update spancat debug data based on new API - Instead of using _get_labels_from_model(), I'm now using _get_labels_from_spancat() (cf. https://github.com/explosion/spaCy/pull10079) - The way information is displayed was also changed (text -> table) * Rename model_labels to ensure mypy works * Update wording on warning messages Use "span type" instead of "entity type" in wording the warning messages. This is because Spans aren't necessarily entities. * Update component type into a Literal This is to make it clear that the component parameter should only accept either 'spancat' or 'ner'. * Update checks to include actual model span_keys Instead of looking at everything in the data, we only check those span_keys from the actual spancat component. Instead of doing the filter inside the for-loop, I just made another dictionary, data_labels_in_component to hold this value. * Update spacy/cli/debug_data.py * Show label counts only when verbose is True Co-authored-by: Adriane Boyd <adrianeboyd@gmail.com> Co-authored-by: Adriane Boyd <adrianeboyd@gmail.com>
1 parent 72fece7 commit 42072f4

1 file changed

Lines changed: 95 additions & 7 deletions

File tree

spacy/cli/debug_data.py

Lines changed: 95 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,70 @@ def debug_data(
193193
else:
194194
msg.info("No word vectors present in the package")
195195

196+
if "spancat" in factory_names:
197+
model_labels_spancat = _get_labels_from_spancat(nlp)
198+
has_low_data_warning = False
199+
has_no_neg_warning = False
200+
201+
msg.divider("Span Categorization")
202+
msg.table(model_labels_spancat, header=["Spans Key", "Labels"], divider=True)
203+
204+
msg.text("Label counts in train data: ", show=verbose)
205+
for spans_key, data_labels in gold_train_data["spancat"].items():
206+
msg.text(
207+
f"Key: {spans_key}, {_format_labels(data_labels.items(), counts=True)}",
208+
show=verbose,
209+
)
210+
# Data checks: only take the spans keys in the actual spancat components
211+
data_labels_in_component = {
212+
spans_key: gold_train_data["spancat"][spans_key]
213+
for spans_key in model_labels_spancat.keys()
214+
}
215+
for spans_key, data_labels in data_labels_in_component.items():
216+
for label, count in data_labels.items():
217+
# Check for missing labels
218+
spans_key_in_model = spans_key in model_labels_spancat.keys()
219+
if (spans_key_in_model) and (
220+
label not in model_labels_spancat[spans_key]
221+
):
222+
msg.warn(
223+
f"Label '{label}' is not present in the model labels of key '{spans_key}'. "
224+
"Performance may degrade after training."
225+
)
226+
# Check for low number of examples per label
227+
if count <= NEW_LABEL_THRESHOLD:
228+
msg.warn(
229+
f"Low number of examples for label '{label}' in key '{spans_key}' ({count})"
230+
)
231+
has_low_data_warning = True
232+
# Check for negative examples
233+
with msg.loading("Analyzing label distribution..."):
234+
neg_docs = _get_examples_without_label(
235+
train_dataset, label, "spancat", spans_key
236+
)
237+
if neg_docs == 0:
238+
msg.warn(f"No examples for texts WITHOUT new label '{label}'")
239+
has_no_neg_warning = True
240+
241+
if has_low_data_warning:
242+
msg.text(
243+
f"To train a new span type, your data should include at "
244+
f"least {NEW_LABEL_THRESHOLD} instances of the new label",
245+
show=verbose,
246+
)
247+
else:
248+
msg.good("Good amount of examples for all labels")
249+
250+
if has_no_neg_warning:
251+
msg.text(
252+
"Training data should always include examples of spans "
253+
"in context, as well as examples without a given span "
254+
"type.",
255+
show=verbose,
256+
)
257+
else:
258+
msg.good("Examples without ocurrences available for all labels")
259+
196260
if "ner" in factory_names:
197261
# Get all unique NER labels present in the data
198262
labels = set(
@@ -238,7 +302,7 @@ def debug_data(
238302
has_low_data_warning = True
239303

240304
with msg.loading("Analyzing label distribution..."):
241-
neg_docs = _get_examples_without_label(train_dataset, label)
305+
neg_docs = _get_examples_without_label(train_dataset, label, "ner")
242306
if neg_docs == 0:
243307
msg.warn(f"No examples for texts WITHOUT new label '{label}'")
244308
has_no_neg_warning = True
@@ -573,6 +637,7 @@ def _compile_gold(
573637
"deps": Counter(),
574638
"words": Counter(),
575639
"roots": Counter(),
640+
"spancat": dict(),
576641
"ws_ents": 0,
577642
"boundary_cross_ents": 0,
578643
"n_words": 0,
@@ -617,6 +682,15 @@ def _compile_gold(
617682
data["boundary_cross_ents"] += 1
618683
elif label == "-":
619684
data["ner"]["-"] += 1
685+
if "spancat" in factory_names:
686+
for span_key in list(eg.reference.spans.keys()):
687+
if span_key not in data["spancat"]:
688+
data["spancat"][span_key] = Counter()
689+
for i, span in enumerate(eg.reference.spans[span_key]):
690+
if span.label_ is None:
691+
continue
692+
else:
693+
data["spancat"][span_key][span.label_] += 1
620694
if "textcat" in factory_names or "textcat_multilabel" in factory_names:
621695
data["cats"].update(gold.cats)
622696
if any(val not in (0, 1) for val in gold.cats.values()):
@@ -687,14 +761,28 @@ def _format_labels(
687761
return ", ".join([f"'{l}'" for l in cast(Iterable[str], labels)])
688762

689763

690-
def _get_examples_without_label(data: Sequence[Example], label: str) -> int:
764+
def _get_examples_without_label(
765+
data: Sequence[Example],
766+
label: str,
767+
component: Literal["ner", "spancat"] = "ner",
768+
spans_key: Optional[str] = "sc",
769+
) -> int:
691770
count = 0
692771
for eg in data:
693-
labels = [
694-
label.split("-")[1]
695-
for label in eg.get_aligned_ner()
696-
if label not in ("O", "-", None)
697-
]
772+
if component == "ner":
773+
labels = [
774+
label.split("-")[1]
775+
for label in eg.get_aligned_ner()
776+
if label not in ("O", "-", None)
777+
]
778+
779+
if component == "spancat":
780+
labels = (
781+
[span.label_ for span in eg.reference.spans[spans_key]]
782+
if spans_key in eg.reference.spans
783+
else []
784+
)
785+
698786
if label not in labels:
699787
count += 1
700788
return count

0 commit comments

Comments
 (0)