Почему ваш голосовой ассистент говорит "двадцать пять" вместо "25"?
Вы запускаете свой голосовой ассистент на Rockchip, даете команду: "Напомни мне о встрече 25.03 в 14:30". А он в ответ выдает нечто вроде "Напомни о встрече двадцать пятая точка ноль три в четырнадцать часов тридцать". Или еще веселее: "Прочитай рецепт: 100 г муки, 2 ст.л. сахара". TTS выдает "сто грамм муки, два стэлэ сахара". Знакомо? Это не проблема модели. Это проблема текста.
Современные TTS-модели в 2026 году, будь то Sonya TTS или Pocket TTS, научились копировать интонации, эмоции, даже диалекты. Но они по-прежнему тупят на уровне символов. Модель видит последовательность символов "25.03" и не знает, что это дата. Для нее это два числа, точка, ноль, три. И она произносит это буквально.
TTS-модель - это не искусственный интеллект в полном смысле. Это сложный статистический преобразователь текста в звук. Она не понимает смысла. Она видит графемы. Ваша задача - превратить "непонятные" графемы в те, которые модель умеет произносить правильно.
Что ломается чаще всего? Три кита проблем TTS
Прежде чем чинить, нужно понять, что именно сломалось. Вот три главных врага натурального звучания.
1. Числа и их контекст
- Даты: "25.03.2026" должно звучать как "двадцать пятого марта две тысячи двадцать шестого года", а не "двадцать пять точка ноль три точка два ноль два шесть".
- Время: "14:30" - это "четырнадцать тридцать" или "полтретьего дня"? А "2:00" - "два часа" или "две ноль ноль"?
- Порядковые номера: "1-й этаж" vs "1 этаж". Одно - "первый этаж", другое - "один этаж". Контекст решает все.
- Деньги: "100$" - "сто долларов" или "сто знаков доллара"? А "100 руб."?
2. Сокращения, которые нужно склонять
Русский язык жесток к TTS. Аббревиатуры и сокращения живут по своим законам.
- И.о. директора - произносится как "исполняющий обязанности директора", а не "и точка о точка директора".
- ст. л. - "столовая ложка", а не "стэ лэ".
- кг, см, м - обычно произносятся как "килограмм", "сантиметр", "метр". Но в составе фразы "5 кг" - "пять килограммов" (родительный падеж!).
- вуз, загс, НАТО - некоторые аббревиатуры стали словами и читаются как единое целое. Другие, как "США", произносятся по буквам.
3. Омонимы и формулы
Самое больное место.
- Что проще: "ключ от замка" или "замок на ключ"? Слова одинаковые, ударение разное. Текст не содержит ударения. TTS может выбрать случайный вариант.
- Формулы: "H2O" - это "аш два о" или "вода"? "E=mc^2" - как это вообще озвучить? "Э равно эм цэ квадрат"?
- Символы: "C++" - "си плюс плюс". "#тег" - "хештег тег". "@user" - "собака user" или "at user"?
Решение: Нормализатор. Это не исправление ошибок, это перевод на язык TTS
Нормализация текста - это процесс преобразования исходного текста в последовательность слов, которые TTS-модель однозначно и правильно произнесет. Вы не исправляете текст. Вы переводите его с "письменного диалекта" на "устный".
Плохая новость: универсального решения нет. Хорошая новость: это инженерная задача, которую можно решить комбинацией правил и, в 2026 году, маленьких моделей классификации контекста.
1 Собираем коллекцию ошибок
Не пытайтесь угадать. Запустите свою TTS на реальных данных. Что у вас будет озвучиваться? Новости, команды, оповещения? Соберите 100-200 примеров и отметьте, где произношение неверное. Это ваш тестовый набор. Без него вы будете чинить то, что не ломалось.
2 Пишем ядро нормализатора на Python
Вам не нужен машинный learning для базовых вещей. Регулярные выражения и словари справятся на 80%.
Как НЕ надо делать: пытаться одной регуляркой найти все числа.
# ПЛОХО: Слишком просто, контекст не учитывается.
import re
text = "Встреча 25.03 в 14:30"
# Найдем все последовательности цифр и точек? Не поможет.
Как надо делать: разбивать на модули по типу сущности.
# Основа нормализатора
class TextNormalizer:
def __init__(self):
self.date_pattern = re.compile(r'\b(\d{1,2})[./](\d{1,2})[./]?(\d{2,4})?\b')
self.time_pattern = re.compile(r'\b(\d{1,2})[:.]?(\d{2})\b')
self.currency_pattern = re.compile(r'\b(\d+)\s*([$€₽]|руб|usd)\b', re.IGNORECASE)
def normalize_dates(self, text: str) -> str:
# Заменяет 25.03.2026 на «двадцать пятого марта две тысячи двадцать шестого года»
def _replace_date(match):
day, month, year = match.groups()
# Здесь должна быть логика склонения и форматирования.
# Для простоты:
return f"{day} числа {month} месяца {year if year else 'текущего'} года"
return self.date_pattern.sub(_replace_date, text)
def normalize_times(self, text: str) -> str:
# Заменяет 14:30 на «четырнадцать часов тридцать минут»
def _replace_time(match):
hours, minutes = match.groups()
# Логика для утра/вечера, склонения "час" / "часа" / "часов"
return f"{hours} часов {minutes} минут"
return self.time_pattern.sub(_replace_time, text)
def normalize_currency(self, text: str) -> str:
# Заменяет 100$ на «сто долларов»
currency_map = {'$': 'долларов', '€': 'евро', '₽': 'рублей', 'руб': 'рублей', 'usd': 'долларов'}
def _replace_currency(match):
amount, curr = match.groups()
curr_lower = curr.lower()
currency_name = currency_map.get(curr_lower, curr_lower)
# Нужно преобразовать число amount в слова (100 → "сто").
# Используйте библиотеку num2words или свою функцию.
amount_words = num2words(int(amount), lang='ru')
return f"{amount_words} {currency_name}"
return self.currency_pattern.sub(_replace_currency, text)
def normalize(self, text: str) -> str:
text = self.normalize_dates(text)
text = self.normalize_times(text)
text = self.normalize_currency(text)
# ... и другие модули
return text
# Использование
normalizer = TextNormalizer()
result = normalizer.normalize("Купить за 100$ до 25.03 в 14:30")
print(result) # «Купить за сто долларов до 25 числа 03 месяца текущего года в 14 часов 30 минут»
Для преобразования чисел в слова (num2words) используйте актуальную библиотеку. В 2026 году она должна поддерживать все нужные склонения и валюты. Не пишите свои конвертеры — это грабли, на которые уже наступили.
3 Добавляем контекстные правила для омонимов
Это сложнее. Нужно понять, в каком значении используется слово. Иногда помогает простой анализ соседних слов.
def normalize_homonyms(text: str) -> str:
# Простейший пример: "ключ" и "замок"
words = text.split()
for i, word in enumerate(words):
if word.lower() == "ключ":
# Смотрим соседние слова
next_word = words[i+1].lower() if i+1 < len(words) else ""
if next_word.startswith("от") or next_word in ["дверной", "металлический"]:
# Это ключ от чего-то. Нужно как-то указать ударение?
# В тексте ударение не обозначишь. Меняем формулировку?
# Для TTS можно использовать символ ударения, если модель его понимает.
# Например, некоторые TTS понимают разметку типа клЮч .
pass
elif "информации" in text or "шифрования" in text:
# Криптографический ключ
pass
return " ".join(words)
Для сложных случаев в 2026 уже можно использовать крошечную языковую модель (например, на основе BERT-подобной архитектуры с 10-50 млн параметров), которая по контексту классифицирует, о чем речь. Но для embedded-устройств типа Rockchip это может быть тяжеловато. Альтернатива — простой классификатор на n-граммах.
4 Интеграция с TTS-пайплайном
Нормализатор должен работать ДО того, как текст попадет в модель. Если вы используете голосового ассистента, вставьте нормализацию после модуля NLU (понимания естественного языка) и перед TTS.
# Упрощенный пайплайн ассистента
def process_command(command: str, tts_engine):
# 1. NLU: Извлечение намерений и сущностей (уже может распознать даты)
intent = nlu_module.understand(command)
# 2. Формирование ответа в текстовом виде
response_text = dialog_manager.generate_response(intent)
# 3. КРИТИЧЕСКИЙ ШАГ: Нормализация текста ответа
normalized_text = text_normalizer.normalize(response_text)
# 4. Синтез речи
audio = tts_engine.synthesize(normalized_text)
return audio
Если вы используете AnyTTS для подключения к ChatGPT, нормализатор нужно встроить в клиентскую часть, которая передает текст в TTS-движок.
Где спрятаны грабли? Нюансы, которые сведут вас с ума
Производительность на embedded-устройствах
Если ваш ассистент работает на Rockchip или аналогичном слабом железе, каждое дополнительное правило — это задержка. Регулярные выражения быстры, но сложные контекстные анализаторы — нет. Профилируйте. Кэшируйте результаты для часто встречающихся фраз.
Языковая зависимость
Все, что мы обсуждали, — для русского языка. В английском свои проблемы: "Dr. Smith" vs "driving", "St. John" vs "street". Ваш нормализатор должен знать язык текста. В многоязычных ассистентах это отдельный головняк.
Переобучение на правилах
Вы написали правило, что "ст." перед фамилией — это "святая" или "святой". А потом попадается "ст. лейтенант". Или "ст. 25 УК РФ". Правила должны иметь приоритеты и проверяться в определенном порядке. А лучше — использовать классификатор.
Когда не нужно нормализовать
Бывают случаи, когда буквальное произношение — это правильно. Например, в программировании: "переменная tmp2". Это должно звучать как "тэ эм пэ два", а не "временная два". Ваш нормализатор должен уметь это определять (например, по окружению других терминов кода).
Частые вопросы (FAQ)
| Вопрос | Короткий ответ | Что делать |
|---|---|---|
| Можно ли использовать нейросеть для нормализации? | Да, но осторожно. | Для сложных случаев (омонимы) можно дообучить маленькую модель на размеченных данных. Для чисел и дат — избыточно. |
| Какие TTS-модели лучше справляются сами? | Почти никакие. | Модели, обученные на качественно размеченных данных (с явно прописанными произношениями), могут быть лучше. Но универсального решения нет. Смотрите наш тест TTS 2026. |
| Как протестировать нормализатор? | На реальных данных. | Соберите корпус текстов, которые будет озвучивать ассистент. Пропустите через нормализатор, затем через TTS. Слушайте. Автоматизировать сложно — нужна человеческая оценка. |
| Есть готовые библиотеки? | Частично. | Для русского языка есть Natasha, PyMorphy, но они для морфологического анализа, не для TTS-нормализации. Адаптируйте под свои нужды. Или напишите свой набор правил, как описано выше. |
Итог: Нормализация — это не опция, это обязательный слой
Хотите, чтобы ваш DIY-ассистент на Rockchip звучал профессионально? Не надейтесь на одну лишь TTS, даже если это супер-новая LuxTTS. Потратьте 80% времени не на выбор модели, а на подготовку текста для нее.
Начните с простого: даты, время, деньги. Добавьте 20 самых частых сокращений из вашей предметной области. Уже это даст огромный прирост натуральности. А дальше — бесконечная борьба с контекстом и омонимами. Добро пожаловать в мир разработки голосовых интерфейсов.
P.S. Если вы делаете ассистента для узкой области (например, чтение кулинарных рецептов), вам повезло. Всего 50 правил — и он будет звучать идеально. Если для общего общения — готовьтесь к долгой итеративной разработке. Но без этого никак.