Bir kelimedeki heceleri algılama


138

Bir kelimede heceleri tespit etmek için oldukça etkili bir yol bulmam gerekiyor. Örneğin,

Görünmez -> in-vi-sib-le

Kullanılabilecek bazı heceleme kuralları vardır:

V CV VC CVC CCV CCCV CVCC

* burada V bir sesli harftir ve C bir ünsüzdür. Örneğin,

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

Regex (sadece heceleri saymak istiyorsanız yardımcı olur) veya sabit kodlanmış kural tanımı (çok verimsiz olduğu kanıtlanan kaba kuvvet yaklaşımı) ve son olarak (son yapılan yararlı bir şeyle sonuçlanmaz).

Uygulamamın amacı, belirli bir dilde tüm hecelerin sözlüğünü oluşturmaktır. Bu sözlük daha sonra yazım denetimi uygulamaları (Bayes sınıflandırıcıları kullanarak) ve metinden konuşmaya sentez için kullanılacaktır.

Daha önceki yaklaşımlarımın yanı sıra bu sorunu çözmenin alternatif bir yolunu bana verebilirse çok memnun olurum.

Java çalışıyorum, ancak C / C ++, C #, Python, Perl ... herhangi bir ipucu benim için işe yarayacak.


Aslında gerçek bölme noktalarını mı yoksa sadece bir kelimedeki hecelerin sayısını mı istiyorsunuz? İkincisi ise, metinden konuşmaya sözlükteki sözcüklere bakmayı ve sesli harfleri kodlayan fonemleri saymayı düşünün.
Adrian McCarthy

En etkili yol (hesaplama-bilge; depolama-bilge değil), sanırım sadece anahtar olarak kelimeler ve hecelerin sayısı olarak bir Python sözlüğüne sahip olmak olurdu. Ancak, yine de sözlükte bulunmayan kelimeler için bir yedeklemeye ihtiyacınız olacaktır. Böyle bir sözlük bulursanız bana bildirin!
Brōtsyorfuzthrāx

Yanıtlar:


120

Tireleme amacıyla bu soruna TeX yaklaşımı hakkında bilgi edinin. Özellikle Com-put-er tarafından Frank Liang'ın tez tez Word Hy-phen-a-tion bakın . Algoritması çok doğrudur ve algoritmanın çalışmadığı durumlar için küçük bir istisna sözlüğü içerir.


52
Konuyla ilgili bir tez tezini belirttiğinizi sevdim, orijinal postere bunun kolay bir soru olmayabileceği konusunda küçük bir ipucu.
Karl

Evet, bunun üzerinde çok çalışmamış olmama rağmen, bunun basit bir soru olmadığının farkındayım. Sorunu hafife aldım, uygulamamın diğer bölümlerinde çalışacağımı düşündüm ve daha sonra bu 'basit' soruna geri döneceğim. Silly me :)
user50705

Ben tez çalışmasını okudum ve çok yararlı buldum. Yaklaşımdaki sorun, Arnavutça dili için herhangi bir örüntüme sahip olmamamdı, ancak bu örüntüleri oluşturabilecek bazı araçlar buldum. Her neyse, amacım için sorunu çözen bir kural tabanlı uygulama yazdım ...
user50705

10
TeX algoritmasının heceli bölümlerle tam olarak aynı olmayan yasal tireleme noktalarını bulmak için olduğunu unutmayın. Tireleme noktalarının heceli bölümlere düştüğü doğrudur, ancak heceli bölümlerin tümü geçerli tireleme noktaları değildir. Örneğin, kısa çizgiler (genellikle) bir sözcüğün her iki ucundan birinde veya iki harfinde kullanılmaz. Ayrıca TeX modellerinin yanlış pozitifler için yanlış negatifleri değiştirecek şekilde ayarlandığına inanıyorum (bazı meşru tireleme fırsatlarını kaçırmak anlamına gelse bile asla ait olmadığı bir tire işareti koymayın).
Adrian McCarthy

1
Cevapın tireleme olduğuna da inanmıyorum.
Ezequiel


41

İşte NLTK kullanan bir çözüm :

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()]] 

Hey teşekkürler, min. İçindeki küçük bebek hatası def nsyl (word) işlevinde olmalıdır: d için [word.lower ()] ]
Gourneau

6
Bu toplulukta olmayan kelimeler için bir geri dönüş olarak ne önerirsiniz?
Dan Gayle

4
@ Purferret cmudict kuzey amerikan ingilizce kelimeler için telaffuz edici bir sözlüktür. kelimeleri hecelerden daha kısa olan fonemlere böler (örneğin 'kedi' kelimesi üç foneme bölünür: K - AE - T). ancak sesli harflerin de bir "stres markörü" vardır: kelimenin telaffuzuna bağlı olarak 0, 1 veya 2 ('cat' içindeki AE AE1 olur). cevaptaki kod, stres işaretleyicilerini ve bu nedenle sesli harf sayısını sayar - bu da hecelerin sayısını etkili bir şekilde verir (OP'nin örneklerinde her bir hecenin tam olarak bir sesli harfin nasıl olduğuna dikkat edin).
billy_chapters

1
Bu, heceleme sayısını değil hecelerin sayısını döndürür.
Adam Michael Wood

19

Bir metin bloğunun eti-kincaid ve eti okuma puanını hesaplayacak bir program için bu sorunu çözmeye çalışıyorum. Algoritmam bu web sitesinde bulduğum şeyi kullanıyor: http://www.howmanysyllables.com/howtocountsyllables.html ve oldukça yakınlaşıyor. Görünmez ve tireleme gibi karmaşık kelimelerde hala sorun var, ancak amaçlarım için ballparkta olduğunu buldum.

Uygulaması kolay olmanın tersidir. Ben "es" ya hece olabilir ya da olmayabilir bulundu. Bu bir kumar, ama algoritmamdaki es kaldırmaya karar verdim.

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

Basit isimlerdeki heceleri bulma konusundaki basit senaryom için bu başlangıçta yeterince iyi görünüyor. Buraya koyduğunuz için teşekkürler.
Norman H


5

Neden hesaplıyoruz? Her çevrimiçi sözlükte bu bilgiler bulunur. http://dictionary.reference.com/browse/invisible in · vis · i · ble


3
Belki de adlar gibi sözlüklerde yer almayan kelimeler için çalışmak zorunda?
Wouter Lievens

4
@WouterLievens: İsimlerin otomatik hece ayrıştırma için yeterince iyi davranılmış bir yerde olduğunu sanmıyorum. İngilizce isimleri için hece ayrıştırıcısı, Hint ve Nijeryalı kökenlerin isimleri hariç, Galce veya İskoç kökenli isimler üzerinde sefil bir şekilde başarısız olacaktır, ancak bunların hepsini örneğin Londra'da bir yerde tek bir odada bulabilirsiniz.
Jean-François Corbett

Bir insanın sağlayabileceğinden daha iyi performans beklemenin makul olmadığını, bunun kabataslak bir alan için tamamen sezgisel bir yaklaşım olduğunu düşünmek gerekir.
Darren Ringer

5

Joe Basirico, C # hızlı ve kirli uygulama paylaşmak için teşekkürler. Büyük kütüphaneleri kullandım ve çalışıyorlar, ancak genellikle biraz yavaşlar ve hızlı projeler için yönteminiz iyi çalışıyor.

Java'da kodunuz ve test senaryoları:

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

Sonuç beklendiği gibi oldu (Flesch-Kincaid için yeterince iyi çalışıyor):

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 ve @ joe-basirico arasında çarpma. Çok kullanışlı fonksiyon, mükemmel değil , çoğu küçük ila orta ölçekli proje için iyi. Joe, kodunuzun bir uygulamasını Python'da yeniden yazdım:

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

Umarım birisi bunu faydalı bulur!


4

Perl Lingua :: Phonology :: Hece modülüne sahiptir. Bunu deneyebilir veya algoritmasına bakmayı deneyebilirsiniz. Orada birkaç eski modül daha gördüm.

Düzenli bir ifadenin size neden sadece bir hece verdiğini anlamıyorum. Yakalama parantezlerini kullanarak heceleri elde edebilmelisiniz. İşe yarayan normal bir ifade oluşturabileceğinizi varsayarsak, yani.


4

Bugün buldum bu oldukça iyi çalışıyor ve Maven Central mevcuttur İngilizce veya Almanca için desenli Frank Liang heceleme algorithmn Java uygulama.

Mağara: .texDesen dosyalarının son satırlarının kaldırılması önemlidir , aksi takdirde bu dosyalar Maven Central'daki geçerli sürümle yüklenemez.

Yüklemek ve kullanmak hyphenatoriçin aşağıdaki Java kod snippet'ini kullanabilirsiniz. gerekli kalıpları içeren dosyaların texTableadıdır .tex. Bu dosyalar proje github sitesinde bulunabilir.

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

Daha sonra Hyphenatorkullanıma hazırdır. Heceleri tespit etmek için temel fikir, verilen tire işaretini ayırmaktır.

    String hyphenedTerm = hyphenator.hyphenate(term);

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

    int syllables = hyphens.length;

"\u00ADAPI normal bir değer döndürmediği için bölmeniz gerekir " "-".

Bu yaklaşım, birçok farklı dili desteklediği ve Alman tirelemesini daha doğru algıladığı için Joe Basirico'nun cevabından daha iyi performans gösterir.


4

Aynı sorunla bir süre önce karşılaştım.

Çoğu kelimenin hızlı ve doğru araması için CMU Telaffuz Sözlüğü'nü kullandım . Sözlükte olmayan kelimeler için, hece sayılarını tahmin etmede ~% 98 doğru olan bir makine öğrenme modeline geri döndüm.

Her şeyi burada kullanımı kolay bir python modülüne tamamladım: https://github.com/repp/big-phoney

Yüklemek: pip install big-phoney

Heceleri Say:

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

Python kullanmıyorsanız ve ML model tabanlı yaklaşımı denemek istiyorsanız , hece sayma modelinin Kaggle'da nasıl çalıştığı hakkında oldukça ayrıntılı bir yazı yazdım .


Bu çok havalı. Sonuçta ortaya çıkan Keras modelini iOS'ta kullanmak için bir CoreML modeline dönüştürme şansı olan var mı?
Alexsander Akers

2

@ Joe-basirico ve @tihamer teşekkür ederiz. @ Tihamer kodunu Lua 5.1, 5.2 ve luajit 2'ye taşıdım ( büyük olasılıkla lua'nın diğer sürümlerinde de çalışacaktır ):

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

Ve çalıştığını doğrulamak için bazı eğlenceli testler ( olması gerektiği kadar ):

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

İki test örneği daha ekledim: "Son" ve "I". Düzeltme dizeleri büyük / küçük harfe karşılaştırmaktı. Ping'ing @ joe-basirico ve tihamer aynı sorundan muzdarip olmaları ve işlevlerini güncellemek istemeleri durumunda.
josefnpat

@tihamer American 4 hece!
josefnpat

2

Heceleri saymak için yeterli bir yol bulamadım, bu yüzden kendim bir yöntem tasarladım.

Yöntemimi buradan görüntüleyebilirsiniz: https://stackoverflow.com/a/32784041/2734752

Heceleri saymak için bir sözlük ve algoritma yöntemi kombinasyonu kullanıyorum.

Kütüphanemi buradan görüntüleyebilirsiniz: https://github.com/troywatson/Lawrence-Style-Checker

Algoritmamı test ettim ve% 99.4'lük bir grev oranı yaşadım!

Lawrence lawrence = new Lawrence();

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

Çıktı:

4
3

1
Genel olarak, bir araca veya kütüphaneye bağlantılara kullanım notları, bağlı kaynağın soruna nasıl uygulanabileceğine dair özel bir açıklama veya bazı örnek kodlar veya mümkünse yukarıdakilerin tümü eşlik etmelidir .
IKavanagh

Bkz. Sözdizimi Vurgulama . SO düzenleyicide sizi bağlantılı sayfaya götürecek bir yardım düğmesi (soru işareti) vardır.
IKavanagh

0

Çok fazla test yaptıktan ve tireleme paketlerini denedikten sonra, birkaç örneğe dayanarak kendim yazdım. Ayrıca tireleme sözlükleriyle arayüz oluşturan pyhyphenve pyphenpaketlerini denedim , ancak birçok durumda yanlış hece üretiyorlar. nltkPaket basitçe bu kullanım şeklini çok yavaştı.

Python'daki uygulamam yazdığım bir sınıfın parçası ve hece sayma rutini aşağıya yapıştırıldı. Sessiz kelime sonlarını açıklamak için hala iyi bir yol bulamadığım için hecelerin sayısını biraz fazla tahmin ediyor.

İşlev, bir Flesch-Kincaid okunabilirlik skoru için kullanıldığından kelime başına hecelerin oranını döndürür. Sayı kesin olmak zorunda değil, sadece bir tahmin için yeterince yakın.

7. nesil i7 CPU'mda, bu işlev 759 kelimelik bir örnek metin için 1.1-1.2 milisaniye sürdü.

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

Bunu bir kez yapmak için jsoup kullandım. İşte örnek hece ayrıştırıcısı:

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

Bu nasıl genel bir hece ayrıştırıcısıdır? Bu kod yalnızca bir sözlükte heceleri yukarı bakıyor gibi görünüyor
Nico Haase
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.