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:

  1. 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.

  2. 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:

  1. Translate monolingual Kusaal text → English using the best checkpoint at the time
  2. Pair the synthetic English output with the original Kusaal source
  3. Filter by confidence and length ratio
  4. 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's save_pretrained() does not persist the lang_code_to_id dictionary. This file must be loaded at inference time to correctly set forced_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.

Downloads last month
22
Safetensors
Model size
0.6B params
Tensor type
F32
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Space using PrinceAlhassanNasamu/kusaal-nllb-600M 1

Evaluation results

  • BLEU (kus → eng) on Kusaal-English Parallel Corpus
    self-reported
    27.570
  • BLEU (eng → kus) on Kusaal-English Parallel Corpus
    self-reported
    13.720