Jak wytrenować własny głos AI po polsku na Google Colab — od datasetu do gotowego modelu. Krok po kroku, łopatologicznie. Bez doktoratu z ML.
XTTS v2 (Cross-lingual Text-to-Speech v2) to otwarty model syntezy mowy stworzony przez firmę Coqui TTS. Potrafi generować naturalnie brzmiący głos na podstawie krótkiej próbki audio — bez konieczności trenowania od zera.
Model obsługuje ponad 17 języków, w tym język polski. Może działać w trybie zero-shot (tylko na podstawie próbki audio, bez żadnego trenowania) lub po fine-tuningu — czyli dostrojeniu do konkretnego głosu.
Kluczowa różnica między zero-shot a fine-tuningiem: W trybie zero-shot podajesz modelowi próbkę głosu (speaker_wav) i on stara się ją naśladować natychmiast. Fine-tuning to trening, w którym model przez wiele kroków uczy się konkretnego głosu na dużym zestawie danych. Fine-tuning daje lepszą jakość, ale wymaga czasu i zasobów.
To kompendium skupia się na fast fine-tuningu — czyli jak wytrenować dobry model w jak najkrótszym czasie, unikając typowych błędów.
30 sekund dobrego speaker_wav zastąpi kilkanaście godzin fine-tuningu. To najważniejszy parametr całego systemu.
XTTS v2 jest zaprojektowany wokół koncepcji speaker embedding — czyli matematycznej reprezentacji głosu wyodrębnionej z krótkiej próbki audio. Model nie "zapamiętuje" głosu jak człowiek — on oblicza jego "odcisk", a potem próbuje go odtworzyć.
Nawet bez żadnego trenowania, podając dobry plik WAV jako speaker_wav, możesz uzyskać zaskakująco dobry wynik. Dlatego zanim zaczniesz tygodniowy trening — najpierw poeksperymentuj z różnymi próbkami speaker_wav.
Dobry speaker_wav to: 20–40 sekund czystego audio, jeden lektor, bez muzyki w tle, dobra jakość nagrania, naturalna mowa (nie zbyt szybka, nie zbyt wolna), wyrażenia z różnymi fonemami.
Ciekawy eksperyment: Mając dataset jednego lektora, warto budować różne speaker_wav z różnych nagrań i porównywać wyniki. Różne fragmenty tego samego głosu dają różne embeddingi — i różnie brzmiące wyniki. To fascynujący temat sam w sobie, wart osobnego artykułu.
Zły checkpoint treningu żadne parametry inferencji nie naprawią. Ale dobry speaker_wav może sprawić, że fine-tuning w ogóle nie będzie potrzebny.
Na potrzeby tych badań użyliśmy datasetu studyjnego jednego polskiego lektora. Poniżej szczegółowe parametry.
| Parametr | Wartość | Uwaga |
|---|---|---|
| Łączny czas audio | ~50 godzin | Poziom komercyjnych syntezatorów |
| Liczba plików WAV | ~50 000 | Po filtrze długości |
| Długość próbek | 1–12s | Optimum: 1–10s |
| Jakość | Studyjna | Znormalizowana, zwalidowana |
| Język | Polski | Jeden lektor |
| Próbek (filtr 3–10s) | ~27 293 | Ostrzejszy filtr |
| Próbek (filtr 1–12s) | ~50 000 | Cały dataset |
| Czas przygotowania | 2–3 tygodnie | Bez nagrywania |
Jakość bije ilość. 50 godzin studyjnych to więcej niż 200 godzin niespójnych nagrań z różnych mikrofonów, z szumem w tle, z różnym EQ. Model uczy się nie tylko głosu — uczy się też artefaktów nagrania. Daj mu złe nagrania, wytrenuje złe artefakty.
Wszystkie pliki muszą być w formacie WAV, 22050 Hz, mono. XTTS v2 przetwarza audio w tej próbce. Inne formaty trzeba skonwertować przed treningiem (np. ffmpeg).
Optimum to 1–10 sekund. Za krótkie próbki (poniżej 1s) mają za mało kontekstu fonetycznego. Za długie (powyżej 12s) mogą destabilizować trening i zwiększać zużycie pamięci GPU.
Każdemu plikowi WAV musi towarzyszyć transkrypcja tekstu. XTTS v2 uczy się relacji dźwięk↔tekst. Błędy w transkrypcjach przekładają się bezpośrednio na błędy w wygenerowanej mowie.
Cały dataset powinien mieć wyrównaną głośność (np. −23 LUFS). Duże różnice głośności między próbkami uczą model niekonsekwentnej dynamiki.
XTTS v2 fine-tuning jest projektowany pod jednego lektora. Mieszanie wielu głosów w datasecie bez odpowiedniej konfiguracji multi-speaker nie da dobrych wyników.
Wszystkie eksperymenty opisane w tym dokumencie były przeprowadzone na Google Colab z GPU Tesla T4 (16 GB VRAM). To najtańsza opcja dostępna w Colab Pro, wystarczająca do fine-tuningu XTTS v2.
Ograniczenie Colaba: Sesje Colab mają limit czasu (zwykle 12h dla Pro). Dlatego trening jest podzielony na sesje po ~1000 kroków, które wznawiamy z zapisanego checkpointu. To wymusiło wypracowanie protokołu sesyjnego opisanego w sekcji 06.
BASHpip install TTS
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
pip install trainer
PYTHONimport torch
print(torch.cuda.is_available()) # True
print(torch.cuda.get_device_name(0)) # Tesla T4
print(torch.cuda.get_device_properties(0).total_memory / 1e9) # ~15.7 GB
Zamiast mixed precision (AMP), który może powodować niestabilności przy fine-tuningu TTS, używamy TF32. Daje przyspieszenie obliczeń macierzowych przy zachowaniu pełnej stabilności.
PYTHONtorch.backends.cuda.matmul.allow_tf32 = True
torch.backends.cudnn.allow_tf32 = True
Poniżej pełna konfiguracja używana w eksperymentach. Każdy parametr jest wyjaśniony — żebyś wiedział co i dlaczego.
PYTHON — STAŁY CONFIGbatch_size=2 # ile próbek jednocześnie — T4 nie obsługuje więcej bezpiecznie
eval_batch_size=1
batch_group_size=8 # grupowanie próbek podobnej długości — mniej paddingu = wydajniej
num_loader_workers=4
num_eval_loader_workers=2
cudnn_benchmark=True # optymalizacja kerneli CUDA dla powtarzalnych rozmiarów
mixed_precision=False # AMP wyłączone — TF32 zamiast
use_grad_scaler=False # brak skalera gradientów (zbędny bez AMP)
shuffle=False # shuffle ręcznie przed .fit() — patrz sekcja 06
optimizer='AdamW'
optimizer_params={
'betas': [0.9, 0.96], # momentum i adaptacja — standardowe dla TTS
'eps': 1e-8,
'weight_decay': 0.01 # L2 regularyzacja — zapobiega przeuczeniu
}
save_step=1000 # zapisuj checkpoint co 1000 kroków
save_n_checkpoints=2 # zachowaj tylko 2 ostatnie — oszczędność miejsca
save_best_after=5000 # nie zapisuj "najlepszego" przed 5k — za wcześnie
epochs=1000 # duża liczba, ale zatrzymujemy ręcznie
scheduler_after_epoch=False
run_eval_steps=500 # ewaluacja co 500 kroków (~90s na T4)
| Tryb | lr | grad_clip | grad_accum | Czas ~T4 | Zastosowanie |
|---|---|---|---|---|---|
| Mikroszlif | 2e-6 | 0.3 | 4 | ~30 min / 1k | Delikatna korekta istniejącego checkpointu |
| Optymalny ← | 5e-6 | 0.3 | 4 | ~1h / 2k | Złoty środek — zalecany |
| Szybki | 1e-5 | 0.3 | 4 | ~1h / 3k | Szybki test, ryzyko "wyciszenia" głosu |
Dlaczego grad_clip=0.3, a nie 1.0? Gradient clipping obcina zbyt duże kroki optimizera. Przy 1.0 (sesja A) model uczył się szybciej numerycznie, ale głos wyszedł "wyciszony i stonowany". Przy 0.3 aktualizacje wag są bardziej konserwatywne — model uczy się wolniej, ale bardziej precyzyjnie. Dla TTS to ważne: chcemy naturalnej prozodii, nie tylko niskiego loss.
Ze względu na limity czasowe Google Colab, trening jest podzielony na sesje po ~1000 kroków. Kluczowe odkrycie: wznawianie treningu z losowym przetasowaniem danych nie destabilizuje modelu — a może poprawiać pokrycie fonemów.
PYTHON — PEŁNY PROTOKÓŁ SESJISESSION_SEED = int(time.time()) % 100000 # nowy seed przy każdej sesji
# Kolejność operacji PRZED .fit()
# 1. Filtr długości próbek
train_samples = [s for s in train_samples
if SAMPLE_DURATION_MIN <= get_duration(s) <= SAMPLE_DURATION_MAX]
# 2. Ustawienie seedów (reprodukowalność w ramach sesji)
random.seed(SESSION_SEED)
np.random.seed(SESSION_SEED)
torch.manual_seed(SESSION_SEED)
torch.cuda.manual_seed_all(SESSION_SEED)
# 3. Losowe przetasowanie próbek
random.shuffle(train_samples)
# 4. Opcjonalne okrojenie datasetu
if MAX_TARGET_WAVS > 0:
train_samples = train_samples[:MAX_TARGET_WAVS]
# 5. Start / wznowienie treningu
trainer.fit()
Dlaczego losowy shuffle jest dobry? Przy 50 000 próbkach losowość jest sprzymierzeńcem. Statystycznie po kilku sesjach model trafi na lepszy przekrój trudnych fonemów języka polskiego (ś, cz, szcz, rz, dż) niż przy jakimkolwiek ręcznym doborze. Różnorodność danych w każdej sesji = lepszy model.
PYTHON — WZNOWIENIE Z CHECKPOINTU# Wskaż ścieżkę do ostatniego checkpointu
trainer = Trainer(
args,
config,
output_path=OUTPUT_PATH,
model=model,
train_samples=train_samples,
eval_samples=eval_samples,
restore_path="./checkpoints/checkpoint_3000.pth" # ← tutaj checkpoint
)
WAŻNE: Przy wznawianiu NIE zmieniaj lr ani grad_clip. AdamW przechowuje zakumulowane momenty skalibrowane pod poprzedni lr. Nagła zmiana = niestabilność, spike loss. Jeśli chcesz innego lr — zacznij od zera z restore_path=None.
Przeprowadzono trzy serie eksperymentów (sesje A, B, C) z różnymi kombinacjami learning rate i grad_clip. Wszystkie na tym samym datasecie ~50k próbek polskiego lektora.
| Faza | Global Step | loss_mel_ce (eval) | loss_mel_ce (MA) | loss (MA) | Ocena |
|---|---|---|---|---|---|
| Start | 0 | 6.091 | 6.091 | 1.538 | — |
| I | 1 000 | 4.229 | 4.354 | 1.097 | gwałtowny spadek |
| II | 2 000 | 3.695 | 3.834 | 0.966 | stabilny spadek |
| III | 3 000 | 3.774 | 3.740 | 0.942 | szlifowanie |
| IV | 4 000 | 3.731 | 3.665 | 0.923 | plateau |
| V | 5 000 | 4.157 | 3.623 | 0.913 | eval spike* |
* Eval spike przy 5000 — to artefakt losowości (trudniejsza pula eval przy nowym seedzie). MA nadal spada, model nie degraduje. Zawsze patrz na MA, nie na pojedynczy eval.
| Sesja | lr | grad_clip | Kroki do ~3.6 | Ocena brzmieniowa |
|---|---|---|---|---|
| A | 1e-5 | 1.0 | ~3 000 | Dobre — wyciszone, stonowane |
| B | 2e-6 | 0.3 | ~7 500 | Dobre, plateau od ~4k |
| C ← zalecana | 5e-6 | 0.3 | ~4 000 | Bardzo dobre, brak różnicy odsłuchowej |
Trening XTTS v2 przebiega przez charakterystyczne strefy. Rozumienie ich pozwala zatrzymać trening w odpowiednim momencie — zanim model zacznie się "przeuczać".
Optimum fine-tuningu: 2000–3000 kroków od zera. Powyżej 3000 — marginalny zysk brzmieniowy, rosnące ryzyko overfittingu. Powyżej 5000 — tylko jeśli lr jest bardzo niskie (2e-6). 50 000+ kroków — model nieużywalny (potwierdzone empirycznie).
AdamW przechowuje zakumulowane momenty skalibrowane pod poprzedni lr. Nagła zmiana = konflikt optimizera = niestabilność, spike loss, przyspieszony overfitting. Jeśli chcesz innego lr: nowa sesja od zera (restore_path=None).
Ustaw shuffle=False w configu i tasuj ręcznie przed każdą sesją. Pozwala kontrolować losowość z seedem i zapobiega konfliktom z wewnętrznym loaderem.
loss_mel_ce MA jest wskaźnikiem pomocniczym. Liczy się subiektywna jakość odsłuchu. Model z wyższym loss może brzmieć lepiej niż model z niższym — dlatego zawsze używaj tych samych zdań referencyjnych przy porównaniach.
Żadnych zmian parametrów w trakcie trwającego treningu. Zmiana batch_size, lr, lub innych parametrów w połowie sesji daje nieprzewidywalne wyniki.
Zawsze te same zdania przy każdym porównaniu checkpointów: (1) trudne fonemy — "Szczególnie rzeczywiście trudne jest rozróżnienie dźwięków w języku polskim", (2) intonacja pytająca, (3) neutralne zdanie, (4) liczby i daty. Porównując różne checkpointy na tych samych zdaniach słyszysz różnice, a nie przypadkowe fluktuacje.
Overfitting (przeuczenie) to sytuacja, w której model tak bardzo "zapamiętał" dane treningowe, że przestał generalizować. Zamiast "mówić", zaczyna "przypominać sobie" konkretne próbki audio.
Mechanizm: Model wbił się tak głęboko w rozkład datasetu, że przy generowaniu mowy próbkuje z bardzo wąskiego, przekrzywionego rozkładu tokenów audio. Przestaje brzmieć jak lektor — zaczyna brzmieć jak "zepsuta taśma".
Sygnały ostrzegawcze w liczbach:
loss_mel_ce MA ~2.5 — zacznij słuchać uważnie.
loss_mel_ce MA ~1.5 = krytyczny overfitting. Model nieużywalny produkcyjnie.
Gdy masz gotowy checkpoint, generujesz audio przez inferencję. Trzy parametry mają największy wpływ na wynik.
PYTHON — PRZYKŁAD INFERENCJIfrom TTS.api import TTS
tts = TTS(model_path="./checkpoints/checkpoint_3000.pth",
config_path="./config.json")
tts.tts_to_file(
text="Szczególnie rzeczywiście trudne jest rozróżnienie dźwięków.",
speaker_wav="./speaker_reference.wav",
language="pl",
file_path="output.wav",
temperature=0.75,
top_p=0.83,
repetition_penalty=1.25
)
Hierarchia ważności: (1) Jakość datasetu → (2) Dobry checkpoint (lr, steps) → (3) Speaker_wav → (4) Parametry inferencji. Parametry inferencji to ostatni szlif — zły checkpoint żadne parametry nie naprawią.
Najlepszy model to nie ten z najniższym loss — to ten który najlepiej brzmi. Liczby są kompasem, nie celem.
| Parametr | Wartość |
|---|---|
| learning_rate | 5e-6 |
| grad_clip | 0.3 |
| Liczba kroków od zera | 2000 – 3000 |
| Wznawianie co | 1000 kroków + nowy shuffle |
| mixed_precision | False (TF32) |
| optimizer | AdamW (betas: 0.9 / 0.96) |
| Eval co | 500 kroków |
Wznawianie z random.shuffle nie destabilizuje AdamW ani nie psuje krzywej uczenia. MA loss zachowuje się identycznie jak przy ciągłym treningu.
Szybszy niż 2e-6, bardziej kontrolowany niż 1e-5. Osiąga dobre wyniki brzmieniowe przy 2000–3000 krokach.
Dalszy trening daje marginalny zysk numeryczny przy rosnącym ryzyku overfittingu. Odsłuch to potwierdza: przy 5000 krokach "nie czuć różnicy" vs 3000.
Skok eval loss przy nowym seedzie to artefakt losowej puli ewaluacyjnej. Zawsze patrz na Moving Average, nie na pojedynczy eval step.
PRO-TIP — Precyzyjne wyłapanie optymalnego checkpointu:
Zamiast zatrzymywać się ślepo przy 3000 krokach, możesz znaleźć dokładny punkt optymalny. Zasada jest prosta: szukaj kroku gdzie eval ≈ MA — czyli gdy obie liczby są niemal identyczne.
Gdy loss_mel_ce (eval) mocno odskakuje od wartości w nawiasie (MA) — to spike, model traci stabilność. Gdy obie wartości są blisko siebie — trening jest w maksymalnej równowadze.
Żeby móc odtworzyć taki trening: zapisuj SESSION_SEED przy każdej sesji. Mając seed + liczbę kroków masz pełną reprodukowalność — możesz wrócić do dokładnie tego samego punktu.
To poziom mikro-szlifów — brzmieniowa różnica będzie minimalna, ale jeśli zależy Ci na precyzji i masz dobry dataset, warto wiedzieć że taka technika istnieje.
Ważna uwaga o przenośności wyników: Eksperymenty przeprowadzono na datasecie ~50h studyjnego audio jednego polskiego lektora. Przy innych datasetach (inna jakość, inny język, mniej danych) wyniki mogą się różnić — ale opisany protokół i zasady powinny być dobrym punktem startowym.