Detección de sílabas en una palabra


Necesito encontrar una manera bastante eficiente de detectar sílabas en una palabra. Por ejemplo,

Invisible - > in-vi-sib-le

Hay algunas reglas de silabificación que podrían usarse:

V CV VC CVC CCV CCCV CVCC

*donde V es una vocal y C es una consonante. Por ejemplo,

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

He probado algunos métodos, entre los cuales estaban el uso de expresiones regulares (que ayuda solo si desea contar sílabas) o la definición de reglas codificadas (a enfoque de fuerza bruta que resulta ser muy ineficiente) y, finalmente, el uso de un autómata de estado finito (que no resultó con nada útil).

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

Agradecería si uno pudiera darme consejos sobre una forma alternativa de resolver este problema además de mi anterior enfoque.

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

Author: Kevin Brown, 2009-01-01

15 answers

Lea sobre el enfoque de TeX para este problema a los efectos de la partición. Especialmente ver la disertación de tesis de Frank Liang Word Hy-phen-a-tion by Com-put-er . Su algoritmo es muy preciso, y luego incluye un pequeño diccionario de excepciones para los casos en los que el algoritmo no funciona.

 104
Author: jason,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-01 17:17:58

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

Eso es a menos que seas del tipo que disfruta leyendo una tesis de 60 páginas en lugar de adaptar código disponible libremente para problemas no únicos. :)

 41
Author: Sean,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-19 00:07:37

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()]] 
 35
Author: hoju,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-21 23:52:31

Estoy tratando de abordar este problema para un programa que calculará la puntuación 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 de palabras, pero he encontrado que se pone en el estadio para mis propósitos.

Tiene la ventaja de ser fácil de implementar. Encontré que la " es " puede ser silábico 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;
    }
 16
Author: Joe Basirico,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-16 21:53:02

Este es un problema particularmente difícil que no está completamente resuelto por el algoritmo de partición de látex. Un buen resumen de algunos métodos disponibles y los desafíos involucrados se puede encontrar en el documento Evaluating Automatic Syllabification Algorithms for English (Marchand, Adsett, and Damper 2007).

 6
Author: Chris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-07 15:40:54

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

Aquí está su código en Java, junto con los 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
Author: Tihamer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-20 15:22:58

Bumping @Tihamer y @joe-basirico. Función muy útil, no perfecto , pero bueno 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!

 5
Author: Tersosauros,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-14 06:24:02

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

No entiendo por qué una expresión regular te da solo una cuenta de sílabas. Debería ser capaz de obtener las sílabas mismas usando paréntesis de captura. Suponiendo que usted puede construir una expresión regular que funciona, es decir.

 4
Author: skiphoppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-11 14:53:06

Hoy encontré esta implementación Java del algoritmo de partició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 de los archivos de patrón .tex, porque de lo contrario esos archivos no se pueden cargar con la versión actual en Maven Central.

Para cargar y usar el hyphenator, puede usar el siguiente fragmento de código Java. texTable es el nombre de los archivos .tex contiene 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;
    }

Después, el Hyphenator está listo para usar. Para detectar sílabas, la idea básica es dividir el término en los guiones proporcionados.

    String hyphenedTerm = hyphenator.hyphenate(term);

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

    int syllables = hyphens.length;

Necesita dividir en "\u00AD", ya que la API no devuelve un "-" normal.

Este enfoque supera la respuesta de Joe Basirico, ya que admite muchos idiomas diferentes y detecta la partición de palabras en alemán con mayor precisión.

 4
Author: rzo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-17 14:47:33

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

 3
Author: Cerin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-20 02:44:51

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 funciona):

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
Author: josefnpat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-09 22:35:06

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

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

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

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

¡Acabo de probar mi algoritmo y tuve 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
 2
Author: troy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:27

Me encontré con este mismo problema hace un rato.

Terminé usando el CMU Pronunciation Dictionary para búsquedas rápidas y precisas de la mayoría de las palabras. Para las palabras que no están en el diccionario, volví a un modelo de aprendizaje automático que es ~98% preciso en la predicción de conteos de sílabas.

Lo envolví todo en un módulo python fácil de usar aquí: https://github.com/repp/big-phoney

Instalar: pip install big-phoney

Contar sílabas:

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

Si no estás usando Python y quieres probar el enfoque basado en el modelo ML, hice un bastante detallado escribir sobre cómo funciona el modelo de conteo de sílabas en Kaggle.

 0
Author: Ryan Epp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-02 19:56:12

Después de hacer muchas pruebas y probar paquetes de guiones también, escribí el mío basado en una serie de ejemplos. También probé los paquetes pyhyphen y pyphen que interactúan con diccionarios de guiones, pero producen el número incorrecto de sílabas en muchos casos. El paquete nltk 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. Sobre-estima el número de sílabas a aunque todavía no he encontrado una buena manera de explicar los finales silenciosos de las palabras.

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

En mi CPU i7 de 7a 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)
 0
Author: Jadzia626,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-23 13:41:03

Usé jsoup para hacer esto una vez. Aquí hay un ejemplo de analizador de sílabas:

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;
    }
 -1
Author: Itamar Fiorino,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-09 16:09:55