waxal-w2vbert-lin-specialist
This model was developed for the
Google WAXAL ASR Challenge on Zindi.
waxal-w2vbert-lin-specialist is a single-language Lingala CTC model, and its frontmatter language
list is
[ln] for that reason. It continues the
waxal-w2vbert-linsnalug-raw checkpoint, itself a
fine-tune of
facebook/w2v-bert-2.0, and inherits that
checkpoint's raw character vocabulary unchanged, capitals and the punctuation CER scores included, so
the CTC head keeps its token order and stays averageable with its siblings. Training is a two-stage
curriculum: AfriVoice Lingala first, then a refresh on WAXAL Lingala gold alone. In the final system
this model is the Lingala branch of a language-identification-routed pair and one of the ensemble
members, and it produced the Lingala pseudo-labels used in a later experiment.
Training data
The WAXAL Lingala and Shona train and validation splits are recorded jointly, as 33,002 rows and
about 179 hours, with no per-language breakdown, so no Lingala-only row count or duration is asserted
here. The speaker-disjoint holdout and the validation rows are removed before training. Stage 1 sees
both sources in every batch; stage 2 sees WAXAL Lingala only.
Training configuration
Stage 1 and stage 2 settings:
| setting | stage 1 | stage 2 |
|---|
| starting weights | waxal-w2vbert-linsnalug-raw | stage 1 |
| corpus | WAXAL Lingala plus AfriVoice Lingala | WAXAL Lingala only |
| epochs | 3 | 1 |
| learning rate | 3.0e-5 | 1.0e-5 |
| warmup ratio | 0.1 | 0.1 |
| batch | 12 per device, 3 gradient-accumulation steps (effective 36) | 12 per device, 3 gradient-accumulation steps (effective 36) |
| precision | bf16, gradient checkpointing on | bf16, gradient checkpointing on |
| augmentation | on, applied in the collator | on, applied in the collator |
Both stages run with add_adapter: true, seed 42, and vocab_dir pointing at the parent checkpoint,
so no vocabulary is rebuilt and the CTC token order is preserved. Target normalization is NFC and
whitespace collapse only: no lowercasing, no punctuation stripping, because CER is computed on the raw
string and WER does not strip punctuation. Hardware: 1x NVIDIA RTX 5090 (32 GB VRAM), 32 CPU cores,
92 GB RAM.
Augmentation is applied on the fly in the data collator, so each epoch sees a fresh draw of the same
clip:
- MUSAN noise (OpenSLR 17,
noise/ subset only, not speech), p = 0.5, SNR 5 to 20 dB
- simulated room impulse responses (OpenSLR 28), p = 0.3
- Gaussian noise p = 0.1, random gain p = 0.3
- pitch and speed perturbation disabled (p = 0.0)
Why the order, and why the refresh. The curriculum was measured rather than assumed. Mixing the
same data throughout scored 0.2905 on the holdout; sequencing external audio first and in-style WAXAL
gold last scored 0.2785. The mechanism is transcription convention, not acoustics: AfriVoice
transcripts carry less punctuation than WAXAL's, so a model trained on them drifts away from the
reference conventions and the refresh epoch pulls it back. Where the external corpus is already
punctuated the refresh buys nothing, which was also measured on a FLEURS-supplemented arm (0.2809
before the refresh, 0.2812 after).
Evaluation
| split | error, 0.5 * (WER + CER) |
|---|
| holdout, Lingala | 0.2785 |
The holdout is a speaker-disjoint carve of the WAXAL training data. It is not the competition
test set, and it shares recording conditions and speakers' language variety with the training corpus,
so it flatters any arm tuned on that corpus and gives no independent read on generalisation. Lower is
better; the competition score is 1 - 0.5*(WER + CER).
No solo public-leaderboard score was recorded for this checkpoint on its own. Routed with its Shona
counterpart by open-set language identification, the specialist pair scored 0.744835 on the public
leaderboard (CER 0.115141, WER 0.395189), above the general waxal-w2vbert-linsnalug-raw arm at
0.742142. Holdout and leaderboard did not always agree in this project, so the holdout figure above
should be read as a training diagnostic, not as a ranking.
Self-training on the WAXAL unlabeled Lingala pool, using this model's own pseudo-labels (22,986 rows,
top 60 per cent kept), was flat in both languages and was dropped.
Intended use and limitations
Intended use is automatic speech recognition for Lingala photo-description speech: spontaneous
descriptions of images, on consumer recording equipment, averaging 20.2 seconds per clip. It is a
specialist and should be given Lingala audio only. Its behaviour on Shona, on Luganda, or on any other
language is untested and expected to be worse than that of a model trained for those languages. The
competition targets Lingala, Shona and Luganda; the corrected Phase 2 test set was measured to contain
only Lingala and Shona, which is why this pair of specialists covers those two.
In the final system the routing decision comes from open-set language identification rather than from
the clip identifiers, which carry no language information: facebook/mms-lid-4017 called 437 of the
892 test clips Lingala at confidence 0.990 and 423 Shona at confidence 0.980, leaving 32
low-confidence clips, one of which it labelled Luganda. The 34 clips that either identification
model (the other being facebook/mms-lid-256) declined to call Lingala or Shona were inspected
individually and are neighbouring-language confusions rather than coverage gaps.
This model is one component of the ensembles behind the submission. The 26-member character-level
ROVER core scored 0.764915, and the scored final submission, recipe p2n_mbr, selects per clip across
seven such ensembles and scored 0.766791 public and 0.772552 private. Solo strength is not the only
criterion for membership in those core ensembles. Some members are deliberately weak alone,
notably the blank-penalty re-decodes, which over-generate words and lose score in isolation (0.743
against 0.746 for the same checkpoint) while contributing +0.0104 in total, because a character vote
can filter a spurious word and can never recover a missing one. The limit of that argument was also
measured: genuinely weak checkpoints did not help, and a 21-member vote including them scored 0.764152
against 0.764759 for a 25-member vote of strong sources.
How to load
1import torch
2import soundfile as sf
3from transformers import Wav2Vec2BertProcessor, Wav2Vec2BertForCTC
4
5MODEL_ID = "anyantudre/waxal-w2vbert-lin-specialist" # or a local checkpoint directory
6
7processor = Wav2Vec2BertProcessor.from_pretrained(MODEL_ID)
8model = Wav2Vec2BertForCTC.from_pretrained(MODEL_ID, torch_dtype=torch.float32).eval()
9
10wav, sr = sf.read("lingala_clip.wav") # mono, resample to 16 kHz before this call
11inputs = processor(wav, sampling_rate=16000, return_tensors="pt")
12with torch.no_grad():
13 logits = model(**inputs).logits
14
15print(processor.batch_decode(logits.argmax(-1))[0])
When transcribing a batch rather than a single clip, pass the attention mask through to the model.
Clips in a batch have different lengths and the shorter ones are zero-padded; without the mask,
self-attention treats that padding as real audio and the encoder output changes for every frame, not
just the padded tail. Omitting it altered roughly half of all transcripts in a side-by-side check.