The problem in brief
Build a grammatical error correction (GEC) system for Romanian: it receives one possibly wrong sentence and must return the corrected sentence. The errors the organisers have in mind are those of real online comments: missing diacritics ("si" for "și"), one or two wrong letters in a word ("duar" for "doar"), wrong prepositions, gender, number, case or person agreement errors, cacophonies and punctuation mistakes. The catch is in the rules: the model must be an encoder-decoder ("base" size, such as mT5-base or mBART), you may not use any existing GEC data or GEC-finetuned model, and the only training data is a set of clean Romanian Wikipedia pages (about 1,560 pages, 5 MB). You hand in the model on the Hugging Face Hub and a two-page report. In other words, the real task is: invent the training data.
This correction is marked partial: fine-tuning a base-size encoder-decoder (580 M parameters for mT5-base) was impossible on the shared machine we had (2 CPU threads, under 2 GB of RAM per process). We explain the full intended pipeline, and we implement and evaluate the parts that do not need a GPU: the error generator and a strong training-free statistical corrector built from the same Wikipedia text.
How you are scored
The organisers evaluate the hub model on a hidden, diverse set of sentences, one sentence at a time. The official notebook computes three text-similarity metrics between the model output and the reference correction: BLEU, METEOR and ROUGE-L. BLEU, the one we report, is the geometric mean of the modified -gram precisions () times a brevity penalty:
with the total length of the outputs and that of the references. The statement gives no threshold or leaderboard, so there is no "expected" score to compare with. Our numbers are measured on our own held-out set (described below), not on the organisers' private sentences.
One property of this metric matters a lot for GEC: copying the input already scores high, because most words of a wrong sentence are correct. Any system must be judged against the "do nothing" baseline.
First look at the data
The data are two JSON files of Wikipedia pages (wiki_valid.json, wiki_test.json), each page a few paragraphs. We cut them into sentences (20 to 200 characters, starting with a capital letter): 23,600 sentences. Two cleaning details:
- Romanian has two encodings of ș and ț: the correct comma-below (ș, ț) and the legacy cedilla (ş, ţ). Wikipedia mixes them. We normalise everything to comma-below, otherwise a model is penalised for "errors" that are invisible on screen.
- We split by page, not by sentence: 10 % of the pages (and their 1,500 sentences we evaluate on) never contribute to training. Sentences of one page share names and phrasing, so a random sentence split would leak.
The examples in the statement are informative: they are YouTube-style comments, fully without diacritics, with missing commas and capitals, with letters dropped ("melodi" for "melodii"). Wikipedia is the opposite style: long, formal, perfect. The whole difficulty of the problem lives in that gap.
The key idea: corrupt clean text to manufacture training pairs
If clean sentences are all we have, we create the wrong sentences ourselves and learn the inverse mapping, wrong clean. The quality of the final model depends mostly on how well the synthetic errors imitate real ones. Our generator applies, at random:
| error | how | probability |
|---|---|---|
| no diacritics at all | ă, â a, î i, ș s, ț t on the whole sentence | 50 % |
| some diacritics missing | the same, word by word with probability 0.5 | 30 % |
| letter typo | deletion, swap, substitution or doubling of one inner letter, per word | 8 % per word |
| commas | removed (or detached: "uriaș ,") | 50 % |
| final punctuation | removed | 40 % |
| first letter | lower-cased | 30 % |
Example of a generated pair: "Dupa incheierea carierei de jucator, el a devenit antrneor al echiei de tineret..." "După încheierea carierei de jucător, el a devenit antrenor al echipei de tineret...".
The intended full solution then fine-tunes mT5-base on millions of such pairs (each clean sentence can be corrupted many times, with different random errors at every epoch), exactly like the official baseline notebook does with its placeholder corruption ("prepend the letter A"). Good generators also add the harder error types: swapping prepositions (în/la/pe), changing noun or adjective endings to break agreement ("cea mai bună" "cea mai bun"), confusing "sa/să", "ca/că", "ia/i-a" and joining words after punctuation ("special,vocea"). A model can only learn to fix what the generator produces.
A first baseline
Copying the input on our 1,500 corrupted held-out sentences: BLEU 0.561, and 5 % of sentences are already exactly right (the generator sometimes leaves a sentence untouched).
The full solution we could run: a statistical corrector
Without a GPU we can still learn a lot from the 21,700 training sentences, because the most frequent errors are local and reversible:
- Candidates. For each word of the input, list the spellings seen in Wikipedia that reduce to the same ASCII form ("si" {si, și}; "incheierea" {încheierea}). For a lowercase word unknown in this form (likely a typo, 4 letters or more), also add the known words at edit distance 1 ("antrneor" "antrenor"). Capitalised words keep their capitalisation, so names are not "corrected".
- Language model. A bigram model with "stupid backoff": when the pair was seen, otherwise .
- Decoding. The Viterbi algorithm picks the sequence of candidates with the highest total score, where changing letters (and not only diacritics) costs a penalty of 3 nats, so the system only "fixes" a typo when the context clearly prefers it.
- Punctuation and case. Capitalise the first letter, add a final period when the sentence has none.
for c in candidates(w): # e.g. "si" -> ["și", "si"]
cost = 0 if fold(c) == fold(w) else -3.0 # changed letters must be worth it
score, path = max((s + logp(prev, c) + cost, p) for prev, (s, p) in beams.items())
new[c] = (score, path + [c])
Why it works: Romanian diacritics are rarely ambiguous in context. "si" alone could be "si" (the musical note) or "și" ("and"), but after a noun and before a verb the bigram counts make "și" overwhelmingly likely. The same goes for "ca/că" or "sa/să", which differ by part of speech and are decided by their neighbours.
Results
1,500 corrupted sentences from held-out Wikipedia pages (our own synthetic test set; the organisers' test sentences are private):
| system | BLEU | exactly corrected |
|---|---|---|
| copy the input | 0.561 | 5.0 % |
| restore diacritics (lattice + bigram LM) | 0.752 | 9.3 % |
| + capital first letter and final period | 0.780 | 16.6 % |
| + typo candidates at edit distance 1 (final) | 0.813 | 21.5 % |
| + re-inserting commas where the LM prefers them (600-sentence subset) | +0.001 | point |
| first version, without case preservation and with a cruder tokeniser (600-sentence subset) | 0.730 | 8.8 % |

Lessons from the failed variants: the first version let the language model change capitalisation freely, and it "corrected" proper names ("Bert van Marwijk" became "Beit Van Marwijk", "Guvernul Catalan" became "guvernul catalan"); keeping the writer's case (together with a tokeniser that keeps "s-a" in one piece) raised BLEU by about +0.08. LM-based comma insertion gave almost nothing: commas depend on the syntax of the whole clause, not on two neighbouring words.
On the statement's examples, the limits show clearly: "Am mai auzit melodi dar asta este cea mai bun" becomes "Am mai auzit melodii dar asta este cea mai bun.": the typo is fixed, but the agreement error "bun bună" is not, because "bun" is a correct word and a bigram cannot see that it must agree with "cea". "Biatul acesta..." keeps "Biatul" because "băiatul", an everyday word, is rare in Wikipedia. These are exactly the cases where a fine-tuned encoder-decoder, which reads the whole sentence and has seen many corrupted agreements in training, is needed.
What we expect from the full solution: on synthetic data of this kind, fine-tuned mT5 models typically restore diacritics almost perfectly and handle agreement when the generator includes it. The main risk is the style gap: a model trained only on corrupted Wikipedia sentences meets comments with slang, missing words and unusual punctuation. Generating the errors in proportions that match the statement's examples (fully missing diacritics, missing commas, lower case) is the most effective lever.
What to remember
- When labelled data is forbidden, synthesise it: corrupt clean text with a noise model of the errors you expect, and learn the inverse. Your model will only fix the error types your generator produces.
- Always compare with "copy the input" in correction tasks: it already scores BLEU 0.56 here.
- Split by document, not by sentence, to avoid leakage between related sentences.
- Normalise invisible variants (ș with cedilla vs comma below) before training and before scoring.
- A candidate lattice + language model + Viterbi is a strong, cheap baseline for local errors (diacritics, typos) and a good way to understand what a neural model must add: long-range agreement and real-world style.