Визначення складів у слові


138

Мені потрібно знайти досить ефективний спосіб виявлення складів у слові. Наприклад,

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

Є деякі правила конспектування, які можна використовувати:

V CV VC CVC CCV CCCV CCCC CVCC

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

Вимова (5 Pro-nun-ci-a -ation; CV-CVC-CV-V-CVC)

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

Мета моєї програми - створити словник усіх складів на даній мові. Цей словник згодом буде використовуватися для перевірки орфографії (за допомогою байєсівських класифікаторів) та синтезу тексту в мові.

Буду вдячний, якби можна було дати мені поради щодо альтернативного способу вирішення цієї проблеми, окрім попередніх підходів.

Я працюю на Java, але будь-яка порада в C / C ++, C #, Python, Perl ... працювала б для мене.


Ви дійсно хочете фактичних точок поділу чи просто кількості складів одним словом? Якщо остання, розгляньте пошук слів у словнику текстового мовлення та порахуйте фонеми, що кодують голосні звуки.
Адріан Маккарті

Я вважаю, що найефективнішим способом (обчислювальним, а не для зберігання даних) буде просто мати словник Python зі словами як ключі та кількістю складів як значеннями. Однак вам все одно знадобиться резервне слово для слів, які не внесли їх у словник. Дайте мені знати, якщо ви коли-небудь знайдете такий словник!
Brōtsyorfuzthrāx

Відповіді:


120

Прочитайте про підхід TeX до цієї проблеми для переносу. Особливо дивіться дисертацію Франка Ліанга на тему « Слово Хе-фен-а -ція» Ком-пут-ером . Його алгоритм дуже точний, а потім включає невеликий словник винятків для випадків, коли алгоритм не працює.


52
Мені подобається, що ви цитували дисертацію на тему, це трохи натякає оригінальному плакату, що це може бути непростим питанням.
Карл

Так, я знаю, що це не просте запитання, хоча я над цим мало працював. Я все ж недооцінював проблему, я думав, що буду працювати над іншими частинами свого додатка, а згодом повернусь до цієї «простої» проблеми.
Дурно

Я прочитав документ про дисертацію і вважав це дуже корисним. Проблема з підходом полягала в тому, що я не мав жодної схеми албанської мови, хоча я знайшов деякі інструменти, які могли б генерувати ці зразки. У будь-якому разі, для своїх цілей я написав додаток на основі правил, який вирішив проблему ...
user50705

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 (список (y для y в x, якщо y [-1] .isdigit ())) для x in d [word.lower ()] ]
Гурно

6
Що б ви запропонували як запасний варіант для слів, які не містяться в цьому корпусі?
Ден Гейл

4
@Pureferret cmudict - це вимовляючи словник для північноамериканських англійських слів. він розбиває слова на фонеми, які коротші складів (наприклад, слово «кішка» розділено на три фонеми: K - AE - T). але голосні також мають "маркер наголосу": або 0, 1, або 2, залежно від вимови слова (так AE в "cat" стає AE1). код у відповіді підраховує маркери наголосу, а отже, кількість голосних - що фактично дає кількість складів (зауважте, як у прикладах ОП кожен склад має рівно один голосний).
billy_chapters

1
Це повертає кількість складів, а не склад.
Адам Майкл Вуд

19

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

Для мого простого сценарію пошуку складів у власних іменах це, здається, спочатку працює досить добре. Дякуємо, що виклали його тут.
Норман Н

7

Це особливо складна проблема, яка не повністю вирішена алгоритмом переносу LaTeX. Хороший підсумок деяких доступних методів та пов'язаних з цим проблем може бути знайдений у статті « Оцінка алгоритмів автоматичного синтезування англійською мовою» (Marchand, Adsett, and Damper 2007).


5

Навіщо його обчислювати? Кожен онлайн-словник має цю інформацію. http://dictionary.reference.com/browse/invisible in · vis · i · ble


3
Можливо, це має працювати для слів, які не відображаються в словниках, наприклад, назви?
Wouter Lievens

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));
}

Результат був таким, як очікувалося (він працює досить добре для 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

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

Сподіваюся, хтось вважає це корисним!


4

У Perl є лінгва :: фонологія :: модуль складання . Ви можете спробувати це або спробувати вивчити його алгоритм. Я також побачив кілька інших старих модулів.

Я не розумію, чому звичайний вираз дає вам лише кількість складів. Ви повинні мати можливість складати самі склади, використовуючи круглі дужки. Припускаючи, що ви можете побудувати регулярний вираз, який працює, тобто.


4

Сьогодні я знайшов цю реалізацію 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 не повертає нормальне значення "-".

Цей підхід перевершує відповідь Джо Басіріко, оскільки він підтримує багато різних мов і більш точно визначає німецьку дефіс.


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 і хочете спробувати підхід на основі ML-моделі, я зробив досить детальну інформацію про те, як працює модель підрахунку складів на Kaggle .


Це супер круто. Хто-небудь пощастив перетворити отриману модель Кераса в модель CoreML для використання на iOS?
Александер Акерс

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.")

Я додав ще два тестові випадки "Кінець" та "Я". Виправленням було порівняння рядків рядків без чутливості. 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 є частиною класу, який я написав, і рутина підрахунку складів вставляється нижче. Це трохи переоцінює кількість складів, оскільки я досі не знайшов хорошого способу обліку мовчазних закінчень слів.

Функція повертає відношення складів на слово, оскільки воно використовується для оцінки читабельності Flesch-Kincaid. Число не повинно бути точним, достатньо близько для оцінки.

У моєму процесорі 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)

-1

Я використовував 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;
    }

Як це загальний аналізатор складів? Схоже, цей код шукає лише склади у словнику
Ніко Хааз
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.