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