Update README.md
Browse files
README.md
CHANGED
|
@@ -1,3 +1,182 @@
|
|
| 1 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
license: apache-2.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
language:
|
| 3 |
+
- th
|
| 4 |
+
- en
|
| 5 |
+
- de
|
| 6 |
+
- fr
|
| 7 |
+
- es
|
| 8 |
+
- it
|
| 9 |
+
- nl
|
| 10 |
+
- pl
|
| 11 |
license: apache-2.0
|
| 12 |
+
library_name: cactus
|
| 13 |
+
datasets:
|
| 14 |
+
- google/fleurs
|
| 15 |
+
- n-order/Thai-dialect-corpus
|
| 16 |
+
- huangjianbyte2024/thai_Elderly_Speech_dataset
|
| 17 |
+
- typhoon-ai/thai-dialect-isan-dataset
|
| 18 |
+
base_model:
|
| 19 |
+
- Cactus-Compute/whistle
|
| 20 |
+
pipeline_tag: automatic-speech-recognition
|
| 21 |
---
|
| 22 |
+
|
| 23 |
+
# MaprangSTT
|
| 24 |
+
|
| 25 |
+
MaprangSTT is a Thai speech-to-Text model built on [Cactus-Compute/whistle](https://huggingface.co/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.
|
| 26 |
+
|
| 27 |
+
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.
|
| 28 |
+
|
| 29 |
+
## Model Summary
|
| 30 |
+
|
| 31 |
+
| Property | Value |
|
| 32 |
+
| :--- | :--- |
|
| 33 |
+
| **Base Architecture** | Cactus Whistle (8 encoder/decoder layers) |
|
| 34 |
+
| **Fine-Tuning Method** | Quantization-Aware LoRA (`rank=16`, `alpha=32`, 15 weight groups) + Trainable New Embedding Rows |
|
| 35 |
+
| **Precision / Quantization** | `W4A8` (4-bit weights with straight-through estimator, 8-bit activations & KV cache) |
|
| 36 |
+
| **Supported Languages** | `th` (Thai), plus base `en`, `de`, `fr`, `es`, `it`, `nl`, `pl` |
|
| 37 |
+
| **Vocabulary Expansion** | `+2,067` Thai tokens (`+10,958` SentencePiece pieces + `<\|th\|>` language tag) |
|
| 38 |
+
| **Standalone `.cact` Size** | **32.49 MB** (`whistle-thai-combined.cact`, 681 tensors) |
|
| 39 |
+
| **LoRA Adapter Size** | **29.0 MB** (`adapter.safetensors`) |
|
| 40 |
+
|
| 41 |
+
### Dataset Summary
|
| 42 |
+
|
| 43 |
+
| Dataset Source | Split / Text Column | Valid Clips | Duration (Hours) |
|
| 44 |
+
| :--- | :--- | ---: | ---: |
|
| 45 |
+
| **Mozilla Common Voice v27 (`th`)** | `validated` − (`dev` + `test`) / `sentence` | 126,690 | 149.41 h |
|
| 46 |
+
| **Google FLEURS (`th_th`)** | `train` / `transcription` | 2,174 | 6.84 h |
|
| 47 |
+
| **`n-order/Thai-dialect-corpus`** | `*_train` (all 4 subsets) / `sentence` | 437,563 | 816.74 h |
|
| 48 |
+
| **`huangjianbyte2024/thai_Elderly_Speech_dataset`** | `train` / `transcription` | 19,198 | 26.56 h |
|
| 49 |
+
| **`typhoon-ai/thai-dialect-isan-dataset`** | `train` / **`isan_spelling`** | **8,606** | **23.22 h** |
|
| 50 |
+
| **TOTAL** | **All 5 Datasets Combined** | **594,231** | **1,022.78 h** |
|
| 51 |
+
|
| 52 |
+
*(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.)*
|
| 53 |
+
|
| 54 |
+
## Preprocessing & Tokenizer Expansion
|
| 55 |
+
|
| 56 |
+
1. **Thai Text Normalization & Cleaning**:
|
| 57 |
+
- Normalized Thai character order and vowel sequences via `pythainlp.util.normalize` (including `ํา` $\rightarrow$ `ำ`).
|
| 58 |
+
- Applied community-verified transcript fixes from [`thai_commonvoice_dataset`](https://github.com/wannaphong/thai_commonvoice_dataset) (`db-lastest.db` and `list_file.tsv`).
|
| 59 |
+
2. **Number-to-Thai-Words Conversion**:
|
| 60 |
+
- 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`.
|
| 61 |
+
- Filtered out any remaining clips containing non-Thai characters or exceeding 30 seconds.
|
| 62 |
+
3. **Word Tokenization**:
|
| 63 |
+
- Segmented all reference transcripts into space-delimited words using `pythainlp.tokenize.word_tokenize(..., engine="newmm")`.
|
| 64 |
+
4. **Thai National Corpus (TNC) Vocabulary Expansion (`tokens.txt`)**:
|
| 65 |
+
- Whistle's base SentencePiece model only covers Latin/European scripts (`en, de, fr, es, it, nl, pl`).
|
| 66 |
+
- 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.
|
| 67 |
+
## Training Progression & Evaluation
|
| 68 |
+
|
| 69 |
+
### Hyperparameters
|
| 70 |
+
- **Epochs**: 3 (`111,420` total steps, `37,140` steps/epoch)
|
| 71 |
+
- **Batch Size**: 16
|
| 72 |
+
- **Optimizer**: AdamW (`peak_lr=1e-3`, warmup `5,571` steps, cosine decay to `0.0`, global grad norm clip `1.0`)
|
| 73 |
+
- **Hardware**: NVIDIA GeForce RTX 4090 (24 GB VRAM)
|
| 74 |
+
|
| 75 |
+
### Epoch-by-Epoch Loss
|
| 76 |
+
|
| 77 |
+
| Epoch | Step | Train Loss | Validation Loss (`Common Voice dev`) |
|
| 78 |
+
| :---: | :---: | :---: | :---: |
|
| 79 |
+
| **Epoch 1 / 3** | `37,140 / 111,420` | `1.3467` | `0.5841` |
|
| 80 |
+
| **Epoch 2 / 3** | `74,280 / 111,420` | `0.2417` | `0.4239` |
|
| 81 |
+
| **Epoch 3 / 3** | `111,420 / 111,420` | **`0.1803`** | **`0.3867`** |
|
| 82 |
+
## Evaluation Results
|
| 83 |
+
|
| 84 |
+
All benchmarks below were evaluated on the exported `W4A8` `.cact` archive using `cactus.transcribe(..., language="th")`.
|
| 85 |
+
- **Character Error Rate (CER)**: Computed over Thai characters (whitespace removed).
|
| 86 |
+
- **Word Error Rate (WER)**: Computed over space-delimited tokens segmented via `pythainlp` (`newmm`).
|
| 87 |
+
|
| 88 |
+
### Google FLEURS (`th_th`) Test Set — Comparison with Popular Thai ASR Models (`1,021` Clips)
|
| 89 |
+
|
| 90 |
+
Evaluated on all **1,021 clips** of the [`google/fleurs`](https://huggingface.co/datasets/google/fleurs) (`th_th`) test set alongside popular open-source Thai ASR models via [`pythaiasr`](https://github.com/PyThaiNLP/pythaiasr) ([compare_fleurs.py](file:///workspace/whistle-thai/compare_fleurs.py)). All outputs were normalized with the same PyThaiNLP text normalizer, number-to-Thai-words converter, and `newmm` word tokenizer:
|
| 91 |
+
|
| 92 |
+
| Model | Architecture / Precision | Model Size | Google FLEURS (`th_th`) CER | Google FLEURS (`th_th`) WER | Size Ratio vs Ours |
|
| 93 |
+
| :---------------------------------------------------------------------------------------------------- | :------------------------------------ | ----------: | :-------------------------: | :-------------------------: | :----------------: |
|
| 94 |
+
| **`MaprangSTT` (Ours)** | **Cactus Whistle (`W4A8` `.cact`)** | **32.5 MB** | **`13.58%`** | **`31.05%`** | **1.0×** |
|
| 95 |
+
| [`openai/whisper-small`](https://huggingface.co/openai/whisper-small) | Whisper Small (`244M`, FP32) | 967.0 MB | `24.21%` | `75.74%` | 29.8× larger |
|
| 96 |
+
| [`airesearch/wav2vec2-large-xlsr-53-th`](https://huggingface.co/airesearch/wav2vec2-large-xlsr-53-th) | Wav2Vec2-Large-XLSR-53 (`315M`, FP32) | 1,260.0 MB | `9.56%` | `35.98%` | 38.8× larger |
|
| 97 |
+
| `typhoon_nemotron_asr` | Nemotron-3.5-ASR (`0.6B`, INT4 ONNX) | ~450.0 MB | `12.28%` | `27.52%` | 13.8× larger |
|
| 98 |
+
| [`biodatlab/whisper-small-th-combined`](https://huggingface.co/biodatlab/whisper-small-th-combined) | Whisper Small Thai (`244M`, FP16) | 563.0 MB | `9.32%` | `24.30%` | 17.3× larger |
|
| 99 |
+
| `typhoon_asr` | FastConformer RNN-T (ONNX) | 435.0 MB | `9.02%` | `20.11%` | 13.4× larger |
|
| 100 |
+
|
| 101 |
+
> **Key Takeaways**:
|
| 102 |
+
> - At only **32.5 MB**, **`MaprangSTT`** outperforms [`openai/whisper-small`](https://huggingface.co/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%`).
|
| 103 |
+
> - It achieves a **4.93% lower WER** (`31.05%` vs `35.98%`) than [`airesearch/wav2vec2-large-xlsr-53-th`](https://huggingface.co/airesearch/wav2vec2-large-xlsr-53-th) while being **38.8× smaller** (`32.5 MB` vs `1.26 GB`).
|
| 104 |
+
> - 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**.
|
| 105 |
+
## Usage
|
| 106 |
+
|
| 107 |
+
### 1. Install Cactus
|
| 108 |
+
|
| 109 |
+
```bash
|
| 110 |
+
pip install cactus-compute huggingface_hub
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
### 2. Transcribe Audio in Python
|
| 114 |
+
|
| 115 |
+
`cactus.transcribe()` expects a 16 kHz mono PCM `.wav` file:
|
| 116 |
+
|
| 117 |
+
```python
|
| 118 |
+
import os
|
| 119 |
+
os.environ.setdefault("NEEDLE_TELEMETRY", "0") # needle reports usage unless these are set
|
| 120 |
+
os.environ.setdefault("DO_NOT_TRACK", "1")
|
| 121 |
+
|
| 122 |
+
from huggingface_hub import hf_hub_download
|
| 123 |
+
from needle import Whistle
|
| 124 |
+
|
| 125 |
+
model = Whistle(hf_hub_download("pythainlp/MaprangSTT", "maprang.cact"))
|
| 126 |
+
print(model.transcribe("speech.wav", language="th")["text"])
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
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`):
|
| 130 |
+
|
| 131 |
+
```python
|
| 132 |
+
import subprocess
|
| 133 |
+
import tempfile
|
| 134 |
+
import cactus
|
| 135 |
+
|
| 136 |
+
def transcribe_any_audio(audio_path: str, weights_path: str = "maprang.cact") -> str:
|
| 137 |
+
with tempfile.NamedTemporaryFile(suffix=".wav") as tmp_wav:
|
| 138 |
+
subprocess.run(
|
| 139 |
+
["ffmpeg", "-y", "-i", audio_path, "-ar", "16000", "-ac", "1", tmp_wav.name],
|
| 140 |
+
stdout=subprocess.DEVNULL,
|
| 141 |
+
stderr=subprocess.DEVNULL,
|
| 142 |
+
check=True,
|
| 143 |
+
)
|
| 144 |
+
res = cactus.transcribe(tmp_wav.name, weights=weights_path, language="th")
|
| 145 |
+
return res["text"].strip()
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
### 3. Rebuild `.cact` Archive from the LoRA Adapter
|
| 149 |
+
|
| 150 |
+
If you want to build a platform-specific runnable bundle or slice decoder layers (`--layers 4` or `--layers 6`) from `adapter.safetensors`:
|
| 151 |
+
|
| 152 |
+
```bash
|
| 153 |
+
pip install "cactus-compute[train]"
|
| 154 |
+
|
| 155 |
+
# Build full 8-layer standalone .cact archive
|
| 156 |
+
cactus whistle build \
|
| 157 |
+
--lora adapter.safetensors \
|
| 158 |
+
--out model.cact
|
| 159 |
+
|
| 160 |
+
# Or build a smaller/faster 4-layer decoder archive
|
| 161 |
+
cactus whistle build \
|
| 162 |
+
--lora adapter.safetensors \
|
| 163 |
+
--layers 4 \
|
| 164 |
+
--out model-l.cact
|
| 165 |
+
```
|
| 166 |
+
|
| 167 |
+
## Repository Files
|
| 168 |
+
|
| 169 |
+
- `maprang.cact` (`32.49 MB`): Standalone quantized (`W4A8`) Whistle model archive ready for `cactus.transcribe()`.
|
| 170 |
+
- `adapter.safetensors` (`29.0 MB`): Trained LoRA weights (`rank=16`, `alpha=32`) and expanded Thai vocabulary embedding rows.
|
| 171 |
+
- `tokens.txt` (`2,067` lines): Base Thai characters and top 2,000 TNC words used to expand the SentencePiece tokenizer.
|
| 172 |
+
|
| 173 |
+
## Citation & Acknowledgements
|
| 174 |
+
|
| 175 |
+
- **Base Model & Framework**: [Cactus Compute — Finetuning Whistle](https://cactuscompute.com/blog/finetuning-whistle)
|
| 176 |
+
- **NLP Preprocessing & Corpus**: [PyThaiNLP](https://github.com/PyThaiNLP/pythainlp) & [thai_commonvoice_dataset](https://github.com/wannaphong/thai_commonvoice_dataset)
|
| 177 |
+
- **Speech Datasets**:
|
| 178 |
+
- [Mozilla Common Voice](https://commonvoice.mozilla.org/)
|
| 179 |
+
- [Google FLEURS (`google/fleurs`)](https://huggingface.co/datasets/google/fleurs)
|
| 180 |
+
- [`n-order/Thai-dialect-corpus`](https://huggingface.co/datasets/n-order/Thai-dialect-corpus)
|
| 181 |
+
- [`huangjianbyte2024/thai_Elderly_Speech_dataset`](https://huggingface.co/datasets/huangjianbyte2024/thai_Elderly_Speech_dataset)
|
| 182 |
+
- [`typhoon-ai/thai-dialect-isan-dataset`](https://huggingface.co/datasets/typhoon-ai/thai-dialect-isan-dataset)
|