Detectando sílabas en una palabra


138

Necesito encontrar una manera bastante eficiente de detectar sílabas en una palabra. P.ej,

Invisible -> in-vi-sib-le

Hay algunas reglas de syllabification que podrían usarse:

V CV VC CVC CCV CCCV CVCC

* donde V es una vocal y C es una consonante. P.ej,

Pronunciación (5 Pro-nun-ci-a-tion; CV-CVC-CV-V-CVC)

He probado algunos métodos, entre los que se encuentran el uso de expresiones regulares (que ayuda solo si quieres contar las sílabas) o la definición de reglas codificadas (un enfoque de fuerza bruta que demuestra ser muy ineficiente) y finalmente usar un autómata de estado finito (que sí no resulte con nada útil).

El propósito de mi aplicación es crear un diccionario de todas las sílabas en un idioma determinado. Este diccionario se utilizará más tarde para aplicaciones de revisión ortográfica (usando clasificadores bayesianos) y síntesis de texto a voz.

Le agradecería que me diera consejos sobre una forma alternativa de resolver este problema además de mis enfoques anteriores.

Trabajo en Java, pero cualquier consejo en C / C ++, C #, Python, Perl ... funcionaría para mí.


¿Realmente quieres los puntos de división reales o solo el número de sílabas en una palabra? Si es lo último, considere buscar las palabras en un diccionario de texto a voz y cuente los fonemas que codifican los sonidos de las vocales.
Adrian McCarthy

Supongo que la forma más eficiente (computacional; no en cuanto al almacenamiento) sería tener un diccionario Python con palabras como claves y la cantidad de sílabas como valores. Sin embargo, aún necesitaría una reserva para las palabras que no aparecieron en el diccionario. ¡Avísame si alguna vez encuentras ese diccionario!
Brōtsyorfuzthrāx

Respuestas:


120

Lea sobre el enfoque de TeX para este problema a los efectos de la separación silábica. Ver especialmente la tesis de la tesis de Frank Liang Word Hy-phen-a-tion de Com-put-er . Su algoritmo es muy preciso y luego incluye un pequeño diccionario de excepciones para casos en los que el algoritmo no funciona.


52
Me gusta que hayas citado una tesis sobre el tema, es una pequeña pista para el póster original de que esta podría no ser una pregunta fácil.
Karl

Sí, soy consciente de que esta no es una pregunta simple, aunque no he trabajado mucho en ello. Sin embargo, subestimé el problema, pensé que trabajaría en otras partes de mi aplicación y luego volvería a este problema 'simple'. Yo tonto :)
user50705 01 de

Leí el documento de disertación, y lo encontré muy útil. El problema con el enfoque fue que no tenía ningún patrón para el idioma albanés, aunque encontré algunas herramientas que podrían generar esos patrones. De todos modos, para mi propósito escribí una aplicación basada en reglas, que resolvió el problema ...
user50705

10
Tenga en cuenta que el algoritmo TeX es para encontrar puntos de separación de sílabas legítimos, que no es exactamente lo mismo que las divisiones de sílabas. Es cierto que los puntos de separación de sílabas se dividen en divisiones de sílabas, pero no todas las divisiones de sílabas son puntos de separación de sílabas válidos. Por ejemplo, los guiones no se usan (generalmente) dentro de una letra o dos de los extremos de una palabra. También creo que los patrones de TeX se ajustaron para intercambiar falsos negativos por falsos positivos (nunca coloque un guión donde no pertenezca, incluso si eso significa perder algunas oportunidades legítimas de separación de sílabas).
Adrian McCarthy

1
Tampoco creo que la separación de sílabas sea la respuesta.
Ezequiel

46

Me encontré con esta página buscando lo mismo, y encontré algunas implementaciones del documento de Liang aquí: https://github.com/mnater/hyphenator o el sucesor: https://github.com/mnater/Hyphenopoly

Eso es a menos que sea del tipo que disfruta leer una tesis de 60 páginas en lugar de adaptar el código disponible libremente para un problema no único. :)


acordado - mucho más conveniente simplemente usar una implicación existente
hoju

41

Aquí hay una solución usando 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()]] 

Hola, pequeño error de bebé en la función debería ser def nsyl (palabra): return [len (list (y para y en x si y [-1] .isdigit ())) para x en d [word.lower ()] ]
Gourneau

66
¿Qué sugerirías como alternativa para las palabras que no están en ese corpus?
Dan Gayle

44
@Pureferret cmudict es un diccionario pronunciador para palabras de inglés norteamericano. divide las palabras en fonemas, que son más cortos que las sílabas (por ejemplo, la palabra 'gato' se divide en tres fonemas: K - AE - T). pero las vocales también tienen un "marcador de estrés": 0, 1 o 2, dependiendo de la pronunciación de la palabra (entonces AE en 'cat' se convierte en AE1). el código en la respuesta cuenta los marcadores de énfasis y, por lo tanto, el número de vocales, lo que efectivamente da el número de sílabas (observe cómo en los ejemplos de OP cada sílaba tiene exactamente una vocal).
billy_chapters

1
Esto devuelve el número de sílabas, no la sílabas.
Adam Michael Wood

19

Estoy tratando de abordar este problema para un programa que calculará el puntaje de lectura flesch-kincaid y flesch de un bloque de texto. Mi algoritmo utiliza lo que encontré en este sitio web: http://www.howmanysyllables.com/howtocountsyllables.html y se acerca razonablemente. Todavía tiene problemas con palabras complicadas como invisible y separación silábica, pero he descubierto que entra en el estadio para mis propósitos.

Tiene la ventaja de ser fácil de implementar. Encontré que las "es" pueden ser silábicas o no. Es una apuesta, pero decidí eliminar las es en mi 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;
    }

Para mi escenario simple de encontrar sílabas en nombres propios, esto parece estar funcionando inicialmente bastante bien. Gracias por publicarlo aquí.
Norman H


5

¿Por qué calcularlo? Cada diccionario en línea tiene esta información. http://dictionary.reference.com/browse/invisible en · vis · i · ble


3
¿Quizás tenga que funcionar para palabras que no aparecen en los diccionarios, como nombres?
Wouter Lievens

44
@WouterLievens: No creo que los nombres estén lo suficientemente bien comportados para el análisis automático de sílabas. Un analizador de sílabas para los nombres en inglés podría fallar miserablemente en los nombres de origen galés o escocés, y mucho menos los nombres de origen indio y nigeriano, sin embargo, puede encontrar todo esto en una sola habitación en algún lugar, por ejemplo, en Londres.
Jean-François Corbett

Hay que tener en cuenta que no es razonable esperar un mejor rendimiento del que podría ofrecer un ser humano teniendo en cuenta que este es un enfoque puramente heurístico para un dominio incompleto.
Darren Ringer

5

Gracias Joe Basirico, por compartir su implementación rápida y sucia en C #. He usado las bibliotecas grandes, y funcionan, pero generalmente son un poco lentas, y para proyectos rápidos, su método funciona bien.

Aquí está su código en Java, junto con casos de prueba:

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

El resultado fue el esperado (funciona lo suficientemente bien para 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

Golpeando a @Tihamer y @ joe-basirico. Función muy útil, no perfecta , pero buena para la mayoría de los proyectos pequeños a medianos. Joe, he reescrito una implementación de tu código en 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

¡Espero que alguien encuentre esto útil!


4

Perl tiene Lingua :: Fonología :: Syllable Módulo . Podrías probar eso, o intentar buscar en su algoritmo. También vi algunos otros módulos más antiguos allí.

No entiendo por qué una expresión regular solo te da un recuento de sílabas. Deberías poder obtener las sílabas usando paréntesis de captura. Suponiendo que puede construir una expresión regular que funcione, es decir.


4

Hoy encontré esta implementación de Java del algoritmo de división de palabras de Frank Liang con patrón para inglés o alemán, que funciona bastante bien y está disponible en Maven Central.

Cueva: es importante eliminar las últimas líneas del .tex archivos de patrones, porque de lo contrario esos archivos no se pueden cargar con la versión actual en Maven Central.

Para cargar y usar hyphenator, puede usar el siguiente fragmento de código Java. texTablees el nombre de los .texarchivos que contienen los patrones necesarios. Esos archivos están disponibles en el sitio del proyecto 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;
    }

Luego el Hyphenatorestá listo para usar. Para detectar sílabas, la idea básica es dividir el término en los guiones provistos.

    String hyphenedTerm = hyphenator.hyphenate(term);

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

    int syllables = hyphens.length;

Debe dividirse en "\u00AD", ya que la API no devuelve un valor normal "-".

Este enfoque supera la respuesta de Joe Basirico, ya que admite muchos idiomas diferentes y detecta la separación silábica más precisa.


4

Me encontré con este mismo problema hace un rato.

Terminé usando el Diccionario de Pronunciación CMU para búsquedas rápidas y precisas de la mayoría de las palabras. Para las palabras que no están en el diccionario, recurrí a un modelo de aprendizaje automático que es ~ 98% exacto al predecir recuentos de sílabas.

Abrigé todo en un módulo de Python fácil de usar aquí: https://github.com/repp/big-phoney

Instalar en pc: pip install big-phoney

Conde Syllables:

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

Si no está utilizando Python y desea probar el enfoque basado en el modelo ML, hice una descripción bastante detallada sobre cómo funciona el modelo de conteo de sílabas en Kaggle .


Esto es super genial. ¿Alguien ha tenido suerte al convertir el modelo Keras resultante en un modelo CoreML para usar en iOS?
Alexsander Akers

2

Gracias @ joe-basirico y @tihamer. He portado el código de @ tihamer a Lua 5.1, 5.2 y luajit 2 (lo más probable es que también se ejecute en otras versiones de 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

Y algunas pruebas divertidas para confirmar que funciona ( tanto como se supone que debe ):

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

Agregué dos casos de prueba más "Fin" y "I". La solución fue comparar las cadenas de mayúsculas y minúsculas sin distinción. Ping'ing @ joe-basirico y tihamer en caso de que sufran el mismo problema y deseen actualizar sus funciones.
josefnpat

¡@tihamer American tiene 4 sílabas!
josefnpat

2

No pude encontrar una manera adecuada de contar las sílabas, así que diseñé un método yo mismo.

Puede ver mi método aquí: https://stackoverflow.com/a/32784041/2734752

Utilizo una combinación de un diccionario y un método de algoritmo para contar las sílabas.

Puede ver mi biblioteca aquí: https://github.com/troywatson/Lawrence-Style-Checker

¡Acabo de probar mi algoritmo y obtuve una tasa de ataque del 99.4%!

Lawrence lawrence = new Lawrence();

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

Salida:

4
3


Ver resaltado de sintaxis . Hay un botón de ayuda (signo de interrogación) en el editor SO que lo llevará a la página vinculada.
IKavanagh

0

Después de hacer muchas pruebas y probar también los paquetes de separación de sílabas, escribí el mío basado en una serie de ejemplos. También probé los paquetes pyhypheny pyphenque interactúan con los diccionarios de separación silábica, pero en muchos casos producen un número incorrecto de sílabas. losnltk paquete era simplemente demasiado lento para este caso de uso.

Mi implementación en Python es parte de una clase que escribí, y la rutina de conteo de sílabas se pega a continuación. Sobreestima un poco la cantidad de sílabas, ya que todavía no he encontrado una buena manera de explicar las terminaciones de palabras silenciosas.

La función devuelve la proporción de sílabas por palabra, ya que se utiliza para una puntuación de legibilidad Flesch-Kincaid. El número no tiene que ser exacto, solo lo suficientemente cerca para una estimación.

En mi CPU i7 de séptima generación, esta función tomó 1.1-1.2 milisegundos para un texto de muestra de 759 palabras.

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

Solía ​​jsoup para hacer esto una vez. Aquí hay un analizador de sílabas de muestra:

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

¿Cómo es eso un analizador de sílabas genérico? Parece que este código solo busca sílabas en un diccionario
Nico Haase
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.