Reference implementation for the ACM TALLIP article:
Cross-Lingual Fine-Tuning of XLM-R for Sentiment Analysis in Hausa-English Code-Switched Dialogues Osei Wusu Brempong Jnr, Junaid Hussain Muzamal, Liu Zhaobin. ACM Transactions on Asian and Low-Resource Language Information Processing (TALLIP), 2026. DOI: 10.1145/3830015
This repository fine-tunes XLM-R for three-class sentiment classification (negative / neutral / positive) on Hausa-English-Pidgin code-switched social-media text. It implements a code-switch-aware pipeline with back-translation + Ghanaian Pidgin injection, self-attention pooling, an auxiliary token-level language-identification objective, adapter-based parameter-efficient fine-tuning, a fairness-aware loss, a full evaluation suite, and ONNX export for deployment.
License & intended use. The code is released under CC BY 4.0 and is intended for non-commercial, academic research use. © 2026 Copyright is held by the owner/author(s). The datasets this repo downloads (NaijaSenti / AfriSenti) are licensed CC BY-NC-SA 4.0 (non-commercial). Please cite the ACM TALLIP article above and the dataset papers (see Citation).
- Requirements & installation
- Datasets: where and how to download
- Preprocessing
- Quickstart (offline smoke run)
- Reproducing the paper
- Ablations, adapters & fairness
- Inference & deployment
- Repository layout
- How each paper component maps to code
- Testing
- Citation
- License
- Python 3.9+
- PyTorch 2.0+ (CPU is fine for the smoke run; a GPU is recommended for full training)
# 1. Clone
git clone https://github.com/JunaidMuzamal/hausa-sentiment-xlmr.git
cd hausa-sentiment-xlmr
# 2. (Recommended) create a virtual environment
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
# 3. Install
pip install --upgrade pip
pip install -e . # core deps
# optional extras:
pip install -e ".[viz]" # matplotlib/seaborn for figures
pip install -e ".[onnx]" # onnx + onnxruntime for deployment
pip install -e ".[marian]" # real MT back-translation (MarianMT)
pip install -e ".[dev]" # pytest, ruff, black
pip install -e ".[all]" # everythingTwo console commands are installed: hausa-sentiment-train and
hausa-sentiment-eval (equivalent to python -m hausa_sentiment.train and
python -m hausa_sentiment.evaluate).
The paper uses the Hausa subset of NaijaSenti / AfriSenti: 14,173 train / 2,678 validation / 5,304 test = 22,155 tweets, three sentiment classes. These are the official, publicly available sources:
| Dataset | HuggingFace | GitHub | License |
|---|---|---|---|
| NaijaSenti (Twitter) | HausaNLP/NaijaSenti-Twitter (config hau) |
hausanlp/NaijaSenti | CC BY-NC-SA 4.0 |
| AfriSenti (SemEval-2023 Task 12) | masakhane/afrisenti or shmuhammad/AfriSenti (config hau) |
afrisenti-semeval/afrisent-semeval-2023 | CC BY-NC-SA 4.0 |
| Sentiment lexicons (optional) | HausaNLP/Naija-Lex (config hau) |
— | CC BY-NC-SA 4.0 |
# Default: NaijaSenti Hausa, from HuggingFace -> data/hausa/{train,validation,test}.csv
python scripts/download_data.py --source naijasenti --out data/hausa
# AfriSenti Hausa instead:
python scripts/download_data.py --source afrisenti --out data/hausa
# No HuggingFace access? Pull the raw CSVs straight from the AfriSenti GitHub:
python scripts/download_data.py --source afrisenti-github --out data/hausaEach command writes three files with exactly two columns — text and
label (label ∈ {0,1,2}, i.e. negative/neutral/positive):
data/hausa/train.csv
data/hausa/validation.csv
data/hausa/test.csv
from datasets import load_dataset
ds = load_dataset("HausaNLP/NaijaSenti-Twitter", "hau") # train / validation / test
print(ds)The download script does not redistribute any data — it fetches from the official sources on demand. Please respect the datasets' CC BY-NC-SA 4.0 license (non-commercial) and cite the dataset papers.
The paper's cleaning (Sec. 4): lowercase, strip @mentions and URLs, keep
emojis (they carry sentiment), collapse whitespace, drop empties, coerce
labels to {0,1,2}. Run it as an explicit, auditable step:
# Whole directory:
python scripts/preprocess.py --in-dir data/hausa --out-dir data/hausa_clean
# Or a single file:
python scripts/preprocess.py --in data/hausa/train.csv --out data/hausa/train.clean.csvThe training pipeline also normalises/tokenizes internally, so this step is optional but recommended for a clean, inspectable corpus.
Prove the whole pipeline works in a couple of minutes on CPU — no dataset download and no large model required (uses a tiny test model + a synthetic corpus):
python scripts/make_synthetic_data.py --out data/synthetic --n 600
python -m hausa_sentiment.train --config configs/smoke_test.yamlOutputs land in outputs/smoke/ (summary.json, per-seed metrics, saved
model + tokenizer). The one-liner below does data → train → plot in one go:
bash scripts/run_all.sh # fast offline smoke
bash scripts/run_all.sh full # full paper reproduction (needs GPU + data)- Get the data (once):
python scripts/download_data.py --source naijasenti --out data/hausa
- Point the config at it — edit
configs/default.yamland uncomment:(Leave them commented to stream directly from HuggingFace instead.)data: train_csv: data/hausa/train.csv validation_csv: data/hausa/validation.csv test_csv: data/hausa/test.csv
- Train the headline model (XLM-R base + back-translation + Pidgin
injection + self-attention pooling + language-ID head; three seeds averaged,
with early stopping):
python -m hausa_sentiment.train --config configs/default.yaml
- Figures:
python scripts/plot_results.py --summary outputs/default/summary.json --outdir figures
The exact hyperparameters from the paper are the defaults: learning rate
2e-5, 3 epochs, batch size 16, 500 warm-up steps, dropout 0.1, max_length
128, early-stopping patience 2, L2 weight_decay 1e-4, seeds [42, 1337, 2024]. Aggregated mean ± std over seeds is written to
outputs/default/summary.json.
Every ablation in the paper has a ready config:
python -m hausa_sentiment.train --config configs/ablation_cls_pooling.yaml # [CLS] instead of attention
python -m hausa_sentiment.train --config configs/ablation_no_langid.yaml # remove language-ID loss
python -m hausa_sentiment.train --config configs/ablation_no_pidgin.yaml # remove Pidgin injection
python -m hausa_sentiment.train --config configs/ablation_no_backtranslation.yaml
python -m hausa_sentiment.train --config configs/adapters.yaml # adapter-based PEFT
python -m hausa_sentiment.train --config configs/fairness.yaml # fairness-aware reweightingThe fairness config expects a gender column in the training CSV and applies
inverse-performance group weights (Sec. 3.6.2).
Single text:
python -m hausa_sentiment.evaluate \
--model-dir outputs/default/seed_42 \
--text "e sweet me wallahi, dis place fine well well"Evaluate a CSV test set:
python -m hausa_sentiment.evaluate \
--model-dir outputs/default/seed_42 \
--test-csv data/hausa/test.csvONNX is exported automatically after full training
(outputs/default/model.onnx) for lightweight mobile/web deployment.
hausa-sentiment-xlmr/
├── src/hausa_sentiment/
│ ├── config.py # typed configs (paper hyperparameters as defaults)
│ ├── augment.py # back-translation + Ghanaian Pidgin injection
│ ├── langid.py # rule-based language tagger (auxiliary-task targets)
│ ├── data.py # loading, augmentation, tokenization, langid alignment
│ ├── model.py # XLM-R + self-attention pooling + langid head + adapters
│ ├── metrics.py # accuracy, macro-F1, per-class P/R, confusion matrix
│ ├── trainer.py # multitask + fairness-aware Trainer
│ ├── train.py # end-to-end training (multi-seed) + ONNX export
│ ├── evaluate.py # evaluation + single-text inference CLI
│ └── export.py # ONNX export
├── configs/ # default + ablation + fairness + smoke configs
├── scripts/
│ ├── download_data.py # fetch + convert NaijaSenti/AfriSenti Hausa -> CSV
│ ├── preprocess.py # paper's cleaning steps
│ ├── make_synthetic_data.py # offline demo corpus (CI / smoke)
│ ├── plot_results.py # reproduce figures
│ └── run_all.sh # one-command pipeline
├── tests/ # unit + smoke tests
├── .github/workflows/ # CI
├── LICENSE # CC BY 4.0
├── NOTICE # attribution + non-commercial-use note
└── CITATION.cff
| Paper element | Where |
|---|---|
| Back-translation (Eq. 4) | augment.Augmenter.back_translate |
| Ghanaian Pidgin injection (Eq. 5) | augment.Augmenter.inject_pidgin |
| Augmented corpus union (Eq. 7) | data.apply_augmentation |
| Multilingual tokenization (Eq. 8) | data.build_tokenized_dataset |
| Language-ID embeddings / targets (Sec. 3.5, 3.9) | langid.py, data._align_langids_to_subwords |
| Self-attention pooling (Eq. 9) | model.SelfAttentionPooling |
[CLS] pooling ablation (Eq. 12) |
model pooling="cls" |
| Classification head + softmax (Eqs. 10–11) | model.XLMRCodeSwitchClassifier |
| Cross-entropy sentiment loss (Eq. 13) | model._compute_loss |
| Fairness-aware reweighting (Eq. 14) | trainer.build_fairness_weights, model._compute_loss |
| Auxiliary language-ID loss (Eq. 15) | model._compute_loss |
| L2 regularization (Eq. 16) | TrainingArguments.weight_decay |
| Adapters (Eq. 17) | model.Adapter |
| Total multitask loss (Eq. 18) | model._compute_loss |
| Adam + warm-up/decay (Eqs. 19–20) | train.TrainingArguments |
| Metrics + confusion matrix (Sec. 3.11) | metrics.py |
| Inference argmax (Eq. 25) | evaluate.predict_texts |
| ONNX deployment (Sec. 4) | export.export_onnx |
pip install -e ".[dev]"
pytest -q -m "not slow" # fast, fully offline
pytest -q # includes tests that fetch a tiny modelCI runs the unit tests plus an end-to-end smoke train on every push
(.github/workflows/ci.yml).
If you use this code, please cite the ACM TALLIP article and the dataset papers.
This work (ACM TALLIP 2026):
@article{brempong2026crosslingual,
title = {Cross-Lingual Fine-Tuning of {XLM-R} for Sentiment Analysis in
{Hausa}-{English} Code-Switched Dialogues},
author = {Brempong Jnr, Osei Wusu and Muzamal, Junaid Hussain and Zhaobin, Liu},
journal = {ACM Transactions on Asian and Low-Resource Language Information Processing (TALLIP)},
year = {2026},
doi = {10.1145/3830015},
note = {\url{https://doi.org/10.1145/3830015}}
}NaijaSenti:
@inproceedings{muhammad-etal-2022-naijasenti,
title = {{NaijaSenti}: A {Nigerian} {Twitter} Sentiment Corpus for Multilingual Sentiment Analysis},
author = {Muhammad, Shamsuddeen Hassan and Adelani, David Ifeoluwa and Ruder, Sebastian and
Ahmad, Ibrahim Said and Abdulmumin, Idris and Bello, Bello Shehu and Choudhury, Monojit and
Emezue, Chris Chinenye and Abdullahi, Saheed Salahudeen and Aremu, Anuoluwapo and
Jorge, Al{\'i}pio and Brazdil, Pavel},
booktitle = {Proceedings of the Thirteenth Language Resources and Evaluation Conference (LREC)},
year = {2022}
}AfriSenti (SemEval-2023 Task 12):
@inproceedings{muhammad2023afrisenti,
title = {{AfriSenti}: A {Twitter} Sentiment Analysis Benchmark for {African} Languages},
author = {Muhammad, Shamsuddeen Hassan and Abdulmumin, Idris and Ayele, Abinew Ali and others},
booktitle = {Proceedings of the 2023 Conference on Empirical Methods in Natural Language Processing (EMNLP)},
year = {2023}
}
@inproceedings{muhammad2023semeval,
title = {{SemEval-2023} {Task 12}: Sentiment Analysis for {African} Languages ({AfriSenti-SemEval})},
author = {Muhammad, Shamsuddeen Hassan and Abdulmumin, Idris and Yimam, Seid Muhie and others},
booktitle = {Proceedings of the 17th International Workshop on Semantic Evaluation (SemEval-2023)},
year = {2023},
doi = {10.18653/v1/2023.semeval-1.315}
}Code: Creative Commons Attribution 4.0 International (CC BY 4.0) —
see LICENSE. © 2026 Copyright is held by the owner/author(s).
Intended for non-commercial, academic research use.
Datasets: NaijaSenti / AfriSenti and lexicons are CC BY-NC-SA 4.0 (non-commercial). Respect those licenses independently when you download and use the data.