Обнаружение слогов в слове

142

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

Невидимый -> in-vi-sib-le

Вот несколько правил слоговой записи, которые можно использовать:

V CV VC CVC CCV CCCV CVCC

* где V - гласная, а C - согласная. Например,

Произношение (5 Pro-nun-ci-a -tion; CV-CVC-CV-V-CVC)

Я пробовал несколько методов, среди которых было использование регулярного выражения (которое помогает только в том случае, если вы хотите подсчитывать слоги) или жестко закодированного определения правила (метод грубой силы, который оказался очень неэффективным) и, наконец, использование конечного автомата (который не приведу ни к чему полезному).

Цель моего приложения - создать словарь всех слогов на данном языке. Этот словарь позже будет использоваться для приложений проверки орфографии (с использованием байесовских классификаторов) и синтеза речи.

Я был бы признателен, если бы можно было дать мне советы по альтернативному способу решения этой проблемы, помимо моих предыдущих подходов.

Я работаю на Java, но любой совет по C / C ++, C #, Python, Perl ... мне подойдет.

user50705
источник
Вы действительно хотите получить фактические точки деления или просто количество слогов в слове? В последнем случае попробуйте найти слова в словаре преобразования текста в речь и посчитайте фонемы, которые кодируют гласные звуки.
Адриан Маккарти,
Я бы предположил, что наиболее эффективным способом (с точки зрения вычислений, а не с точки зрения хранения) было бы просто иметь словарь Python со словами в качестве ключей и количеством слогов в качестве значений. Однако вам все равно понадобится запасной вариант для слов, которых нет в словаре. Дайте мне знать, если найдете такой словарь!
Brōtsyorfuzthrāx

Ответы:

123

Прочтите о подходе TeX к этой проблеме для расстановки переносов. Особенно см. Диссертацию Фрэнка Ляна Word Hy-phen-a -tion by Com-put-er . Его алгоритм очень точен, а также включает небольшой словарь исключений для случаев, когда алгоритм не работает.

Джейсон
источник
53
Мне нравится, что вы процитировали диссертацию на эту тему, это небольшой намек на оригинальный постер, что это может быть непростой вопрос.
Карл
Да, я понимаю, что это непростой вопрос, хотя я над ним мало работал. Однако я недооценил проблему, я думал, что буду работать над другими частями своего приложения, а позже вернусь к этой «простой» проблеме. Глупый я :)
user50705 01
Я прочитал докладную записку, и она мне очень помогла. Проблема с этим подходом заключалась в том, что у меня не было никаких шаблонов для албанского языка, хотя я нашел некоторые инструменты, которые могли бы генерировать эти шаблоны. В любом случае, для своей цели я написал приложение на основе правил, которое решило проблему ...
user50705 03
10
Обратите внимание, что алгоритм TeX предназначен для поиска правильных точек переноса, что не совсем то же самое, что и разделение на слоги. Верно, что точки переноса попадают на слоги, но не все части слогов являются допустимыми точками переноса. Например, дефисы (обычно) не используются внутри одной или двух букв на обоих концах слова. Я также считаю, что шаблоны TeX были настроены так, чтобы заменять ложноотрицательные ложные срабатывания (никогда не ставьте дефис там, где ему не место, даже если это означает упущение некоторых законных возможностей расстановки переносов).
Адриан Маккарти,
1
Я тоже не верю, что расстановка переносов - это ответ.
Эсекьель
46

Я наткнулся на эту страницу, ища то же самое, и нашел здесь несколько реализаций статьи Лян: https://github.com/mnater/hyphenator или преемник: https://github.com/mnater/Hyphenopoly

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

Шон
источник
согласился - гораздо удобнее просто использовать существующую
имплантацию
41

Вот решение с использованием NLTK :

from nltk.corpus import cmudict
d = cmudict.dict()
def nsyl(word):
  return [len(list(y for y in x if y[-1].isdigit())) for x in d[word.lower()]] 
Ходзю
источник
Привет, спасибо, крошечная детская ошибка в функции должна быть def nsyl (word): return [len (list (y for y in x if y [-1] .isdigit ())) for x in d [word.lower ()] ]
Gourneau
6
Что бы вы посоветовали в качестве альтернативы словам, которых нет в этом корпусе?
Dan Gayle
4
@Pureferret cmudict - это словарь произношения слов североамериканского английского языка. он разбивает слова на фонемы, которые короче слогов (например, слово «кошка» разбивается на три фонемы: K - AE - T). но у гласных также есть «маркер ударения»: 0, 1 или 2, в зависимости от произношения слова (поэтому AE в слове «кошка» становится AE1). код в ответе считает маркеры ударения и, следовательно, количество гласных, что фактически дает количество слогов (обратите внимание, как в примерах OP каждый слог имеет ровно одну гласную).
billy_chapters 09
1
Это возвращает количество слогов, а не слоговую форму.
Адам Майкл Вуд
20

Я пытаюсь решить эту проблему для программы, которая будет вычислять оценку чтения по методу flesch-kincaid и flesch для блока текста. В моем алгоритме используется то, что я нашел на этом веб-сайте: http://www.howmanysyllables.com/howtocountsyllables.html, и он становится достаточно близким. У него все еще есть проблемы со сложными словами, такими как невидимое и расстановка переносов, но я обнаружил, что он подходит для моих целей.

Его преимущество в том, что его легко реализовать. Я обнаружил, что «es» могут быть как слоговыми, так и нет. Это авантюра, но я решил убрать es в моем алгоритме.

private int CountSyllables(string word)
    {
        char[] vowels = { 'a', 'e', 'i', 'o', 'u', 'y' };
        string currentWord = word;
        int numVowels = 0;
        bool lastWasVowel = false;
        foreach (char wc in currentWord)
        {
            bool foundVowel = false;
            foreach (char v in vowels)
            {
                //don't count diphthongs
                if (v == wc && lastWasVowel)
                {
                    foundVowel = true;
                    lastWasVowel = true;
                    break;
                }
                else if (v == wc && !lastWasVowel)
                {
                    numVowels++;
                    foundVowel = true;
                    lastWasVowel = true;
                    break;
                }
            }

            //if full cycle and no vowel found, set lastWasVowel to false;
            if (!foundVowel)
                lastWasVowel = false;
        }
        //remove es, it's _usually? silent
        if (currentWord.Length > 2 && 
            currentWord.Substring(currentWord.Length - 2) == "es")
            numVowels--;
        // remove silent e
        else if (currentWord.Length > 1 &&
            currentWord.Substring(currentWord.Length - 1) == "e")
            numVowels--;

        return numVowels;
    }
Джо Басирико
источник
Для моего простого сценария поиска слогов в именах собственных это, кажется, изначально работает достаточно хорошо. Спасибо, что разместили это здесь.
Norman H
8

Это особенно сложная проблема, которая полностью не решается алгоритмом расстановки переносов LaTeX. Хороший обзор некоторых доступных методов и связанных с этим проблем можно найти в статье Evaluating Automatic Syllabification Algorithms for English (Marchand, Adsett, and Damper 2007).

Крис
источник
5

Зачем рассчитывать? Эта информация есть в каждом онлайн-словаре. http://dictionary.reference.com/browse/invisible in · vis · i · ble

Cerin
источник
3
Может быть, он должен работать со словами, которых нет в словарях, например с именами?
Воутер Ливенс,
4
@WouterLievens: Я не думаю, что имена достаточно хороши для автоматического синтаксического анализа слогов. Слоговый синтаксический анализатор английских имен с треском провалился бы с именами валлийского или шотландского происхождения, не говоря уже об именах индийского и нигерийского происхождения, но вы можете найти все это в одной комнате где-нибудь, например, в Лондоне.
Жан-Франсуа Корбетт,
Следует иметь в виду, что неразумно ожидать лучшей производительности, чем может обеспечить человек, учитывая, что это чисто эвристический подход к отрывочной области.
Даррен Рингер,
5

Спасибо Джо Басирико за то, что поделился вашей быстрой и грязной реализацией на C #. Я использовал большие библиотеки, и они работают, но обычно немного медленные, и для быстрых проектов ваш метод работает нормально.

Вот ваш код на Java вместе с тестовыми примерами:

public static int countSyllables(String word)
{
    char[] vowels = { 'a', 'e', 'i', 'o', 'u', 'y' };
    char[] currentWord = word.toCharArray();
    int numVowels = 0;
    boolean lastWasVowel = false;
    for (char wc : currentWord) {
        boolean foundVowel = false;
        for (char v : vowels)
        {
            //don't count diphthongs
            if ((v == wc) && lastWasVowel)
            {
                foundVowel = true;
                lastWasVowel = true;
                break;
            }
            else if (v == wc && !lastWasVowel)
            {
                numVowels++;
                foundVowel = true;
                lastWasVowel = true;
                break;
            }
        }
        // If full cycle and no vowel found, set lastWasVowel to false;
        if (!foundVowel)
            lastWasVowel = false;
    }
    // Remove es, it's _usually? silent
    if (word.length() > 2 && 
            word.substring(word.length() - 2) == "es")
        numVowels--;
    // remove silent e
    else if (word.length() > 1 &&
            word.substring(word.length() - 1) == "e")
        numVowels--;
    return numVowels;
}

public static void main(String[] args) {
    String txt = "what";
    System.out.println("txt="+txt+" countSyllables="+countSyllables(txt));
    txt = "super";
    System.out.println("txt="+txt+" countSyllables="+countSyllables(txt));
    txt = "Maryland";
    System.out.println("txt="+txt+" countSyllables="+countSyllables(txt));
    txt = "American";
    System.out.println("txt="+txt+" countSyllables="+countSyllables(txt));
    txt = "disenfranchized";
    System.out.println("txt="+txt+" countSyllables="+countSyllables(txt));
    txt = "Sophia";
    System.out.println("txt="+txt+" countSyllables="+countSyllables(txt));
}

Результат оказался ожидаемым (для Флеша-Кинкейда он работает достаточно хорошо):

txt=what countSyllables=1
txt=super countSyllables=2
txt=Maryland countSyllables=3
txt=American countSyllables=3
txt=disenfranchized countSyllables=5
txt=Sophia countSyllables=2
Тихамер
источник
5

Удар @Tihamer и @ joe-basirico. Очень полезная функция, не идеальная , но подходящая для большинства небольших и средних проектов. Джо, я переписал реализацию вашего кода на Python:

def countSyllables(word):
    vowels = "aeiouy"
    numVowels = 0
    lastWasVowel = False
    for wc in word:
        foundVowel = False
        for v in vowels:
            if v == wc:
                if not lastWasVowel: numVowels+=1   #don't count diphthongs
                foundVowel = lastWasVowel = True
                        break
        if not foundVowel:  #If full cycle and no vowel found, set lastWasVowel to false
            lastWasVowel = False
    if len(word) > 2 and word[-2:] == "es": #Remove es - it's "usually" silent (?)
        numVowels-=1
    elif len(word) > 1 and word[-1:] == "e":    #remove silent e
        numVowels-=1
    return numVowels

Надеюсь, кто-то сочтет это полезным!

Tersosauros
источник
4

Perl имеет модуль Lingua :: Phonology :: Syllable . Вы можете попробовать это или попробовать изучить его алгоритм. Я видел там и несколько других старых модулей.

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

скипоппи
источник
4

Сегодня я нашел эту Java-реализацию алгоритма расстановки переносов Фрэнка Ляна с шаблоном для английского или немецкого языков, который работает довольно хорошо и доступен на Maven Central.

Cave: важно удалить последние строки .texфайлов шаблонов, потому что в противном случае эти файлы не могут быть загружены с текущей версией в Maven Central.

Чтобы загрузить и использовать hyphenator, вы можете использовать следующий фрагмент кода Java. texTable- это имя .texфайлов, содержащих нужные шаблоны. Эти файлы доступны на сайте проекта на github.

 private Hyphenator createHyphenator(String texTable) {
        Hyphenator hyphenator = new Hyphenator();
        hyphenator.setErrorHandler(new ErrorHandler() {
            public void debug(String guard, String s) {
                logger.debug("{},{}", guard, s);
            }

            public void info(String s) {
                logger.info(s);
            }

            public void warning(String s) {
                logger.warn("WARNING: " + s);
            }

            public void error(String s) {
                logger.error("ERROR: " + s);
            }

            public void exception(String s, Exception e) {
                logger.error("EXCEPTION: " + s, e);
            }

            public boolean isDebugged(String guard) {
                return false;
            }
        });

        BufferedReader table = null;

        try {
            table = new BufferedReader(new InputStreamReader(Thread.currentThread().getContextClassLoader()
                    .getResourceAsStream((texTable)), Charset.forName("UTF-8")));
            hyphenator.loadTable(table);
        } catch (Utf8TexParser.TexParserException e) {
            logger.error("error loading hyphenation table: {}", e.getLocalizedMessage(), e);
            throw new RuntimeException("Failed to load hyphenation table", e);
        } finally {
            if (table != null) {
                try {
                    table.close();
                } catch (IOException e) {
                    logger.error("Closing hyphenation table failed", e);
                }
            }
        }

        return hyphenator;
    }

После этого Hyphenatorвсе готово к использованию. Основная идея для определения слогов - разделить термин по имеющимся дефисам.

    String hyphenedTerm = hyphenator.hyphenate(term);

    String hyphens[] = hyphenedTerm.split("\u00AD");

    int syllables = hyphens.length;

Вам нужно разделить на "\u00AD", так как API не возвращает нормальный "-".

Этот подход превосходит ответ Джо Басирико, поскольку он поддерживает множество разных языков и более точно определяет немецкие расстановки переносов.

Rzo
источник
4

Некоторое время назад я столкнулся с этой же проблемой.

В итоге я использовал Словарь произношения CMU для быстрого и точного поиска большинства слов. Для слов, которых нет в словаре, я вернулся к модели машинного обучения, которая ~ 98% точна при прогнозировании количества слогов.

Я обернул все это в простой в использовании модуль Python здесь: https://github.com/repp/big-phoney

Установить: pip install big-phoney

Считайте слоги:

from big_phoney import BigPhoney
phoney = BigPhoney()
phoney.count_syllables('triceratops')  # --> 4

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

Райан Эпп
источник
Это супер круто. Кому-нибудь повезло преобразовать полученную модель Keras в модель CoreML для использования на iOS?
Alexsander Akers
2

Спасибо @ joe-basirico и @tihamer. Я портировал код @ tihamer на Lua 5.1, 5.2 и luajit 2 ( скорее всего, он будет работать и на других версиях lua ):

countsyllables.lua

function CountSyllables(word)
  local vowels = { 'a','e','i','o','u','y' }
  local numVowels = 0
  local lastWasVowel = false

  for i = 1, #word do
    local wc = string.sub(word,i,i)
    local foundVowel = false;
    for _,v in pairs(vowels) do
      if (v == string.lower(wc) and lastWasVowel) then
        foundVowel = true
        lastWasVowel = true
      elseif (v == string.lower(wc) and not lastWasVowel) then
        numVowels = numVowels + 1
        foundVowel = true
        lastWasVowel = true
      end
    end

    if not foundVowel then
      lastWasVowel = false
    end
  end

  if string.len(word) > 2 and
    string.sub(word,string.len(word) - 1) == "es" then
    numVowels = numVowels - 1
  elseif string.len(word) > 1 and
    string.sub(word,string.len(word)) == "e" then
    numVowels = numVowels - 1
  end

  return numVowels
end

И несколько забавных тестов, чтобы подтвердить, что он работает ( в той степени, в которой он должен ):

countsyllables.tests.lua

require "countsyllables"

tests = {
  { word = "what", syll = 1 },
  { word = "super", syll = 2 },
  { word = "Maryland", syll = 3},
  { word = "American", syll = 4},
  { word = "disenfranchized", syll = 5},
  { word = "Sophia", syll = 2},
  { word = "End", syll = 1},
  { word = "I", syll = 1},
  { word = "release", syll = 2},
  { word = "same", syll = 1},
}

for _,test in pairs(tests) do
  local resultSyll = CountSyllables(test.word)
  assert(resultSyll == test.syll,
    "Word: "..test.word.."\n"..
    "Expected: "..test.syll.."\n"..
    "Result: "..resultSyll)
end

print("Tests passed.")
Josefnpat
источник
Я добавил еще два тестовых случая «Конец» и «Я». Исправление заключалось в том, чтобы сравнивать строки без учета регистра. Ping'ing @ joe-basirico и tihamer, если они страдают той же проблемой и хотят обновить свои функции.
josefnpat
@tihamer American - это 4 слога!
josefnpat
2

Я не мог найти адекватного способа подсчета слогов, поэтому сам разработал метод.

Вы можете просмотреть мой метод здесь: https://stackoverflow.com/a/32784041/2734752

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

Вы можете просмотреть мою библиотеку здесь: https://github.com/troywatson/Lawrence-Style-Checker

Я только что протестировал свой алгоритм и получил 99,4% результатов!

Lawrence lawrence = new Lawrence();

System.out.println(lawrence.getSyllable("hyphenation"));
System.out.println(lawrence.getSyllable("computer"));

Выход:

4
3
тройка
источник
См. Подсветка синтаксиса . В редакторе SO есть кнопка справки (знак вопроса), которая приведет вас к связанной странице.
IKavanagh
0

После большого количества тестов и опробования пакетов расстановки переносов я написал свой собственный, основанный на ряде примеров. Я также попробовал pyhyphenи pyphenпакеты , что интерфейсы с переносами словарями, но они производят неверное число слогов во многих случаях. nltkПакет был просто слишком медленно для этого случая использования.

Моя реализация на Python является частью написанного мной класса, а процедура подсчета слогов вставлена ​​ниже. Он немного переоценивает количество слогов, поскольку я до сих пор не нашел хорошего способа учесть тихие окончания слов.

Функция возвращает соотношение слогов на слово, используемое для оценки читабельности Флеша-Кинкейда. Число не обязательно должно быть точным, достаточно близким для оценки.

На моем процессоре i7 7-го поколения эта функция занимала 1,1–1,2 миллисекунды для образца текста из 759 слов.

def _countSyllablesEN(self, theText):

    cleanText = ""
    for ch in theText:
        if ch in "abcdefghijklmnopqrstuvwxyz'’":
            cleanText += ch
        else:
            cleanText += " "

    asVow    = "aeiouy'’"
    dExep    = ("ei","ie","ua","ia","eo")
    theWords = cleanText.lower().split()
    allSylls = 0
    for inWord in theWords:
        nChar  = len(inWord)
        nSyll  = 0
        wasVow = False
        wasY   = False
        if nChar == 0:
            continue
        if inWord[0] in asVow:
            nSyll += 1
            wasVow = True
            wasY   = inWord[0] == "y"
        for c in range(1,nChar):
            isVow  = False
            if inWord[c] in asVow:
                nSyll += 1
                isVow = True
            if isVow and wasVow:
                nSyll -= 1
            if isVow and wasY:
                nSyll -= 1
            if inWord[c:c+2] in dExep:
                nSyll += 1
            wasVow = isVow
            wasY   = inWord[c] == "y"
        if inWord.endswith(("e")):
            nSyll -= 1
        if inWord.endswith(("le","ea","io")):
            nSyll += 1
        if nSyll < 1:
            nSyll = 1
        # print("%-15s: %d" % (inWord,nSyll))
        allSylls += nSyll

    return allSylls/len(theWords)
Jadzia626
источник
-2

Я однажды использовал jsoup для этого. Вот пример синтаксического анализатора слогов:

public String[] syllables(String text){
        String url = "https://www.merriam-webster.com/dictionary/" + text;
        String relHref;
        try{
            Document doc = Jsoup.connect(url).get();
            Element link = doc.getElementsByClass("word-syllables").first();
            if(link == null){return new String[]{text};}
            relHref = link.html(); 
        }catch(IOException e){
            relHref = text;
        }
        String[] syl = relHref.split("·");
        return syl;
    }
Итамар Фиорино
источник
1
Как это обычный синтаксический анализатор слогов? Похоже, этот код ищет только слоги в словаре
Нико Хаасе,