--- 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: ![Architecture](arch.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.