Automatic Speech Recognition
cactus
MaprangSTT / README.md
wannaphong's picture
Update README.md
e071365 verified
|
Raw History Blame Contribute Delete
11.2 kB
metadata
language:
  - th
  - en
  - de
  - fr
  - es
  - it
  - nl
  - pl
license: apache-2.0
library_name: cactus
datasets:
  - google/fleurs
  - n-order/Thai-dialect-corpus
  - huangjianbyte2024/thai_Elderly_Speech_dataset
  - typhoon-ai/thai-dialect-isan-dataset
base_model:
  - Cactus-Compute/whistle
pipeline_tag: automatic-speech-recognition

MaprangSTT

MaprangSTT is a Thai speech-to-Text model built on Cactus-Compute/whistle for Standard Thai and Regional Thai Dialects (Central, Isan / Northeastern, Northern / Khummuang, Korat, Southern / Pattani, and Elderly speech). It was trained on open-data datasets.

Trained on 1,022.78 hours (594,231 clips) of curated Thai speech using quantization-aware LoRA (CQ W4 STE + A8) and an expanded Thai SentencePiece vocabulary, the final model compiles into a standalone 32.49 MB .cact archive ready for fast on-device CPU/GPU/mobile inference.

Model Summary

Property Value
Base Architecture Cactus Whistle (8 encoder/decoder layers)
Fine-Tuning Method Quantization-Aware LoRA (rank=16, alpha=32, 15 weight groups) + Trainable New Embedding Rows
Precision / Quantization W4A8 (4-bit weights with straight-through estimator, 8-bit activations & KV cache)
Supported Languages th (Thai), plus base en, de, fr, es, it, nl, pl
Vocabulary Expansion +2,067 Thai tokens (+10,958 SentencePiece pieces + <|th|> language tag)
Standalone .cact Size 32.49 MB (maprang.cact, 681 tensors)
LoRA Adapter Size 29.0 MB (adapter.safetensors)

Dataset Summary

Dataset Source Split / Text Column Valid Clips Duration (Hours)
Mozilla Common Voice v27 (th) validated − (dev + test) / sentence 126,690 149.41 h
Google FLEURS (th_th) train / transcription 2,174 6.84 h
n-order/Thai-dialect-corpus *_train (all 4 subsets) / sentence 437,563 816.74 h
huangjianbyte2024/thai_Elderly_Speech_dataset train / transcription 19,198 26.56 h
typhoon-ai/thai-dialect-isan-dataset train / isan_spelling 8,606 23.22 h
TOTAL All 5 Datasets Combined 594,231 1,022.78 h

(1,381 clips from typhoon-ai/thai-dialect-isan-dataset were filtered out because they were $>30\text{s}$ or contained non-Thai characters after number-to-Thai-words conversion.)

Preprocessing & Tokenizer Expansion

  1. Thai Text Normalization & Cleaning:
    • Normalized Thai character order and vowel sequences via pythainlp.util.normalize (including ํา $\rightarrow$ ำ).
    • Applied community-verified transcript fixes from thai_commonvoice_dataset (db-lastest.db and list_file.tsv).
  2. Number-to-Thai-Words Conversion:
    • Converted both Thai digits (๐-๙) and Arabic digits (0-9), comma-formatted numbers, and decimals into spoken Thai words using pythainlp.util.num_to_thaiword and pythainlp.util.digit_to_text.
    • Filtered out any remaining clips containing non-Thai characters or exceeding 30 seconds.
  3. Word Tokenization:
    • Segmented all reference transcripts into space-delimited words using pythainlp.tokenize.word_tokenize(..., engine="newmm").
  4. Thai National Corpus (TNC) Vocabulary Expansion (tokens.txt):
    • Whistle's base SentencePiece model only covers Latin/European scripts (en, de, fr, es, it, nl, pl).
    • We expanded the vocabulary with 2,067 tokens consisting of all 67 base Thai characters (ก-ฮ, vowels, and tone marks) plus the top 2,000 most frequent Thai words from the Thai National Corpus (pythainlp.corpus.tnc), adding 10,958 prefix-closed SentencePiece pieces.

Training Progression & Evaluation

Hyperparameters

  • Epochs: 3 (111,420 total steps, 37,140 steps/epoch)
  • Batch Size: 16
  • Optimizer: AdamW (peak_lr=1e-3, warmup 5,571 steps, cosine decay to 0.0, global grad norm clip 1.0)
  • Hardware: NVIDIA GeForce RTX 4090 (24 GB VRAM)

Epoch-by-Epoch Loss

Epoch Step Train Loss Validation Loss (Common Voice dev)
Epoch 1 / 3 37,140 / 111,420 1.3467 0.5841
Epoch 2 / 3 74,280 / 111,420 0.2417 0.4239
Epoch 3 / 3 111,420 / 111,420 0.1803 0.3867

Evaluation Results

All benchmarks below were evaluated on the exported W4A8 .cact archive using cactus.transcribe(..., language="th").

  • Character Error Rate (CER): Computed over Thai characters (whitespace removed).
  • Word Error Rate (WER): Computed over space-delimited tokens segmented via pythainlp (newmm).

Google FLEURS (th_th) Test Set — Comparison with Popular Thai ASR Models (1,021 Clips)

Evaluated on all 1,021 clips of the google/fleurs (th_th) test set alongside popular open-source Thai ASR models via pythaiasr (compare_fleurs.py). All outputs were normalized with the same PyThaiNLP text normalizer, number-to-Thai-words converter, and newmm word tokenizer:

Model Architecture / Precision Model Size Google FLEURS (th_th) CER Google FLEURS (th_th) WER Size Ratio vs Ours
MaprangSTT (Ours) Cactus Whistle (W4A8 .cact) 32.5 MB 13.58% 31.05% 1.0×
openai/whisper-small Whisper Small (244M, FP32) 967.0 MB 24.21% 75.74% 29.8× larger
airesearch/wav2vec2-large-xlsr-53-th Wav2Vec2-Large-XLSR-53 (315M, FP32) 1,260.0 MB 9.56% 35.98% 38.8× larger
typhoon_nemotron_asr Nemotron-3.5-ASR (0.6B, INT4 ONNX) ~450.0 MB 12.28% 27.52% 13.8× larger
biodatlab/whisper-small-th-combined Whisper Small Thai (244M, FP16) 563.0 MB 9.32% 24.30% 17.3× larger
typhoon_asr FastConformer RNN-T (ONNX) 435.0 MB 9.02% 20.11% 13.4× larger

Key Takeaways:

  • At only 32.5 MB, MaprangSTT outperforms openai/whisper-small (967 MB) by 10.63% absolute CER (13.58% vs 24.21%) and 44.69% absolute WER (31.05% vs 75.74%).
  • It achieves a 4.93% lower WER (31.05% vs 35.98%) than airesearch/wav2vec2-large-xlsr-53-th while being 38.8× smaller (32.5 MB vs 1.26 GB).
  • It comes within 1.30% CER of the 0.6B-parameter typhoon_nemotron_asr (13.58% vs 12.28%) at 1/14th the model size.

Usage

1. Install Cactus

pip install cactus-compute huggingface_hub cactus-needle

2. Transcribe Audio in Python

cactus.transcribe() expects a 16 kHz mono PCM .wav file:

import os
os.environ.setdefault("NEEDLE_TELEMETRY", "0")  # needle reports usage unless these are set
os.environ.setdefault("DO_NOT_TRACK", "1")

from huggingface_hub import hf_hub_download
from needle import Whistle

model = Whistle(hf_hub_download("pythainlp/MaprangSTT", "maprang.cact"))
print(model.transcribe("speech.wav", language="th")["text"])

If your input audio is .mp3 or a different sample rate, convert it to 16 kHz mono .wav first (e.g., with ffmpeg or soundfile + soxr):

import subprocess
import tempfile
import cactus

def transcribe_any_audio(audio_path: str, weights_path: str = "maprang.cact") -> str:
    with tempfile.NamedTemporaryFile(suffix=".wav") as tmp_wav:
        subprocess.run(
            ["ffmpeg", "-y", "-i", audio_path, "-ar", "16000", "-ac", "1", tmp_wav.name],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
            check=True,
        )
        res = cactus.transcribe(tmp_wav.name, weights=weights_path, language="th")
        return res["text"].strip()

3. Rebuild .cact Archive from the LoRA Adapter

If you want to build a platform-specific runnable bundle or slice decoder layers (--layers 4 or --layers 6) from adapter.safetensors:

pip install "cactus-compute[train]"

# Build full 8-layer standalone .cact archive
cactus whistle build \
    --lora adapter.safetensors \
    --out model.cact

# Or build a smaller/faster 4-layer decoder archive
cactus whistle build \
    --lora adapter.safetensors \
    --layers 4 \
    --out model-l.cact

Repository Files

  • maprang.cact (32.49 MB): Standalone quantized (W4A8) Whistle model archive ready for cactus.transcribe().
  • adapter.safetensors (29.0 MB): Trained LoRA weights (rank=16, alpha=32) and expanded Thai vocabulary embedding rows.
  • tokens.txt (2,067 lines): Base Thai characters and top 2,000 TNC words used to expand the SentencePiece tokenizer.

Citation & Acknowledgements