cascabel-ner 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cascabel_ner-0.8.0/LICENSE +21 -0
- cascabel_ner-0.8.0/PKG-INFO +247 -0
- cascabel_ner-0.8.0/README.md +219 -0
- cascabel_ner-0.8.0/cascabel/__init__.py +86 -0
- cascabel_ner-0.8.0/cascabel/__main__.py +7 -0
- cascabel_ner-0.8.0/cascabel/alignment.py +363 -0
- cascabel_ner-0.8.0/cascabel/bootstrap.py +131 -0
- cascabel_ner-0.8.0/cascabel/card.py +182 -0
- cascabel_ner-0.8.0/cascabel/cascabel.py +1154 -0
- cascabel_ner-0.8.0/cascabel/charlm.py +325 -0
- cascabel_ner-0.8.0/cascabel/cli.py +150 -0
- cascabel_ner-0.8.0/cascabel/constants.py +15 -0
- cascabel_ner-0.8.0/cascabel/dimensions.py +186 -0
- cascabel_ner-0.8.0/cascabel/doc.py +282 -0
- cascabel_ner-0.8.0/cascabel/extensions.py +333 -0
- cascabel_ner-0.8.0/cascabel/metrics.py +66 -0
- cascabel_ner-0.8.0/cascabel/perturb.py +145 -0
- cascabel_ner-0.8.0/cascabel/report.py +629 -0
- cascabel_ner-0.8.0/cascabel/shape.py +41 -0
- cascabel_ner-0.8.0/cascabel_ner.egg-info/PKG-INFO +247 -0
- cascabel_ner-0.8.0/cascabel_ner.egg-info/SOURCES.txt +37 -0
- cascabel_ner-0.8.0/cascabel_ner.egg-info/dependency_links.txt +1 -0
- cascabel_ner-0.8.0/cascabel_ner.egg-info/entry_points.txt +2 -0
- cascabel_ner-0.8.0/cascabel_ner.egg-info/requires.txt +4 -0
- cascabel_ner-0.8.0/cascabel_ner.egg-info/top_level.txt +1 -0
- cascabel_ner-0.8.0/pyproject.toml +59 -0
- cascabel_ner-0.8.0/setup.cfg +4 -0
- cascabel_ner-0.8.0/tests/test_alignment.py +248 -0
- cascabel_ner-0.8.0/tests/test_bootstrap.py +53 -0
- cascabel_ner-0.8.0/tests/test_card.py +59 -0
- cascabel_ner-0.8.0/tests/test_cascabel.py +200 -0
- cascabel_ner-0.8.0/tests/test_charlm.py +147 -0
- cascabel_ner-0.8.0/tests/test_cli.py +82 -0
- cascabel_ner-0.8.0/tests/test_dimensions.py +644 -0
- cascabel_ner-0.8.0/tests/test_doc.py +173 -0
- cascabel_ner-0.8.0/tests/test_metrics.py +51 -0
- cascabel_ner-0.8.0/tests/test_perturb.py +120 -0
- cascabel_ner-0.8.0/tests/test_report.py +256 -0
- cascabel_ner-0.8.0/tests/test_shape.py +28 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Elena Álvarez Mellado
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cascabel-ner
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: Span profiling and fine-grained error analysis for NER and span-labeling datasets and models
|
|
5
|
+
Author-email: Elena Álvarez Mellado <ealvarezmellado@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/lirondos/cascabel
|
|
8
|
+
Keywords: ner,named-entity-recognition,sequence-labeling,evaluation,error-analysis,nlp
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: spacy<4,>=3.4
|
|
24
|
+
Requires-Dist: numpy>=1.21
|
|
25
|
+
Requires-Dist: click>=8
|
|
26
|
+
Requires-Dist: torch>=2.0
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# cascabel 🔔
|
|
30
|
+
|
|
31
|
+
**Span profiling and fine-grained error analysis for NER and span-labeling datasets and models.**
|
|
32
|
+
|
|
33
|
+
cascabel stands for **ChAracterizing Spans for SequenCe lABELing**. It
|
|
34
|
+
implements the span dimensions and error analysis introduced in
|
|
35
|
+
[Álvarez-Mellado & Gonzalo (2024)](http://journal.sepln.org/sepln/ojs/ojs/index.php/pln/article/view/6613)
|
|
36
|
+
(see [Citing](#citing)).
|
|
37
|
+
|
|
38
|
+
A single F1 score tells you *how much* your model fails — cascabel tells you
|
|
39
|
+
*how* and *where*. It aligns predictions against gold annotations, classifies
|
|
40
|
+
every disagreement into a fine-grained error taxonomy, profiles datasets along
|
|
41
|
+
linguistic dimensions, and generates controlled perturbations for stress
|
|
42
|
+
testing. It is language-agnostic and works with any BIO-tagged data.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install cascabel-ner
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The package is called `cascabel-ner` on PyPI and is imported as `cascabel`. (A
|
|
49
|
+
different, unrelated package already uses the name `cascabel` on PyPI, so do not
|
|
50
|
+
install both in one environment.)
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from cascabel import Cascabel
|
|
56
|
+
|
|
57
|
+
test = [
|
|
58
|
+
(["Un", "look", "total", "black"], ["O", "B-ENG", "B-ENG", "I-ENG"]),
|
|
59
|
+
(["Me", "gusta", "el", "shopping"], ["O", "O", "O", "B-ENG"]),
|
|
60
|
+
]
|
|
61
|
+
training = [
|
|
62
|
+
(["Fracaso", "total", "del", "look"], ["O", "O", "O", "B-ENG"]),
|
|
63
|
+
(["El", "streaming", "es", "popular"], ["O", "B-ENG", "O", "O"]),
|
|
64
|
+
]
|
|
65
|
+
prediction = [
|
|
66
|
+
(["Un", "look", "total", "black"], ["O", "B-ENG", "O", "B-ENG"]),
|
|
67
|
+
(["Me", "gusta", "el", "shopping"], ["O", "O", "O", "B-ENG"]),
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
casc = Cascabel.from_sentences(test, training=training, prediction=prediction)
|
|
71
|
+
|
|
72
|
+
casc.span_scores() # strict span-level P/R/F1
|
|
73
|
+
casc.token_scores() # token-level P/R/F1 over BIO tags
|
|
74
|
+
casc.boundary_scores() # span P/R/F1 with labels ignored (exact boundaries only)
|
|
75
|
+
casc.scores_by_label() # span P/R/F1 per entity type
|
|
76
|
+
casc.error_counts() # Counter({'overlap_missed': 1})
|
|
77
|
+
|
|
78
|
+
print(casc.summary()) # everything above as one formatted text report
|
|
79
|
+
casc.report() # ... or as one JSON-serializable dict
|
|
80
|
+
casc.to_html("report.html") # ... or as a color-coded HTML error report
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Or straight from the command line:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
cascabel test.conll pred.conll --training train.conll # text report
|
|
87
|
+
cascabel test.conll pred.conll -f html -o report.html # HTML report
|
|
88
|
+
cascabel test.conll pred.conll -f json # JSON report
|
|
89
|
+
cascabel test.conll --training train.conll # dataset profile only
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Data can also be loaded from CoNLL column files or serialized spaCy `DocBin`s:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
casc = Cascabel.from_conll("test.conll", training="train.conll",
|
|
96
|
+
prediction="pred.conll")
|
|
97
|
+
casc = Cascabel.from_spacy_binary("test.spacy", prediction="pred.spacy")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## The error taxonomy
|
|
101
|
+
|
|
102
|
+
Exact-match evaluation treats a prediction that is one token off exactly like
|
|
103
|
+
no prediction at all. cascabel distinguishes:
|
|
104
|
+
|
|
105
|
+
| Error type | Meaning |
|
|
106
|
+
| -------------------------- | ---------------------------------------------------- |
|
|
107
|
+
| `missed` | gold entity with no overlapping prediction |
|
|
108
|
+
| `spurious` | prediction with no overlapping gold entity |
|
|
109
|
+
| `type` | correct boundaries, wrong label |
|
|
110
|
+
| `fused` | one prediction covering two gold entities |
|
|
111
|
+
| `split` | two predictions inside one gold entity |
|
|
112
|
+
| `missegmented` | prediction boundaries straddle gold entity boundaries|
|
|
113
|
+
| `overlap_missed` | prediction strictly inside a gold entity |
|
|
114
|
+
| `overlap_spurious` | prediction strictly containing a gold entity |
|
|
115
|
+
| `overlap_spurious_missed` | staggered overlap (extra on one side, missing on the other) |
|
|
116
|
+
|
|
117
|
+
Every alignment is available for inspection:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
for alignment in casc.errors():
|
|
121
|
+
print(alignment.error_type,
|
|
122
|
+
[g.text for g in alignment.gold],
|
|
123
|
+
[p.text for p in alignment.predicted])
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The HTML report (`casc.to_html("report.html")`) renders every sentence with
|
|
127
|
+
each predicted span highlighted in the colour of its error type (green when
|
|
128
|
+
correct), plus an error chip per alignment, score cards, and the
|
|
129
|
+
per-label and per-dimension tables. It is a single self-contained file you
|
|
130
|
+
can open in a browser or attach to an experiment log.
|
|
131
|
+
|
|
132
|
+
Segmentation-focused diagnostics build on the same alignments:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
casc.retrieval_capacity() # fraction of gold entity tokens retrieved
|
|
136
|
+
casc.noise_level() # fraction of O tokens labeled as entity
|
|
137
|
+
casc.segmentation_error_rate() # fused/split/missegmented per gold entity
|
|
138
|
+
casc.segmentation_success_rate() # exact matches among adjacent entities
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Dimensions: where does the model fail?
|
|
142
|
+
|
|
143
|
+
cascabel profiles entities along linguistic dimensions — surface properties
|
|
144
|
+
(length in fixed bins, sentence-initial, casing as upper / title / lower /
|
|
145
|
+
mixed, starts capitalized, quoted, adjacent to another entity), exposure properties (how often
|
|
146
|
+
the exact span occurs in training as a whole span or as `O`, and in what
|
|
147
|
+
share of those occurrences it carries its own label: unseen, seen with the
|
|
148
|
+
same label, ambiguous, seen only with other labels; plus, for every word of
|
|
149
|
+
the span, how it was seen in training: inside spans, only as `O`, or never)
|
|
150
|
+
and shape properties (see below). Continuous properties
|
|
151
|
+
(length, training count, label consistency, surprisal, prototypicality,
|
|
152
|
+
label fit) are stored on every span; binary dimensions are views on them.
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
casc.dimension_report() # how the dataset distributes over dimensions
|
|
156
|
+
casc.scores_by_dimension() # P/R/F1 per bucket (read recall: gold side)
|
|
157
|
+
casc.scores_by_dimension(side="predicted") # precision per bucket (predicted spans)
|
|
158
|
+
casc.error_profile("gold") # correct vs. erroneous spans, per dimension
|
|
159
|
+
casc.error_profile("predicted") # the same for predicted spans (spurious too)
|
|
160
|
+
casc.span_table("test") # one record per span, every attribute + outcome
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`scores_by_dimension()` answers questions like *"how much does performance
|
|
164
|
+
drop on entities never seen in training?"*; `error_profile()` asks the
|
|
165
|
+
reverse question, *"what do the spans behind the errors look like, compared
|
|
166
|
+
with the spans handled correctly?"*, on both the gold (recall) and the
|
|
167
|
+
predicted (precision) side.
|
|
168
|
+
|
|
169
|
+
Lexicon and gazetteer dimensions are optional: they appear only when you
|
|
170
|
+
pass your own `lexicon=` / `gazetteer=` word list.
|
|
171
|
+
|
|
172
|
+
## Shape: surprisal and prototypicality
|
|
173
|
+
|
|
174
|
+
Entities often *look* different from ordinary words, and some look more like
|
|
175
|
+
one label than another. cascabel learns both from the training data with
|
|
176
|
+
character-level LSTM language models (PyTorch), which read whole strings,
|
|
177
|
+
spaces and case included, and score them in bits per character:
|
|
178
|
+
|
|
179
|
+
- **Surprisal** (vs. ordinary tokens) — one model trained on the training
|
|
180
|
+
`O` tokens. Every token gets `token._.surprisal`; every span keeps its
|
|
181
|
+
profile (`span._.surprisal_profile`) and summaries: `surprisal_mean`,
|
|
182
|
+
`surprisal_spread` (max − min: partially odd spans) and
|
|
183
|
+
`least_surprising_position` (`"single"` / `"first"` / `"last"` /
|
|
184
|
+
`"interior"`).
|
|
185
|
+
- **Prototypicality** (vs. the training spans of each label) — one model per
|
|
186
|
+
label. `span._.prototypicality` is the span's bits per character under its
|
|
187
|
+
own label's model (form typicality: characters *and* word pattern);
|
|
188
|
+
`span._.label_fit` is own label minus best other label (negative = looks
|
|
189
|
+
like another label, named in `span._.closest_other_label`).
|
|
190
|
+
|
|
191
|
+
Each score also has a per-character perplexity version (`2 ** bits`):
|
|
192
|
+
`surprisal_perplexity`, `span_typicality_perplexity`,
|
|
193
|
+
`prototypicality_perplexity`. Reports list, per label, the 5 least and
|
|
194
|
+
5 most prototypical spans without numbers; the scores are in `span_table()`.
|
|
195
|
+
|
|
196
|
+
Scores are relative to one training set. Hyperparameters live in
|
|
197
|
+
`CharLMConfig` (`charlm_config=`); `shape=False` skips the models.
|
|
198
|
+
|
|
199
|
+
## Perturbations: controlled stress tests
|
|
200
|
+
|
|
201
|
+
Generate modified copies of a dataset that isolate one surface cue, run your
|
|
202
|
+
model on them, and compare:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
from cascabel import recase, add_quotes, strip_quotes
|
|
206
|
+
|
|
207
|
+
lowercased = recase(casc.test, "lower") # does casing matter?
|
|
208
|
+
uppercased = recase(casc.test, "upper", scope="entities")
|
|
209
|
+
quoted = add_quotes(casc.test) # do quotes give it away?
|
|
210
|
+
unquoted = strip_quotes(casc.test)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
All functions return new spaCy `Doc`s with gold annotations preserved.
|
|
214
|
+
|
|
215
|
+
## Scope and lineage
|
|
216
|
+
|
|
217
|
+
cascabel was originally developed to analyze lexical borrowing detection
|
|
218
|
+
(anglicisms in Spanish) and generalized into a language-independent library.
|
|
219
|
+
The original rule-based Spanish graphotactics check was removed in 0.7.0:
|
|
220
|
+
surprisal is now learned from the training data. The evaluation methodology (error taxonomy, dimensions,
|
|
221
|
+
perturbations) follows the author's research on borrowing detection
|
|
222
|
+
evaluation.
|
|
223
|
+
|
|
224
|
+
## Citing
|
|
225
|
+
|
|
226
|
+
If you use cascabel in your research, please cite the paper that introduced
|
|
227
|
+
the span dimensions and error analysis this library implements:
|
|
228
|
+
|
|
229
|
+
> Elena Álvarez-Mellado and Julio Gonzalo. 2024. *Characterizing Spans for
|
|
230
|
+
> Sequence Labeling: A Case on Anglicism Detection.* Procesamiento del
|
|
231
|
+
> Lenguaje Natural, 73, 235–246.
|
|
232
|
+
|
|
233
|
+
```bibtex
|
|
234
|
+
@article{alvarez-mellado-gonzalo-2024-characterizing,
|
|
235
|
+
title = {Characterizing Spans for Sequence Labeling: A Case on Anglicism Detection},
|
|
236
|
+
author = {{\'A}lvarez-Mellado, Elena and Gonzalo, Julio},
|
|
237
|
+
journal = {Procesamiento del Lenguaje Natural},
|
|
238
|
+
volume = {73},
|
|
239
|
+
pages = {235--246},
|
|
240
|
+
year = {2024},
|
|
241
|
+
url = {http://journal.sepln.org/sepln/ojs/ojs/index.php/pln/article/view/6613}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
MIT
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# cascabel 🔔
|
|
2
|
+
|
|
3
|
+
**Span profiling and fine-grained error analysis for NER and span-labeling datasets and models.**
|
|
4
|
+
|
|
5
|
+
cascabel stands for **ChAracterizing Spans for SequenCe lABELing**. It
|
|
6
|
+
implements the span dimensions and error analysis introduced in
|
|
7
|
+
[Álvarez-Mellado & Gonzalo (2024)](http://journal.sepln.org/sepln/ojs/ojs/index.php/pln/article/view/6613)
|
|
8
|
+
(see [Citing](#citing)).
|
|
9
|
+
|
|
10
|
+
A single F1 score tells you *how much* your model fails — cascabel tells you
|
|
11
|
+
*how* and *where*. It aligns predictions against gold annotations, classifies
|
|
12
|
+
every disagreement into a fine-grained error taxonomy, profiles datasets along
|
|
13
|
+
linguistic dimensions, and generates controlled perturbations for stress
|
|
14
|
+
testing. It is language-agnostic and works with any BIO-tagged data.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install cascabel-ner
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The package is called `cascabel-ner` on PyPI and is imported as `cascabel`. (A
|
|
21
|
+
different, unrelated package already uses the name `cascabel` on PyPI, so do not
|
|
22
|
+
install both in one environment.)
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from cascabel import Cascabel
|
|
28
|
+
|
|
29
|
+
test = [
|
|
30
|
+
(["Un", "look", "total", "black"], ["O", "B-ENG", "B-ENG", "I-ENG"]),
|
|
31
|
+
(["Me", "gusta", "el", "shopping"], ["O", "O", "O", "B-ENG"]),
|
|
32
|
+
]
|
|
33
|
+
training = [
|
|
34
|
+
(["Fracaso", "total", "del", "look"], ["O", "O", "O", "B-ENG"]),
|
|
35
|
+
(["El", "streaming", "es", "popular"], ["O", "B-ENG", "O", "O"]),
|
|
36
|
+
]
|
|
37
|
+
prediction = [
|
|
38
|
+
(["Un", "look", "total", "black"], ["O", "B-ENG", "O", "B-ENG"]),
|
|
39
|
+
(["Me", "gusta", "el", "shopping"], ["O", "O", "O", "B-ENG"]),
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
casc = Cascabel.from_sentences(test, training=training, prediction=prediction)
|
|
43
|
+
|
|
44
|
+
casc.span_scores() # strict span-level P/R/F1
|
|
45
|
+
casc.token_scores() # token-level P/R/F1 over BIO tags
|
|
46
|
+
casc.boundary_scores() # span P/R/F1 with labels ignored (exact boundaries only)
|
|
47
|
+
casc.scores_by_label() # span P/R/F1 per entity type
|
|
48
|
+
casc.error_counts() # Counter({'overlap_missed': 1})
|
|
49
|
+
|
|
50
|
+
print(casc.summary()) # everything above as one formatted text report
|
|
51
|
+
casc.report() # ... or as one JSON-serializable dict
|
|
52
|
+
casc.to_html("report.html") # ... or as a color-coded HTML error report
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Or straight from the command line:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
cascabel test.conll pred.conll --training train.conll # text report
|
|
59
|
+
cascabel test.conll pred.conll -f html -o report.html # HTML report
|
|
60
|
+
cascabel test.conll pred.conll -f json # JSON report
|
|
61
|
+
cascabel test.conll --training train.conll # dataset profile only
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Data can also be loaded from CoNLL column files or serialized spaCy `DocBin`s:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
casc = Cascabel.from_conll("test.conll", training="train.conll",
|
|
68
|
+
prediction="pred.conll")
|
|
69
|
+
casc = Cascabel.from_spacy_binary("test.spacy", prediction="pred.spacy")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## The error taxonomy
|
|
73
|
+
|
|
74
|
+
Exact-match evaluation treats a prediction that is one token off exactly like
|
|
75
|
+
no prediction at all. cascabel distinguishes:
|
|
76
|
+
|
|
77
|
+
| Error type | Meaning |
|
|
78
|
+
| -------------------------- | ---------------------------------------------------- |
|
|
79
|
+
| `missed` | gold entity with no overlapping prediction |
|
|
80
|
+
| `spurious` | prediction with no overlapping gold entity |
|
|
81
|
+
| `type` | correct boundaries, wrong label |
|
|
82
|
+
| `fused` | one prediction covering two gold entities |
|
|
83
|
+
| `split` | two predictions inside one gold entity |
|
|
84
|
+
| `missegmented` | prediction boundaries straddle gold entity boundaries|
|
|
85
|
+
| `overlap_missed` | prediction strictly inside a gold entity |
|
|
86
|
+
| `overlap_spurious` | prediction strictly containing a gold entity |
|
|
87
|
+
| `overlap_spurious_missed` | staggered overlap (extra on one side, missing on the other) |
|
|
88
|
+
|
|
89
|
+
Every alignment is available for inspection:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
for alignment in casc.errors():
|
|
93
|
+
print(alignment.error_type,
|
|
94
|
+
[g.text for g in alignment.gold],
|
|
95
|
+
[p.text for p in alignment.predicted])
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The HTML report (`casc.to_html("report.html")`) renders every sentence with
|
|
99
|
+
each predicted span highlighted in the colour of its error type (green when
|
|
100
|
+
correct), plus an error chip per alignment, score cards, and the
|
|
101
|
+
per-label and per-dimension tables. It is a single self-contained file you
|
|
102
|
+
can open in a browser or attach to an experiment log.
|
|
103
|
+
|
|
104
|
+
Segmentation-focused diagnostics build on the same alignments:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
casc.retrieval_capacity() # fraction of gold entity tokens retrieved
|
|
108
|
+
casc.noise_level() # fraction of O tokens labeled as entity
|
|
109
|
+
casc.segmentation_error_rate() # fused/split/missegmented per gold entity
|
|
110
|
+
casc.segmentation_success_rate() # exact matches among adjacent entities
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Dimensions: where does the model fail?
|
|
114
|
+
|
|
115
|
+
cascabel profiles entities along linguistic dimensions — surface properties
|
|
116
|
+
(length in fixed bins, sentence-initial, casing as upper / title / lower /
|
|
117
|
+
mixed, starts capitalized, quoted, adjacent to another entity), exposure properties (how often
|
|
118
|
+
the exact span occurs in training as a whole span or as `O`, and in what
|
|
119
|
+
share of those occurrences it carries its own label: unseen, seen with the
|
|
120
|
+
same label, ambiguous, seen only with other labels; plus, for every word of
|
|
121
|
+
the span, how it was seen in training: inside spans, only as `O`, or never)
|
|
122
|
+
and shape properties (see below). Continuous properties
|
|
123
|
+
(length, training count, label consistency, surprisal, prototypicality,
|
|
124
|
+
label fit) are stored on every span; binary dimensions are views on them.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
casc.dimension_report() # how the dataset distributes over dimensions
|
|
128
|
+
casc.scores_by_dimension() # P/R/F1 per bucket (read recall: gold side)
|
|
129
|
+
casc.scores_by_dimension(side="predicted") # precision per bucket (predicted spans)
|
|
130
|
+
casc.error_profile("gold") # correct vs. erroneous spans, per dimension
|
|
131
|
+
casc.error_profile("predicted") # the same for predicted spans (spurious too)
|
|
132
|
+
casc.span_table("test") # one record per span, every attribute + outcome
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`scores_by_dimension()` answers questions like *"how much does performance
|
|
136
|
+
drop on entities never seen in training?"*; `error_profile()` asks the
|
|
137
|
+
reverse question, *"what do the spans behind the errors look like, compared
|
|
138
|
+
with the spans handled correctly?"*, on both the gold (recall) and the
|
|
139
|
+
predicted (precision) side.
|
|
140
|
+
|
|
141
|
+
Lexicon and gazetteer dimensions are optional: they appear only when you
|
|
142
|
+
pass your own `lexicon=` / `gazetteer=` word list.
|
|
143
|
+
|
|
144
|
+
## Shape: surprisal and prototypicality
|
|
145
|
+
|
|
146
|
+
Entities often *look* different from ordinary words, and some look more like
|
|
147
|
+
one label than another. cascabel learns both from the training data with
|
|
148
|
+
character-level LSTM language models (PyTorch), which read whole strings,
|
|
149
|
+
spaces and case included, and score them in bits per character:
|
|
150
|
+
|
|
151
|
+
- **Surprisal** (vs. ordinary tokens) — one model trained on the training
|
|
152
|
+
`O` tokens. Every token gets `token._.surprisal`; every span keeps its
|
|
153
|
+
profile (`span._.surprisal_profile`) and summaries: `surprisal_mean`,
|
|
154
|
+
`surprisal_spread` (max − min: partially odd spans) and
|
|
155
|
+
`least_surprising_position` (`"single"` / `"first"` / `"last"` /
|
|
156
|
+
`"interior"`).
|
|
157
|
+
- **Prototypicality** (vs. the training spans of each label) — one model per
|
|
158
|
+
label. `span._.prototypicality` is the span's bits per character under its
|
|
159
|
+
own label's model (form typicality: characters *and* word pattern);
|
|
160
|
+
`span._.label_fit` is own label minus best other label (negative = looks
|
|
161
|
+
like another label, named in `span._.closest_other_label`).
|
|
162
|
+
|
|
163
|
+
Each score also has a per-character perplexity version (`2 ** bits`):
|
|
164
|
+
`surprisal_perplexity`, `span_typicality_perplexity`,
|
|
165
|
+
`prototypicality_perplexity`. Reports list, per label, the 5 least and
|
|
166
|
+
5 most prototypical spans without numbers; the scores are in `span_table()`.
|
|
167
|
+
|
|
168
|
+
Scores are relative to one training set. Hyperparameters live in
|
|
169
|
+
`CharLMConfig` (`charlm_config=`); `shape=False` skips the models.
|
|
170
|
+
|
|
171
|
+
## Perturbations: controlled stress tests
|
|
172
|
+
|
|
173
|
+
Generate modified copies of a dataset that isolate one surface cue, run your
|
|
174
|
+
model on them, and compare:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from cascabel import recase, add_quotes, strip_quotes
|
|
178
|
+
|
|
179
|
+
lowercased = recase(casc.test, "lower") # does casing matter?
|
|
180
|
+
uppercased = recase(casc.test, "upper", scope="entities")
|
|
181
|
+
quoted = add_quotes(casc.test) # do quotes give it away?
|
|
182
|
+
unquoted = strip_quotes(casc.test)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
All functions return new spaCy `Doc`s with gold annotations preserved.
|
|
186
|
+
|
|
187
|
+
## Scope and lineage
|
|
188
|
+
|
|
189
|
+
cascabel was originally developed to analyze lexical borrowing detection
|
|
190
|
+
(anglicisms in Spanish) and generalized into a language-independent library.
|
|
191
|
+
The original rule-based Spanish graphotactics check was removed in 0.7.0:
|
|
192
|
+
surprisal is now learned from the training data. The evaluation methodology (error taxonomy, dimensions,
|
|
193
|
+
perturbations) follows the author's research on borrowing detection
|
|
194
|
+
evaluation.
|
|
195
|
+
|
|
196
|
+
## Citing
|
|
197
|
+
|
|
198
|
+
If you use cascabel in your research, please cite the paper that introduced
|
|
199
|
+
the span dimensions and error analysis this library implements:
|
|
200
|
+
|
|
201
|
+
> Elena Álvarez-Mellado and Julio Gonzalo. 2024. *Characterizing Spans for
|
|
202
|
+
> Sequence Labeling: A Case on Anglicism Detection.* Procesamiento del
|
|
203
|
+
> Lenguaje Natural, 73, 235–246.
|
|
204
|
+
|
|
205
|
+
```bibtex
|
|
206
|
+
@article{alvarez-mellado-gonzalo-2024-characterizing,
|
|
207
|
+
title = {Characterizing Spans for Sequence Labeling: A Case on Anglicism Detection},
|
|
208
|
+
author = {{\'A}lvarez-Mellado, Elena and Gonzalo, Julio},
|
|
209
|
+
journal = {Procesamiento del Lenguaje Natural},
|
|
210
|
+
volume = {73},
|
|
211
|
+
pages = {235--246},
|
|
212
|
+
year = {2024},
|
|
213
|
+
url = {http://journal.sepln.org/sepln/ojs/ojs/index.php/pln/article/view/6613}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
MIT
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""cascabel — fine-grained error analysis for NER and span-labeling models.
|
|
2
|
+
|
|
3
|
+
Instead of a single F1 score, cascabel aligns predictions against gold
|
|
4
|
+
annotations, classifies every disagreement into a fine-grained error taxonomy
|
|
5
|
+
(missed, spurious, type swap, fused, split, missegmented, partial overlaps),
|
|
6
|
+
profiles datasets along linguistic dimensions (length, casing, position,
|
|
7
|
+
quotation, seen/unseen, surprisal, prototypicality) and supports controlled perturbations for stress
|
|
8
|
+
testing.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from cascabel.alignment import (
|
|
12
|
+
Alignment,
|
|
13
|
+
ErrorType,
|
|
14
|
+
SEGMENTATION_ERRORS,
|
|
15
|
+
align_doc,
|
|
16
|
+
align_sentence,
|
|
17
|
+
)
|
|
18
|
+
from cascabel.bootstrap import bootstrap_scores
|
|
19
|
+
from cascabel.cascabel import Cascabel
|
|
20
|
+
from cascabel.dimensions import (
|
|
21
|
+
CATEGORICAL_ATTRIBUTES,
|
|
22
|
+
ENTITY_DIMENSIONS,
|
|
23
|
+
LENGTH_BINS,
|
|
24
|
+
NUMERIC_ATTRIBUTES,
|
|
25
|
+
DimensionStats,
|
|
26
|
+
dimension_report,
|
|
27
|
+
)
|
|
28
|
+
from cascabel.doc import (
|
|
29
|
+
bio_tag,
|
|
30
|
+
doc_from_conll,
|
|
31
|
+
doc_from_sentences,
|
|
32
|
+
doc_from_spacy_binary,
|
|
33
|
+
doc_from_tokens,
|
|
34
|
+
fold_text,
|
|
35
|
+
load_wordlist,
|
|
36
|
+
occurrence_label,
|
|
37
|
+
read_conll,
|
|
38
|
+
repair_bio,
|
|
39
|
+
repair_bio_sentences,
|
|
40
|
+
to_bio,
|
|
41
|
+
)
|
|
42
|
+
from cascabel.extensions import register_extensions
|
|
43
|
+
from cascabel.metrics import Score
|
|
44
|
+
from cascabel.perturb import add_quotes, recase, strip_quotes
|
|
45
|
+
from cascabel.charlm import CharLM, CharLMConfig, ShapeModels
|
|
46
|
+
from cascabel.shape import word_shape
|
|
47
|
+
|
|
48
|
+
__version__ = "0.8.0"
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
"Alignment",
|
|
52
|
+
"CATEGORICAL_ATTRIBUTES",
|
|
53
|
+
"Cascabel",
|
|
54
|
+
"CharLM",
|
|
55
|
+
"CharLMConfig",
|
|
56
|
+
"DimensionStats",
|
|
57
|
+
"ENTITY_DIMENSIONS",
|
|
58
|
+
"ErrorType",
|
|
59
|
+
"LENGTH_BINS",
|
|
60
|
+
"NUMERIC_ATTRIBUTES",
|
|
61
|
+
"SEGMENTATION_ERRORS",
|
|
62
|
+
"Score",
|
|
63
|
+
"ShapeModels",
|
|
64
|
+
"__version__",
|
|
65
|
+
"add_quotes",
|
|
66
|
+
"align_doc",
|
|
67
|
+
"align_sentence",
|
|
68
|
+
"bootstrap_scores",
|
|
69
|
+
"bio_tag",
|
|
70
|
+
"dimension_report",
|
|
71
|
+
"doc_from_conll",
|
|
72
|
+
"doc_from_sentences",
|
|
73
|
+
"doc_from_spacy_binary",
|
|
74
|
+
"doc_from_tokens",
|
|
75
|
+
"fold_text",
|
|
76
|
+
"load_wordlist",
|
|
77
|
+
"occurrence_label",
|
|
78
|
+
"read_conll",
|
|
79
|
+
"recase",
|
|
80
|
+
"register_extensions",
|
|
81
|
+
"repair_bio",
|
|
82
|
+
"repair_bio_sentences",
|
|
83
|
+
"to_bio",
|
|
84
|
+
"strip_quotes",
|
|
85
|
+
"word_shape",
|
|
86
|
+
]
|