keystats/kiswahili_sahihi_asr_adapted_2 is a refined
Swahili automatic speech recognition (ASR) model optimized for
on-device use and
low-resource settings.
It extends the
Whisper Medium architecture through
parameter-efficient fine-tuning (PEFT) and
LoRA adapters, achieving high transcription accuracy while keeping the model lightweight and deployable.
This model builds upon the foundation of
Kiswahili Sahihi ASR and was developed for the
Your Voice, Your Device, Your Language Challenge, aiming to advance
accessible speech technology for over 200 million Swahili speakers across Africa and beyond.
1✅ Added <|pad|> token to tokenizer
2✅ Resized embeddings to 51866 tokens
3✅ PEFT + LoRA adapters merged
4✅ Dataset: Train=3,758 | Validation=77
5✅ 1,000 noise clips used for data augmentation
1# ============================================
2# 🪄 Full Swahili ASR Transcription with Adapted Model
3# ============================================
4# 📦 Installation
5!pip install -q transformers "datasets<4.0.0"
6!pip install -q torchaudio==2.6.0 torchvision==0.21.0 jiwer evaluate
7!pip install -q soundfile librosa accelerate>=0.26.0 tensorboard bitsandbytes
8!pip install -q pydub
9!apt-get -y install ffmpeg
10
11# ============================================
12# 1️⃣ Imports
13# ============================================
14import torch
15import librosa
16import numpy as np
17from pydub import AudioSegment
18from transformers import WhisperProcessor, WhisperForConditionalGeneration
19from peft import PeftModel, PeftConfig
20import imageio_ffmpeg as ffmpeg_lib
21import os
22
23# ============================================
24# 2️⃣ Register ffmpeg for pydub (no sudo)
25# ============================================
26from pydub.utils import which
27
28ffmpeg_path = ffmpeg_lib.get_ffmpeg_exe()
29AudioSegment.converter = ffmpeg_path
30AudioSegment.ffmpeg = ffmpeg_path
31AudioSegment.ffprobe = ffmpeg_path
32
33print("✅ ffmpeg and ffprobe linked successfully!")
34
35# ============================================
36# 3️⃣ Load Adapted Model (with vocab fix)
37# ============================================
38base_model_id = "keystats/kiswahili_sahihi_asr"
39adapter_model_path = "keystats/kiswahili_sahihi_asr_adapted_2"
40
41print(f"🔹 Loading processor from: {adapter_model_path}")
42processor = WhisperProcessor.from_pretrained(adapter_model_path)
43vocab_size = len(processor.tokenizer)
44print(f"🔹 Tokenizer vocab size: {vocab_size}")
45
46# Load PEFT config
47peft_config = PeftConfig.from_pretrained(adapter_model_path)
48print(f"🔹 PEFT base model: {peft_config.base_model_name_or_path}")
49
50# Load base Whisper model
51base_model = WhisperForConditionalGeneration.from_pretrained(
52 peft_config.base_model_name_or_path,
53 ignore_mismatched_sizes=True,
54)
55
56# Fix vocab size mismatch
57base_model.resize_token_embeddings(vocab_size)
58print(f"✅ Resized token embeddings to match vocab size ({vocab_size})")
59
60# Load and merge adapter
61model = PeftModel.from_pretrained(base_model, adapter_model_path)
62print("✅ Adapter loaded successfully")
63
64model = model.merge_and_unload()
65print("✅ Adapter merged and unloaded")
66
67# Move to device
68device = "cuda" if torch.cuda.is_available() else "cpu"
69model = model.to(device, dtype=torch.float32)
70model.eval()
71print(f"🚀 Model ready on {device.upper()}")
72
73# ============================================
74# 4️⃣ Convert Any Format to WAV
75# ============================================
76def convert_to_wav(input_path, output_path="converted.wav"):
77 """Convert MP3, M4A, or any audio file to mono 16kHz WAV."""
78 try:
79 audio = AudioSegment.from_file(input_path)
80 audio = audio.set_frame_rate(16000).set_channels(1)
81 audio.export(output_path, format="wav")
82 return output_path
83 except Exception as e:
84 raise RuntimeError(f"❌ Could not convert file. Error: {e}")
85
86# 🎧 Replace this with your Swahili audio file
87audio_path = "Your audio here"
88wav_path = convert_to_wav(audio_path)
89print(f"✅ Converted to: {wav_path}")
90
91# ============================================
92# 5️⃣ Load Audio and Chunk
93# ============================================
94audio_input, sr = librosa.load(wav_path, sr=16000, mono=True)
95chunk_length_s = 60 # seconds
96chunk_size = chunk_length_s * sr
97num_chunks = int(np.ceil(len(audio_input) / chunk_size))
98print(f"🔹 Total length: {len(audio_input)/sr:.2f}s | Splitting into {num_chunks} chunks...")
99
100# ============================================
101# 6️⃣ Transcribe (without forced_decoder_ids)
102# ============================================
103full_transcription = []
104
105# Add language token manually for safety
106lang_token = processor.tokenizer.convert_tokens_to_ids("<|swahili|>")
107task_token = processor.tokenizer.convert_tokens_to_ids("<|transcribe|>")
108start_tokens = torch.tensor([[lang_token, task_token]], device=device)
109
110for i in range(num_chunks):
111 start = i * chunk_size
112 end = min((i + 1) * chunk_size, len(audio_input))
113 chunk = audio_input[start:end]
114
115 inputs = processor(
116 chunk,
117 sampling_rate=16000,
118 return_tensors="pt",
119 return_attention_mask=True,
120 ).to(device, dtype=torch.float32)
121
122 with torch.no_grad():
123 generated_ids = model.generate(
124 **inputs,
125 max_new_tokens=256,
126 num_beams=2,
127 repetition_penalty=1.1,
128 )
129
130 text = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
131 full_transcription.append(text.strip())
132
133 print(f"🟢 Chunk {i+1}/{num_chunks} done")
134
135
136# ============================================
137# 7️⃣ Combine Final Transcript
138# ============================================
139final_text = " ".join(full_transcription)
140print("\n📝 Final Transcription:\n")
141print(final_text)
142
This model builds upon the architecture and open-source contributions of
Salifou Abdourahamane — creator of the excellent
swahili_asr_sota_model repository.
His work served as an inspiration and foundation for the lightweight, adapter-fused design used here.