sudi.lt

PRAKTINIS AI AUTOMATIZACIJOS GIDAS

Kaip pasidaryti lietuvišką sintetinį balsą iš savo įrašų

Visas kelias nuo mikrofono iki sakinio jūsų balsu: sakinių parinkimas, įrašymas, patikra, kirčiavimas, modelio mokymas už porą eurų ir generavimas savo kompiuteryje.

Šiame gide – visas kelias nuo mikrofono iki sakinio, kurį perskaito jūsų balsas: kokius sakinius įrašyti, kaip juos patikrinti ir apkarpyti, kaip sukirčiuoti tekstą, kaip apmokyti modelį už porą eurų ir kaip generuoti garsą savo kompiuteryje. Viskas, kas čia aprašyta, buvo padaryta per vieną savaitgalį su „Mac“ kompiuteriu ir maždaug 12 eurų nuomotam GPU. Rezultatą galite pasiklausyti: sudi.lt/balsas.

Ko reikia

  • Kompiuteris su „Apple Silicon“ (M1 ar naujesnis) generavimui. Modelis veikia ir su NVIDIA vaizdo plokšte; be jokio GPU – veikia, bet lėtai.
  • Geras mikrofonas ir tyli patalpa. Mes įrašinėjome su „Scarlett Solo“ ir kondensatoriniu mikrofonu; USB mikrofonas taip pat tinka. Svarbiausia – tas pats mikrofonas ir ta pati patalpa visiems įrašams.
  • Apie valanda jūsų skaitymo – 600–800 sakinių. Tai 2–3 valandos prie mikrofono su pertraukomis.
  • Vienas GPU serveris valandai modelio mokymui: „RunPod“ A100 kainuoja apie 2–3 eurus už valandą, o mokymas trunka 40 minučių. Jei turite savo NVIDIA plokštę su 24 GB atminties, ir jos užteks.
  • Python 3.12 ir keli paketai (sąrašas repozitorijoje).

Modelis, kurį naudojame, – OmniVoice (k2-fsa, 0,6 mlrd. parametrų). Jį pasirinkome, nes jis iš karto supranta lietuvių kalbą, turi pilną dotreniravimo pipeline'ą ir veikia „Mac“ kompiuteryje. Svarbu žinoti: kodas – Apache 2.0, bet svoriai – CC BY-NC. Tai reiškia, kad savo balsą galite naudoti asmeniniams ir nekomerciniams tikslams; parduoti balsą ar paslaugą su šiuo modeliu negalima.

Visas kelias vienoje schemoje:

Viršuje – kaip pasidaromas balsas: sakiniai iš sukirčiuoto tekstyno, įrašymas po vieną sakinį, patikra ir apkarpymas, mokymas serveryje, modelis jūsų kompiuteryje. Apačioje – kaip iš teksto gaunamas garsas: normalizatorius, kirčiuoklis, modelis.
Viršuje – kaip pasidaromas balsas: sakiniai iš sukirčiuoto tekstyno, įrašymas po vieną sakinį, patikra ir apkarpymas, mokymas serveryje, modelis jūsų kompiuteryje. Apačioje – kaip iš teksto gaunamas garsas: normalizatorius, kirčiuoklis, modelis.

Balso gaminimas: sakiniai iš sukirčiuoto tekstyno → įrašymas po vieną sakinį → patikra ir apkarpymas → mokymas serveryje (40 min, 2–3 Eur) → modelis jūsų kompiuteryje.

Kasdienis naudojimas: naujas tekstas → normalizatorius (skaičiai, datos, santrumpos) → kirčiuoklis banano-kirtis v2 → modelis → garsas jūsų balsu.

1. Kodėl lietuvių kalbai reikia kirčiuoklio

Lietuvių kalboje kirtis nerašomas, bet nuo jo priklauso tarimas ir prasmė: kasa (plaukai) ir kasa (kasa duobę), sūnus ir sūnus, mano ir mano. Modelis, kuris mato tik raides, kirtį spėja – ir dažnai spėja blogai. Todėl visą tekstą – ir mokymo sakinius, ir tai, ką generuosite vėliau – pirmiausia sukirčiuojame. Modelis mokosi skaityti sukirčiuotą tekstą, ir tada kãsa bei kasà jam yra du skirtingi žodžiai.

Kirčiuoklį banano-kirtis v2 galite išbandyti čia: bananas.sudi.lt. Jo tikslumas – 99,3 % žodžių sutampa su VDU sukirčiuotu tekstu; vardus mes papildomai tikriname VLKK sąvade (to žodyno repozitorijoje nėra – tai ne mūsų duomenys skelbti, o be jo vardus kirčiuoja pats modelis). Prieš kirčiuoklį dirba normalizatorius: „2334 Eur“, „5,5 %“, „2024 m.“, „14:30“ ar „G. Nausėda“ virsta žodžiais su teisingais linksniais, nes skaitmenų modelis niekada nematė ir nematys.

Idėja kirčiuoti tekstą prieš paduodant jį balso modeliui – Mindaugo Žvirblio: 32 raidžių balsui per mažai, o sukirčiavus gaunasi apie 80 skirtingų garsų.

2. Kokius sakinius įrašyti

Modelis išmoksta tai, ką girdi. Jei įrašysite paskaitą, gausite balsą, kuris nemoka užbaigti sakinio – mes taip ir pradėjome, ir tai buvo pirmoji pamoka: spontaniškoje kalboje beveik nėra švarių sakinių pabaigų, o modelis, matydamas tašką ir girdėdamas „tęsiančią“ intonaciją, taško reikšmę išmoko neteisingai. Skaitykite paruoštus sakinius.

Iš kur juos imti. Mes ėmėme iš VDU sukirčiuoto tekstyno (MATAS ir ALKSNIS, CC BY 4.0). Privalumas dvigubas: kirčiai jau padėti žmonių, o ne spėti, ir galima parinkti sakinius pagal poreikį. Mūsų 600 sakinių rinkinys atrodė taip:

  • Ilgis: 70 % vidutinių (6–15 žodžių), 15 % trumpų (2–5 žodžiai – „Gerai.“, „Kaip sekasi?“), 15 % ilgų (16–25 žodžiai). Trumpi sakiniai su švariomis pabaigomis modeliui trūksta labiausiai.
  • Skyryba: 15 % klausimų, 7 % šauktukų, kabutės, dvitaškiai, brūkšniai. Klausimo intonaciją modelis išmoksta tik iš klausimų.
  • Žanrai: grožinė literatūra (dialogai), spauda, Seimo stenogramos (šnekamasis registras).
  • Be skaitmenų ir santrumpų – arba su jais, jei sutariate, kaip juos skaitysite (žr. toliau).
  • Antra dalis (300 sakinių): santrumpos (ES, JAV, UAB, PVM), inicialai („G. Nausėda“), užsienietiški pavadinimai („Gemini“, „Excel“), angliški intarpai. Modelis išmoksta jūsų būdą juos tarti.

Svarbiausia taisyklė: tą patį užrašą visada skaitykite vienodai. Jei „JAV“ skaitote „jav“, o „KTU“ – „ka tė u“, taip ir darykite visuose sakiniuose. Modelis išmoksta atitikmenį tarp užrašo ir garso; jei jis kaitaliojasi, modelis nežinos, ką daryti.

3. Įrašymas: po vieną sakinį

Įrašinėkite po vieną sakinį į atskirą failą. Tada nereikia nei pjaustyti ilgo įrašo, nei spėlioti, kur baigiasi sakinys, o pakartojimas – tiesiog perrašytas failas.

Repozitorijoje yra record/rec_server.py – nedidelis įrankis, kuris veikia naršyklėje (tik standartinė Python biblioteka, nieko diegti nereikia): rodo po vieną sukirčiuotą sakinį, klavišas „Space“ pradeda ir sustabdo įrašymą, rodyklė – kitas sakinys. Garsas rašomas nesuspaustas (WAV, 48 kHz), o naršyklės apdorojimas – aido slopinimas, triukšmo mažinimas, automatinis garsumas – išjungtas. Tai svarbu: „pagerintas“ garsas modeliui yra iškraipytas garsas.

Kaip skaityti:

  • Sakinys – sekundė tylos – kitas sakinys. Pirmiausia pauzė, tik tada įkvėpkite. Įkvėpimą iškart po paskutinio žodžio modelis išmoks kaip sakinio dalį.
  • Suklydote – tyla ir visas sakinys iš naujo. Nebandykite pataisyti vieno žodžio.
  • Skaitykite su ta intonacija, kurią norite girdėti iš balso. Klaustukas – klauskite, šauktukas – šaukite. Modelis kopijuoja tiksliai tai.
  • Vienas atstumas iki mikrofono, vienas garsumo lygis per visą sesiją. Mes įrašėme 600 sakinių per vieną vakarą ir 300 per kitą – tarp jų nieko nekeitėme.
  • Jei sakinys tekste keistas ar laužytas – praleiskite.

Mūsų 600 sakinių davė 56 minutes garso, 900 sakinių – apie 1 val. 20 min. Trečdalį sakinių įrašėme po kelis kartus – tai normalu.

4. Patikrinimas ir apkarpymas

Prieš mokymą kiekvieną įrašą patikriname dviem klausimais: ar perskaityta tai, kas parašyta, ir kur tiksliai prasideda bei baigiasi kalba.

Ar perskaityta tai, kas parašyta. Kiekvieną įrašą transkribuojame ir palyginame su scenarijumi. Tai daro prepare/asr_check.py, pagal nutylėjimą su Kmynas v4 – naujausiu mūsų lietuvišku atpažinimo modeliu, kuris veikia jūsų kompiuteryje ir nieko nekainuoja (tinka ir Paprika V3, jei norite whisper šeimos modelio). (Mes patys pirmą kartą naudojome „MAI-Transcribe-2“ už 10 centų už valandą; rezultatas tas pats.) Sakinius, kuriuose skiriasi žodžiai, o ne tik rašyba – „kurgi“ ir „kur gi“ yra tas pats – išmetame. Iš 600 mūsų sakinių iškrito 13.

Kur prasideda ir baigiasi kalba. Sakinį įraše randame pagal garso energiją, ne pagal atpažinimo modelio žodžių laikus: vieno sakinio failui whisper tipo modelis beveik visada grąžina „nuo 0 iki 6 sekundžių“, o iškirpti reikia būtent įkvėpimo, kurio jis nežymi. Todėl: 10 ms kadrai, garsesni nei −35 dB nuo įrašo piko, yra kalba; kalbos atkarpos, tarp kurių yra ilgesnė nei 0,3 s tyla, yra atskiri įvykiai; sakinys – nuo pirmos iki paskutinės atkarpos, kurioje yra bent ketvirtis ilgiausios atkarpos kalbos. Įkvėpimas, lūpų čepsėjimas ar netikras startas yra trumpi ir iškrenta, o frazė po kablelio – ne. Būtent todėl įrašymo taisyklė yra „sakinys, tyla, ir tik tada įkvėpkite“: ta tyla ir atskiria įkvėpimą.

Patikrinome tai prieš pirmąją versiją, kuri kirpo pagal komercinio atpažinimo modelio žodžių laikus: tuose pačiuose 60 įrašų klipų ribos skiriasi vidutiniškai 58 ms, o kur skiriasi – šis būdas palieka šiek tiek daugiau tylos, o ne nurėžia paskutinį priebalsį.

Tada prie sakinio pridedame ne daugiau kaip 80 ms priekyje ir 120 ms gale, visus klipus suvienodiname pagal garsumą (−20 dBFS) ir konvertuojame į 24 kHz mono – tokio garso tikisi modelis. Visa tai daro prepare/build_clips.py. Rezultatas – katalogas su WAV failais ir du JSONL failai (mokymo ir tikrinimo), kur kiekviena eilutė yra {"id", "audio_path", "text", "language_id": "lt"}, o text – sukirčiuotas sakinys. Įrašai, kuriuose perskaitėte ne tai, kas parašyta, atsiduria review.json – juos perklausote patys.

5. Mokymas

OmniVoice turi savo dotreniravimo pipeline'ą: pirmiausia garsas paverčiamas tokenais (extract_audio_tokens), tada mokomas modelis (omnivoice.cli.train). Mūsų konfigūracija, kuri veikė iš pirmo karto:

{
  "init_from_checkpoint": "k2-fsa/OmniVoice",
  "learning_rate": 1e-5,
  "batch_tokens": 8192,
  "prompt_ratio_range": [0.0, 0.3],
  "steps": 4000,
  "save_steps": 500,
  "mixed_precision": "bf16"
}

Kelios pastabos iš patirties:

  • Nuo ko pradėti. Galima mokyti nuo bazinio OmniVoice arba nuo modelio, kuris jau matė sukirčiuotą lietuvių kalbą. Mes turėjome tokį – apmokytą iš LIEPA garsyno (p. Reginos balsas, 3 valandos, CC BY). Pradėjus nuo jo, kirčių skaitymą modelis moka nuo pirmo žingsnio; pradėjus nuo bazinio, jis kirčius mokosi tik iš jūsų valandos. Abu variantai veikė, bet pirmasis buvo stabilesnis.
  • Kiek žingsnių. Su 800 klipų 2 500–4 000 žingsnių reiškia 150–250 praėjimų per duomenis. Skamba daug, bet būtent tiek reikėjo; išsaugokite tarpinius taškus kas 500 žingsnių ir rinkitės klausydami. Mums geriausiai skambėjo ne paskutinis, o vidurinis.
  • Du balsai viename modelyje. Kiekvienam klipui galima pridėti žymę ("instruct": "Speaker: Vardas") ir mokyti kelis balsus kartu; generuojant balsą pasirenkate žyme. Veikia, bet su viena išlyga: jei vieno kalbėtojo duomenų yra daugiau, jo balsas ilgesniuose tekstuose „prasimuša“ į kito. Dviem atskirais modeliais saugiau.
  • Kaina ir laikas. A100 80 GB „RunPod“ serveryje: įkėlimas 5 min, tokenai 2 min, 4 000 žingsnių – 30 min. Su visais nepavykusiais bandymais sutilpome į 12 eurų. Serverį išjunkite iš karto – ir patikrinkite, kad tikrai išsijungė.

Repozitorijoje yra train/lifecycle.sh, kuris sukuria serverį, įkelia duomenis, paleidžia mokymą, parsisiunčia visus išsaugotus taškus ir išjungia serverį – net jei kas nors nepavyksta, net jei nutraukiate „Ctrl-C“. Išjungimą jis dar ir patikrina per API, o ne patiki: vieną kartą mums „terminate“ grąžino sėkmę, o serveris dirbo ir buvo apmokestintas dešimt valandų.

Šitaip apmokytas p. Reginos balsas paskelbtas: kristijonas/krapas-Regina – 3,05 valandos LIEPA garsyno, 5 000 žingsnių, vienas A100 apie 35 minutes.

6. Generavimas savo kompiuteryje

Jei norite tik pasiklausyti, kaip visa tai veikia, nieko neįrašinėję – paleiskite paskelbtą balsą:

pip install "omnivoice==0.2.1" torch transformers num2words soundfile numpy
echo 'Prie stoties mus pasitiko 3 vaikai ir vienas šuo.' > tekstas.txt
python3 generate/speak.py tekstas.txt out/

Pirmą kartą parsisiunčia balsą (3 GB) ir kirčiuoklį (2 GB). Modelis įkeliamas per OmniVoice.from_pretrained(kelias, device_map="mps"), o generavimas – vienas generate() kvietimas su sukirčiuotu tekstu. „Mac“ kompiuteryje minutė garso sugeneruojama per maždaug 1,3 minutės; su 16 difuzijos žingsnių vietoj 32 – per 45 sekundes, bet atsiranda daugiau artefaktų.

Du nustatymai, kurie lėmė daugiausiai:

  • guidance_scale = 4 (numatytoji reikšmė 2). Didesnė reikšmė verčia modelį tiksliau laikytis teksto – ir kirčių. Su 2 modelis dalį kirčių ignoruodavo.
  • Sakinys po sakinio. Ilgą tekstą generuojame po vieną sakinį ir sujungiame su 0,4 s pauze tarp sakinių ir 0,9 s tarp pastraipų. Vienas sakinys neturėtų būti ilgesnis nei ~15 sekundžių.

Prieš modelį – tekstas praeina normalizatorių ir kirčiuoklį. Kelios smulkmenos, kurias išmokome iš pastabų: skliaustus („(KAM)“) geriau išmesti, nes jie laužo intonaciją; inicialai su tašku („M. Sinkevičius“) modeliui reiškia sakinio pabaigą, todėl juos rašome be taško; brūkšnys intervaluose („7–12 metrų“) tampa ilga pauze, todėl jo atsisakome. Viską daro generate/speak.py: paduodate tekstinį failą, gaunate WAV – ir stressed.json, kuriame matote kiekvieną sakinį trimis pakopomis (kaip parašyta, po normalizatoriaus, su kirčiais). Būtent į jį žiūrite, kai kas nors skamba ne taip.

7. Ko tikėtis

Su valanda įrašų balsas atpažįstamas ir tempas jūsų; kartais pasitaiko artefaktų, o tas pats sakinys kiekvieną kartą sugeneruojamas šiek tiek kitaip. Kirčiavimo klaidos beveik visada yra kirčiuoklio, ne balso – todėl bananas.sudi.lt mums yra ir testavimo įrankis: pastebėjote blogą kirtį – tai vieno žodžio pataisa žodyne, o ne modelio permokymas.

Ko modelis nemoka: skaityti skaitmenų (tam normalizatorius), tarti retų svetimvardžių, kurių nematė, ir kalbėti emocijomis, kurių neįrašėte.

8. Etika ir licencijos

Savo balsas – jūsų sprendimas. Kito žmogaus balsui klonuoti reikia jo sutikimo, o LIEPA garsyno balsas (p. Regina) čia naudojamas su nuoroda į šaltinį (Vilniaus universitetas, projektas LIEPA, CC BY 4.0). Tokia pati technologija tinka ir sukčiams: telefoninis skambutis „mamos balsu“ jau nėra fantastika. Sutarkite su artimaisiais, kaip patikrinsite, kad skambina tikrai jie.

OmniVoice svoriai – CC BY-NC: asmeniniam naudojimui, ne prekybai. Kirčiuoklis banano-kirtis v2 ir šio gido skriptai – CC BY 4.0.

Dažni klausimai

Ar užtenka dešimties sekundžių įrašo, kaip žada „ElevenLabs“?

Zero-shot klonas iš trumpo įrašo veikia ir su OmniVoice – taip mes ir pradėjome. Tembras panašus, bet dalis sakinių skamba kaip kitas žmogus, o kirčiai priklauso nuo atsitiktinumo. Valanda perskaitytų sakinių duoda balsą, kuris yra jūsų visuose sakiniuose, ne kas antrame. Palyginimą girdite sudi.lt/balsas.

Ar reikia vaizdo plokštės?

Generavimui – ne: „Apple Silicon“ kompiuteryje minutė garso gaunasi per maždaug 1,3 minutės. Mokymui – taip, bet tik valandai: nuomotas A100 serveris kainuoja 2–3 eurus, ir mokymas trunka 40 minučių.

Ar galiu klonuoti kito žmogaus balsą?

Techniškai – taip, todėl ir klausimas ne technikos, o sutikimo. Savo balsui sutikimo nereikia, kito žmogaus balsui – reikia. LIEPA garsyno balsai išleisti CC BY licencija su nuoroda į šaltinį.

Ar galiu balsą naudoti komerciškai?

Ne su šiuo modeliu: OmniVoice svoriai – CC BY-NC. Komerciniam balsui reikėtų bazės su Apache ar MIT licencija (pavyzdžiui, Qwen3-TTS ar Chatterbox), kuri lietuvių kalbos dar nemoka ir kurią reikėtų išmokyti nuo pradžių iš LIEPA garsyno – tai kitas projektas.

Kodėl kirčiavimo klaidos, jei modelis apmokytas iš sukirčiuoto teksto?

Nes klaida beveik visada yra tekste, o ne balse: modelis perskaito tai, ką pažymėjo kirčiuoklis. Kirčiuoklio žodyną galima taisyti po vieną žodį be jokio permokymo – todėl tokias klaidas praneškite.

Failai

Viskas, ką čia aprašėme, yra atviroje repozitorijoje github.com/kristijonasatpro/krapas (CC BY 4.0):

script/build_script.pyparenka sakinius iš VDU sukirčiuoto tekstyno su kvotomis (trumpi sakiniai, klausimai ir šauktukai – dažnesni, nei jie pasitaiko tekste, nes balsui jų labiausiai trūksta)
script/script-a-567.*, script-b-289.*scenarijai, kuriuos skaitėme mes: 567 paprasti sakiniai ir 289 su santrumpomis, inicialais bei svetimvardžiais. Galite skaityti iš karto
record/rec_server.pyįrašymas naršyklėje po vieną sakinį, visas naršyklės „pagerinimas“ išjungtas
prepare/asr_check.pytranskribuoja kiekvieną įrašą vietoje su Paprika V3
prepare/build_clips.pyišmeta įrašus, kur perskaityta ne tai, iškerpa sakinį, konvertuoja į 24 kHz, suvienodina garsumą, parašo manifestus
train/lifecycle.sh, pod_train.sh, train_config.jsonmokymas nuomotame serveryje ir konfigūracija, kuri veikė iš pirmo karto
generate/speak.pytekstas → normalizatorius → kirčiuoklis → garsas, sakinys po sakinio
text/normalize_lt.pyskaičiai, datos, laikas, valiuta, matai, kelintiniai, telefono numeriai – žodžiais ir teisingu linksniu
text/kirtis_v2.pykirčiavimas: tekstyno žodynas, po jo modelis, pagal kontekstą

Modeliai: balsas krapas-Regina, kirčiuoklis banano-kirtis-v2, patikrai – Paprika V3. Kiekvieno šaltinio licencijos surašytos repozitorijos DATA.md faile; svarbiausia: OmniVoice svoriai yra CC BY-NC, tad ir bet kuris iš jų apmokytas balsas – nekomercinis.

Jei pasidarėte savo balsą arba užstrigote – parašykite. Kitas žingsnis mūsų darže – „krapas“: modelis, kuriam jūsų balsui užteks kelių minučių.