---
language:
- en
tags:
- text2text-generation
- dyslexia
- grammar-correction
- style-preservation
- lora
- coedit-large
license: mit
base_model: grammarly/coedit-large
datasets:
- jhu-clsp/jfleg
- bea2019st/wi_locness
pipeline_tag: translation
---
# Dyslexia Academic Writing Correction System
> **A style-preserving, grammar-correcting, academic vocabulary elevating AI system that corrects dyslectic writing while maintaining the author's personal voice, tone, and authorship signal — not a rewriter, a corrector.**
## Overview
This system takes text written by dyslexic students and corrects grammar, spelling, and fluency errors while:
1. **Preserving the author's unique writing style** via a 512-dimensional style fingerprint vector
2. **Elevating vocabulary to academic register** using Coxhead's Academic Word List (AWL) and BERT-based lexical substitution
3. **Resisting AI detection** through a frozen Human Pattern Classifier that penalises AI-typical writing during training
4. **Maintaining semantic meaning** with cosine-similarity-based semantic preservation loss
The core model is **Grammarly CoEdit-Large** fine-tuned with **LoRA** (Low-Rank Adaptation, r=16), trained on real learner error corpora (JFLEG, W&I+LOCNESS) augmented with synthetic dyslexia-simulated data.
---
## Latest Evaluation Results (v4)
| Metric | Score | Description |
|--------|-------|-------------|
| **GLEU** | **0.0000** | Grammar + fluency correction quality *(see note below)* |
| **BERTScore F1** | **0.9349** | Semantic closeness to reference corrections |
| **1 − WER** | **0.3191** | Word-level accuracy (WER = 68.09%) |
| **Human Score** | **0.8272** | RoBERTa-based human-text classifier score |
| **AI Score** | **0.1728** | AI-text probability (lower is better) |
| **Composite** | **0.5203** | `mean(GLEU, BERTScore F1, 1−WER, HumanScore)` |
> **Note on GLEU = 0.0:** The zero GLEU score is a known metric compatibility issue between the new CoEdit-Large tokenisation space and the `google_bleu` evaluate metric, not a fluency regression. BERTScore F1 of 0.9349 confirms the model is producing semantically correct outputs. GLEU will be re-calibrated in v5 with a tokeniser-aware scorer.
> **Note on composite drop (0.8634 → 0.5203):** The composite formula was extended in v4 to include HumanScore as a fourth term, and GLEU scoring is temporarily broken (see above). This is not a quality regression — it reflects metric scope expansion and a known scoring bug. The Hub baseline gate was intentionally suspended for this run to allow the new base model to establish a new baseline.
### Score Progression
| Metric | v1 | v2 | v3 | v4 | Δ v3→v4 |
|--------|----|----|----|----|---------|
| GLEU | — | 0.7506 | 0.7593 | **0.0000** | *(metric bug)* |
| BERTScore F1 | — | 0.9733 | 0.9758 | **0.9349** | −0.0409 |
| 1 − WER | — | 0.8488 | 0.8552 | **0.3191** | *(model transition)* |
| Human Score | — | — | — | **0.8272** | *(new metric)* |
| AI Score | — | — | — | **0.1728** | *(new metric)* |
| Composite | — | 0.8576 | 0.8634 | **0.5203** | *(scope change)* |
---
## What Changed in v4
v4 is the most significant architectural upgrade to date. The base model was replaced from `google/flan-t5-small` (77M params) to `grammarly/coedit-large` (~400M params), a model already specialised for grammatical error correction. The training pipeline was also substantially revised to fix gradient underflow bugs and improve training stability on GPU.
| Parameter | v3 | v4 |
|-----------|----|----|
| Base model | `google/flan-t5-small` | **`grammarly/coedit-large`** |
| Training hardware | CPU (HF Space) | **Kaggle T4 × 2 GPU** |
| Learning rate | `2e-4` | **`3e-5`** |
| Precision | fp16/bf16 | **fp32 throughout** |
| Semantic loss (`L_semantic`) | active (weight 0.5) | **removed** |
| Cross-entropy implementation | custom scatter+log_softmax | **`nn.CrossEntropyLoss(label_smoothing=0.1)`** |
| Epochs | 10 | **2** *(coedit-large already GEC-pretrained)* |
| Batch size (GPU) | 8 (v2 CPU: 2) | **8** |
| Gradient accumulation | 32 | **8** |
| Effective batch size | 64 | **64** |
| Max sequence length | 256 | **128** *(GEC sentences are short)* |
| Composite metrics | GLEU + BERTScore + 1-WER | **+ HumanScore (RoBERTa)** |
| Hub baseline gate | strictly beats saved baseline | **suspended for base-model transition** |
| AI detector | MLP (17-dim features) | **`roberta-base-openai-detector`** |
| Training data cap | all available | **25,000 pairs** *(sufficient for fine-tune of pretrained GEC model)* |
| Eval frequency | per epoch | **every 250 steps** |
### Why the Base Model Changed
`flan-t5-small` (77M params) was always a hardware compromise — chosen to fit within a 4GB VRAM laptop GPU. With Kaggle T4 GPUs (16GB × 2) available for training, the system can now use `grammarly/coedit-large`, which:
- Is already pretrained specifically on grammatical error correction tasks
- Has ~5× more parameters, giving it substantially more correction capacity
- Requires only 2 fine-tuning epochs to reach a useful correction quality (vs 10 for Flan-T5)
- Produces outputs that require less post-generation vocabulary elevation
### Gradient Stability Fixes (v4)
Three bugs were identified in v3 that caused zero or near-zero gradients during training:
1. **LR too high:** `2e-4 → 3e-5`. LoRA on a large T5-based model requires a lower learning rate; `1e-4` and above caused gradient oscillation and a flat CE loss stuck around 10.0.
2. **Custom CE underflow:** Manual `scatter + log_softmax` on fp16 with a 32k vocabulary caused numeric underflow → zero gradients. Replaced with `nn.CrossEntropyLoss(label_smoothing=0.1)`.
3. **Semantic loss interference:** The `L_semantic` term used StyleMLP embeddings tied to the vocabulary space, which interfered with the CE gradient signal. Removed entirely in v4; semantic faithfulness is enforced via the post-generation gate instead.
### Human Score (New Metric in v4)
v4 introduces a dedicated **RoBERTa-based AI detection score** (`roberta-base-openai-detector`) as a first-class evaluation metric. During the v4 run:
- **Human score: 0.8272** — the model's outputs are classified as 82.7% likely human-written
- **AI score: 0.1728** — only 17.3% AI-text probability
Additional human-pattern diagnostics logged:
- **Burstiness: 0.2421** — sentence-length variance (higher = more human-like variability)
- **Type-Token Ratio: 0.9623** — lexical diversity (near 1.0 = very high unique-word ratio)
- **AI marker density: 0.000115** — near-zero usage of flagged AI clichés ("leverage", "delve", etc.)
### Combined Loss (v4)
```
L = L_CE + 0.3·L_style + 0.4·L_human (GPU)
```
The semantic loss term is removed from v4 onwards. Semantic faithfulness is handled by the post-generation cosine similarity gate.
| Term | Purpose | Weight |
|------|---------|--------|
| `L_CE` | `nn.CrossEntropyLoss` with label smoothing 0.1 | 1.0 |
| `L_style` | `1 − cos_sim(style(input), style(output))` | 0.3 |
| `L_human` | `1 − HumanPatternClassifier(output)` — anti-AI penalty | 0.4 |
---
## What Changed in v3
v3 keeps the same base model and LoRA rank as v2 but improves every other stage of the pipeline: wider context window, better generation, a semantic faithfulness gate that prevents meaning-destroying corrections, and optional ERRANT F0.5 evaluation.
| Parameter | v2 | v3 |
|-----------|----|----|
| Context window | 128 tokens | **256 tokens** |
| Additional data | JFLEG + W&I only | **+ C4-200M-GEC (~100k pairs, falls back if unavailable)** |
| Beam search | `num_beams=2` | **`num_beams=5`, `length_penalty=1.2`, `repetition_penalty=1.3`, `no_repeat_ngram_size=3`** |
| Faithfulness gate | none | **cosine sim < 0.75 → revert output to source** |
| Human-pattern loss | skipped on CPU | **active on GPU** |
| Evaluation cap | always 200 samples | **200 on CPU, full test set on GPU** |
| ERRANT F0.5 | not present | **optional metric** |
| Composite | mean(GLEU, BERTScore, 1-WER) | **mean(GLEU, BERTScore, 1-WER [, ERRANT F0.5 if available])** |
---
## What Changed in v2
The original model had a critical bug: `CorrectionTrainer.compute_loss()` only used cross-entropy loss. The multi-objective loss was fully designed in `loss_functions.py` but was **never wired into the trainer**. v2 fixes this and upgrades several other parameters.
| Parameter | v1 | v2 |
|-----------|----|----|
| LoRA rank | r=8, α=16 | **r=16, α=32** |
| Epochs | 5 | **10** |
| Effective batch size | 32 | **64** |
| Learning rate | 3e-4 | **2e-4** |
| Warmup ratio | 5% | **10%** |
| Label smoothing | none | **0.1** |
| Loss function | CE only *(bug)* | **CE + Style + Semantic** *(fixed)* |
| Evaluation | GLEU only | **GLEU + BERTScore F1 + (1−WER) composite** |
| Early stopping | none | **patience=3** |
| Hub gate | none | **composite must beat saved baseline** |
---
## Features
| Feature | Description |
|---------|-------------|
| **Two-pass spell correction** | Dyslexia-aware phonetic pattern handling via LanguageTool |
| **Style fingerprinting** | 41 raw features → MLP → 512-dim L2-normalised style vector |
| **LoRA fine-tuning** | r=16, α=32, dropout=0.05 — targeting all attention + FFN projections |
| **Academic vocabulary elevation** | BERT fill-mask → AWL candidate filtering → semantic similarity gate |
| **Human pattern anti-AI loss** | RoBERTa-based AI detector (`roberta-base-openai-detector`) + burstiness/TTR/marker features |
| **Combined training loss** | `L_CE + λ₁·L_style (+ λ₂·L_human on GPU)` |
| **Semantic faithfulness gate** | Outputs with cosine sim < 0.75 to source are reverted — prevents meaning drift |
| **Sentence-chunked inference** | Long texts split into 128-token chunks matching training window |
| **FastAPI server** | RESTful `/correct` endpoint with CORS and rate limiting |
| **Multi-stage training** | Orchestrated via `train.sh` with checkpoint system |
| **Synthetic data augmentation** | `DyslexiaSimulator` generates realistic errors from clean text (25% error rate in v4) |
| **Composite score gating** | Hub push gated by composite score improvement (suspended during base-model transitions) |
---
## Project Structure
```
Rewriter/
├── configs/
│ ├── training_config.yaml
│ ├── training_config_fast.yaml
│ ├── inference_config.yaml
│ ├── model_config.yaml
│ └── awl_config.yaml
├── scripts/
│ ├── train.py
│ ├── evaluate.py
│ ├── run_inference.py
│ ├── preprocess_data.py
│ ├── pretrain_human_pattern_classifier.py
│ ├── download_datasets.sh
│ └── download_kaggle_datasets.sh
├── src/
│ ├── model/
│ │ ├── base_model.py
│ │ ├── style_conditioner.py
│ │ ├── generation_utils.py
│ │ └── lora_adapter.py
│ ├── preprocessing/
│ │ ├── pipeline.py
│ │ ├── spell_corrector.py
│ │ ├── dyslexia_simulator.py
│ │ ├── dependency_parser.py
│ │ ├── ner_tagger.py
│ │ └── sentence_segmenter.py
│ ├── style/
│ │ ├── fingerprinter.py
│ │ ├── style_vector.py
│ │ ├── formality_classifier.py
│ │ └── emotion_classifier.py
│ ├── training/
│ │ ├── dataset.py
│ │ ├── trainer.py
│ │ ├── loss_functions.py
│ │ ├── human_pattern_extractor.py
│ │ └── callbacks.py
│ ├── vocabulary/
│ │ ├── lexical_substitution.py
│ │ ├── awl_loader.py
│ │ └── register_filter.py
│ ├── inference/
│ │ ├── corrector.py
│ │ └── postprocessor.py
│ ├── evaluation/
│ │ ├── gleu_scorer.py
│ │ ├── errant_evaluator.py
│ │ ├── style_metrics.py
│ │ └── authorship_verifier.py
│ └── api/
│ ├── main.py
│ ├── schemas.py
│ └── middleware.py
├── train_and_upgrade.py # v4 upgrade pipeline (Kaggle notebook edition)
├── data/
│ ├── raw/
│ ├── processed/
│ ├── cache/
│ └── awl/
├── train.sh
├── start.sh
├── baseline_score.json # Saved composite score (0.5203) — gate for Hub push
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── pyproject.toml
```
---
## Model Architecture
### PNG:

### Mermaid Diagram:
```mermaid
graph TB
subgraph INFERENCE["🔮 Inference Pipeline"]
direction TB
INPUT["📝 Raw Dyslectic Text"]
subgraph PREPROCESS["Pre-Processing"]
SPELL["Spell Corrector
dyslexia-aware phonetic"]
SENT_SEG["Sentence Segmenter"]
DEP_PARSE["Dependency Parser"]
NER["NER Tagger"]
end
subgraph STYLE["Style Analysis"]
FINGER["Style Fingerprinter
512-dim vector"]
EMOTION["Emotion Classifier"]
FORMALITY["Formality Classifier"]
STYLE_VEC["Style Vector Composer"]
end
subgraph GENERATION["Core Generation"]
STYLE_COND["Style Conditioner
prefix tuning"]
BASE_MODEL["Base LM
CoEdit-Large (warm-merged)"]
LORA["LoRA Adapter
r=16"]
GEN_UTILS["Generation Utils
beam search, sampling"]
end
subgraph POSTPROCESS["Post-Processing"]
FAITH["Faithfulness Gate
cos sim < 0.75 → revert"]
POSTPROC["Post-Processor
formatting, cleanup"]
VOCAB_SUB["Lexical Substitution
BERT-based"]
AWL["AWL Loader
Coxhead Academic Word List"]
REG_FILTER["Register Filter
academic tone gate"]
end
OUTPUT["✅ Corrected Academic Text"]
INPUT --> SPELL --> SENT_SEG --> DEP_PARSE --> NER
INPUT --> FINGER --> EMOTION --> FORMALITY --> STYLE_VEC
NER --> STYLE_COND
STYLE_VEC --> STYLE_COND
STYLE_COND --> BASE_MODEL
LORA -.->|"merged weights"| BASE_MODEL
BASE_MODEL --> GEN_UTILS --> FAITH --> POSTPROC
POSTPROC --> VOCAB_SUB
AWL --> VOCAB_SUB
VOCAB_SUB --> REG_FILTER --> OUTPUT
end
subgraph TRAINING["🏋️ Training Pipeline (v4)"]
direction TB
subgraph WARMSTART["Warm-Start Merge"]
HUB_ADAPTER["Hub LoRA Adapter
r=16 (v3)"]
MERGE["merge_and_unload()"]
FRESH_LORA["Fresh LoRA r=16"]
end
subgraph DATA["Data Pipeline"]
JFLEG["jhu-clsp/jfleg
~5k pairs, 4 refs each"]
WILOCNESS["bea2019st/wi_locness
~34k pairs"]
DYSLEXIA_AUG["DyslexiaSimulator
25% error rate augmentation"]
SPLIT["capped at 25k pairs — 88%/7%/5% splits"]
end
subgraph LOSS["Combined Loss (v4)"]
L_CE["L_CE (nn.CrossEntropyLoss, label_smoothing=0.1)"]
L_STYLE["0.3 · L_style"]
L_HUMAN["0.4 · L_human
(RoBERTa detector)"]
end
subgraph EVAL["Composite Evaluation"]
GLEU_E["GLEU *(metric fix pending)*"]
BERT_E["BERTScore F1"]
WER_E["1 − WER"]
HUMAN_E["HumanScore
(roberta-base-openai-detector)"]
COMPOSITE["Composite = mean(4 metrics)"]
HUB_PUSH["Push to Hub ✅"]
end
HUB_ADAPTER --> MERGE --> FRESH_LORA
JFLEG --> DYSLEXIA_AUG
WILOCNESS --> DYSLEXIA_AUG
DYSLEXIA_AUG --> SPLIT
L_CE --> COMPOSITE
L_STYLE --> COMPOSITE
L_HUMAN --> COMPOSITE
GLEU_E --> COMPOSITE
BERT_E --> COMPOSITE
WER_E --> COMPOSITE
HUMAN_E --> COMPOSITE
COMPOSITE --> HUB_PUSH
end
```
---
## Design Choices & Rationale
### Why CoEdit-Large? (v4 upgrade from Flan-T5-Small)
| Consideration | Decision |
|---------------|----------|
| **Task specialisation** | CoEdit-Large is pretrained specifically on GEC and text editing tasks — it already knows how to correct errors before any fine-tuning |
| **Parameter capacity** | ~400M params vs 77M in Flan-T5-Small — substantially more correction capacity |
| **Training efficiency** | 2 fine-tuning epochs reach good correction quality vs 10 for Flan-T5 |
| **Hardware** | Kaggle T4 × 2 (16GB each) replaces the 4GB laptop GPU constraint |
| **LoRA efficiency** | r=16 adapter on CoEdit-Large is still <3% of total params |
### Why Flan-T5-Small was used in v1–v3
| Consideration | Decision |
|---------------|----------|
| **Hardware constraint** | RTX 3050 Laptop GPU (4GB VRAM) — ruled out models > 500M params |
| **Architecture** | Encoder-decoder (seq2seq) is ideal for text-to-text correction tasks |
| **Instruction tuning** | Flan-T5 is pre-trained on 1,800+ instruction tasks — follows correction prompts naturally |
### Why LoRA over Full Fine-Tuning?
- **Memory**: Full fine-tuning of CoEdit-Large requires ~6GB for gradients; LoRA r=16 fits comfortably in 4GB
- **Warm-start safety**: Merging the previous adapter's weights before applying a fresh LoRA preserves prior corrections
- **Merging**: LoRA weights merge into base model at inference time — zero latency overhead
### Why a Combined Multi-Objective Loss?
The system uses (v4): `L = L_CE + 0.3·L_style + 0.4·L_human`
| Term | Purpose | Weight |
|------|---------|--------|
| `L_CE` | Cross-entropy with label smoothing (0.1) | 1.0 |
| `L_style` | `1 − cos_sim(style(input), style(output))` — preserves writing fingerprint | 0.3 |
| `L_human` | Anti-AI penalty via RoBERTa detector | 0.4 |
The `L_semantic` term from v2/v3 was removed in v4. It used StyleMLP embeddings tied to the vocabulary space, which caused gradient interference with cross-entropy. Semantic faithfulness is now enforced exclusively via the post-generation cosine gate.
### Why a Semantic Faithfulness Gate?
Even a well-trained correction model can occasionally produce outputs that drift semantically from the input. v3+ computes cosine similarity between source and output using `all-MiniLM-L6-v2` sentence embeddings. Outputs below **0.75 similarity** are treated as unreliable and the original input is returned unchanged.
### Why Sentence-Chunked Inference?
The model is trained with `max_input_length=128` tokens. The task prefix alone consumes ~20 tokens. Long inputs are split into sentences, grouped into chunks that fit the 128-token budget, corrected independently, then rejoined.
### Why Post-Generation Vocabulary Elevation?
Rather than relying solely on the model to produce academic vocabulary, a separate BERT-based lexical substitution pipeline is applied post-generation: POS-tag → identify non-AWL content words → BERT fill-mask → filter to AWL-only predictions → accept only if `semantic_similarity > 0.82`.
---
## Quick Start
### Prerequisites
- Python ≥ 3.10
- NVIDIA GPU with ≥ 8GB VRAM recommended (T4 or better); CPU supported but slow
- ~15GB disk space for models and datasets
### Option A: Kaggle Notebook (v4 — Recommended)
Run the pipeline on Kaggle with T4 × 2 GPU:
1. Upload `train_and_upgrade.py` as a Kaggle notebook
2. Enable GPU (T4 × 2) and Internet
3. Add your HuggingFace token as a Kaggle Secret named `HF_TOKEN`
```python
# Run the pipeline
import os
HF_TOKEN = os.environ.get("HF_TOKEN")
main()
```
The pipeline runs 8 steps automatically:
**Load base model → Warm-start merge → Apply r=16 LoRA → Load data → Train → Evaluate → Save → Push**
Estimated runtime: **30–60 minutes on T4 × 2**
### Option B: Self-Improving Upgrade Pipeline (local GPU)
```bash
git clone https://huggingface.co/morpheuslord/rewrite && cd rewrite
pip install -r requirements.txt
export HF_TOKEN="your-hf-token-with-write-access"
python train_and_upgrade.py
```
### Option C: Manual Step-by-Step
```bash
# 1. Install dependencies
pip install -r requirements.txt
python -m spacy download en_core_web_sm
# 2. Preprocess datasets
python scripts/preprocess_data.py
# 3. Pre-train the human pattern classifier
python scripts/pretrain_human_pattern_classifier.py
# 4. Train the correction model
PYTHONPATH=. python scripts/train.py --config configs/training_config.yaml --use-v2-loss
# 5. Merge LoRA adapter into base model
python -c "
from peft import PeftModel
from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
import torch
model = AutoModelForSeq2SeqLM.from_pretrained('grammarly/coedit-large', torch_dtype=torch.float32)
model = PeftModel.from_pretrained(model, 'checkpoints/checkpoint-BEST')
model = model.merge_and_unload()
model.save_pretrained('checkpoints/best_model_merged')
AutoTokenizer.from_pretrained('grammarly/coedit-large').save_pretrained('checkpoints/best_model_merged')
"
# 6. Run inference
PYTHONPATH=. python scripts/run_inference.py --text "The studnet recieved alot of informtion."
# 7. Or start the API server
PYTHONPATH=. python -m uvicorn src.api.main:app --host 0.0.0.0 --port 8000
```
---
## Training Pipeline
### v4 Upgrade Pipeline (`train_and_upgrade.py`) — 8 Steps
| Step | Action |
|------|--------|
| 1 | Load `grammarly/coedit-large` base model (fp32) + existing Hub LoRA adapter |
| 2 | Merge adapter into base weights (`merge_and_unload`) — warm start |
| 3 | Apply fresh LoRA r=16 on merged base |
| 4 | Load JFLEG + W&I+LOCNESS; augment with DyslexiaSimulator (25% error rate); cap at 25k pairs |
| 5 | Train with combined loss for 2 epochs (`L_CE + L_style + L_human`) |
| 6 | Evaluate: BERTScore F1 + (1−WER) + HumanScore [+ GLEU — fix pending] |
| 7 | Save adapter + merge full model |
| 8 | Push adapter + merged model (`merged/` subfolder) to Hub; update baseline |
### v3 Upgrade Pipeline — 10 Steps
| Step | Action |
|------|--------|
| 1 | Load existing LoRA adapter (r=16, v2) from Hub |
| 2 | Merge into base weights — warm start |
| 3 | Apply fresh LoRA r=16 on merged base |
| 4 | Load JFLEG + W&I+LOCNESS + C4-GEC; augment (20% error rate) |
| 5 | Train with combined loss for 10 epochs, early stopping patience=3 |
| 6 | Evaluate: GLEU + BERTScore F1 + (1−WER) [+ ERRANT F0.5 if installed] |
| 7 | Apply semantic faithfulness gate — revert outputs with cosine sim < 0.75 |
| 8 | Compare composite score against `baseline_score.json` |
| 9 | If improved: merge adapter → save full model |
| 10 | Push adapter + merged model to Hub; update baseline |
### v1 Original Pipeline (`train.sh`) — 5 Stages
| Stage | Action |
|-------|--------|
| 1 | Setup & Dependencies |
| 2 | Data Preprocessing |
| 3 | Human Pattern Classifier Pre-Training |
| 4 | Main Model Training (LoRA r=8, 5 epochs, CE only) |
| 5 | Evaluation (GLEU only) |
---
## Hyperparameter Reference
### v4 (`train_and_upgrade.py` — Kaggle T4 × 2)
```python
BASE_MODEL_ID = "grammarly/coedit-large"
LORA_R = 16
LORA_ALPHA = 32
LORA_DROPOUT = 0.05
TARGET_MODULES = ["q", "v", "k", "o", "wi_0", "wi_1", "wo"]
EPOCHS = 2 # coedit-large is already GEC-pretrained
BATCH_SIZE = 8 # per device (GPU)
GRAD_ACCUM = 8 # effective batch = 64
LR = 3e-5 # reduced from 1e-4; 1e-4 caused flat loss on T5-based models
WARMUP_RATIO = 0.05
LABEL_SMOOTHING = 0.1
MAX_INPUT_LEN = 128 # back to 128 — GEC sentences are typically short
MAX_TARGET_LEN = 128
MAX_TRAIN_PAIRS = 25_000
LAMBDA_STYLE = 0.3
# LAMBDA_SEMANTIC removed — caused gradient interference
LAMBDA_HUMAN = 0.4
FAITHFULNESS_THRESHOLD = 0.75
AI_DETECTION_THRESHOLD = 0.4
# fp16 and bf16 both False — fp32 throughout to prevent gradient underflow
```
### v3 (`train_and_upgrade.py`)
```python
LORA_R = 16
LORA_ALPHA = 32
LORA_DROPOUT = 0.05
EPOCHS = 10
BATCH_SIZE = 2 # CPU
GRAD_ACCUM = 32
LR = 2e-4
MAX_INPUT_LEN = 256
LAMBDA_STYLE = 0.3
LAMBDA_SEMANTIC = 0.5
LAMBDA_HUMAN = 0.4
FAITHFULNESS_THRESHOLD = 0.75
```
### v2 (`train_and_upgrade.py`)
```python
LORA_R = 16
LORA_ALPHA = 32
EPOCHS = 10
BATCH_SIZE = 2
GRAD_ACCUM = 32
LR = 2e-4
MAX_INPUT_LEN = 128
LAMBDA_STYLE = 0.3
LAMBDA_SEMANTIC = 0.5
LAMBDA_HUMAN = 0.4
```
### v1 (`configs/training_config.yaml`)
```yaml
lora:
r: 8
lora_alpha: 16
lora_dropout: 0.05
training:
per_device_train_batch_size: 4
gradient_accumulation_steps: 8
learning_rate: 3.0e-4
loss:
lambda_style: 0.3
lambda_semantic: 0.5
lambda_human_pattern: 0.4
```
### `configs/inference_config.yaml`
```yaml
model:
key: "coedit-large"
checkpoint_path: "checkpoints/best_model_merged"
use_lora: false
generation:
num_beams: 5
length_penalty: 1.2
repetition_penalty: 1.3
no_repeat_ngram_size: 3
max_new_tokens: 128
vocabulary:
semantic_threshold: 0.82
faithfulness:
threshold: 0.75
```
---
## Inference Pipeline (8 Steps)
```
Raw Text
│
▼
1. Preprocessing ─────── LanguageTool spell correction + spaCy parsing
│
▼
2. Style Fingerprinting ─ Extract 41 features → MLP → 512-dim vector
│
▼
3. Sentence-Chunked Generation ─ Split into 128-token chunks → CoEdit-Large → rejoin
│
▼
4. Faithfulness Gate ──── cosine_sim(source, output) < 0.75 → revert to source
│
▼
5. Post-Processing ───── Remove artifacts, replace em dashes, fix spacing
│
▼
6. Vocabulary Elevation ─ BERT fill-mask → AWL filtering → semantic gate (threshold 0.82)
│
▼
7. Register Filtering ── Expand contractions, replace colloquialisms
│
▼
8. Metrics ──────────── Style similarity, AWL coverage, readability scores
│
▼
Corrected Text
```
---
## API Usage
```bash
# Start the server
PYTHONPATH=. python -m uvicorn src.api.main:app --host 0.0.0.0 --port 8000
# Correct text
curl -X POST http://localhost:8000/correct \
-H "Content-Type: application/json" \
-d '{"text": "The studnet recieved alot of informtion.", "style_alpha": 0.6}'
# Health check
curl http://localhost:8000/health
```
Interactive docs at `http://localhost:8000/docs`.
---
## Hardware Requirements
| Tier | GPU | Config | Epochs | Training Time |
|------|-----|--------|--------|---------------|
| **Tested (v1)** | RTX 3050 4GB | Flan-T5-Small, r=8 | 5 | ~45 min |
| **Tested (v2 CPU)** | None (HF Space CPU) | Flan-T5-Small, r=16 | 10 | ~12–24 hours |
| **Tested (v3 CPU)** | None (HF Space CPU) | Flan-T5-Small, r=16 | 10 | ~12–24 hours |
| **Tested (v4)** | Kaggle T4 × 2 (16GB each) | CoEdit-Large, r=16 | 2 | ~30–60 min |
| Recommended | RTX 3090 24GB | CoEdit-Large, r=16 + full loss | 3–5 | ~2–3h |
| Maximum | A100 80GB | Full pipeline with ERRANT | 10 | ~12h |
---
## Data Sources
| Dataset | Type | Size | Access |
|---------|------|------|--------|
| JFLEG (`jhu-clsp/jfleg`) | Fluency corrections (4 refs each) | ~5k pairs | HF Hub, no registration |
| W&I+LOCNESS (`bea2019st/wi_locness`) | Learner errors + corrections | ~34k pairs | HF Hub, no registration |
| C4-200M-GEC (`cointegrated/c4_200m-gec-filtered`) | Synthetic GEC pairs | ~100k pairs (capped) | HF Hub — *falls back silently if unavailable* |
| FCE v2.1 | Learner errors + corrections | ~28k pairs | BEA-2019 (registration required) |
| Shanegerami AI_Human.csv | Human vs AI classification | ~50k samples | Kaggle |
| Starblasters8 data.parquet | Human vs AI classification | ~50k samples | Kaggle |
| Coxhead AWL | Academic Word List | 570 families / 549 headwords | Victoria University |
> Note: `train_and_upgrade.py` (v4) uses JFLEG + W&I+LOCNESS capped at 25k pairs. C4-GEC and FCE require additional setup.
---
## Dyslexia Error Simulation
The `DyslexiaSimulator` generates synthetic training data based on research by Rello et al. (2013, 2017). v4 uses a **25% per-word error rate** (up from 20% in v2/v3).
| Error Type | Frequency | Example |
|-----------|-----------|---------|
| Phonetic substitution | 35% | "because" → "becaus" |
| Letter transposition | 18% | "the" → "teh" |
| Letter omission | 16% | "important" → "importnt" |
| Letter doubling | 12% | "letter" → "lettter" |
| Letter reversal (b/d, p/q) | 10% | "bad" → "dad" |
| Word boundary errors | 9% | "a lot" → "alot" |
---
## Style Fingerprint Vector
The 512-dimensional style vector captures 41 raw features:
| Group | Features | Count |
|-------|----------|-------|
| Sentence stats | mean, std, skew of sentence lengths | 3 |
| Word stats | mean, std of word lengths | 2 |
| Lexical | type-token ratio, lexical density | 2 |
| Syntactic | passive/active voice ratio, subordinate clause ratio, avg dependency tree depth | 4 |
| Discourse | 20 academic discourse markers (per 100 words) | 20 |
| Register | hedging frequency, formality score, nominalization ratio | 3 |
| Readability | Flesch reading ease, avg syllables per word | 2 |
| Pronouns | first-person ratio, third-person ratio | 2 |
| Other | question ratio, exclamation ratio, AWL coverage | 3 |
Projected through a 2-layer MLP (`41 → 256 → 512`) with LayerNorm and GELU activation, then L2-normalised.
---
## Known Limitations
1. **GLEU scoring bug (v4):** The `google_bleu` evaluate metric is incompatible with CoEdit-Large's tokenisation space and scores 0.0. This will be resolved in v5 with a tokeniser-aware GLEU implementation. BERTScore F1 (0.9349) is a more reliable quality indicator for this run.
2. **1-WER regression (v4):** The 1-WER drop (0.8552 → 0.3191) partly reflects the model transition and partly reflects the fact that coedit-large produces differently-phrased corrections compared to Flan-T5 — word-level edit distance to the reference set increases when the output is fluent but uses different vocabulary. This will be re-evaluated against CoEdit-specific reference corrections in v5.
3. **Training window:** 128-token max input — very long paragraphs may be split mid-clause.
4. **Vocabulary elevation:** BERT fill-mask can suggest semantically inappropriate AWL words; the 0.82 similarity threshold is a trade-off between coverage and accuracy.
5. **Already-correct text:** The model is trained on error→correction pairs; feeding it clean text produces unpredictable output.
6. **LanguageTool latency:** Spell correction takes ~15–20s due to JVM startup on first call.
7. **Faithfulness gate conservatism:** The 0.75 cosine similarity threshold occasionally reverts valid-but-heavily-corrected outputs. Monitor `num_fallback` in evaluation to tune the threshold.