単語の音節を検出する


138

単語の音節を検出するかなり効率的な方法を見つける必要があります。例えば、

非表示-> in-vi-sib-le

使用できるいくつかの音節規則があります。

V CV VC CVC CCV CCCV CVCC

* Vは母音、Cは子音です。例えば、

発音(5 Pro-nun-ci-a-tion; CV-CVC-CV-V-CVC)

私はいくつかの方法を試しましたが、そのうちの1つは正規表現(音節を数える場合にのみ役立ちます)またはハードコードされたルール定義(非常に非効率的であることが判明したブルートフォースアプローチ)を使用し、最後に有限状態オートマトン(実際には役立つものは何もありません)。

私のアプリケーションの目的は、特定の言語ですべての音節の辞書を作成することです。この辞書は、後でスペルチェックアプリケーション(ベイズ分類器を使用)およびテキストから音声への合成に使用されます。

以前のアプローチ以外に、この問題を解決する別の方法のヒントを教えていただければ幸いです。

私はJavaで作業しますが、C / C ++、C#、Python、Perlのヒントがあればうまくいきます。


実際に実際の分割ポイントが必要ですか、それとも単語内の音節の数だけですか。後者の場合は、音声合成辞書で単語を検索し、母音をエンコードする音素を数えることを検討してください。
エイドリアン・マッカーシー

最も効率的な方法(計算の観点ではなく、ストレージの観点から)は、単語としてのキーと音節の数としてのPython辞書を使用することだと思います。ただし、辞書に載らなかった単語にはフォールバックが必要です。そのような辞書を見つけた場合はお知らせください。
Brōtsyorfuzthrāx

回答:


120

ハイフネーションのためのこの問題に対するTeXのアプローチについて読んでください。特に、フランク・リャンの論文 、Com-put-erによるHy-phen-a-tionを参照してください。彼のアルゴリズムは非常に正確で、アルゴリズムが機能しない場合のための小さな例外辞書が含まれています。


52
私はあなたが主題に関する論文を引用したのが好きです、それはこれが簡単な質問ではないかもしれないというオリジナルのポスターへの少しのヒントです。
カール

はい、私はこれについてはあまり取り組みませんでしたが、これは単純な質問ではないことを認識しています。私は問題を過小評価しましたが、自分のアプリの他の部分に取り組み、後でこの「単純な」問題に戻ると思いました。愚かな私:)
user50705

論文を読んで、とても参考になりました。このアプローチの問題は、アルバニア語のパターンがまったくないことですが、それらのパターンを生成できるツールがいくつか見つかりました。とにかく、私の目的のために、私は問題を解決するルールベースのアプリを書きました...
user50705 2009年

10
TeXアルゴリズムは合法的なハイフネーションポイントを見つけるためのものであり、音節の分割とはまったく同じではないことに注意してください。ハイフネーションポイントが音節分割に該当することは事実ですが、すべての音節分割が有効なハイフネーションポイントであるとは限りません。たとえば、ハイフンは(通常)単語の両端の1つまたは2つの文字内では使用されません。また、TeXパターンは、偽陰性と偽陽性をトレードオフするように調整されたと考えています(正当なハイフネーションの機会を逃していることを意味する場合でも、属していない場所にハイフンを置かないでください)。
エイドリアンマッカーシー

1
ハイフネーションもその答えだとは思いません。
エゼキエル2014

46

私は同じことを探してこのページを偶然見つけました、そしてここでLiangペーパーのいくつかの実装を見つけました:https : //github.com/mnater/hyphenatorまたは後継者:https : //github.com/mnater/Hyphenopoly

これは、自由に入手できるコードを非固有の問題に適応させる代わりに、60ページの論文を読むのが好きなタイプでない限りです。:)


合意-既存の実装をそのまま使用する方がはるかに便利
hoju 2010年

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 x for d [word.lower()] ]
Gourneau 2010

6
そのコーパスにない単語のフォールバックとして何を提案しますか?
Dan Gayle

4
@Pureferret cmudictは、北米の英語の単語を発音できる辞書です。単語を音節よりも短い音素に分割します(たとえば、「猫」という単語は3つの音素に分割されます:K-AE-T)。ただし、母音には「ストレスマーカー」もあります。単語の発音に応じて、0、1、または2のいずれかになります(したがって、「猫」のAEはAE1になります)。回答のコードは、ストレスマーカーを数え、したがって母音の数を数えます。これにより、実質的に音節の数がわかります(OPの例では、各音節に正確に1つの母音があることに注意してください)。
billy_chapters 2016年

1
これは、音節ではなく音節の数を返します。
アダムマイケルウッド

19

私は、テキストのブロックのフレッシュキンケードとフレッシュリーディングスコアを計算するプログラムのために、この問題に取り組んでいます。私のアルゴリズムは、このWebサイト(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

なぜそれを計算するのですか?すべてのオンライン辞書にこの情報があります。http://dictionary.reference.com/browse/invisible in・vis・i・ble


3
名前など、辞書に載っていない単語でも機能するのでしょうか。
Wouter Lievens

4
@WouterLievens:私は名前が自動音節解析のために十分に行儀のよい場所に近いとは思いません。英語名の音節パーサーは、ウェールズ語またはスコットランド語の起源の名前はもちろんのこと、インディアンおよびナイジェリアの起源の名前は無意味に失敗しますが、ロンドンなどのどこかの単一の部屋でこれらすべてを見つける可能性があります。
ジャン=フランソワ・コルベット

これは大まかな領域に対する純粋なヒューリスティックなアプローチであることを考えると、人間が提供できるパフォーマンスよりも優れたパフォーマンスを期待することは妥当ではないことを覚えておく必要があります。
ダレンリンガー

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のバンピング。非常に便利な機能で、完璧ではありませんが、ほとんどの中小規模のプロジェクトに適しています。ジョー、私はあなたのコードの実装を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にはLingua :: Phonology :: Syllableモジュールがあります。あなたはそれを試すか、そのアルゴリズムを調べてみるかもしれません。他の古いモジュールもいくつか見ました。

なぜ正規表現が音節の数だけを与えるのか分かりません。キャプチャ括弧を使用して、音節自体を取得できるはずです。正常に機能する正規表現を作成できると想定します。


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;

"\u00ADAPIは通常を返さないため、 "で分割する必要があります"-"

このアプローチは多くの異なる言語をサポートし、ドイツ語のハイフネーションをより正確に検出するため、Joe Basiricoの回答よりも優れています。


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でどのように機能するかをかなり詳細に記述しました


これは超かっこいいです。結果として得られるKerasモデルをiOSで使用するためにCoreMLモデルに変換する運があった人はいますか?
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.")

さらに2つのテストケース「End」と「I」を追加しました。修正は、大文字と小文字を区別せずに文字列を比較することでした。@ joe-basiricoとtihamerが同じ問題を抱えており、機能を更新したい場合に備えて、pingを実行します。
josefnpat

@tihamer Americanは4音節です。
josefnpat 2015

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


Syntax Highlightingを参照してください。SOエディターには、リンクされたページに移動するためのヘルプボタン(疑問符)があります。
IKavanagh 2015

0

多くのテストを行い、ハイフネーションパッケージも試した後、いくつかの例に基づいて独自のパッケージを作成しました。また、ハイフネーション辞書と連動するpyhyphenおよびpyphenパッケージを試してみましたが、多くの場合、それらは間違った数の音節を生成します。nltkこの使用例では、パッケージが単純に遅すぎました。

私のPythonでの実装は、私が作成したクラスの一部であり、音節カウントルーチンが以下に貼り付けられています。私はまだサイレントな単語の終わりを説明する良い方法を見つけていないので、それは音節の数を少し過大評価しています。

この関数は、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を使用してこれを1回実行しました。次に、サンプルの音節パーサーを示します。

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.