gector-bert-es / README.md
Imperatore's picture
Card: what the model finds and the known issues the app-only guards cover; no fixed-in-version history.
06d7cbe verified
|
Raw History Blame Contribute Delete
14.2 kB
---
license: cc-by-4.0
language:
- es
base_model: dccuchile/bert-base-spanish-wwm-cased
pipeline_tag: token-classification
library_name: onnx
tags:
- grammatical-error-correction
- gector
- onnx
- on-device
datasets:
- wikimedia/wikipedia
---
# Proofbird Grammar — Spanish
`gector-bert-es` · on-device Spanish grammar correction
[![Try it in your browser](https://huggingface.co/datasets/huggingface/badges/resolve/main/open-in-hf-spaces-lg.svg)](https://huggingface.co/spaces/proofbird/try-grammar)
**Try it in your browser:** type a sentence and see the corrections. The model runs on your own computer; nothing to install, and your text never leaves the page.
**Version 1.3.0** · run `2026-11-13-0717` (1.0.0–1.2.0 = run `2026-11-06-2304`) · tag [`v1.3.0`](https://huggingface.co/proofbird/gector-bert-es/tree/v1.3.0), the files the Proofbird app downloads. Pin it with `revision="v1.3.0"`. Versions: a change to the model's design (encoder, inputs, outputs) raises the first number, a new training run or changed guards and rules the second, card or metadata fixes the third.
A GECToR-style sequence tagger for Spanish grammatical error correction, exported to ONNX (fp32) to run on the CPU.
It is the on-device grammar model of [Proofbird](https://proofbird.app), a writing assistant for Windows, and it is
published here so anyone can inspect what runs on their machine.
GECToR does not rewrite text. For each token it predicts one edit tag (keep, delete, replace with a word, append a
word, change case, change a verb form), and the edits are applied and the sentence re-tagged up to five times. The
tagger only proposes local edits; it never paraphrases.
## Quick start
[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/#fileId=https%3A//huggingface.co/proofbird/gector-bert-es/blob/main/quickstart.ipynb)
No API key, no GPU. One file, [`quickstart.py`](quickstart.py), downloads the model and runs it with ONNX Runtime.
```bash
pip install onnxruntime tokenizers huggingface_hub
```
```python
import importlib.util
from huggingface_hub import hf_hub_download
spec = importlib.util.spec_from_file_location("quickstart", hf_hub_download("proofbird/gector-bert-es", "quickstart.py"))
quickstart = importlib.util.module_from_spec(spec)
spec.loader.exec_module(quickstart)
model = quickstart.Gector("proofbird/gector-bert-es")
corrected, suggestions = model.correct("La casa son muy bonito.")
print(corrected)
print(suggestions)
```
```text
La casa es muy bonita.
[{"start": 8, "length": 3, "original": "son", "replacement": "es"}, {"start": 16, "length": 6, "original": "bonito", "replacement": "bonita"}]
```
Each suggestion gives the character position in **your** text (`start`, `length`), what is there now (`original`) and what to put instead (`replacement`), so you can underline it and offer the fix. From the command line: `python quickstart.py "La casa son muy bonito."` prints every step below.
## What it corrects
Gender and number agreement (articles, adjectives, participles, nouns after numerals and determiners), subject-verb
agreement, article gender (`el agua` stays, `un mesa` → `una mesa`), contractions (`de el` → `del`, `a el` → `al`,
and a preposition written twice: `Vamos a al cine` → `al`, `Vengo de del trabajo` → `del`),
accents and diacritics (`cancion` → `canción`, question words such as `¿Como` → `¿Cómo`), opening question and
exclamation marks, capitalisation at the start of a sentence, and everyday-register errors in questions, imperatives
and the first and second person.
## How it was trained
- **Base encoder:** [`dccuchile/bert-base-spanish-wwm-cased`](https://huggingface.co/dccuchile/bert-base-spanish-wwm-cased)
(BETO, CC BY 4.0). A tagging head (5 000 edit labels) and an error-detection head sit on top.
- **Data: synthetic only.** 3 000 000 sentences from Spanish Wikipedia
([`wikimedia/wikipedia`](https://huggingface.co/datasets/wikimedia/wikipedia), `20231101.es`) and 250 000 sentences
from [Tatoeba](https://tatoeba.org) were corrupted by hand-written Spanish error rules; about 10% stay clean, so the
model learns to leave correct text alone. 203 000 additional template sentences teach patterns Wikipedia rarely
contains (first and second person, questions, imperatives, long-distance agreement, an adjective followed by more of
the sentence, a doubled preposition).
- **No learner corpora** and no non-commercial data in the fine-tuning. No user text was used.
- One epoch (100 396 steps, batch 64, learning rate 3e-5) on one NVIDIA L4. Run id `2026-11-13-0717`.
## How it was measured
| Check | Result |
|---|---|
| Gold set (581 hand-written sentences: 439 with one error each, 142 correct) | **438 fixed, 142 left alone, 1 missed, 0 made worse** |
| False alarms on 4 000 everyday Tatoeba sentences (held out from training) | 22 changed (0.55%); 21 of them correct an error in Tatoeba's own text |
| Stability: the gold re-run with one extra token in front of each sentence | 2 sentences change result |
The gold score is with the apply-side guards in `gector-guards.json` (below). The model was trained for precision: a
missed error costs less than a wrong correction. The one miss is `¿Dónde están las llave de casa?` when the question
mark is missing.
The Tatoeba changes are corrections of Tatoeba's own text (missing accents such as `volveria` → `volvería`, `No sabe que
decir` → `qué`, `si tu quieres` → `tú`, `Mary y yo están` → `estamos`) and one false alarm: `Entraban al templo descalzos`
→ `descalzo` (the adjective agrees with the subject the verb leaves out, not with `templo`). It has not been measured on
learner essays.
**The guards.** `gector-guards.json` holds back edits we measured as wrong in a known shape. They run in the Proofbird
app (not in `quickstart.py` or the demo) and are part of every score above. Among them: a change of the verb's person needs a subject for the new person in the sentence (`¿Acaso soy un
ladrón?` stays); `tu` stays after a preposition (`por tu bien`); `el que` / `la que` are both right; a bare plural after
`de`, `solo`, `sin` or `con` stays (`de video juegos`); a plural of a word ending in an unstressed vowel takes `-s`, never
`-es` (`interesante` → `interesantes`). Also:
- `tu` stays before `solo` / `sola` (`practicado tu solo de flauta`: "your solo" and "you alone" read the same).
- After `no` or an object pronoun (`te`, `se`, `lo` …) the next word is a verb, so an accent that would turn it into
another word that is not a verb is held: `El que no llora, no mama` (not `mamá`), `No es verdad que te secuestraran`
(not `secuestrarán`). An accent that gives a verb still goes through (`no esta` → `está`, `No se` → `sé`).
## Files
| File | What it is |
|---|---|
| `model.onnx` | The tagger (fp32, opset 17). Inputs `input_ids`, `attention_mask`; outputs `logits_labels` [1, T, 5000] and `logits_d` [1, T, 2]. |
| `config.json` | The edit-label vocabulary (`id2label`). |
| `vocab.txt`, `tokenizer.json`, `tokenizer_config.json` | The base encoder's WordPiece tokenizer. |
| `gector-meta.json` | Export facts: encoder, tokenizer family, decoding defaults, run id. |
| `gector-guards.json` | Apply-side guards: rules that hold back an edit the model gets wrong in a known way (for example a change of the verb's person when the sentence names no subject). Part of the measured result. |
| `language-rules.json` | Language facts the runtime needs (sentence-final punctuation handling). |
| `verb-form-vocab.txt` | Verb-form table for the verb-transform tags (425 verbs, present tense). |
| `grammar-messages.json` | Short explanations shown with each correction. |
| `quickstart.py` | A complete Python runner: download, tokenize, run, decode, apply, suggestions. See *Quick start*. |
| `quickstart.ipynb` | The same, as a notebook (the *Open in Colab* button). |
The Proofbird app runs the same algorithm in C# with ONNX Runtime; the files are standard, so any GECToR-style
decoder can use them.
## How it works, step by step
The model never sees letters and never writes text. It reads numbers (token ids) and answers, for every word, which **edit** to make: one of 5 000 tags such as `$KEEP`, `$DELETE`, `$APPEND_la`, `$REPLACE_abren` or `$TRANSFORM_VERB_P3P_P3S` (change a verb's form). `quickstart.py` does everything around it. This is the real run of the example above.
**1. Prepare the text.** `language-rules.json` says the final full stop comes off before the model sees the sentence (it goes back on at the end): `La casa son muy bonito`.
**2. The request.** The words are looked up in the model's dictionary (`vocab.txt`). A rare word is split into pieces; the edit is read at its first piece. `[CLS]` stands for a dummy first word, `$START`, so an edit on it can add a word at the very beginning; `[SEP]` marks the end.
```text
words $START La casa son muy bonito
tokens [CLS] La casa son muy bonito [SEP]
input_ids [4, 1198, 1753, 1404, 1456, 7927, 5]
attention_mask [1, 1, 1, 1, 1, 1, 1]
```
Two inputs, both whole numbers (int64), shape `[1, 7]`: one sentence of 7 tokens.
**3. The response.** Two outputs (float32): `logits_labels` `[1, 7, 5000]`, a score for each of the 5 000 edits at every token, and `logits_d` `[1, 7, 2]`, a score for "this word is correct" and "this word is wrong". A softmax turns each row into probabilities; the most probable edit at each word's first token is the one applied.
**4. Pass by pass.** The corrected sentence goes back in, until a pass changes nothing (at most five passes). Connected errors are fixed one after the other.
*Pass 1:* `La casa son muy bonito`
| Word | Wrong | Chosen edit | Probability |
|---|---|---|---|
| $START | 0.0% | $KEEP | 100.0% |
| La | 0.0% | $KEEP | 100.0% |
| casa | 0.0% | $KEEP | 100.0% |
| son | 92.9% | **$TRANSFORM_VERB_P3P_P3S** | 97.2% |
| muy | 0.0% | $KEEP | 100.0% |
| bonito | 20.5% | $KEEP | 76.9% |
→ `La casa es muy bonito`
*Pass 2:* `La casa es muy bonito`
| Word | Wrong | Chosen edit | Probability |
|---|---|---|---|
| $START | 0.0% | $KEEP | 100.0% |
| La | 0.0% | $KEEP | 100.0% |
| casa | 0.0% | $KEEP | 100.0% |
| es | 0.0% | $KEEP | 100.0% |
| muy | 0.0% | $KEEP | 100.0% |
| bonito | 100.0% | **$TRANSFORM_SUF_o_a** | 99.9% |
→ `La casa es muy bonita`
*Pass 3:* `La casa es muy bonita`
| Word | Wrong | Chosen edit | Probability |
|---|---|---|---|
| $START | 0.0% | $KEEP | 100.0% |
| La | 0.0% | $KEEP | 100.0% |
| casa | 0.0% | $KEEP | 100.0% |
| es | 0.0% | $KEEP | 100.0% |
| muy | 0.0% | $KEEP | 100.0% |
| bonita | 0.0% | $KEEP | 100.0% |
→ `La casa es muy bonita` — no change, so it stops.
**5. The suggestions.** The original and the final words are compared, and each changed stretch becomes one suggestion with its position in the original text (unchanged text keeps its own spacing), as in the Quick start output above.
**6. Settings.** `Gector(repo, keep_confidence=0.0, min_error_prob=0.0)` takes the two GECToR precision knobs (the Proofbird app runs with both at 0, and at most five passes):
- `keep_confidence` adds a bonus to `$KEEP` before choosing: higher means fewer, safer edits.
- `min_error_prob` ignores an edit the model is less sure of, and leaves the whole sentence alone when no word is at least that likely to be wrong. With `min_error_prob=0.9` the example is still corrected (`La casa es muy bonita.`): the model is 92.9% sure one word is wrong.
## What Proofbird adds
`quickstart.py` is the model on its own. The [Proofbird](https://proofbird.app) app runs the same algorithm in C# and adds:
- **Guards**: per-language rules that hold back edits we measured as wrong in a known shape. `gector-guards.json`, in
this repository, lists which guards are on for Spanish and their word lists; what each guard does is code in the app.
The scores under *How it was measured* are with the guards.
- **Spelling, checked separately** by the operating system's spell checker; the guards use it so a grammar edit is not made on a word that is misspelled.
- **A card for each change** that names the error ("Subject–verb agreement") and, on request, explains it.
`quickstart.py` and the in-browser demo run the model on its own, without the guards.
## Licence and attribution
The model weights and the files in this repository are released under the
[Creative Commons Attribution 4.0 International licence](LICENSE) (CC BY 4.0), the licence of the base encoder.
The base encoder is BETO, `dccuchile/bert-base-spanish-wwm-cased`, by José Cañete, Gabriel Chaperon, Rodrigo Fuentes,
Jou-Hui Ho, Hojin Kang and Jorge Pérez (Universidad de Chile), CC BY 4.0: *Spanish Pre-Trained BERT Model and
Evaluation Data*, PML4DC at ICLR 2020. Its authors note that CC BY 4.0 best describes their intentions, but that they
are not sure every dataset used to pre-train BETO carries a licence compatible with CC BY 4.0, especially for
commercial use, and ask users to verify that it fits their needs. That note applies to this model too.
The fine-tuning data carries its own terms. Wikipedia text is © Wikipedia contributors,
[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). Tatoeba sentences are from
[tatoeba.org](https://tatoeba.org), [CC BY 2.0 FR](https://creativecommons.org/licenses/by/2.0/fr/). The tagging
scheme follows GECToR (Omelianchuk et al., 2020, *GECToR – Grammatical Error Correction: Tag, Not Rewrite*); the
Spanish verb-form table was built for this model.
## How to cite
If you use or build on this model, please cite it:
```bibtex
@misc{hartnett2026gectores,
author = {Hartnett, Patrick Andrew},
title = {Proofbird GECToR grammar model (Spanish)},
year = {2026},
publisher = {Hugging Face},
howpublished = {\url{https://huggingface.co/proofbird/gector-bert-es}},
note = {Proofbird, https://proofbird.app}
}
```
If you build on this model, a link back to this page is appreciated.
## Contact
Published by Patrick Andrew Hartnett as personal work. Questions and reports: open a discussion on this repository.