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.
Files changed (39) hide show
  1. cascabel_ner-0.8.0/LICENSE +21 -0
  2. cascabel_ner-0.8.0/PKG-INFO +247 -0
  3. cascabel_ner-0.8.0/README.md +219 -0
  4. cascabel_ner-0.8.0/cascabel/__init__.py +86 -0
  5. cascabel_ner-0.8.0/cascabel/__main__.py +7 -0
  6. cascabel_ner-0.8.0/cascabel/alignment.py +363 -0
  7. cascabel_ner-0.8.0/cascabel/bootstrap.py +131 -0
  8. cascabel_ner-0.8.0/cascabel/card.py +182 -0
  9. cascabel_ner-0.8.0/cascabel/cascabel.py +1154 -0
  10. cascabel_ner-0.8.0/cascabel/charlm.py +325 -0
  11. cascabel_ner-0.8.0/cascabel/cli.py +150 -0
  12. cascabel_ner-0.8.0/cascabel/constants.py +15 -0
  13. cascabel_ner-0.8.0/cascabel/dimensions.py +186 -0
  14. cascabel_ner-0.8.0/cascabel/doc.py +282 -0
  15. cascabel_ner-0.8.0/cascabel/extensions.py +333 -0
  16. cascabel_ner-0.8.0/cascabel/metrics.py +66 -0
  17. cascabel_ner-0.8.0/cascabel/perturb.py +145 -0
  18. cascabel_ner-0.8.0/cascabel/report.py +629 -0
  19. cascabel_ner-0.8.0/cascabel/shape.py +41 -0
  20. cascabel_ner-0.8.0/cascabel_ner.egg-info/PKG-INFO +247 -0
  21. cascabel_ner-0.8.0/cascabel_ner.egg-info/SOURCES.txt +37 -0
  22. cascabel_ner-0.8.0/cascabel_ner.egg-info/dependency_links.txt +1 -0
  23. cascabel_ner-0.8.0/cascabel_ner.egg-info/entry_points.txt +2 -0
  24. cascabel_ner-0.8.0/cascabel_ner.egg-info/requires.txt +4 -0
  25. cascabel_ner-0.8.0/cascabel_ner.egg-info/top_level.txt +1 -0
  26. cascabel_ner-0.8.0/pyproject.toml +59 -0
  27. cascabel_ner-0.8.0/setup.cfg +4 -0
  28. cascabel_ner-0.8.0/tests/test_alignment.py +248 -0
  29. cascabel_ner-0.8.0/tests/test_bootstrap.py +53 -0
  30. cascabel_ner-0.8.0/tests/test_card.py +59 -0
  31. cascabel_ner-0.8.0/tests/test_cascabel.py +200 -0
  32. cascabel_ner-0.8.0/tests/test_charlm.py +147 -0
  33. cascabel_ner-0.8.0/tests/test_cli.py +82 -0
  34. cascabel_ner-0.8.0/tests/test_dimensions.py +644 -0
  35. cascabel_ner-0.8.0/tests/test_doc.py +173 -0
  36. cascabel_ner-0.8.0/tests/test_metrics.py +51 -0
  37. cascabel_ner-0.8.0/tests/test_perturb.py +120 -0
  38. cascabel_ner-0.8.0/tests/test_report.py +256 -0
  39. 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
+ ]
@@ -0,0 +1,7 @@
1
+ """Entry point for ``python -m cascabel``."""
2
+
3
+ import sys
4
+
5
+ from cascabel.cli import main
6
+
7
+ sys.exit(main())