Все статьи

· Casimiro Ferreira· 10 мин чтения

Как устроен фонологический стек

  • G2P
  • IPA
  • Phonetics
  • NLP
  • TTS
  • Linguistics
  • FOSS

Возьмём английское слово «read». На письме оно не говорит вам, как его произносить. «I read the book yesterday» и «I read the book every day» используют одни и те же пять букв для двух разных звуков — один рифмуется с «red», другой с «reed». Экранный диктор, голосовой ассистент или строка поиска, которые смотрят только на написание, не могут сделать это правильно. Нужно рассуждать о произношении, а не только о тексте.

Именно эту задачу рассуждения — превращение написанных слов в звуки, которые они представляют — решает наш фонологический стек. Этот пост — карта того, как его части складываются вместе, от сырой нотации до языкоспецифичных движков произношения и поиска по звучанию.

Несколько терминов, простыми словами

Несколько слов, которые встречаются на протяжении всего текста:

  • Графема: письменный символ — буква или сочетание букв вроде «ch».
  • Фонема: отдельная единица звука в языке, например звук «к» в «кот».
  • IPA (Международный фонетический алфавит): стандартный алфавит для точной записи звуков, независимый от орфографии какого-либо языка. «Cat» записывается как kæt в IPA.
  • G2P (графема-в-фонему): общая задача преобразования написания в звук.
  • Аллофон: вариант реализации одной и той же фонемы в зависимости от контекста — «т» в «top» и «т» в «stop» — это одна и та же фонема в английском, но произносятся они слегка по-разному.
  • Слогоделение: разбиение слова на слоги, например «extraordinário» на ex-tra-or-di-ná-ri-o.
  • Омограф: два слова, написанные одинаково, но с разными значениями; гетерофонный омограф (или гетерофон) — это омограф, произносимый по-разному в зависимости от того, какое значение имеется в виду, как «read»/«read» выше.
  • Морфология: внутренняя структура слов — приставки, корни, суффиксы, флексии.
  • Морфосинтаксическая разметка (POS-теггинг): обозначение каждого слова в предложении как существительного, глагола, прилагательного и так далее.

Основная проблема

Написание — это лоссовое кодирование звука. Три отдельные вещи мешают его обратить:

  1. Неоднозначность. Одни и те же буквы могут отображаться в разные звуки в зависимости от значения, грамматики или просто нерегулярности («read» выше; английский полон подобных случаев).
  2. Диалект. Одно и то же слово, на одном и том же языке, произносится по-разному в зависимости от того, откуда говорящий. Европейский и бразильский португальский разделяют написание, но не гласные.
  3. Покрытие. У большинства языков мира вообще нет профессионально составленного словаря произношения. Система G2P, работающая только через таблицу поиска, — это система, работающая только для горстки языков.

Любая серьёзная попытка синтеза речи, обучающих данных для распознавания речи или фонетически осведомлённого поиска должна справляться со всеми тремя.

Почему стек слоистый

Стек разбивает задачу на слои, которым не нужно знать друг о друге:

  • Нотация — преобразование между фонетическими алфавитами и системами письма. Это никак не связано с фонологией какого-либо конкретного языка; это перевод символов.
  • Фонология — отображение написания в IPA для данного языка с использованием спецификации звуковой системы этого языка.
  • Языкоспецифичная обработка исключений — нерегулярные слова, диалектные особенности, омографы и морфологическая структура, которые общий движок не может вывести из одних лишь орфографических правил.

Разделение этих слоёв — осознанное архитектурное решение, а не случайность, и оно даёт прямую отдачу: добавление нового языка означает написание спецификации (данных, описывающих его звуковую систему), а не новой программы. Движок, потребляющий спецификацию, поиск по решётке, токенизатор, метрики расстояния — ничто из этого не переписывается. Слой нотации под всем этим общий для каждого языка, включая те, о которых фонологический движок никогда не слышал.

Слой нотации: scriptconv

scriptconv — это ядро без зависимостей для фонетической нотации и обработки систем письма: определение письменности по ISO-15924, преобразования IPA в ARPABET и обратно, X-SAMPA, Lexique, Kirshenbaum, нотации Cotovía и RFE, транслитерация Buckwalter для арабского, разложение хангыля на джамо и обработка каны. Ничто из этого не требует знания того, к какому языку относится слово — строка фонем в IPA конвертируется в ARPABET одинаково независимо от исходного языка:

>>> import scriptconv as s
>>> s.ipa_to_arpa("kæt")
'K AE T'
>>> s.ipa_to_xsampa("kæt")
'k{t'

Каждый слой над этим может считать преобразование нотации уже решённой задачей.

Движок: orthography2ipa

orthography2ipa — это межъязыковой движок. Он принимает спецификацию языка — декларативное описание правил графема-в-фонему для этого языка — и фрагмент текста, и производит IPA. На момент написания этой статьи он поставляется со спецификациями, покрывающими 820 языков (available_codes() в установленном пакете возвращает список такой длины; относитесь к точной цифре как к движущейся цели, поскольку спецификации добавляются со временем).

>>> import orthography2ipa as o
>>> len(o.available_codes())
820

Сам движок не содержит запечённого языкоспецифичного кода. Новый язык — это новый файл спецификации, проверяемый по той же схеме, что и любая другая спецификация.

Решётка: ранжированные кандидаты, а не одна догадка

Учитывая проблему неоднозначности выше, выбор единственного результата на слово часто ошибочен. Вместо этого orthography2ipa производит решётку — набор ранжированных кандидатов произношения — и позволяет более высоким слоям сужать её, используя контекст, которого у самого движка нет (значение, часть речи, запись в лексиконе).

Возьмём снова «read»:

>>> from orthography2ipa import G2P
>>> g = G2P("en")
>>> g.transcribe("read")
'ɹiːd'
>>> g.candidates("read")
[IPAPath('ɹiːd', score=0.0), IPAPath('ɹɛd', score=1.0)]

Без дополнительного контекста движок возвращает свою лучшую догадку (настоящее время, меньшая стоимость), но сохраняет альтернативу (прошедшее время) в решётке с прикреплённой к ней стоимостью. Нижестоящий компонент, который знает, что предложение стоит в прошедшем времени, может выбрать второго кандидата вместо первого. Это та же идея, что используется, в большем масштабе, библиотекой bifonia (ниже) для португальских гетерофонов: решётка общего назначения поставляет кандидатов, а более узкий, лучше информированный слой выбирает среди них.

Диалекты как полноправные объекты

Два носителя одного языка могут произносить одно и то же предложение по-разному, и фонологический стек, который трактует «португальский» как единую фиксированную звуковую систему, ошибётся во всех диалектах, кроме одного. orthography2ipa раскрывает обработку диалектов напрямую — available_profiles() в установленном пакете перечисляет диалектные и лектные профили, такие как lisbon, porto, estremenho, galician и другие — а tugaphone, построенный на нём португальский фронтенд, фонемизирует одно и то же предложение по всем лузофонным вариантам. Вот одно предложение, пропущенное через все пять поддерживаемых диалектов:

ДиалектРезультат
pt-PT (Португалия)ˈbõ ˈdiɐ ˈkomu eˈʃta vɔˈse
pt-BR (Бразилия)ˈbõ ˈdʒiɐ ˈkɔ̃mʊ eˈsta voˈse
pt-AO (Ангола)ˈbõ ˈdiɐ ˈkomʊ eˈsta vɔˈse
pt-MZ (Мозамбик)ˈbõ ˈdiɐ ˈkomu eˈsta vɔˈse
pt-TL (Восточный Тимор)ˈbõ ˈdiə ˈkoɔmʊ eˈsta vɔˈse

(«Bom dia, como está você?» — «Доброе утро, как у вас дела?») Согласный скелет остаётся узнаваемым во всех пяти, но два хорошо известных маркера сразу их разделяют. В «dia» бразильский португальский превращает d перед i в — звук в начале английского «jam» — остальные сохраняют обычный d. В «está» европейский португальский произносит s в конце слога как ʃ — «ш» в «shoe» — а любой другой вариант сохраняет s. Словарь произношения, построенный на правилах одного диалекта, ошибётся в обоих этих случаях для слушателя любого другого диалекта.

euskaphone делает то же самое для баскских диалектов, построенный прямо на решётке orthography2ipa, а не на отдельном движке:

>>> from euskaphone import EuskaPhonemizer
>>> EuskaPhonemizer().phonemize_sentence("Kaixo, zer moduz zaude?")
'kai̯ʃo s̻er modus̻ s̻au̯de'

Языкоспецифичные фронтенды

Над общим движком стоят фронтенды, которые добавляют то, что не может общая спецификация: нерегулярные слова, курируемый лексикон, сандхи (звуковые изменения на границах слов) и диалект-специфичные переопределения.

  • tugaphone — португальский, по pt-PT, pt-BR, pt-AO, pt-MZ и pt-TL, сочетающий курируемый лексикон с откатом на основе правил (показано выше).

  • euskaphone — баскский, с учётом диалектов, построенный на той же решётке (показано выше).

  • mwl_phonemizer — мирандский, астурлеонский язык Terra de Miranda, Португалия, с межсловным сандхи, аллофонией и ударением:

    >>> from mwl_phonemizer import phonemize
    >>> phonemize("Falo la lhéngua mirandesa.")
    'ˈfalu lɐ ˈʎɛŋɡwa miɾɐˈndez̺ɐ.'
  • g2p_barranquenho — первый открытый G2P для барранкеньу, иберо-романского контактного языка Барранкуша, на границе Португалии и Испании. См. Представляем первый фонемизатор для барранкеньу о том, как его правила были выведены из собственной орфографической конвенции муниципалитета.

  • arbtok — арабский, построенный на решётке orthography2ipa, добавляющий диалект-осведомлённую диакритизацию и покрывающий современный стандартный, классический и ряд региональных вариантов. Арабское письмо обычно опускает знаки кратких гласных, необходимые фонемизатору, так что главная задача arbtok — восстановить их, прежде чем передать результат общему движку. Его поддерживает человек, для которого арабский не родной, так что относитесь к нему как к активно разрабатываемому, а не законченному, проверенному носителем языка референсу — полезен, но результат стоит перепроверить у носителя языка перед использованием в чём-либо, обращённом к пользователю.

Каждый из этих фронтендов — тонкий слой языкоспецифичной логики поверх одного и того же общего движка решётки и одного и того же общего слоя нотации под ним. Ни один из них не переписывает заново преобразование IPA или поиск по решётке.

Вспомогательные инструменты для португальского

У португальского самый глубокий стек, потому что произношение португальского зависит не только от орфографических правил: оно зависит от слоговой структуры, части речи, а иногда и просто от значения.

  • silabificador разбивает слова на слоги по вручную составленным правилам:

    >>> from silabificador import syllabify
    >>> syllabify("extraordinário")
    ['ex', 'tra', 'or', 'di', 'ná', 'ri', 'o']
  • tugalex — это лексикон, стоящий за tugaphone: транскрипции IPA, данные о слогах и орфографические правила для реальных слов, так что общую и нерегулярную лексику не приходится каждый раз заново выводить из написания.

  • tugatagger оборачивает несколько бэкендов POS-теггинга (spaCy, Stanza, теггер в стиле Брилла, эвристический откат без зависимостей) за одним интерфейсом, так что другие инструменты могут спросить «какая часть речи у этого слова», не привязываясь к одному конкретному бэкенду.

  • tugamorph — морфологический анализатор на основе правил: он разбивает слово на приставку, корень, суффикс, флексию и клитику, используя только стандартную библиотеку Python, опционально уточняемый silabificador и tugatagger.

  • bifonia разрешает гетерофонные омографы европейского португальского — слова вроде «sede» (жажда, ˈsedɨ, против штаб-квартиры, ˈsɛdɨ), где правильное произношение зависит от значения, а не от грамматики. См. Как сказать правильно: устранение неоднозначности португальских гетерофонов для TTS о том, как это было построено и оценено. Это конкретный случай, стоящий за идеей решётки выше: orthography2ipa может предоставить оба варианта чтения «sede», но только слой, осведомлённый о значении, такой как bifonia, может выбрать между ними.

Подробнее о том, как silabificador и tugaphone работают вместе изо дня в день, см. Классический NLP для португальского: слогоделение и графема-фонема, а о более широком движке, лежащем в основе всего этого, — Графема-в-IPA для 820 языков.

Поиск по звучанию: phonematcher

Всё вышеперечисленное превращает текст в звук. phonematcher работает с самими звуковыми представлениями: он вычисляет фонетическое расстояние между символами IPA и выполняет нечёткий поиск по спискам слов на основе того, как слова звучат, а не как они пишутся.

>>> from phonematcher.distance import phonetic_distance
>>> phonetic_distance('b', 'p')   # voiced vs. voiceless bilabial stop — very similar
0.043478260869565216
>>> phonetic_distance('p', 'k')   # bilabial vs. velar stop — less similar
0.34782608695652173
>>> phonetic_distance('a', 'k')   # vowel vs. consonant — maximally different
1.0

Эта метрика расстояния полезна в двух конкретных ситуациях: поиск по каталогу слов или имён по тому, как что-то звучит, а не по точному написанию (полезно для голосовых интерфейсов, терпимых к опечаткам, и для сопоставления заимствований между системами письма), и сравнение того, насколько фонологически близки два родственных лекта — тот же вид сравнения, что таблица диалектов выше делает на глаз, но вычисленный, а не оценённый визуально. phonematcher отсутствует на PyPI; он устанавливается из исходников (pip install -e . на клон с GitHub, плюс rapidfuzz).

Честные ограничения

Покрытие по 820 языковым спецификациям неравномерно по построению: языки с устоявшейся фонологической литературой и лексиконом дают лучший результат, чем языки с тонкой спецификацией, выведенной в основном из общих орфографических соглашений. Качество стабильно наилучшее там, где существует курируемый лексикон — португальский, подкреплённый tugalex, самый сильный случай в стеке; языки, опирающиеся исключительно на правила спецификации без лексикона, будут неверно обрабатывать нерегулярную и заимствованную лексику.

Несколько компонентов явно не являются законченными, проверенными носителями языка референсами: arbtok поддерживается неносителем арабского и должен быть проверен по суждению носителя языка перед использованием в чём-либо, обращённом к пользователю. Фронтенды, построенные на тонких спецификациях, наследуют эту тонкость — фронтенд настолько же хорош, насколько хороши спецификация и лексикон под ним.

Почему это важно, если у вашего языка нет речевого инструментария

У большинства языков мира нет коммерческого голоса TTS, коммерческой модели STT и профессионально поддерживаемого словаря произношения. Слоистый дизайн выше означает, что этот пробел не требует построения фонологического движка с нуля: он требует написания спецификации звуковой системы целевого языка и, где возможно, лексикона его нерегулярных слов. Движок решётки, преобразования нотации и инструментарий поиска уже существуют. Если вашему языку, диалекту или продукту нужна поддержка произношения, которой ещё не существует, — это тот вид работы, за который мы беремся — см. наши услуги или свяжитесь с нами.