Kompendium · TTS · XTTS v2

Fast Fine-Tuning
XTTS v2
dla każdego

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 · Coqui TTS ✓ Potwierdzone empirycznie Google Colab T4 ~50h dataset Język polski

Spis treści

  1. 01 Czym jest XTTS v2?
  2. 02 Speaker WAV — najważniejsza rzecz
  3. 03 Dataset — co i jak
  4. 04 Środowisko: Google Colab
  5. 05 Konfiguracja treningu
  6. 06 Podejście sesyjne i shuffle
  7. 07 Wyniki eksperymentów
  8. 08 Krzywa uczenia — strefy
  9. 09 Zasady bezwzględne
  10. 10 Overfitting — jak go rozpoznać
  11. 11 Parametry inferencji
  12. 12 Wnioski końcowe

Czym jest XTTS v2?

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.

Speaker WAV — najważniejsza rzecz w XTTS

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ć.

Co to oznacza w praktyce?

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.

Dataset — co i jak przygotować

Na potrzeby tych badań użyliśmy datasetu studyjnego jednego polskiego lektora. Poniżej szczegółowe parametry.

Parametr Wartość Uwaga
Łączny czas audio~50 godzinPoziom komercyjnych syntezatorów
Liczba plików WAV~50 000Po filtrze długości
Długość próbek1–12sOptimum: 1–10s
JakośćStudyjnaZnormalizowana, zwalidowana
JęzykPolskiJeden lektor
Próbek (filtr 3–10s)~27 293Ostrzejszy filtr
Próbek (filtr 1–12s)~50 000Cały dataset
Czas przygotowania2–3 tygodnieBez 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.

Co warto wiedzieć o przygotowaniu danych

1

Format plików

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).

2

Długość próbek

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.

3

Pliki tekstowe (metadata)

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.

4

Normalizacja głośności

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.

5

Jeden lektor

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.

Środowisko: Google Colab T4

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.

Wymagane biblioteki

BASHpip install TTS
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
pip install trainer

Sprawdzenie GPU

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

TF32 — stabilność bez AMP

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

Konfiguracja treningu

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)

Zalecane tryby treningu

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.

learning_rate
5e-6
Złoty środek. Szybszy niż 2e-6, bardziej kontrolowany niż 1e-5. Osiąga loss_mel_ce ~3.6 po ~4000 krokach.
grad_clip
0.3
Konserwatywne obcinanie gradientów. Lepsza jakość brzmieniowa niż 1.0 — potwierdzone odsłuchowo.
grad_accum_steps
4
Akumulacja gradientów z 4 batchy przed krokiem. Efektywny batch_size = 2×4 = 8.
mixed_precision
False
AMP wyłączone. Używamy TF32 — stabilniejsze przy fine-tuningu TTS.

Podejście sesyjne i shuffle

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.

Wznawianie treningu (resume)

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.

Wyniki eksperymentów

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.

● Sesja A: lr=1e-5, grad_clip=1.0 ● Sesja B: lr=2e-6, grad_clip=0.3 ● Sesja C: lr=5e-6, grad_clip=0.3 + re-shuffle
loss_mel_ce — porównanie sesji (eval moving average)

Szczegółowe dane — Sesja C (lr=5e-6, re-shuffle co 1k)

Faza Global Step loss_mel_ce (eval) loss_mel_ce (MA) loss (MA) Ocena
Start06.0916.0911.538
I1 0004.2294.3541.097gwałtowny spadek
II2 0003.6953.8340.966stabilny spadek
III3 0003.7743.7400.942szlifowanie
IV4 0003.7313.6650.923plateau
V5 0004.1573.6230.913eval 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 C — loss_mel_ce eval vs moving average

Porównanie szybkości zejścia do loss_mel_ce ~3.6

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

Krzywa uczenia — strefy

Trening XTTS v2 przebiega przez charakterystyczne strefy. Rozumienie ich pozwala zatrzymać trening w odpowiednim momencie — zanim model zacznie się "przeuczać".

0 – 500 kroków
Gwałtowny spadek
−25% loss, model "łapie głos". Grube dopasowanie — szybkie uczenie podstawowych cech głosu.
500 – 2000 kroków
Stabilny spadek
Równomierny zjazd. Model uczy się prozodii, rytmu i intonacji.
2000 – 3500 kroków
Szlifowanie
Tempo maleje, ale wchodzi "błysk" barwy. Widoczna poprawa brzmieniowa.
3500+ kroków
Plateau / ryzyko
Marginalny zysk numeryczny. Rośnie ryzyko overfittingu. Zwykle warto tu się zatrzymać.
Strefy krzywej uczenia — schematycznie
🛑

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).

Zasady bezwzględne

🚫
Nie zmieniaj lr ani grad_clip przy wznawianiu treningu

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).

🔀
Shuffle raz przed .fit(), nie per-batch

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.

👂
Testuj audio, nie liczby

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.

🔒
Jeden config przez całą sesję

Żadnych zmian parametrów w trakcie trwającego treningu. Zmiana batch_size, lr, lub innych parametrów w połowie sesji daje nieprzewidywalne wyniki.

📋
Protokół zdań referencyjnych

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 — jak go rozpoznać

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".

Stadium wczesne
Falowanie barwy, lekkie przyspieszenie tempa, drobne niestabilności intonacji. Często jeszcze używalny.
Stadium średnie
Wahania tonu między zdaniami, utrata rytmu, sporadyczne dziwne fonemy. Model degraduje widocznie.
Stadium zaawansowane
"Pijany głos", bełkotanie, powtórzenia sylab, halucynacje fonetyczne, skoki tonalne. Model nieużywalny.
🚨

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.

Parametry inferencji

Gdy masz gotowy checkpoint, generujesz audio przez inferencję. Trzy parametry mają największy wpływ na wynik.

TEMPERATURE
Za niskie
< 0.65 → monotonia
Optimum
0.70 – 0.78
Za wysokie
> 0.85 → skoki tonalne
TOP_P
Za niskie
< 0.75 → deterministyczny
Optimum
0.80 – 0.85
Za wysokie
> 0.90 → dziwne fonemy
REP_PENALTY
Za niskie
< 1.1 → pętle sylab
Optimum
1.20 – 1.30
Za wysokie
> 1.4 → zaburza rytm
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ą.

Wnioski końcowe

Najlepszy model to nie ten z najniższym loss — to ten który najlepiej brzmi. Liczby są kompasem, nie celem.

Optymalna recepta (potwierdzona empirycznie)

Parametr Wartość
learning_rate5e-6
grad_clip0.3
Liczba kroków od zera2000 – 3000
Wznawianie co1000 kroków + nowy shuffle
mixed_precisionFalse (TF32)
optimizerAdamW (betas: 0.9 / 0.96)
Eval co500 kroków

Kluczowe odkrycia

Re-shuffle co sesję jest bezpieczny

Wznawianie z random.shuffle nie destabilizuje AdamW ani nie psuje krzywej uczenia. MA loss zachowuje się identycznie jak przy ciągłym treningu.

lr=5e-6 to złoty środek dla tego datasetu

Szybszy niż 2e-6, bardziej kontrolowany niż 1e-5. Osiąga dobre wyniki brzmieniowe przy 2000–3000 krokach.

Po 3000 krokach — stop

Dalszy trening daje marginalny zysk numeryczny przy rosnącym ryzyku overfittingu. Odsłuch to potwierdza: przy 5000 krokach "nie czuć różnicy" vs 3000.

⚠️
Eval spike ≠ degradacja

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.