Downloads · 30 days
11
18% of all-time downloads
keystats/w2v-bert-2.0-luganda-main
w2v-bert-2.0-luganda-main is a automatic speech recognition model from keystats. Use it when you need speech turned into text. The card lists the license as apache-2.0.
A Luganda automatic speech recognition (ASR) model, fine-tuned from facebook/w2v-bert-2.0 on a combined Luganda speech corpus pooled from four sources (see Training data below).
Downloads · 30 days
11
18% of all-time downloads
All-time downloads
62
Public
Parameters
606M
2.4 GB on disk
Likes
0
Public
Click a slice to open those files.
.safetensors2.4 GB · 100%
From the Hugging Face model README
A Luganda automatic speech recognition (ASR) model, fine-tuned from facebook/w2v-bert-2.0 on a combined Luganda speech corpus pooled from four sources (see Training data below).
facebook/w2v-bert-2.0 — a large-scale, multilingual self-supervised
speech encoder pretrained with a BERT-style masked prediction
objective — is used as the backbone, with a from-scratch
character-level CTC (Connectionist Temporal Classification) head
fine-tuned specifically for Luganda.
Text casing note: training targets were lowercased before the
vocabulary was built. This is the -main checkpoint in a two-model
pair — a sibling checkpoint, w2v-bert-2.0-luganda-main-best, was
trained identically except on raw (cased) text. Use this -main
checkpoint when you want the model to focus its capacity purely on
word content rather than also learning casing conventions; use the
-best sibling if you need cased/punctuated output directly from the
acoustic model.
Training pool (train split only from each source, deduplicated by
audio hash and by transcription-within-source):
| Source | Role |
|---|---|
google/WaxalNLP (lug_asr config) | Benchmark dataset — train split pooled into training, validation split held out untouched as the fixed evaluation benchmark |
| keystats/luganda_asr_dataset | All splits pooled into training |
| FarmerlineML/luganda_dataset_2.0 | All splits pooled into training |
| Bateesa/luganda-tts-toby | All splits pooled into training |
None of the three additional sources overlap with WAXAL's own
train/validation/test split boundaries, so pooling every split from
them carries no evaluation leakage risk. WAXAL's validation split
is the only data used for evaluation, and it was never included in
training.
facebook/w2v-bert-2.0Wav2Vec2BertForCTC, add_adapter=TrueWav2Vec2BertProcessor — SeamlessM4TFeatureExtractor
for audio features + a Wav2Vec2CTCTokenizer built from scratch on
the combined training + validation transcriptions (character-level
vocabulary, lowercased text, | as the word delimiter, [PAD]
doubling as the CTC blank token)output_steps < 2 * label_length) are dropped from
both train and validation before trainingEvaluated on both the WAXAL Luganda test and validation splits.
| Decoding | Split | WER | CER |
|---|---|---|---|
| Greedy (no LM) | test | 12.50% | 2.62% |
| Greedy (no LM) | validation | 12.53% | 2.63% |
+ KLM (keystats/waxal-kenlm-models) | test | 11.91% | 2.47% |
+ KLM (keystats/waxal-kenlm-models) | validation | 11.74% | 2.47% |
(n=638 test, n=664 validation, 0 skipped)
Pairing this model with its matching KLM (see Using this model with a KenLM language model below) gives a consistent WER improvement over greedy decoding alone.
Two ways to use this model, depending on your needs:
pyctcdecode +
KenLM, noticeably higher accuracy via beam-search decoding with a
matching language model.import torch
import librosa
from transformers import Wav2Vec2BertForCTC, Wav2Vec2BertProcessor
MODEL_ID = "keystats/w2v-bert-2.0-luganda-main"
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
processor = Wav2Vec2BertProcessor.from_pretrained(MODEL_ID)
model = Wav2Vec2BertForCTC.from_pretrained(MODEL_ID).to(DEVICE).eval()
audio_array, sr = librosa.load("path/to/audio.wav", sr=16000, mono=True)
inputs = processor(audio_array, sampling_rate=16000, return_tensors="pt")
with torch.no_grad():
logits = model(input_features=inputs.input_features.to(DEVICE)).logits
predicted_ids = torch.argmax(logits, dim=-1)
transcription = processor.batch_decode(predicted_ids)[0]
print(transcription) # lowercase Luganda text
A companion n-gram KenLM language model, trained on the same
normalized (lowercased) text convention as this ASR model, is
available at
keystats/waxal-kenlm-models
(luganda/luganda_5gram_correct.arpa). Pairing this ASR model with
its matching KLM via beam-search decoding gives a significant accuracy
improvement over greedy decoding alone.
Important: use the matching KLM variant for whichever ASR
checkpoint you're using — this -main model pairs with
keystats/waxal-kenlm-models (normalized text), while the -best
sibling checkpoint pairs with the separate
keystats/waxal-kenlm-models-best repo (raw/cased text). Mixing a
normalized-text ASR model with a raw-text KLM (or vice versa) will
cause a vocabulary mismatch during decoding.
# pip install pyctcdecode
# pip install https://github.com/kpu/kenlm/archive/master.zip
import torch
import librosa
from huggingface_hub import hf_hub_download
from transformers import Wav2Vec2BertForCTC, Wav2Vec2BertProcessor
from pyctcdecode import build_ctcdecoder
MODEL_ID = "keystats/w2v-bert-2.0-luganda-main"
KLM_REPO_ID = "keystats/waxal-kenlm-models"
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
processor = Wav2Vec2BertProcessor.from_pretrained(MODEL_ID)
model = Wav2Vec2BertForCTC.from_pretrained(MODEL_ID).to(DEVICE).eval()
klm_path = hf_hub_download(repo_id=KLM_REPO_ID, repo_type="dataset",
filename="luganda/luganda_5gram_correct.arpa")
def build_vocab_list(tokenizer, vocab_size):
vocab_dict = tokenizer.get_vocab()
vocab_list = [None] * vocab_size
for tok, idx in sorted(vocab_dict.items(), key=lambda kv: kv[1]):
if idx < vocab_size:
vocab_list[idx] = tok
pad_id = tokenizer.pad_token_id
if pad_id is not None and pad_id < len(vocab_list):
vocab_list[pad_id] = ""
word_delim = getattr(tokenizer, "word_delimiter_token", None)
if word_delim:
delim_id = vocab_dict.get(word_delim)
if delim_id is not None:
vocab_list[delim_id] = " "
return vocab_list
vocab_list = build_vocab_list(processor.tokenizer, model.config.vocab_size)
decoder = build_ctcdecoder(
vocab_list,
kenlm_model_path=klm_path,
alpha=0.5, # LM weight -- tune against your own validation set
beta=0.7, # word insertion bonus -- tune against your own validation set
)
audio_array, sr = librosa.load("path/to/audio.wav", sr=16000, mono=True)
inputs = processor(audio_array, sampling_rate=16000, return_tensors="pt")
with torch.no_grad():
logits = model(input_features=inputs.input_features.to(DEVICE)).logits
transcription = decoder.decode(logits.cpu().numpy()[0], beam_width=100)
print(transcription)
Note on alpha/beta: the values above are starting points, not universal defaults — grid-search them against your own labeled validation set, since optimal weights depend on your specific audio domain.
-best sibling checkpoint.keystats/w2v-bert-2.0-luganda-main-best — same architecture and training procedure, trained on raw (cased) text insteadIf you use this model, please cite the training/fine-tuning work and the underlying datasets:
@misc{keystats_wav2vec2bert_luganda,
title={w2v-bert-2.0-luganda-main: A Luganda ASR model fine-tuned from facebook/w2v-bert-2.0},
author={keystats},
year={2026},
howpublished={\url{https://huggingface.co/keystats/w2v-bert-2.0-luganda-main}}
}
@misc{waxal,
title={WAXAL: A Multilingual African Speech Dataset},
author={Google},
howpublished={\url{https://huggingface.co/datasets/google/WaxalNLP}}
}
@misc{keystats_luganda_asr_dataset,
title={luganda\_asr\_dataset},
author={keystats},
howpublished={\url{https://huggingface.co/datasets/keystats/luganda_asr_dataset}}
}
@misc{farmerline_luganda,
title={luganda\_dataset\_2.0},
author={FarmerlineML},
howpublished={\url{https://huggingface.co/datasets/FarmerlineML/luganda_dataset_2.0}}
}
@misc{bateesa_luganda_tts,
title={luganda-tts-toby},
author={Bateesa},
howpublished={\url{https://huggingface.co/datasets/Bateesa/luganda-tts-toby}}
}
@inproceedings{w2vbert2,
title={Seamless: Multilingual Expressive and Streaming Speech Translation},
author={Seamless Communication and others},
year={2023},
howpublished={\url{https://huggingface.co/facebook/w2v-bert-2.0}}
}