Определения слогов в слове
Мне нужно найти довольно эффективный способ определения слогов в слове. Например,
невидимый -> 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 ответов:
прочитайте о подходе 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; }