Определения слогов в слове


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

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

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

V РЕЗЮМЕ ВИРТУАЛЬНЫЙ КАНАЛ Вах ККТ Зарядки CVCC

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

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

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

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

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

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

15 121

15 ответов:

прочитайте о подходе TeX к этой проблеме для целей переноса. Особенно смотрите Фрэнка Ляна диссертационныйслово хй-фен-телевизор-вом с COM-положить-РП. Его алгоритм очень точен, а затем включает в себя небольшой словарь исключений для случаев, когда алгоритм не работает.

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

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

вот решение с помощью 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()]] 

Я пытаюсь решить эту проблему для программы, которая будет вычислять оценку чтения 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;
    }

Это особенно сложная проблема, которая не полностью решена алгоритмом переноса латекса. Хорошее резюме некоторых доступных методов и связанных с ними проблем можно найти в документе оценки автоматических алгоритмов переносов для английского (Маршан, Адсетт, и демпфер 2007).

спасибо Джо Basirico, для обмена быстрой и грязной реализации в 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));
}

результат был ожидаемым (он работает достаточно хорошо для Flesch-Kincaid):

txt=what countSyllables=1
txt=super countSyllables=2
txt=Maryland countSyllables=3
txt=American countSyllables=3
txt=disenfranchized countSyllables=5
txt=Sophia countSyllables=2

натыкаясь на @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

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

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

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

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

пещера: важно удалить последние строки .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 не возвращает нормальное "-".

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

зачем ее вычислять? Каждый онлайн словарь имеет эту информацию. http://dictionary.reference.com/browse/invisible in * vis·i * ble

спасибо @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.")

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

вы можете просмотреть мой метод здесь: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

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

Я в конечном итоге с помощью словарь произношения 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 и хотите попробовать подход на основе ML-модели, я сделал довольно подробный напишите о том, как модель подсчета слогов работает на Kaggle.

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

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

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

на моем процессоре 7-го поколения i7 эта функция заняла 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)

я использовал 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;
    }