KEN’S CAT LOG
▣

Я создал модель синтеза речи для хорватского языка на 32 млн параметров. Сравниваю голоса по ходу обучения

Вслед за LLM для RAG на хорватском языке я решил создать небольшую модель синтеза речи (TTS) специально для хорватского языка. Цель — работа в реальном времени на CPU смартфона.

Я использовал дистилляцию: большая модель синтеза речи (HiggsAudio v3) начитывала хорватские сценарии, создавая обучающие данные, на которых затем с нуля обучалась маленькая модель. Сначала я провёл пилотное обучение на 7,5 часа данных и сравнил, как меняется одна и та же фраза по мере обучения.

Сразу к выводам.

  • При 32 млн параметров (Matcha-TTS — 18,3 млн + Vocos — 13,5 млн) модель действительно заговорила по-хорватски
  • Уже через 50 минут обучения (5 000 шагов) она читала строго по сценарию. Дальше улучшался главным образом «шум»
  • Финальная частота символьных ошибок на 14 оценочных фразах составила 0,9% (10 из 14 фраз совпали полностью)
  • Даже на одном потоке CPU можно было сгенерировать 1 секунду речи за 0,09 секунды (i7-13700K)

Демонстрация: заставим её говорить

На песочнице хорватского TTS размещена модель из этой статьи — финальная версия на 30 000 шагов. Введите любой хорватский текст, и она озвучит его прямо на месте.

  • Можно выбрать один из 50 примеров (приветствия, путешествия, больница и т. д.) или ввести до 1 000 символов своего текста
  • Можно менять скорость речи, силу интонации и число шагов синтеза. При фиксированном seed одна и та же фраза всегда даёт один и тот же голос
  • Синтез работает только на CPU сервера (ONNX Runtime; ни GPU, ни внешние API не используются)
  • Числа модель не читает, поэтому их нужно писать словами — например, «dvadeset pet (25)»

Что я сделал

1. Подготовил сценарии: локальная Gemma 4 31B писала реплики для повседневных диалогов на хорватском. Я случайным образом задавал ситуацию, собеседника, эмоцию и длину — например, «разговаривает с бабушкой на рынке» или «испугалась странного звука ночью» — по 22 строки за раз. По задумке говорит одна женщина примерно 30 лет. Были смешаны 23 типа эмоций: нейтральная, радость, злость, шёпот и другие.

2. Сгенерировал речь: HiggsAudio v3 начитывала сценарии голосом из референсной записи длительностью 16 секунд. С тегами эмоций (<|emotion:anger|> и т. п.) она действительно злилась и радовалась. За примерно 5 часов было создано 4 500 файлов.

3. Проверил автоматически: Whisper large-v3 расшифровывала аудио, а я сравнивал текст со сценарием и отбрасывал ошибки чтения. Дополнительно модель распознавания говорящего измеряла сходство голоса с референсной записью, чтобы исключить клипы с изменившимся голосом. Прошли проверку 4 393 из 4 500 файлов (97,6%). После обрезки тишины в начале и конце получилось 7,46 часа.

4. Обучил маленькую модель: Matcha-TTS — TTS на основе flow matching — обучалась на RTX 4090 пять часов: около 30 тысяч шагов. Для преобразования обратно в звуковую волну, то есть вокодер, я использовал готовый публичный Vocos.

Хорватский почти всегда читается так, как пишется, поэтому я подавал на вход исходные буквы, не преобразуя их в фонетические символы. Однако lj, nj и dž — это по одному звуку, поэтому я объединял их как один символ.

Сравнение звука в процессе обучения

Я синтезировал фразу, отсутствующую в обучающих данных: «Molim Vas, možete li mi reći koliko košta ova haljina?» («Извините, сколько стоит это платье?»), используя модели на разных этапах обучения.

шаг 5 000 (примерно 50 минут с начала обучения)

шаг 10 000 (примерно 1 час 40 минут)

шаг 15 000 (примерно 2 часа 20 минут)

шаг 20 000 (примерно 3 часа 10 минут)

шаг 30 000 (примерно 5 часов, финальная версия)

Для сравнения — один обучающий пример, сгенерированный HiggsAudio в роли учителя: другая фраза — «Samo polako hodajte, nema potrebe da žurimo kroz ovaj mrak.»

При расшифровке Whisper эта фраза на всех этапах — от шага 5 000 до финального — совпадала со сценарием слово в слово. То есть с самого начала модель умела произнести нужные слова.

Разница — в чистоте звука. На слух на шаге 5 000 слышен зернистый шум, а к шагу 10 000 он заметно уменьшается. Это хорошо видно на спектрограммах одного и того же аудио: по горизонтали время, по вертикали частота.

Сравнение спектрограмм по шагам обучения

Горизонтальные полосы, соответствующие высоте голоса, то есть гармоники, на шаге 5 000 размыты, а промежутки между ними словно затянуты дымкой. Именно это слышится как шум. Начиная с шага 10 000 полосы становятся чёткими, а промежутки — тёмными. По мере обучения полосы плавнее изгибаются вверх и вниз, и, похоже, становится отчётливее изменение высоты голоса — интонация.

В цифрах

Я расшифровал Whisper large-v3 14 оценочных фраз, все вне обучающих данных, и измерил частоту символьных ошибок (CER). В набор вошли как короткие приветствия, так и монологи примерно по 16 секунд.

Шаги Время обучения Частота символьных ошибок Полное совпадение
5 000 около 50 минут 1,1% 9 / 14
10 000 около 1 часа 40 минут 3,4% 8 / 14
15 000 около 2 часов 20 минут 0,7% 11 / 14
20 000 около 3 часов 10 минут 0,9% 11 / 14
30 000 около 5 часов 0,9% 10 / 14
  • Показатель ухудшился на шаге 10 000 почти исключительно из-за одной фразы «Bok! (Привет!)», которую распознали как «Vok». Разница в один символ из трёх означает для этой одной фразы ошибку 33%. На слух версия с 10 000 шагов была гораздо чище версии с 5 000. Частота символьных ошибок измеряет только то, правильно ли текст прочитан, поэтому чистоту звучания приходится оценивать на слух
  • До конца оставалась ошибка в đ и dž в фразе «Đak je pojeo džem» («Ученик съел джем»). Эти звуки редко встречаются даже в хорватском, и распознавание выдавало «Žak» или «Džak»

Скорость на CPU

Я измерил скорость на i7-13700K в обычном PyTorch, без оптимизаций. Это средний результат трёх генераций четырёх фраз общей длительностью 47,6 секунды. RTF — это «время генерации ÷ длительность аудио»: чем меньше, тем быстрее.

Число потоков Число шагов Matcha Vocos Общий RTF
1 2 0.042 0.025 0.067
1 4 0.067 0.025 0.092
4 4 0.032 0.010 0.042
8 4 0.028 0.007 0.036

«Число шагов» — это количество итераций, в которых при синтезе из шума создаётся звук; в примерах выше использовалось 4. Даже на одном потоке 1 секунду аудио можно создать за 0,09 секунды. CPU смартфона медленнее, но, вероятно, сможет работать в реальном времени — это пока только предположение, поскольку на реальном устройстве я ещё не измерял.

Сколько часов данных нужно для TTS?

Раз за 7,5 часа удалось добиться такого результата, я изучил обычные ориентиры.

  • Англоязычный LJSpeech, часто используемый для TTS одного голоса, содержит около 24 часов данных. На нём же обучена официальная модель Matcha-TTS
  • Японский JSUT и китайский CSMSC содержат около 10 часов и обычно используются как корпуса одного голоса
  • При обучении с нуля нижний предел — примерно 1 час, хотя в зависимости от модели может стать больше ошибок произношения
  • При дообучении готовой модели есть сообщение, что 30 минут данных дали примерно такой же результат, как 27 часов обучения с нуля

Нашлась и работа об обучении Matcha-TTS исключительно на синтетической речи Шанхайского университета Цзяо Тун, опубликованная в декабре 2025 года. В ней говорится, что качество выше при большем разнообразии текстов, чистом аудио без шума и единообразной манере речи. В синтетических данных нет шумов и стиль речи стабилен, поэтому при одинаковой длительности они, возможно, обучаются легче, чем реальные записи. Вероятно, этим и объясняется результат на 7,5 часа в данном случае.

С чем пришлось повозиться

  • Компьютер завис: две 31B-модели для создания сценариев случайно одновременно загрузились в GPU и переполнили VRAM. С тех пор правило такое: «на GPU всегда загружается только одна модель»
  • Gemma 4 слишком много думала: около двух третей вывода занимали «размышления». После их отключения работа ускорилась примерно втрое
  • Галлюцинация HiggsAudio: в первом тесте после фразы модель самовольно продолжила тишину словами «Hvala što pratite kanal» («Спасибо, что смотрите канал»), и получился клип на 81 секунду. Похоже, она запомнила заключительную фразу из YouTube. Я начал отбрасывать файлы, чья длина сильно отличается от ожидаемой
  • Matcha-TTS не работал в Windows: некоторые расширения C не удавалось собрать, поэтому я переписал эту часть на Python (numba)