- PVSM.RU - https://www.pvsm.ru -
При решении задач, связанных с распознаванием (Speech-To-Text) и генерацией (Text-To-Speech) речи важно, чтобы транскрипт соответствовал тому, что произнёс говорящий — то есть реально устной речи. Это означает, что прежде чем письменная речь станет нашим транскриптом, её нужно нормализовать.
Другими словами, текст нужно провести через несколько этапов:
1984 год
-> тысяча девятьсот восемьдесят четвёртый год;2 мин. ненависти
-> две минуты ненависти; Orwell
-> Оруэлл
и т.д.
В этой статье я коротко расскажу о том, как развивалась нормализация в датасете русской речи Open_STT [1], какие инструменты использовались и о нашем подходе к задаче.
Как вишенка на торте, мы решили выложить наш нормализатор на базе seq2seq в открытый доступ: ссылка на github [2]. Он максимально прост в использовании и вызывается одним методом:
norm = Normalizer()
result = norm.norm_text('С 9 до 11 котики кушали whiskas')
>>> 'С девяти до одиннадцати котики кушали уискас'
Так в чём же, собственно, проблема? Сокращения на то и сокращения, что их можно легко привести к начальной форме. На самом деле, тут всё не так однозначно и без интуиции носителя языка во всех нюансах тяжело разобраться.
Чтобы показать, насколько глубока кроличья нора, приведу несколько примеров с числительными:
2е
— второе(ые), но д. 2е
— два е;2 части
— две части, но нет 2 части
— нет второй части;длиной до 2 км
— длиной до двух километров, но едем до 2 км
— едем до второго километра;= 2/5
— равно две пятых, но д. 2/5
— дом два дробь пять или даже — два пять.Не меньше проблем с расшифровкой сокращений: одна и та же аббревиатура может читаться по-разному в зависимости от контекста(г
— город или год) и человека(БЦ
— б ц или бизнес центр?). Думаю, что творится с транскрипцией других языков, вы уже и сами догадались. Особенно остро проблема стоит с обработкой разговорной речи.
Во всем этом многоообразии легко потеряться и уйти в бесконечный цикл с поиском и обработкой всё новых и новых кейсов. В какой-то момент лучше остановиться и вспомнить о принципе Парето. Вместо того, чтобы решать задачу в общем виде, мы можем обработать ~20% самых частотных случаев, но покрыть ~80% языка.
В первых релизах Open_STT мы подошли к решению ещё брутальней: видим число — заменяем его на дефолтное количественное числительное. С точки зрения STT такое решение было даже оправданным, всё-таки 2020 год
лучше превратить в две тысячи двадцать год и ошибиться только в одном символе, чем проигнорировать целых три слова.
Постепенно мы подкрутили ещё контекстозависимость. Теперь числительные перед словом год
становились порядковыми и 2020 год
наконец превратилось в две тысячи двадцатый. Так появился наш "ручной" статистический пайплайн — находим наиболее популярные комбинации и добавляем их в набор правил.
В какой-то момент стало понятно, что архитектура sequence-to-sequence (seq2seq) идеально подходит под описание нашей задачи. Действительно, seq2seq умеет делать всё то же самое, что и "ручной" пайплайн, и даже больше:
График attention весов для готовой модели на последовательности "5 января". Можно заметить, что для генерации окончания "пятОГО" модель учитывает не только "5", но и последующие символы из слова "января".
В качестве основы мы взяли реализацию модели seq2seq на PyTorch отсюда [3]. Не будем подробно останавливаться на архитектуре — лучше прочитайте исходный пост. На вход подавалась последовательность символов из словаря русских букв + латиница + пунктуация + спец токены, на выходе — только русские буквы + пунктуация.
Очевидный факт — качество модели напрямую зависит от качества данных, на которых она училась. Найти такие данные в достаточном количестве для того же английского языка не составляет труда (более того, гуглятся даже готовые решения с открытым кодом) Но вот с русским пришлось попотеть.
Так, для обучающей выборки мы замиксовали:
Нашей целью было не только решение задачи, но и тест разных интересных вещей, одним из которых стал Torchscript.
TorchScript — это замечательный инструмент из PyTorch, с помощью которого можно выкатить свою модель далеко за рамки Python и даже встроить в C++.
Если коротко, PyTorch даёт нам на выбор два пути:
torch.jit.script
(и torch.jit.trace
), работающий из коробки. Как оказалось, для чуть более сложных моделей, чем те, что представлены в гайде [6], придётся переписать пару строк кода. Однако, тут можно обойтись малой кровью: подумать о типизации, переписать неподдерживаемые функции и т.д. Больше подробностей в посте на канале [7].
Как итог, приведу несколько примеров работы уже готового нормализатора. Все предложения взяты из тестового корпуса, то есть во время тренировки модель с ними не встречалась.
norm.norm_string("Вторая по численности группа — фарсиваны — от 27 до 38 %.")
'Вторая по численности группа — фарсиваны — от двадцати семи до тридцати восьми процентов.'
norm.norm_string("Висенте Каньяс родился 22 октября 1939 года")
'Висенте Каньяс родился двадцать второго октября тысяча девятьсот тридцать девятого года'
norm.norm_string("играет песня «The Crying Game»")
'играет песня «зэ краинг гейм»'
norm.norm_string("к началу XVIII века")
'к началу восемнадцатого века'
norm.norm_string("и в 2012 году составляла 6,6 шекеля")
Автор: Islanna
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/open-source/348719
Ссылки в тексте:
[1] Open_STT: https://habr.com/ru/post/474462/
[2] ссылка на github: https://github.com/snakers4/russian_stt_text_normalization
[3] отсюда: https://bastings.github.io/annotated_encoder_decoder/
[4] Russian Text Normalization: https://www.kaggle.com/c/text-normalization-challenge-russian-language/overview
[5] данные со случайных веб-сайтов: https://spark-in.me/post/parsing-common-crawl-in-four-simple-commands
[6] гайде: https://pytorch.org/tutorials/beginner/Intro_to_TorchScript_tutorial.html
[7] посте на канале: https://t.me/snakers4/2418
[8] Источник: https://habr.com/ru/post/491260/?utm_campaign=491260&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.