Kusaal ↔ English Machine Translation
An open-source machine translation model for Kusaal — a Gur language spoken by ~400,000 people in northern Ghana and parts of Burkina Faso.
Built by a native Kusaal speaker from Bawku, Ghana. Fine-tuned on a parallel corpus assembled from scratch and expanded through back-translation augmentation.
Why This Exists
Kusaal is not in Google Translate. It is not in Meta's NLLB-200 (which covers 200 languages). It has no speech recognizer, no text-to-speech system, and very limited NLP resources.
A Kusaal speaker navigating a hospital form, a legal document, or an agricultural advisory in northern Ghana has no open digital translation tool. This project is a step toward changing that.
Model
Fine-tuned from facebook/nllb-200-distilled-600M on a parallel corpus built from scratch and expanded via back-translation augmentation.
| Base model | facebook/nllb-200-distilled-600M |
| New language | kus_Latn (Kusaal, Latin script) |
| Embedding seed | dag_Latn (Dagbani — closest Gur relative in NLLB) |
| Training pairs | 34,568 (bidirectional: 69,136 examples) |
| Directions | kus → eng and eng → kus in one model |
| BLEU (kus → eng) | 27.57 |
| BLEU (eng → kus) | 13.72 |
Why Dagbani as the seed?
Kusaal and Dagbani are both Oti-Volta (Gur) languages. Instead of initialising the new kus_Latn embedding randomly, we copy Dagbani's embedding as a warm-start. The model begins with a linguistically grounded prior about what kind of language Kusaal is, which leads to faster convergence and better early translation quality than a cold random start.
Dataset
Built from multiple sources:
| Source | Pairs | Domain |
|---|---|---|
| YouVersion (Bible KJV) | ~29,257 | Religious / formal |
| English-Kusaal Index | 3,504 | Index / vocabulary |
| GhanaNLP | 3,489 | Daily life, health, agriculture, greetings, numbers |
| Lexique Pro (Kusaal lexical database) | 2,775 | Dictionary |
| Wikipedia | 1,136 | General |
| Back-translation augmentation | ~2,407 | Mixed |
| Total | ~34,568 |
Splits: Train ~27,300 · Val ~5,187 · Test ~2,081
Data pipeline
The raw data had two problems that would have silently hurt training:
HTML pollution — the YouVersion scraper stored
class="verse v2" > 2 In those days...instead of clean text in thousands of Bible pairs. Fixed by stripping the HTML pattern and re-sourcing clean text.CSV quoting corruption — rows were silently dropped by pandas due to unescaped apostrophes in Bible text. Fixed by re-saving all files with
QUOTE_ALL.
After cleaning: deduplication on Kusaal+English pairs, length ratio filter (max 6×), UTF-8 throughout.
Back-translation augmentation
To grow the corpus beyond the initial human-verified pairs, a back-translation pipeline was run on Lightning AI:
- Translate monolingual Kusaal text → English using the best checkpoint at the time
- Pair the synthetic English output with the original Kusaal source
- Filter by confidence and length ratio
- Mix synthetic pairs with the original human-verified data at a controlled ratio
Usage
Install
pip install transformers torch sentencepiece
Quick inference
import json, re, torch
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
from huggingface_hub import hf_hub_download
MODEL_ID = "PrinceAlhassanNasamu/kusaal-nllb-600M"
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
model = AutoModelForSeq2SeqLM.from_pretrained(MODEL_ID)
model.eval()
# Re-register language codes (not saved by save_pretrained)
lang_codes_path = hf_hub_download(repo_id=MODEL_ID, filename="kusaal_lang_codes.json")
with open(lang_codes_path) as f:
lang_codes = json.load(f)
for lang, lid in lang_codes.items():
if hasattr(tokenizer, "lang_code_to_id"): tokenizer.lang_code_to_id[lang] = lid
if hasattr(tokenizer, "id_to_lang_code"): tokenizer.id_to_lang_code[lid] = lang
EOS = tokenizer.eos_token_id
def translate(text, direction="kus-eng"):
src = "kus_Latn" if direction == "kus-eng" else "eng_Latn"
tgt = "eng_Latn" if direction == "kus-eng" else "kus_Latn"
src_id = tokenizer.convert_tokens_to_ids(src)
tgt_id = tokenizer.convert_tokens_to_ids(tgt)
raw = tokenizer(text.strip(), add_special_tokens=False, return_tensors="pt")
ids = torch.cat([torch.tensor([[src_id]]), raw["input_ids"], torch.tensor([[EOS]])], dim=1)
with torch.no_grad():
out = model.generate(
ids,
attention_mask=torch.ones_like(ids),
forced_bos_token_id=tgt_id,
decoder_start_token_id=EOS,
max_new_tokens=64,
num_beams=4,
repetition_penalty=3.5,
no_repeat_ngram_size=4,
early_stopping=True,
length_penalty=0.6,
)
result = tokenizer.decode(out[0], skip_special_tokens=True).strip()
parts = re.split(r'(?<=[.?!])\s+', result)
return parts[0].strip() if parts else result
print(translate("Laafi bɛ?", "kus-eng")) # -> "Are you all right?"
print(translate("How are you?", "eng-kus")) # -> Kusaal output
Interactive script
python test_kusaal.py --model_dir path/to/kusaal-nllb-final
Model Files
| File | Description |
|---|---|
model.safetensors |
Trained model weights (~2.4 GB) |
tokenizer.json |
Tokenizer vocabulary with kus_Latn added |
kusaal_lang_codes.json |
Language → token ID map (required at inference) |
generation_config.json |
Default generation settings |
config.json |
Model architecture config |
Note on
kusaal_lang_codes.json: HuggingFace'ssave_pretrained()does not persist thelang_code_to_iddictionary. This file must be loaded at inference time to correctly setforced_bos_token_id.
Limitations
- Domain bias: ~77% of training data is Bible text. The model handles formal and religious Kusaal better than everyday conversational language.
- Synthetic data noise: Back-translated pairs introduce noise — the model that generated them makes errors, and those errors appear in training. The filtering step mitigates but does not eliminate this.
- Tokenization: NLLB's SentencePiece tokenizer was built without Kusaal data. Kusaal words are over-segmented into small fragments, limiting word-level pattern learning.
- Dataset size: 34,568 pairs remains modest for MT. Generalisation to unseen vocabulary is limited.
- No formal native speaker evaluation: BLEU measures n-gram overlap with reference translations. The only real quality test is assessment by fluent Kusaal speakers.
What's Next
This model is a step toward a larger planned project: a trimodal Kusaal corpus — parallel text, aligned Kusaal audio recordings, and structured linguistic annotations. The goal is to enable speech recognition, text-to-speech, and language learning tools for Kusaal communities.
If you work in African NLP, speak Kusaal, or want to contribute data — open an issue or reach out.
Technical Details
| Parameter | Value |
|---|---|
| Max steps | 6,000 |
| Effective batch size | 32 (4 × 8 grad accumulation) |
| Learning rate | 5e-5 |
| Warmup steps | 600 |
| Max sequence length | 128 tokens |
| Evaluation metric | SacreBLEU |
| Best checkpoint selection | BLEU (not loss) |
| Precision | FP16 |
| Hardware | Google Colab T4 GPU; Lightning AI (back-translation pipeline) |
Citation
@misc{alhassan2026kusaal,
author = {Alhassan, Prince Nasamu},
title = {Kusaal-English Machine Translation: First Open-Source NLP Model for Kusaal},
year = {2026},
publisher = {HuggingFace},
url = {https://huggingface.co/PrinceAlhassanNasamu/kusaal-nllb-600M}
}
About
Built by Prince Nasamu Alhassan — native Kusaal speaker from Bawku, Ghana. Mathematical Science with Computer Science student at the University of Ghana, Legon. Independent NLP researcher affiliated with GhanaNLP.
- GitHub: @NasamuAlhassan
- HuggingFace: PrinceAlhassanNasamu
- Email: pnalhassan@gmail.com
- Downloads last month
- 22
Space using PrinceAlhassanNasamu/kusaal-nllb-600M 1
Evaluation results
- BLEU (kus → eng) on Kusaal-English Parallel Corpusself-reported27.570
- BLEU (eng → kus) on Kusaal-English Parallel Corpusself-reported13.720