단어에서 음절 감지


138

단어로 음절을 감지하는 상당히 효율적인 방법을 찾아야합니다. 예 :

보이지 않는-> in-vi-sib-le

사용할 수있는 일부 음절 규칙이 있습니다.

V CV VC CVC CCV CCCV CVCC

* 여기서 V는 모음이고 C는 자음입니다. 예 :

발음 (5 개 발음); CV-CVC-CV-V-CVC)

나는 정규식 (음절을 세고 싶을 때만 도움이 됨) 또는 하드 코딩 된 규칙 정의 (매우 비효율적 인 것으로 판명 된 무력 접근법)를 사용하고 마침내 유한 상태 오토 마타를 사용하는 몇 가지 방법을 시도했습니다. 유용한 결과는 없습니다).

내 응용 프로그램의 목적은 주어진 언어로 모든 음절의 사전을 만드는 것입니다. 이 사전은 나중에 맞춤법 검사 응용 프로그램 (베이지 분류기를 사용) 및 텍스트-음성 합성에 사용됩니다.

이전 접근법 외에도이 문제를 해결할 수있는 다른 방법에 대한 팁을 줄 수 있다면 감사하겠습니다.

Java로 작업하지만 C / C ++, C #, Python, Perl의 팁은 저에게 효과적입니다.


실제로 실제 나눗셈 포인트 또는 단어의 음절 수를 원하십니까? 후자의 경우, 텍스트 음성 변환 사전에서 단어를 찾고 모음 소리를 인코딩하는 음소를 계산하십시오.
Adrian McCarthy

가장 효율적인 방법 (계산 방식; 스토리지 방식이 아닌)은 단어로 키를 사용하고 음절 수를 값으로 사용하여 Python 사전을 갖는 것입니다. 그러나 여전히 사전에 포함되지 않은 단어에 대해서는 대체가 필요합니다. 그런 사전을 찾으면 알려주십시오!
Brōtsyorfuzthrāx

답변:


120

하이픈 넣기 목적으로이 문제에 대한 TeX 접근 방식에 대해 읽으십시오. 특히 Frank Liang의 논문 논문 Hy-phen-a-tion by Comp-put-er 참조 . 그의 알고리즘은 매우 정확하며 알고리즘이 작동하지 않는 경우에 대한 작은 예외 사전을 포함합니다.


52
나는 당신이 주제에 논문 논문을 인용 한 것을 좋아합니다. 이것은 쉬운 질문이 아닐 수도 있다는 원래 포스터에 대한 작은 힌트입니다.
Karl

그렇습니다. 나는 많은 질문을하지는 않았지만 간단한 질문은 아니라는 것을 알고 있습니다. 그래도 문제를 과소 평가했지만 앱의 다른 부분에서 작업하고 나중에이 '간단한'문제로 돌아갈 것이라고 생각했습니다. Silly me :)
user50705

나는 논문을 읽고 매우 도움이되었다는 것을 알았습니다. 이 접근 방식의 문제점은 알바니아 언어에 대한 패턴이 없지만 패턴을 생성 할 수있는 도구를 찾았다는 것입니다. 어쨌든, 내 목적을 위해 규칙 기반 앱을 작성하여 문제를 해결했습니다.
user50705

10
TeX 알고리즘은 음절과 정확히 일치하지 않는 합법적 인 하이픈 포인트를 찾기위한 것입니다. 하이픈 넣기 지점이 음절 구분에 해당하는 것이 사실이지만 모든 음절 구분이 올바른 하이픈 점은 아닙니다. 예를 들어, 하이픈은 (보통) 글자 나 단어의 양쪽 끝에서 사용되지 않습니다. 또한 TeX 패턴은 가양성에 대한가 음성을 차단하도록 조정되었다고 생각합니다 (합법적 인 하이픈 기회가 누락 된 경우에도 하이픈을 속하지 않는 곳에 절대 하이픈을 넣지 마십시오).
Adrian McCarthy

1
나는 하이픈이 답이라고 믿지 않습니다.
Ezequiel

46

나는이 페이지를 우연히 찾아서 같은 것을 찾고, Liang 논문의 몇 가지 구현을 여기에서 발견했다 : https://github.com/mnater/hyphenator 또는 후속 : https://github.com/mnater/Hyphenopoly

고유하지 않은 문제에 대해 자유롭게 사용할 수있는 코드를 적용하는 대신 60 페이지 논문을 읽는 것을 좋아하지 않는 한 그렇지 않습니다. :)


동의 - 훨씬 더 편리 그냥 기존 implmentation 사용
hoju

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 (list (y (-1의 경우 y [-1] .isdigit ())의 경우 x의 d [word.lower ()]) ]
Gourneau

6
그 말에없는 단어의 대체물로 무엇을 제안 하시겠습니까?
Dan Gayle

4
@Pureferret cmudict 는 북미 영어 단어를 발음하는 사전입니다. 음절보다 짧은 음소로 단어를 나눕니다 (예 : 'cat'이라는 단어는 K-AE-T의 세 음소로 나뉩니다). 그러나 모음에는 단어의 발음에 따라 0, 1 또는 2의 "스트레스 마커"도 있습니다 (따라서 'cat'의 AE는 AE1이됩니다). 답변의 코드는 스트레스 마커와 모음 수를 계산하여 음절 수를 효과적으로 제공합니다 (OP의 예에서 각 음절에 정확히 하나의 모음이있는 방법에 유의하십시오).
billy_chapters

1
이것은 음절이 아닌 음절의 수를 반환합니다.
Adam Michael Wood

19

텍스트 블록의 flesch-kincaid 및 flesch reading score를 계산하는 프로그램 에서이 문제를 해결하려고합니다. 내 알고리즘은이 웹 사이트에서 찾은 것 ( 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;
    }

적절한 이름으로 음절을 찾는 간단한 시나리오의 경우 처음에는 충분히 잘 작동하는 것 같습니다. 그것을 넣어 주셔서 감사합니다.
Norman H

7

LaTeX 하이픈 넣기 알고리즘으로 완전히 해결되지 않는 특히 어려운 문제입니다. 사용 가능한 몇 가지 방법과 문제에 대한 요약은 영어 자동 실 라벨 알고리즘 평가 (Marchand, Adsett, Damper 2007)에서 확인할 수 있습니다.


5

왜 계산합니까? 모든 온라인 사전에는이 정보가 있습니다. http://dictionary.reference.com/browse/invisible in · vis · i · ble


3
이름과 같이 사전에 나타나지 않는 단어에 대해서는 효과가있을 수 있습니다.
Wouter Lievens

4
@WouterLievens : 이름이 자동 음절 구문 분석을 위해 충분히 동작하는 곳은 없다고 생각합니다. 영어 이름에 대한 음절 파서는 인도어와 나이지리아 출신의 이름은 물론 웨일스 어 또는 스코틀랜드 출신의 이름에서 비참하게 실패하지만 런던과 같은 어딘가의 단일 방 에서이 모든 것을 찾을 수 있습니다.
Jean-François Corbett

이것은 스케치 영역에 대한 순전 한 휴리스틱 접근법을 고려할 때 인간이 제공 할 수있는 것보다 더 나은 성능을 기대하는 것이 합리적이지 않다는 점을 명심해야합니다.
대런 링거

5

C #에서 빠르고 더러운 구현을 공유해 주신 Joe Basirico에게 감사드립니다. 나는 큰 라이브러리를 사용했지만 작동하지만 일반적으로 약간 느리고 빠른 프로젝트의 경우 방법이 잘 작동합니다.

다음은 테스트 사례와 함께 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를 범핑. 매우 유용한 기능으로 완벽 하지는 않지만 대부분의 중소 프로젝트에 적합합니다. Joe, 나는 당신의 코드의 구현을 파이썬으로 다시 작성했습니다 :

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에는 Lingua :: Phonology :: Syllable 모듈이 있습니다. 시도해 보거나 알고리즘을 살펴보십시오. 나는 거기에 몇 가지 다른 오래된 모듈도 보았습니다.

정규식이 왜 음절 만 제공하는지 이해하지 못합니다. 캡처 괄호를 사용하여 음절 자체를 얻을 수 있어야합니다. 작동하는 정규 표현식을 구성 할 수 있다고 가정하십시오.


4

오늘 저는 Frank Liang의 하이픈 넣기 알고리즘을 영어 또는 독일어 패턴으로 구현 한 Java 구현을 발견 했습니다 .

동굴 : .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;

"\u00ADAPI는 normal을 반환하지 않기 때문에 " 로 분할해야합니다 "-".

이 방법은 다양한 언어를 지원하고 독일어 하이픈을 더 정확하게 감지하므로 Joe Basirico의 답변보다 성능이 우수합니다.


4

나는 조금 전에이 똑같은 문제에 부딪쳤다.

대부분의 단어를 빠르고 정확하게 조회하기 위해 CMU 발음 사전 을 사용했습니다 . 사전에없는 단어의 경우 음절 수를 예측할 때 ~ 98 % 정확한 기계 학습 모델로 돌아 왔습니다.

사용하기 쉬운 파이썬 모듈로 모든 것을 마무리했습니다 : https://github.com/repp/big-phoney

설치: pip install big-phoney

음절 수 :

from big_phoney import BigPhoney
phoney = BigPhoney()
phoney.count_syllables('triceratops')  # --> 4

파이썬을 사용하지 않고 ML 모델 기반 접근 방식을 사용 하려면 음절 계산 모델이 Kaggle에서 어떻게 작동하는지에 대해 자세히 작성했습니다 .


이것은 정말 멋지다. iOS에서 사용하기 위해 결과 Keras 모델을 CoreML 모델로 변환 한 사람이 있습니까?
Alexsander Akers 2016 년

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

"End"와 "I"테스트 케이스를 두 개 더 추가했습니다. 수정은 대소 문자를 구분하지 않고 문자열을 비교하는 것이 었습니다. 동일한 문제로 고통 받고 기능을 업데이트하려는 경우 @ joe-basirico와 tihamer를 Ping'ing합니다.
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

많은 테스트를 수행하고 하이픈 넣기 패키지를 시험해 본 후에 여러 예제를 기반으로 직접 작성했습니다. 또한 하이픈 딕셔너리 사전과 인터페이스 하는 pyhyphenpyphen패키지를 시도했지만 많은 경우 음절 수가 잘못되었습니다. nltk패키지는 단순히이 사용 사례 너무 느렸다.

파이썬으로 구현 한 것은 내가 작성한 클래스의 일부이며 음절 계산 루틴은 아래에 붙여 넣습니다. 조용한 단어 엔딩을 설명하는 좋은 방법을 찾지 못했기 때문에 음절 수를 약간 과대 평가합니다.

이 함수는 Flesch-Kincaid 가독성 점수에 사용되므로 단어 당 음절의 비율을 반환합니다. 숫자는 정확할 필요는 없으며 추정치에 가깝습니다.

7 세대 i7 CPU에서이 함수는 759 단어 샘플 텍스트에 1.1-1.2 밀리 초가 걸렸습니다.

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

일반적인 음절 파서는 어떻습니까? 이 코드는 사전에서만 음절을 찾는 것 같습니다
Nico Haase
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.