检测单词中的音节


137

我需要找到一种相当有效的方法来检测单词中的音节。例如,

看不见-> 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的任何技巧都可以为我工作。


您实际上是想要实际的分割点还是仅需要一个单词中的音节数?如果是后者,请考虑在文本语音词典中查找单词并计算编码元音的音素。
Adrian McCarthy

我猜想,最有效的方式(从计算方式而不是从存储方式)是将Python字典作为关键字,将单词数作为值。但是,对于在词典中没有使用的单词,您仍然需要进行后备。让我知道您是否找到过这样的字典!
Brōtsyorfuzthrāx

Answers:


119

阅读有关TeX解决此问题的方法,以进行连接。特别是请参见计算机作者弗兰克·梁(Frank Liang)的论文论文 “字样”。他的算法非常准确,然后针对该算法不起作用的情况提供了一个小的例外字典。


52
我喜欢您引用了有关该主题的论文,这对最初的发布者有一点暗示,那就是这可能不是一个简单的问题。
卡尔

是的,我知道这不是一个简单的问题,尽管我没有做很多工作。我确实低估了这个问题,我以为我可以在应用程序的其他部分上工作,然后再回到这个“简单”的问题上。傻了我:)
user50705

我阅读了论文论文,发现它很有帮助。这种方法的问题在于,尽管我发现了一些可以生成这些模式的工具,但我没有阿尔巴尼亚语的任何模式。无论如何,出于我的目的,我编写了一个基于规则的应用程序,该应用程序解决了该问题……
user50705 2009年

10
请注意,TeX算法用于查找合法的连字符点,这与音节分割并不完全相同。的确,连字符点属于音节分隔,但并非所有音节分隔都是有效的连字符点。例如,连字符(通常)不在一个单词的两端或两个字母之内。我还相信,TeX模式已经过调整,可以在误报与误报之间进行权衡(即使在连字符都错掉的情况下,也切勿将连字符放在不属于该字符的位置)。
Adrian McCarthy 2012年

1
我也不认为连字符是答案。
Ezequiel 2014年


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 for y in x if y [-1] .isdigit()))for d in x [word.lower()] ]
Gourneau 2010年

6
对于不属于该语料库的单词,您有何建议作为后备呢?
Dan Gayle

4
@Pureferret cmudict是北美英语单词的发音词典。它将单词分解为音素,比音节短(例如,单词“ cat”被分解为三个音素:K-AE-T)。但是元音也有一个“重音标记”:根据单词的发音为0、1或2(因此“ cat”中的AE变为AE1)。答案中的代码会计算重音标记,并因此计算出元音的数量-这有效地给出了音节的数量(请注意,在OP的示例中,每个音节恰好具有一个元音)。
billy_chapters 16-3-9

1
这将返回音节的数量,而不是音节化。
亚当·迈克尔·伍德

19

我正在尝试为该程序解决该问题,该程序将计算文本块的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;
    }

对于我以专有名称查找音节的简单方案而言,这最初似乎已经足够好了。感谢您将它放到这里。
诺曼H


5

感谢Joe 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

5

碰碰@Tihamer和@ joe-basirico。非常有用的功能,虽然不完美,但对大多数中小型项目都很有用。Joe,我用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

为什么要计算呢?每个在线词典都有此信息。http://dictionary.reference.com/browse/invisible in·vis·i·ble


3
也许它必须适用于字典中没有出现的单词,例如名称?
Wouter Lievens

4
@WouterLievens:我认为名称的行为不适合自动音节解析。对于威尔士或苏格兰血统的名称,英文名称的音节解析器将惨遭失败,更不用说印度和尼日利亚血统的名称了,但是您可能会在伦敦等地的单个房间中找到所有这些名称。
让·弗朗索瓦·科贝特

必须牢记的是,考虑到这是对草图领域的纯粹启发式方法,期望获得比人类所能提供的更好的性能是不合理的。
达伦·林格

4

Perl具有Lingua :: Phonology :: Syllable模块。您可以尝试一下,或者尝试研究其算法。我也在那里看到了其他一些较旧的模块。

我不明白为什么正则表达式只给您一个音节数。您应该能够使用捕获括号来获取音节本身。假设您可以构造一个有效的正则表达式。


4

今天我发现了这个 Java实现弗兰克·梁与英语或德语,它工作得很好,并提供对Maven的中央图案断字algorithmn的。

洞穴:重要的是删除 .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不会返回normal "-"

这种方法优于Joe Basirico的答案,因为它支持许多不同的语言,并且可以更准确地检测德语连字符。


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上的工作方式进行详尽的撰写


这太酷了。有人能将生成的Keras模型转换为CoreML模型以在iOS上使用吗?
Alexsander Akers

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”。解决方法是不区分大小写地比较字符串。如果他们遇到相同的问题并希望更新其功能,请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

1
通常,到工具或库的链接应随附使用说明,对链接资源如何适用于问题的具体说明,或一些示例代码,或者如果可能的话,还应包括所有上述内容。
IKavanagh 2015年

请参见语法突出显示。SO编辑器中有一个帮助按钮(问号),可将您带到链接页面。
IKavanagh

0

在进行了大量测试并尝试了断字连接程序包之后,我根据许多示例编写了自己的程序。我还尝试了与连字词典对接的pyhyphenpyphen包,但在许多情况下它们产生的音节数错误。nltk对于此用例,该软件包太慢了。

我在Python中的实现是我编写的类的一部分,并且音节计数例程粘贴在下面。由于我仍然没有找到解决无声单词结尾的好方法,因此它高估了音节的数量。

该函数返回用于Flesch-Kincaid可读性评分的每个单词的音节比率。该数字不必精确,只需足够接近即可估算。

在我的第7代i7 CPU上,此功能花了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;
    }

通用音节解析器如何?看起来这段代码只是在字典中查找音节
Nico Haase
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.