Rilevare sillabe in una parola


138

Devo trovare un modo abbastanza efficace per rilevare sillabe in una parola. Per esempio,

Invisibile -> in-vi-sib-le

Esistono alcune regole di sillabazione che potrebbero essere utilizzate:

V CV VC CVC CCV CCCV CVCC

* dove V è una vocale e C è una consonante. Per esempio,

Pronuncia (5 Pro-suora-ci-a-zione; CV-CVC-CV-V-CVC)

Ho provato alcuni metodi, tra cui l'utilizzo di regex (che aiuta solo se si desidera contare le sillabe) o la definizione di regole codificate (un approccio a forza bruta che si rivela molto inefficiente) e infine l'utilizzo di automi a stati finiti (che ha fatto non risulta con nulla di utile).

Lo scopo della mia applicazione è quello di creare un dizionario di tutte le sillabe in una determinata lingua. Questo dizionario verrà successivamente utilizzato per le applicazioni di controllo ortografico (utilizzando classificatori bayesiani) e per la sintesi vocale.

Gradirei se uno potesse darmi consigli su un modo alternativo per risolvere questo problema oltre ai miei precedenti approcci.

Lavoro in Java, ma qualsiasi suggerimento in C / C ++, C #, Python, Perl ... funzionerebbe per me.


Vuoi effettivamente i punti di divisione effettivi o solo il numero di sillabe in una parola? Se quest'ultimo, considera di cercare le parole in un dizionario di sintesi vocale e conta i fonemi che codificano i suoni vocalici.
Adrian McCarthy,

Il modo più efficiente (dal punto di vista del calcolo; non dal punto di vista dell'archiviazione), immagino sarebbe solo avere un dizionario Python con parole come chiavi e il numero di sillabe come valori. Tuttavia, avresti comunque bisogno di un fallback per le parole che non sono state inserite nel dizionario. Fammi sapere se hai mai trovato un dizionario del genere!
Brōtsyorfuzthrāx,

Risposte:


120

Leggi l'approccio TeX a questo problema ai fini della sillabazione. Soprattutto vedere la tesi di tesi di Frank Liang Word Hy-phen-a-tion di Com-put-er . Il suo algoritmo è molto preciso e include quindi un piccolo dizionario delle eccezioni per i casi in cui l'algoritmo non funziona.


52
Mi piace che tu abbia citato una tesi di laurea sull'argomento, è un piccolo suggerimento per il poster originale che questa potrebbe non essere una domanda facile.
Karl

Sì, sono consapevole che questa non è una domanda semplice, anche se non ci ho lavorato molto. Tuttavia, ho sottovalutato il problema, ho pensato di lavorare su altre parti della mia app, e in seguito sono tornato a questo "semplice" problema. Silly me :)
user50705

Ho letto il documento di disertazione e l'ho trovato molto utile. Il problema con l'approccio era che non avevo schemi per la lingua albanese, anche se ho trovato alcuni strumenti in grado di generare tali schemi. Ad ogni modo, per il mio scopo ho scritto un'app basata su regole, che ha risolto il problema ...
user50705

10
Si noti che l'algoritmo TeX serve a trovare punti di sillabazione legittimi, che non sono esattamente gli stessi delle divisioni di sillabe. È vero che i punti di sillabazione rientrano nelle divisioni della sillaba, ma non tutte le divisioni della sillaba sono punti di sillabazione validi. Ad esempio, i trattini non sono (di solito) utilizzati all'interno di una lettera o due di una delle due estremità di una parola. Credo anche che i modelli TeX siano stati messi a punto per scambiare falsi negativi con falsi positivi (non mettere mai un trattino a cui non appartiene, anche se ciò significa perdere alcune legittime opportunità di sillabazione).
Adrian McCarthy,

1
Non credo nemmeno che la sillabazione sia la risposta.
Ezequiel,


41

Ecco una soluzione che utilizza 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()]] 

Ehi grazie piccolo errore del bambino in dovrebbe essere la funzione def nsyl (word): return [len (list (y for y in x if y [-1] .isdigit ())) for x in d [word.lower ()] ]
Gourneau,

6
Cosa suggeriresti come fallback per le parole che non sono in quel corpus?
Dan Gayle,

4
@Pureferret cmudict è un dizionario pronunciato per le parole inglesi nordamericane. divide le parole in fonemi, che sono più corti delle sillabe (ad es. la parola "gatto" è divisa in tre fonemi: K - AE - T). ma anche le vocali hanno un "marcatore di stress": 0, 1 o 2, a seconda della pronuncia della parola (quindi AE in 'cat' diventa AE1). il codice nella risposta conta i marcatori di stress e quindi il numero delle vocali - che fornisce effettivamente il numero di sillabe (notare come negli esempi di OP ogni sillaba abbia esattamente una vocale).
billy_chapters

1
Ciò restituisce il numero di sillabe, non la sillaba.
Adam Michael Wood,

19

Sto cercando di affrontare questo problema per un programma che calcolerà il punteggio di lettura di flesch-kincaid e flesch di un blocco di testo. Il mio algoritmo utilizza ciò che ho trovato su questo sito Web: http://www.howmanysyllables.com/howtocountsyllables.html e si avvicina ragionevolmente. Ha ancora problemi con parole complicate come invisibile e sillabazione, ma ho scoperto che entra nel campo da baseball per i miei scopi.

Ha il vantaggio di essere facile da implementare. Ho scoperto che gli "es" possono essere sillabici o no. È una scommessa, ma ho deciso di rimuovere gli es dal mio algoritmo.

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

Per il mio semplice scenario di ricerca di sillabe con nomi propri questo sembra inizialmente funzionare abbastanza bene. Grazie per averlo messo qui.
Norman H


5

Perché calcolarlo? Ogni dizionario online ha queste informazioni. http://dictionary.reference.com/browse/invisible in · vis · i · ble


3
Forse deve funzionare per parole che non compaiono nei dizionari, come i nomi?
Wouter Lievens,

4
@WouterLievens: non credo che i nomi siano abbastanza vicini per il parsing automatico della sillaba. Un parser di sillabe per nomi inglesi fallirebbe miseramente su nomi di origine gallese o scozzese, per non parlare di nomi di origini indiane e nigeriane, eppure potresti trovarli tutti in una singola stanza da qualche parte ad esempio a Londra.
Jean-François Corbett,

Bisogna tenere presente che non è ragionevole aspettarsi prestazioni migliori di quelle che un umano potrebbe fornire considerando che si tratta di un approccio puramente euristico a un dominio impreciso.
Darren Ringer

5

Grazie Joe Basirico, per aver condiviso la tua implementazione veloce e sporca in C #. Ho usato le grandi librerie e funzionano, ma di solito sono un po 'lente e per progetti rapidi il tuo metodo funziona bene.

Ecco il tuo codice in Java, insieme ai casi di test:

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

Il risultato è stato come previsto (funziona abbastanza bene per 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

Bumping @Tihamer e @ joe-basirico. Funzione molto utile, non perfetta , ma buona per la maggior parte dei progetti medio-piccoli. Joe, ho riscritto un'implementazione del tuo codice in 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

Spero che qualcuno lo trovi utile!


4

Perl ha il modulo Lingua :: Fonologia :: Sillaba . Potresti provarlo o provare a esaminare il suo algoritmo. Ho visto anche alcuni altri moduli più vecchi lì.

Non capisco perché un'espressione regolare ti dia solo un conteggio di sillabe. Dovresti essere in grado di ottenere le sillabe stesse usando le parentesi di cattura. Supponendo che tu possa costruire un'espressione regolare che funzioni, cioè.


4

Oggi ho trovato questa implementazione Java dell'algoritmo di sillabazione di Frank Liang con pattern per inglese o tedesco, che funziona abbastanza bene ed è disponibile su Maven Central.

Cave: è importante rimuovere le ultime righe dei .texfile di pattern, perché altrimenti questi file non possono essere caricati con la versione corrente su Maven Central.

Per caricare e utilizzare il hyphenator, è possibile utilizzare il seguente frammento di codice Java. texTableè il nome dei .texfile contenenti i modelli necessari. Tali file sono disponibili sul sito github del progetto.

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

Successivamente Hyphenatorè pronto per l'uso. Per rilevare sillabe, l'idea di base è quella di dividere il termine in corrispondenza dei trattini forniti.

    String hyphenedTerm = hyphenator.hyphenate(term);

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

    int syllables = hyphens.length;

Devi dividere "\u00AD", poiché l'API non restituisce un valore normale "-".

Questo approccio supera la risposta di Joe Basirico, poiché supporta molte lingue diverse e rileva una sillabazione tedesca più accurata.


4

Ho incontrato questo stesso identico problema poco fa.

Ho finito per usare il Dizionario di pronuncia CMU per ricerche rapide e accurate della maggior parte delle parole. Per le parole che non sono nel dizionario, sono tornato a un modello di apprendimento automatico con una precisione del 98% circa nella previsione del conteggio delle sillabe.

Ho racchiuso il tutto in un modulo Python facile da usare qui: https://github.com/repp/big-phoney

Installare: pip install big-phoney

Conte Sillabe:

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

Se non stai usando Python e vuoi provare l'approccio basato sul modello ML, ho fatto un resoconto abbastanza dettagliato su come il modello di conteggio delle sillabe funziona su Kaggle .


Questo è fantastico. Qualcuno ha avuto fortuna a convertire il modello Keras risultante in un modello CoreML per l'uso su iOS?
Alexsander Akers,

2

Grazie @ joe-basirico e @tihamer. Ho portato il codice di @ tihamer su Lua 5.1, 5.2 e luajit 2 ( molto probabilmente funzionerà anche su altre versioni di 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

E alcuni test divertenti per confermare che funziona ( per quanto dovrebbe ):

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

Ho aggiunto altri due casi di test "End" e "I". La correzione consisteva nel confrontare le stringhe in modo insensibile. Ping'ing @ joe-basirico e tihamer nel caso in cui soffrano dello stesso problema e vorrebbero aggiornare le loro funzioni.
josefnpat,

@tihamer American è di 4 sillabe!
josefnpat,

2

Non sono riuscito a trovare un modo adeguato per contare le sillabe, quindi ho progettato un metodo da solo.

Puoi visualizzare il mio metodo qui: https://stackoverflow.com/a/32784041/2734752

Uso una combinazione di un dizionario e un metodo algoritmico per contare le sillabe.

Puoi visualizzare la mia biblioteca qui: https://github.com/troywatson/Lawrence-Style-Checker

Ho appena testato il mio algoritmo e ho avuto un tasso di strike del 99,4%!

Lawrence lawrence = new Lawrence();

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

Produzione:

4
3


Vedi Evidenziazione della sintassi . C'è un pulsante di aiuto (punto interrogativo) nell'editor SO che ti porterà alla pagina collegata.
IKavanagh,

0

Dopo aver fatto molti test e provato anche i pacchetti di sillabazione, ho scritto il mio sulla base di numerosi esempi. Ho anche provato i pacchetti pyhyphene pyphenche si interfacciano con i dizionari di sillabazione, ma in molti casi producono un numero errato di sillabe. Il nltkpacchetto era semplicemente troppo lento per questo caso d'uso.

La mia implementazione in Python fa parte di una classe che ho scritto e la routine di conteggio delle sillabe è incollata di seguito. Sovra-stima il numero di sillabe un po 'perché non ho ancora trovato un buon modo per spiegare la fine delle parole silenziose.

La funzione restituisce il rapporto di sillabe per parola poiché viene utilizzata per un punteggio di leggibilità di Flesch-Kincaid. Il numero non deve essere esatto, abbastanza vicino per un preventivo.

Sulla mia CPU i7 di settima generazione, questa funzione ha richiesto 1,1-1,2 millisecondi per un testo di esempio di 759 parole.

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

Ho usato jsoup per farlo una volta. Ecco un parser di sillabe di esempio:

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

Com'è un analizzatore di sillabe generico? Sembra che questo codice cerchi solo sillabe in un dizionario
Nico Haase
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.