Вопрос

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

Невидимый -> 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...мне бы подошло.

Это было полезно?

Решение

Прочтите о подходе TeX к этой проблеме при расстановке переносов.Особенно см. Фрэнка Ляна. диссертация Слово перенос с помощью компьютера.Его алгоритм очень точен и включает небольшой словарь исключений для случаев, когда алгоритм не работает.

Другие советы

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

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

Вот решение с использованием НЛТК:

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()]] 

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

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

Спасибо Джо Басирико за то, что поделился своей быстрой и грязной реализацией на 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

Сталкиваемся с @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 не возвращает нормальный "-".

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

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

Спасибо @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 и хотите попробовать подход на основе модели машинного обучения, я сделал довольно подробное описание. напишите, как работает модель подсчета слогов на Kaggle.

Проведя большое тестирование и опробовав пакеты расстановки переносов, я написал свой собственный на основе ряда примеров.Я также попробовал 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)

Я использовал 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;
    }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top