Mendeteksi suku kata dalam satu kata

142

Saya perlu menemukan cara yang cukup efisien untuk mendeteksi suku kata dalam sebuah kata. Misalnya,

Tak terlihat -> in-vi-sib-le

Ada beberapa aturan silabifikasi yang dapat digunakan:

V CV VC CVC CCV CCCV CVCC

* di mana V adalah vokal dan C adalah konsonan. Misalnya,

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

Saya telah mencoba beberapa metode, di antaranya menggunakan regex (yang hanya membantu jika Anda ingin menghitung suku kata) atau definisi aturan hard code (pendekatan brute force yang terbukti sangat tidak efisien) dan akhirnya menggunakan automata keadaan terbatas (yang memang tidak menghasilkan sesuatu yang berguna).

Tujuan aplikasi saya adalah membuat kamus dari semua suku kata dalam bahasa tertentu. Kamus ini nantinya akan digunakan untuk aplikasi pemeriksa ejaan (menggunakan pengklasifikasi Bayesian) dan sintesis teks ke ucapan.

Saya akan sangat menghargai jika seseorang dapat memberi saya tip tentang cara alternatif untuk menyelesaikan masalah ini selain pendekatan saya sebelumnya.

Saya bekerja di Java, tetapi tip apa pun di C / C ++, C #, Python, Perl ... akan berhasil untuk saya.

pengguna50705
sumber
Apakah Anda benar-benar menginginkan titik pembagian sebenarnya atau hanya jumlah suku kata dalam satu kata? Jika yang terakhir, pertimbangkan untuk mencari kata-kata dalam kamus teks-ke-ucapan dan hitung fonem yang menyandikan suara vokal.
Adrian McCarthy
Cara paling efisien (dari segi komputasi; bukan penyimpanan), saya kira hanya memiliki kamus Python dengan kata-kata sebagai kunci dan jumlah suku kata sebagai nilai. Namun, Anda masih memerlukan penggantian untuk kata-kata yang tidak masuk kamus. Beri tahu saya jika Anda pernah menemukan kamus seperti itu!
Brōtsyorfuzthrāx

Jawaban:

123

Baca tentang pendekatan TeX untuk masalah ini untuk tujuan tanda hubung. Terutama lihat disertasi tesis Frank Liang Word Hy-phen-a-tion oleh Com-put-er . Algoritmanya sangat akurat, dan kemudian menyertakan kamus pengecualian kecil untuk kasus-kasus di mana algoritme tidak berfungsi.

jason
sumber
53
Saya suka Anda telah mengutip disertasi tesis tentang subjek, ini sedikit petunjuk untuk poster asli bahwa ini mungkin bukan pertanyaan yang mudah.
Karl
Ya, saya sadar bahwa ini bukanlah pertanyaan yang sederhana, meskipun saya belum banyak mengerjakannya. Saya memang meremehkan masalahnya, saya pikir saya akan mengerjakan bagian lain dari aplikasi saya, dan kemudian kembali ke masalah 'sederhana' ini. Konyol saya :)
user50705
Saya membaca makalah disertasi, dan merasa sangat membantu. Masalah dengan pendekatan ini adalah saya tidak memiliki pola apa pun untuk bahasa Albania, meskipun saya menemukan beberapa alat yang dapat menghasilkan pola tersebut. Bagaimanapun, untuk tujuan saya, saya menulis aplikasi berbasis aturan, yang memecahkan masalah ...
user50705
10
Perhatikan bahwa algoritme TeX adalah untuk menemukan titik penghubung yang sah, yang tidak persis sama dengan pembagian suku kata. Memang benar bahwa titik pemenggalan jatuh pada pembagian suku kata, tetapi tidak semua pembagian suku kata merupakan titik penghubung yang valid. Misalnya, tanda hubung (biasanya) tidak digunakan dalam satu atau dua huruf di salah satu ujung kata. Saya juga yakin pola TeX disetel untuk menukar negatif palsu dengan positif palsu (jangan pernah meletakkan tanda hubung di tempat yang bukan tempatnya, bahkan jika itu berarti kehilangan beberapa peluang tanda hubung yang sah).
Adrian McCarthy
1
Saya juga tidak percaya tanda hubung adalah jawabannya.
Ezequiel
46

Saya menemukan halaman ini mencari hal yang sama, dan menemukan beberapa implementasi dari makalah Liang di sini: https://github.com/mnater/hyphenator atau penerusnya: https://github.com/mnater/Hyphenopoly

Itu kecuali Anda adalah tipe yang suka membaca tesis 60 halaman alih-alih mengadaptasi kode yang tersedia secara bebas untuk masalah non-unik. :)

Sean
sumber
setuju - jauh lebih nyaman untuk hanya menggunakan implmentasi yang ada
hoju
41

Berikut adalah solusi menggunakan 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()]] 
hoju
sumber
Hai terima kasih kesalahan bayi kecil di seharusnya fungsi def nsyl (kata): return [len (daftar (y untuk y di x jika y [-1] .isdigit ())) untuk x di d [word.lower ()] ]
Gourneau
6
Apa yang Anda sarankan sebagai pengganti kata-kata yang tidak ada dalam korpus itu?
Dan Gayle
4
@Pureferret cmudict adalah kamus pengucapan untuk kata-kata bahasa Inggris Amerika Utara. itu membagi kata-kata menjadi fonem, yang lebih pendek dari suku kata (misalnya kata 'cat' dibagi menjadi tiga fonem: K - AE - T). tetapi vokal juga memiliki "penanda stres": 0, 1, atau 2, bergantung pada pengucapan kata tersebut (jadi AE dalam 'cat' menjadi AE1). kode dalam jawaban menghitung penanda stres dan oleh karena itu jumlah vokal - yang secara efektif memberikan jumlah suku kata (perhatikan bagaimana dalam contoh OP setiap suku kata memiliki tepat satu vokal).
billy_chapters
1
Ini mengembalikan jumlah suku kata, bukan silabifikasi.
Adam Michael Wood
20

Saya mencoba mengatasi masalah ini untuk program yang akan menghitung skor membaca flesch-kincaid dan flesch dari sebuah blok teks. Algoritme saya menggunakan apa yang saya temukan di situs web ini: http://www.howmanysyllables.com/howtocountsyllables.html dan hasilnya cukup mendekati. Masih bermasalah pada kata-kata rumit seperti tak terlihat dan tanda hubung, tetapi saya telah menemukannya di kasar untuk tujuan saya.

Ini memiliki keuntungan karena mudah diterapkan. Saya menemukan "es" bisa berupa suku kata atau tidak. Ini pertaruhan, tetapi saya memutuskan untuk menghapus es dalam algoritme saya.

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;
    }
Joe Basirico
sumber
Untuk skenario sederhana saya menemukan suku kata dalam nama yang tepat, ini tampaknya pada awalnya bekerja dengan cukup baik. Terima kasih telah memadamkannya di sini.
Norman H
5

Mengapa menghitungnya? Setiap kamus online memiliki info ini. http://dictionary.reference.com/browse/invisible in · vis · i · ble

Cerin
sumber
3
Mungkin itu harus bekerja untuk kata-kata yang tidak muncul dalam kamus, seperti nama?
Wouter Lievens
4
@WouterLievens: Saya rasa nama-nama tidak cukup berperilaku baik untuk penguraian suku kata otomatis. Pengurai suku kata untuk nama Inggris akan gagal total pada nama asal Welsh atau Skotlandia, apalagi nama asal India dan Nigeria, namun Anda mungkin menemukan semua ini dalam satu ruangan di suatu tempat di misalnya London.
Jean-François Corbett
Harus diingat bahwa tidak masuk akal untuk mengharapkan kinerja yang lebih baik daripada yang dapat diberikan oleh manusia mengingat ini adalah pendekatan heuristik murni untuk domain samar.
Darren Ringer
5

Terima kasih Joe Basirico, untuk berbagi implementasi cepat dan kotor Anda di C #. Saya telah menggunakan perpustakaan besar, dan berfungsi, tetapi biasanya agak lambat, dan untuk proyek cepat, metode Anda berfungsi dengan baik.

Berikut kode Anda di Java, bersama dengan kasus pengujian:

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

Hasilnya seperti yang diharapkan (ini bekerja cukup baik untuk 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
Tihamer
sumber
5

Menabrak @Tihamer dan @ joe-basirico. Fungsi yang sangat berguna, tidak sempurna , tetapi bagus untuk sebagian besar proyek kecil hingga menengah. Joe, saya telah menulis ulang implementasi kode Anda dengan 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

Semoga seseorang menemukan ini berguna!

Tersosauros
sumber
4

Perl memiliki modul Lingua :: Phonology :: Syllable . Anda dapat mencobanya, atau mencoba melihat algoritmanya. Saya juga melihat beberapa modul lama lainnya di sana.

Saya tidak mengerti mengapa ekspresi reguler hanya memberi Anda hitungan suku kata. Anda seharusnya bisa mendapatkan suku kata itu sendiri menggunakan tanda kurung pengambilan. Dengan asumsi Anda dapat membuat ekspresi reguler yang berfungsi, yaitu.

cakrawala
sumber
4

Hari ini saya menemukan ini implementasi Java hyphenation algorithmn Frank Liang dengan pola untuk bahasa Inggris atau Jerman, yang bekerja cukup baik dan tersedia pada Maven Central.

Gua: Sangat penting untuk menghapus baris terakhir dari .texfile pola, karena jika tidak, file tersebut tidak dapat dimuat dengan versi saat ini di Maven Central.

Untuk memuat dan menggunakan hyphenator, Anda dapat menggunakan potongan kode Java berikut. texTableadalah nama .texfile yang berisi pola yang dibutuhkan. File-file tersebut tersedia di situs github proyek.

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

Setelah Hyphenatoritu siap digunakan. Untuk mendeteksi suku kata, ide dasarnya adalah membagi istilah pada tanda hubung yang disediakan.

    String hyphenedTerm = hyphenator.hyphenate(term);

    String hyphens[] = hyphenedTerm.split("\u00AD");

    int syllables = hyphens.length;

Anda perlu membagi "\u00AD", karena API tidak mengembalikan normal "-".

Pendekatan ini mengungguli jawaban Joe Basirico, karena mendukung banyak bahasa berbeda dan mendeteksi tanda hubung bahasa Jerman dengan lebih akurat.

rzo
sumber
4

Saya mengalami masalah yang persis sama beberapa waktu yang lalu.

Saya akhirnya menggunakan Kamus Pengucapan CMU untuk pencarian kata yang cepat dan akurat. Untuk kata-kata yang tidak ada dalam kamus, saya kembali ke model pembelajaran mesin yang ~ 98% akurat dalam memprediksi jumlah suku kata.

Saya menyelesaikan semuanya dalam modul python yang mudah digunakan di sini: https://github.com/repp/big-phoney

Install: pip install big-phoney

Hitung Suku Kata:

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

Jika Anda tidak menggunakan Python dan ingin mencoba pendekatan berbasis model ML, saya menulis cukup mendetail tentang cara kerja model penghitungan suku kata di Kaggle .

Ryan Epp
sumber
Ini sangat keren. Adakah yang beruntung mengubah model Keras yang dihasilkan menjadi model CoreML untuk digunakan di iOS?
Alexsander Akers
2

Terima kasih @ joe-basirico dan @tihamer. Saya telah mem-porting kode @ tihamer ke Lua 5.1, 5.2 dan luajit 2 ( kemungkinan besar akan berjalan di versi lua lain juga ):

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

Dan beberapa tes menyenangkan untuk memastikannya berfungsi ( sebanyak yang seharusnya ):

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.")
josefnpat
sumber
Saya menambahkan dua kasus uji lagi "End" dan "I". Cara mengatasinya adalah membandingkan huruf besar-kecil secara tidak sensitif. Ping @ joe-basirico dan tihamer jika mereka mengalami masalah yang sama dan ingin memperbarui fungsinya.
josefnpat
@tihamer American adalah 4 suku kata!
josefnpat
2

Saya tidak dapat menemukan cara yang memadai untuk menghitung suku kata, jadi saya merancang metode sendiri.

Anda dapat melihat metode saya di sini: https://stackoverflow.com/a/32784041/2734752

Saya menggunakan kombinasi metode kamus dan algoritma untuk menghitung suku kata.

Anda dapat melihat perpustakaan saya di sini: https://github.com/troywatson/Lawrence-Style-Checker

Saya baru saja menguji algoritme saya dan memiliki tingkat serangan 99,4%!

Lawrence lawrence = new Lawrence();

System.out.println(lawrence.getSyllable("hyphenation"));
System.out.println(lawrence.getSyllable("computer"));

Keluaran:

4
3
troy
sumber
Lihat Penyorotan Sintaks . Ada tombol bantuan (tanda tanya) di editor SO yang akan membawa Anda ke halaman yang ditautkan.
IKavanagh
0

Setelah melakukan banyak pengujian dan mencoba paket tanda hubung juga, saya menulis sendiri berdasarkan sejumlah contoh. Saya juga mencoba paket pyhyphendan pyphenyang berinteraksi dengan kamus tanda hubung, tetapi mereka menghasilkan jumlah suku kata yang salah dalam banyak kasus. The nltkpaket hanya terlalu lambat untuk kasus penggunaan ini.

Implementasi saya dengan Python adalah bagian dari kelas yang saya tulis, dan rutinitas penghitungan suku kata ditempel di bawah ini. Ini sedikit melebih-lebihkan jumlah suku kata karena saya masih belum menemukan cara yang baik untuk menjelaskan akhiran kata diam.

Fungsi tersebut mengembalikan rasio suku kata per kata seperti yang digunakan untuk skor keterbacaan Flesch-Kincaid. Jumlahnya tidak harus tepat, cukup dekat untuk perkiraan.

Pada CPU i7 generasi ke-7 saya, fungsi ini memerlukan waktu 1,1-1,2 milidetik untuk teks sampel 759 kata.

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)
Jadzia 626
sumber
-2

Saya menggunakan jsoup untuk melakukan ini sekali. Berikut adalah contoh pengurai suku kata:

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;
    }
Itamar Fiorino
sumber
1
Bagaimana itu pengurai suku kata generik? Sepertinya kode ini hanya mencari suku kata dalam kamus
Nico Haase