--- license: cc-by-4.0 language: - lt base_model: openai/whisper-large-v3 pipeline_tag: automatic-speech-recognition tags: - whisper - lithuanian - asr - speech-recognition - liepa library_name: transformers --- # Ąžuolas — lietuvių kalbos atpažinimo modelis Ąžuolas atpažįsta lietuvišką kalbą iš garso įrašų. Tai `whisper-large-v3` modelis, SFT apmokytas su visu LIEPA-3 garsynu — skaitoma kalba, laisva kalba ir fonetiniai įrašai. Iš viso apie **9,8 tūkst. valandų** garso: 5 230 val. skaitomos kalbos, 4 359 val. laisvos kalbos ir 217 val. fonetinių įrašų. Pats LIEPA-3 garsynas yra maždaug 11,4 tūkst. valandų — likusi dalis nenaudota, nes reikia apdoroti tą dataset'o dalį, o tikslas buvo išvengti papildomo apdorojimo. Modelis pavadintas simbolinio Ąžuolo vardu [Ąžuolyno bibliotekos](https://azuolynobiblioteka.lt/) garbei. Jis didžiausias ir lėčiausias iš čia lyginamų lietuviškų modelių, tačiau tiksliausias. > **Vienas svarbus dalykas prieš pradedant.** Ąžuolas prigalvoja tekstą, kai > garse nieko nėra — per tylą ar foninį šnypštimą jis „išgirsta" sklandžius > lietuviškus sakinius. Tai reali problema, ne smulkmena. > Kaip su tuo dirbti — skyriuje [Ką reikia žinoti](#ką-reikia-žinoti). ## Rezultatai Visi trys modeliai išbandyti su ta pačia programa — ta, kurią parašė `paprika-whisper-lt-v3` autorius ([`kristijonasatpro/paprika`](https://github.com/kristijonasatpro/paprika)). Naudotos jo dekodavimo nuostatos ir jo teksto normalizavimas. Taip visi trys lyginami vienodai, o ne kiekvienas savo taisyklėmis. **FLEURS LT — 985 įrašai, 2,96 val.** | modelis | dydis | WER % | WER % be skaitmenų | greitis | |---|---|---|---|---| | **Ąžuolas** (whisper-large-v3) | 1,55 mlrd. | **9,05** | **4,68** | 26× realaus laiko | | paprika-whisper-lt-v3 (large-v3-turbo) | 0,81 mlrd. | 12,06 | 7,78 | 83× realaus laiko | | kmynas-parakeet-lt-v3 (Parakeet-TDT) | 0,6 mlrd. | 17,73 | 13,57 | **819× realaus laiko** | **Common Voice 26.0 LT — 5 877 įrašai, 8,50 val.** | modelis | WER % | greitis | |---|---|---| | **Ąžuolas** | **4,60** | 27× realaus laiko | | paprika-whisper-lt-v3 | 8,60 | 76× realaus laiko | | kmynas-parakeet-lt-v3 | 22,86 | **956× realaus laiko** | WER — žodžių klaidų dalis. Mažiau yra geriau. Abu testai visiems trims modeliams yra **iš kitos srities**: nei FLEURS, nei Common Voice nėra LIEPA-3 dalis, ir jų įrašymo sąlygos kitokios. Tai verta pabrėžti, nes paprika autorius sąmoningai neskelbia tokio skaičiaus — jo paruoštas FLEURS rinkinys pasirodė esąs 44 iš 44 skaitmeninės tylos įrašų, ir visi iš jo gauti skaičiai buvo atšaukti. Tad ši lentelė yra pirmas šių trijų modelių palyginimas su nepažįstamu garsu ir su vienoda tvarka. Greičiai išmatuoti viena RTX PRO 6000, fp16. Su kita įranga skaičiai bus kitokie. Ąžuolas maždaug 3× lėtesnis už paprika, nes turi 32 dekoderio sluoksnius, o turbo — tik 4. Kmynas yra visai kitos sandaros ir maždaug 30× spartesnis už Ąžuolą. ### Kodėl yra stulpelis „be skaitmenų" FLEURS atsakymuose skaičiai rašomi skaitmenimis (`25`), o šie modeliai rašo žodžiais (`dvidešimt penki`). Formaliai tai klaida, nors atpažinta teisingai. Paprika autorius įvertino, kad tai kainuoja „maždaug 4 WER". Pasitvirtino beveik tiksliai, ir visiems trims vienodai: | | visas | be skaitmenų | skaitmenų kaina | |---|---|---|---| | Ąžuolas | 9,05 | 4,68 | 4,37 | | paprika-whisper-lt-v3 | 12,06 | 7,78 | 4,28 | | kmynas-parakeet-lt-v3 | 17,73 | 13,57 | 4,16 | Kaina beveik vienoda visiems, tad tai atsakymų teksto, o ne modelių savybė. Stulpelis „be skaitmenų" geriau parodo tikrą atpažinimo kokybę. ### Tikras įrašas — 57 minutės iš renginio Testų įrašai trumpi, tvarkingi ir skaitomi. O čia tie patys trys modeliai su tikru įrašu: Vido Mačiulio knygos „Kitas gyvenimas" pristatymu [Ąžuolyno bibliotekoje](https://azuolynobiblioteka.lt/). 57,1 minutės nerepetuotos kalbos su publika, persidengiančiais balsais ir plojimais ([youtube.com/watch?v=BrfheG5EJR4](https://www.youtube.com/watch?v=BrfheG5EJR4)). Šįkart naudota visa paprika ilgų įrašų programa, ne tik nuostatos: tylos filtras, karpymas pauzėse, tada atpažinimas kiekvienoje dalyje atskirai. | modelis | žodžių | teksto įvairumas | greitis | dažniausiai pasikartojusi frazė | |---|---|---|---|---| | **Ąžuolas** | 6 646 | 0,998 | 8,9× realaus laiko | `„ir taip toliau aš"` ×4 | | paprika-whisper-lt-v3 | 6 636 | 0,998 | 35,0× realaus laiko | `„ir taip toliau aš"` ×4 | | kmynas-parakeet-lt-v3 | 6 554 | 0,998 | **1 039× realaus laiko** | `„ir taip toliau aš"` ×4 | Kiek modelių tekstai sutampa tarpusavyje: | | | |---|---| | Ąžuolas ↔ paprika | 93,1 % | | Ąžuolas ↔ kmynas | 90,3 % | | paprika ↔ kmynas | 88,6 % | - **Visi trys sutampa 92 žodžių ribose — 1,4 %.** Nė vienas nepraleidžia ir neprigalvoja didelių gabalų. Būtent tai svarbiausia ilguose įrašuose, ir trumpų įrašų WER to nerodo. - **Teksto įvairumas 0,998 visiems** — niekas neįstrigo kartodamas tą patį. Dažniausia frazė visuose trijuose tekstuose ta pati — „ir taip toliau aš", keturis kartus. Tai tikra kalbos frazė, ne modelio klaida. Taip veikia karpymas pauzėse; tie patys modeliai, karpant fiksuotu žingsniu, jau įstringa. Šios lentelės greičiai išmatuoti fp32, o ne fp16, nes paprika programa garso požymius verčia į fp32. Todėl Ąžuolas čia rodo 8,9×, o FLEURS lentelėje su fp16 — 26×. Šių dviejų lentelių greičių tarpusavyje nelyginkite. ### Patikrinta ir kita programa Tie patys modeliai išmatuoti dar ir nepriklausoma programa (šio projekto `eval_compare.py`, kitos nuostatos, kitas WER skaičiavimas): | FLEURS / CV 26.0 | paprika programa | nepriklausoma | skirtumas | |---|---|---|---| | Ąžuolas | 9,05 / 4,60 | 9,12 / 4,97 | 0,07 / 0,37 | | paprika-whisper-lt-v3 | 12,06 / 8,60 | 11,84 / 8,42 | 0,22 / 0,18 | Dvi nepriklausomos programos sutampa 0,4 punkto ribose visur ir į abi puses. Todėl eiliškumu galima tikėti. Nė viena programa nepataikauja savo autoriaus modeliui: paprika programa pačią paprika įvertina net kiek griežčiau. Dar viena patikra: kmynas modelis per šią programą gavo FLEURS **17,73** ir be skaitmenų **13,57**, o jo pačio kortelėje paskelbta **17,15** ir **13,11**. Skirtumas 0,6 ir 0,5 punkto. Tą skaičių gavo pats programos autorius su tuo pačiu modeliu, tad sutapimas rodo, kad programa perkelta teisingai. ## Kaip naudoti Modelis rašo **mažosiomis raidėmis ir be skyrybos ženklų** — taip užrašyti LIEPA-3 atsakymai. Jis rašo ir taip, kaip pasakyta, o ne kaip taisyklinga: `turim`, ne `turime`. Jei reikia skyrybos, didžiųjų raidžių ar kalbėtojų žymų — tai atskiri modeliai. Paprika saugykloje esantis `transcribe_file.py` visa tai padaro aplink Whisper modelį ir veikia su šiuo per `--model`. ### Formatai Modelis paskelbtas trimis pavidalais. Svoriai visur tie patys — tas pats `best_model.pt`, iš kurio gauti visi šios kortelės skaičiai — ir visur fp16. | pavidalas | kur | kaip įkelti | |---|---|---| | `transformers` | [`akisviete/azuolas-whisper-lt`](https://huggingface.co/akisviete/azuolas-whisper-lt) | `WhisperForConditionalGeneration.from_pretrained(...)` | | `faster-whisper` | [`akisviete/azuolas-whisper-lt-ct2`](https://huggingface.co/akisviete/azuolas-whisper-lt-ct2) | `WhisperModel(...)` | | `openai-whisper` | `original/best_model.pt` pagrindinėje saugykloje | `whisper.load_model(kelias)` | Abiejuose paruoštuose pavidaluose įrašytos **10 `alignment_heads` porų** — oficialus OpenAI rinkinys `large-v3` modeliui (iš 640 dekoderio kryžminio dėmesio galvų). Be jų `faster-whisper` pats pasirenka 320 porų iš paskutinės dekoderio pusės; išmatuota, kad tada nulinės trukmės žodžių padvigubėja (42 → 70). Svoriai fp16. Jei dirbate procesoriumi, įkeldami nurodykite `dtype=torch.float32` — fp16 procesoriuje lėtas. Tikslumas nuo to nekinta: skaičiai tie patys, tik kitaip užrašyti. ### Trumpi įrašai ```python import torch, soundfile as sf from transformers import WhisperForConditionalGeneration, WhisperProcessor MODEL = "akisviete/azuolas-whisper-lt" proc = WhisperProcessor.from_pretrained(MODEL, language="lithuanian", task="transcribe") mdl = WhisperForConditionalGeneration.from_pretrained(MODEL, dtype=torch.float16).to("cuda").eval() wav, sr = sf.read("irasas.wav", dtype="float32") # 16 kHz, vienas kanalas f = proc(wav, sampling_rate=16000, return_tensors="pt") ids = mdl.generate(f.input_features.to("cuda", torch.float16), language="lithuanian", task="transcribe", condition_on_prev_tokens=False, temperature=(0.0, 0.2, 0.4, 0.6, 0.8, 1.0), logprob_threshold=-1.0, compression_ratio_threshold=1.35, no_speech_threshold=0.6) print(proc.decode(ids[0], skip_special_tokens=True)) ``` Su `faster-whisper` — maždaug 4× greičiau ir mažiau atminties. `vad_filter=True` kartu išsprendžia tylos problemą, aprašytą žemiau: ```python from faster_whisper import WhisperModel m = WhisperModel("akisviete/azuolas-whisper-lt-ct2", device="cuda", compute_type="float16") segs, _ = m.transcribe("irasas.wav", language="lt", beam_size=5, vad_filter=True, condition_on_previous_text=False) for s in segs: print(f"[{s.start:6.2f} -> {s.end:6.2f}] {s.text}") ``` ### Ilgi įrašai — karpykite patys Supjaustykite garsą į dalis **iki 22 sekundžių**, kirpdami tylose *tarp* kalbos, ir atpažinkite kiekvieną dalį atskirai. Kirpimo vietas imkite iš **Silero VAD** — raskite kalbos sritis ir kirpkite tarpų tarp jų viduriuose. Tai svarbu ne mažiau už patį ilgį: išmatuota, kad toks kirpimas duoda apie **4 punktais** geresnį rezultatą nei paieška pagal garso lygį, kuri gali pataikyti į tylesnę vietą žodžio viduryje. 22 sekundės nėra spėjimas. Su 57 minučių įrašu išbandytos visos ribos ir palygintos su atskaitos tekstu: | dalies ilgis | skirtumas nuo atskaitos | |---|---| | 16 s | 13,86 % | | **22 s** | **13,38 %** | | 28 s | 14,07 % | | 40 s | 15,10 % | | 60 s | 24,62 % | Trumpesnės dalys verčia kirpti kalbos viduryje (prie 8 s taip nutinka 71 % kirpimų, prie 28 s — 56 %), ir kiekvienas nevykęs kirpimas prigalvoja žodžių abiejuose galuose. Ilgesnės dalys pradeda mesti tekstą: prie 60 s praleidimų penkis kartus daugiau. Tai patikrinta su dviem skirtingai apmokytais modeliais, ir geriausia riba nepasikeitė. Du įspėjimai, abu patirti savo kailiu: - **Nenaudokite `chunk_length_s`** iš `transformers`. Jis karpo fiksuotu žingsniu ir lipdo tekstą pagal persidengimus, o ko nesulipdo — išmeta. Paprika autorius išmatavo, kad taip tyliai prarandama 30 ir 52 žodžiai sandūrose. Tekstas atrodo tvarkingas, niekas nerodo, kad kažko nėra. - **Per ilgos dalys sugenda tyliai.** Prie 60 s tekste pritrūksta apie 13 % žodžių, o visi „sveikatos" rodikliai atrodo geri. Tik žodžių skaičius, palygintas su atskaita, tai parodo. ## Ką reikia žinoti **Modelis prigalvoja tekstą per tylą, ir apsauga neveikia.** Su 60 sekundžių įrašais: visiška tyla — 78 simboliai, tylus šnypštimas — **947 simboliai** sklandaus lietuviško teksto, kambario fonas — 247. Pati Whisper apsauga nesuveikia: išmatuota tikimybė „nėra kalbos" per šnypštimą 0,0009, o originaliam large-v3 — 0,8154. Du būdai, abu veikia: - **Išfiltruokite tylą prieš atpažinimą** — paprastas garso lygio patikrinimas, kaip daro paprika programa. Užima mikrosekundes ir problemą pašalina. - **Naudokite `vad_filter=True`** su `faster-whisper`. Išmatuota: 0 simbolių visais trimis atvejais. Kodėl taip atsitiko: dėl mokymo duomenų, ne dėl sandaros. Garsynas buvo paruoštas suklijuojant sakinių įrašus į 30 sekundžių langus be tarpų, tad modelis niekada nematė lango be kalbos ir neišmoko nutilti. **Kiti apribojimai:** - Rašo mažosiomis raidėmis, be skyrybos. - 16 kHz, vienas kanalas. Vyrauja skaitoma ir laisva kalba. **Tarmių dalies nėra** — paprika v3 ją turi. - Maždaug 3× lėtesnis už paprika. Išmatuota: 404 s prieš 128 s su 985 FLEURS įrašais. - Ankstesnėse šios linijos versijose buvo sugedęs ilgų įrašų laiko žymėjimas. Priežastis — langų klijavimas, kuris išjungdavo laiko žymų mokymą. Pataisyta nustatant `no_timestamp_rate: 0.5`; klaidų skaičius nukrito nuo 23 iki 0. Šioje versijoje laiko žymos patikimos, bet tekstui vis tiek geriau karpyti patiems. - Mokymo metu matytas 2,99 % WER yra iš tos pačios srities kaip ir mokymo duomenys. Tai ne tikras vertinimas — remkitės FLEURS ir CV skaičiais. ## Kaip apmokyta Metodas ne savas. Jis paimtas iš i4Ds/FHNW darbo *Fine-tuning Whisper on Low-Resource Languages for Real-World Applications* ([arXiv:2412.15726](https://arxiv.org/html/2412.15726v1)) — ten aprašytas ir duomenų klijavimas, ir mokymo tvarka. Skirtumai nuo straipsnio surašyti [šaltiniuose](#šaltiniai). | | | |---|---| | pagrindas | `openai/whisper-large-v3`, apmokyti visi svoriai | | duomenys | visas LIEPA-3 — 5 230 val. skaitoma + 4 359 val. laisva + 217 val. fonetinė | | paruošimas | i4Ds Whisper-prep, klijavimas į 30 s langus, `overlap_chance 0,4` | | optimizatorius | **AdamW + Muon**, `muon_ndim_threshold 2` | | mokymo greitis | 2e-5, kosinusinis, 64 įšilimo žingsniai | | Muon | momentas 0,95, svorių slopinimas 0,01 | | AdamW (pagalbinis) | betos [0,9 · 0,98], eps 1e-6, svorių slopinimas 0,01 | | paketas | 32 × 8 = **256** | | epochos | 2 | | reguliarizacija | žymų lyginimas 0,1, atsitiktinis gylis 0,1, `max_grad_norm` 1,0 | | garso iškraipymai | SpecAugment ir deep SpecAugment | | skaičių tikslumas | fp16, gradientų perskaičiavimas koduotuve ir dekoduotuve | | laiko žymos | `no_timestamp_rate 0,5`, `prompt_use_rate 0,5` | | failas | 3,09 GB fp16 | Muon taikomas tik dvimačiams svoriams transformerio blokuose. Įterptiniai vektoriai, konvoliucinis įėjimas, normalizavimo sluoksniai ir visi poslinkiai tvarkomi pagalbiniu AdamW. ## Šaltiniai **Lyginti modeliai** - `paprika-whisper-lt-v3` — https://huggingface.co/kristijonas/paprika-whisper-lt-v3 - `kmynas-parakeet-lt-v3` — https://huggingface.co/kristijonas/kmynas-parakeet-lt-v3 - `openai/whisper-large-v3` — https://huggingface.co/openai/whisper-large-v3 - `nvidia/parakeet-tdt-0.6b-v3` (kmynas pagrindas) — https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3 **Vertinimo programa** (naudota visoms lentelėms) - `kristijonasatpro/paprika` — https://github.com/kristijonasatpro/paprika — Apache-2.0. `chunk_longform.py` — dekodavimo nuostatos ir karpymas pauzėse; `eval_kmynas.py::norm` — teksto normalizavimas. - `kristijonasatpro/kmynas` — https://github.com/kristijonasatpro/kmynas — ilgų įrašų programa ir skolintų žodžių žodynas. **Pagrindinis mokymo šaltinis** - Vincenzo Timmel, Claudio Paonessa, Reza Kakooee, Manfred Vogel, Daniel Perruchoud. *Fine-tuning Whisper on Low-Resource Languages for Real-World Applications*. arXiv:2412.15726, 2024 m. gruodžio 20 d., CC BY 4.0 — https://arxiv.org/html/2412.15726v1 FHNW (Šiaurės vakarų Šveicarijos taikomųjų mokslų universitetas) ir Noxenum. Būtent šis darbas aprašo metodą, kuriuo Ąžuolas paruoštas ir apmokytas, o `Whisper-prep` bei `Whisper-finetune` yra jo kodas. Iš jo perimta: sakinių lygio įrašų klijavimas į 30 s langus su VAD pataisytomis laiko žymomis, kalbėtojo išlaikymas, persidengimai, `no_timestamp_rate` ir `prompt_use_rate` po 0,5, paketas 256, gradientų perskaičiavimas koduotuve ir dekoduotuve, atsitiktinis gylis ir SpecAugment. Kur nukrypta: jie pradėjo nuo `large-v2` (šveicarų vokiečių kalbai jis pasirodė geresnis), Ąžuolas — nuo `large-v3`; jų mokymo greitis 2e-4 ir tiesinis mažėjimas, Ąžuolo 2e-5 ir kosinusinis; jie naudojo 8 bitų AdamW, Ąžuolas — AdamW + Muon. Vertas dėmesio ir straipsnio 4.4 skyrius: autoriai patys pastebi, kad po tokio permokymo tikrų ilgų įrašų skaidymas ir atpažinimas pablogėja. Tas pats matyti ir čia — todėl kortelėje ir rašoma karpyti patiems. **Mokymo kodas** - `i4Ds/Whisper-finetune` — https://github.com/i4Ds/Whisper-finetune - `i4Ds/Whisper-prep` — https://github.com/i4Ds/Whisper-prep - `jumon/whisper-finetuning` — https://github.com/jumon/whisper-finetuning — pirminis projektas, kurio duomenų dalį Whisper-prep atkartoja. Naudotos šios pataisos, visos pasiūlytos ir pirminiams projektams. `no_timestamp_rate` pataisa yra ta, dėl kurios šioje versijoje laiko žymos naudotinos: | pataisa | projektas | ką pataiso | |---|---|---| | [whisper-prep#6](https://github.com/i4Ds/whisper-prep/pull/6) | whisper-prep | palieka visą 30 s langą ir įrašo persidengiančio segmento pradžios laiką | | [whisper-prep#7](https://github.com/i4Ds/whisper-prep/pull/7) | whisper-prep | VTT `MM:SS.mmm` skaitymas, prarasta paskutinė eilutė, pasenęs buferis (perkelta iš [jumon#33](https://github.com/jumon/whisper-finetuning/pull/33)) | | [whisper-finetune#11](https://github.com/i4Ds/whisper-finetune/pull/11) | whisper-finetune | įspėja, kad `no_timestamp_training` tyliai išjungia `no_timestamp_rate` | | [whisper-finetune#12](https://github.com/i4Ds/whisper-finetune/pull/12) | whisper-finetune | priima vieną likusią pradžios žymą `_get_partial_segment_start` | | [whisper-finetune#13](https://github.com/i4Ds/whisper-finetune/pull/13) | whisper-finetune | praleidžia nieko nekeičiantį kalbos `map` (~65 min. garso perrašymo per kiekvieną mokymą) | **Duomenys** - **LIEPA-3 garsynas** — Vilniaus universitetas / raštija.lt, CC BY 4.0 — *dėkojame*. Svetainė: https://www.xn--ratija-ckb.lt (`www.raštija.lt`). Naudota Hugging Face kopija: https://huggingface.co/datasets/meldynamics/liepa-3 - FLEURS — https://huggingface.co/datasets/google/fleurs - Common Voice 26.0 (lietuvių), leidimas `cv-corpus-26.0-2026-06-12` — https://commonvoice.mozilla.org/en/datasets — čia naudoti 5 877 įrašai (8,50 val.) - Tikras įrašas: Vido Mačiulio knygos „Kitas gyvenimas" pristatymas, Ąžuolyno biblioteka (https://azuolynobiblioteka.lt/) — https://www.youtube.com/watch?v=BrfheG5EJR4 **Metodai** - Muon — https://kellerjordan.github.io/posts/muon/ ir *Muon is Scalable for LLM Training*, arXiv:2502.16982 - Silero VAD — https://github.com/snakers4/silero-vad - *Distil-Whisper*, arXiv:2311.00430 — iš kur 22 sekundės. Jų 23 lentelė rodo, kad geriausias gabalo ilgis seka mokymo lango ilgį: Whisper optimalus ties 30 s, Distil-Whisper ties 15 s, nes pastarojo mokymo imties vidurkis 7,1 s. Ten pat ir haliucinacijų matavimas per pasikartojančias 5-gramas. Patys autoriai naudoja persidengiantį `chunk_length_s` karpymą; išbandyta su šiuo modeliu — Silero VAD karpymas už jį geresnis. - *Semantic Segmentation with Bidirectional Language Models Improves Long-form ASR*, arXiv:2305.18419 — **išbandyta ir atmesta.** Siūloma kirpti ties sakinių ribomis, o ne ties pauzėmis. Patikrinta su šiuo modeliu ir 57 minučių įrašu: pralaimi Silero VAD kirpimui. Pasirodo, kad svarbu pataikyti į tikrą tylą, o ne į sakinio ribą — Silero kerpa per sakinio vidurį dažniau, ir vis tiek yra geresnis. ## Licencija Svoriai — CC BY 4.0, perimant LIEPA-3 garsyno nuorodų grandinę (VU / raštija.lt). Pagrindinis modelis `openai/whisper-large-v3` — Apache-2.0. ## Kontaktai Dėl klausimų ir pasiūlymų rašykite **akisviete@gmail.com**.