Détecter les syllabes dans un mot


138

J'ai besoin de trouver un moyen assez efficace de détecter les syllabes dans un mot. Par exemple,

Invisible -> in-vi-sib-le

Certaines règles de syllabification pourraient être utilisées:

V CV VC CVC CCV CCCV CVCC

* où V est une voyelle et C est une consonne. Par exemple,

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

J'ai essayé quelques méthodes, parmi lesquelles l'utilisation de regex (qui aide seulement si vous voulez compter les syllabes) ou la définition de règle codée en dur (une approche de la force brute qui s'avère très inefficace) et enfin l'utilisation d'un automate à états finis (ce qui a fait ne donne rien d’utile).

Le but de mon application est de créer un dictionnaire de toutes les syllabes dans une langue donnée. Ce dictionnaire sera utilisé plus tard pour les applications de vérification orthographique (utilisant des classificateurs bayésiens) et la synthèse de texte à parole.

J'apprécierais si l'on pouvait me donner des conseils sur une autre façon de résoudre ce problème en plus de mes approches précédentes.

Je travaille en Java, mais toute astuce en C / C ++, C #, Python, Perl ... fonctionnerait pour moi.


Voulez-vous réellement les points de division réels ou simplement le nombre de syllabes dans un mot? Dans ce dernier cas, envisagez de rechercher les mots dans un dictionnaire de synthèse vocale et comptez les phonèmes qui codent les voyelles.
Adrian McCarthy

Le moyen le plus efficace (en termes de calcul; pas en termes de stockage), je suppose que ce serait juste d'avoir un dictionnaire Python avec des mots comme clés et le nombre de syllabes comme valeurs. Cependant, vous avez toujours besoin d'une solution de remplacement pour les mots qui ne figurent pas dans le dictionnaire. Faites-moi savoir si jamais vous trouvez un tel dictionnaire!
Brōtsyorfuzthrāx

Réponses:


120

Découvrez l'approche TeX de ce problème aux fins de la césure. Voir en particulier la thèse de Thèse de Frank Liang Word Hy-phen-a-tion de Com-put-er . Son algorithme est très précis, et inclut alors un dictionnaire de petites exceptions pour les cas où l'algorithme ne fonctionne pas.


52
J'aime que vous ayez cité une thèse sur le sujet, c'est un petit indice à l'affiche originale que ce n'est peut-être pas une question facile.
Karl

Oui, je suis conscient que ce n'est pas une question simple, même si je n'y ai pas beaucoup travaillé. J'ai cependant sous-estimé le problème, je pensais que je travaillerais sur d'autres parties de mon application et que je reviendrais plus tard à ce problème `` simple ''. Silly me :)
user50705

J'ai lu le mémoire et je l'ai trouvé très utile. Le problème avec cette approche était que je n'avais aucun modèle pour la langue albanaise, bien que j'aie trouvé des outils qui pourraient générer ces modèles. Quoi qu'il en soit, dans mon but, j'ai écrit une application basée sur des règles, qui a résolu le problème ...
user50705

10
Notez que l'algorithme TeX sert à trouver des points de césure légitimes, ce qui n'est pas exactement la même chose que les divisions syllabiques. Il est vrai que les points de césure tombent sur les divisions syllabiques, mais toutes les divisions syllabiques ne sont pas des points de césure valides. Par exemple, les tirets ne sont pas (généralement) utilisés dans une lettre ou deux de l'une ou l'autre extrémité d'un mot. Je pense également que les modèles TeX ont été réglés pour échanger les faux négatifs contre les faux positifs (ne mettez jamais un trait d'union là où il n'a pas sa place, même si cela signifie manquer des opportunités de césure légitimes).
Adrian McCarthy

1
Je ne pense pas non plus que la césure soit la réponse.
Ezequiel

46

Je suis tombé sur cette page à la recherche de la même chose et j'ai trouvé quelques implémentations du document de Liang ici: https://github.com/mnater/hyphenator ou le successeur: https://github.com/mnater/Hyphenopoly

À moins que vous ne soyez du genre à aimer lire une thèse de 60 pages au lieu d'adapter du code disponible gratuitement pour un problème non unique. :)


d'accord - beaucoup plus pratique d'utiliser simplement une implémentation existante
hoju

41

Voici une solution utilisant 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()]] 

Hey merci petite erreur de bébé dans le devrait être la fonction def nsyl (mot): return [len (list (y pour y dans x si y [-1] .isdigit ())) pour x dans d [word.lower ()] ]
Gourneau

6
Que suggéreriez-vous comme solution de remplacement pour les mots qui ne figurent pas dans ce corpus?
Dan Gayle

4
@Pureferret cmudict est un dictionnaire de prononciation des mots anglais nord-américains. il divise les mots en phonèmes, qui sont plus courts que les syllabes (par exemple, le mot «chat» est divisé en trois phonèmes: K - AE - T). mais les voyelles ont aussi un «marqueur de stress»: soit 0, 1 ou 2, selon la prononciation du mot (donc AE dans «chat» devient AE1). le code dans la réponse compte les marqueurs de stress et donc le nombre de voyelles - ce qui donne effectivement le nombre de syllabes (remarquez comment dans les exemples de OP chaque syllabe a exactement une voyelle).
billy_chapters

1
Cela renvoie le nombre de syllabes, pas la syllabification.
Adam Michael Wood

19

J'essaie de résoudre ce problème pour un programme qui calculera le score de lecture flesch-kincaid et flesch d'un bloc de texte. Mon algorithme utilise ce que j'ai trouvé sur ce site Web: http://www.howmanysyllables.com/howtocountsyllables.html et il se rapproche raisonnablement. Il a encore des problèmes avec des mots compliqués comme invisible et césure, mais j'ai trouvé que cela correspondait à mes besoins.

Il a l'avantage d'être facile à mettre en œuvre. J'ai trouvé que le "es" pouvait être syllabique ou non. C'est un pari, mais j'ai décidé de supprimer les es dans mon algorithme.

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

Pour mon scénario simple de recherche de syllabes dans les noms propres, cela semble fonctionner assez bien au départ. Merci de l'avoir mis ici.
Norman H


5

Pourquoi le calculer? Chaque dictionnaire en ligne a cette information. http://dictionary.reference.com/browse/invisible in · vis · i · ble


3
Peut-être que cela doit fonctionner pour les mots qui n'apparaissent pas dans les dictionnaires, comme les noms?
Wouter Lievens

4
@WouterLievens: Je ne pense pas que les noms soient assez bien comportés pour l'analyse automatique des syllabes. Un analyseur de syllabes pour les noms anglais échouerait lamentablement sur les noms d'origine galloise ou écossaise, sans parler des noms d'origine indienne et nigériane, mais vous pourriez trouver tout cela dans une seule pièce quelque part à Londres, par exemple.
Jean-François Corbett

Il faut garder à l'esprit qu'il n'est pas raisonnable de s'attendre à de meilleures performances qu'un humain pourrait fournir étant donné qu'il s'agit d'une approche purement heuristique d'un domaine fragmentaire.
Darren Ringer le

5

Merci Joe Basirico, pour le partage de votre implémentation rapide et sale en C #. J'ai utilisé les grandes bibliothèques, et elles fonctionnent, mais elles sont généralement un peu lentes, et pour les projets rapides, votre méthode fonctionne bien.

Voici votre code en Java, ainsi que les cas de 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));
}

Le résultat était comme prévu (cela fonctionne assez bien pour 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 et @ joe-basirico. Fonction très utile, pas parfaite , mais bonne pour la plupart des petits et moyens projets. Joe, j'ai réécrit une implémentation de votre code 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

J'espère que quelqu'un trouvera cela utile!


4

Perl a le module Lingua :: Phonology :: Syllable . Vous pouvez essayer cela, ou essayer de regarder dans son algorithme. J'y ai vu aussi quelques autres modules plus anciens.

Je ne comprends pas pourquoi une expression régulière ne vous donne qu'un nombre de syllabes. Vous devriez pouvoir obtenir les syllabes elles-mêmes en utilisant les parenthèses de capture. En supposant que vous puissiez construire une expression régulière qui fonctionne, c'est-à-dire.


4

Aujourd'hui, j'ai trouvé cette implémentation Java de l'algorithme de césure de Frank Liang avec un motif pour l'anglais ou l'allemand, qui fonctionne assez bien et est disponible sur Maven Central.

Cave: Il est important de supprimer les dernières lignes des .texfichiers de signatures, car sinon ces fichiers ne pourront pas être chargés avec la version actuelle sur Maven Central.

Pour charger et utiliser le hyphenator, vous pouvez utiliser l'extrait de code Java suivant. texTableest le nom des .texfichiers contenant les modèles nécessaires. Ces fichiers sont disponibles sur le site github du projet.

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

Ensuite, le Hyphenatorest prêt à être utilisé. Pour détecter les syllabes, l'idée de base est de diviser le terme aux tirets fournis.

    String hyphenedTerm = hyphenator.hyphenate(term);

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

    int syllables = hyphens.length;

Vous devez diviser sur "\u00AD", car l'API ne retourne pas un normal "-".

Cette approche surpasse la réponse de Joe Basirico, car elle prend en charge de nombreuses langues différentes et détecte plus précisément la césure allemande.


4

J'ai rencontré exactement le même problème il y a quelque temps.

J'ai fini par utiliser le dictionnaire de prononciation CMU pour des recherches rapides et précises de la plupart des mots. Pour les mots qui ne figurent pas dans le dictionnaire, je suis revenu à un modèle d'apprentissage automatique qui est précis à environ 98% pour prédire le nombre de syllabes.

J'ai tout emballé dans un module python facile à utiliser ici: https://github.com/repp/big-phoney

Installer: pip install big-phoney

Compter les syllabes:

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

Si vous n'utilisez pas Python et que vous souhaitez essayer l'approche basée sur le modèle ML, j'ai rédigé un article assez détaillé sur le fonctionnement du modèle de comptage de syllabes sur Kaggle .


C'est super cool. Quelqu'un a-t-il eu la chance de convertir le modèle Keras résultant en un modèle CoreML pour une utilisation sur iOS?
Alexsander Akers

2

Merci @ joe-basirico et @tihamer. J'ai porté le code de @ tihamer sur Lua 5.1, 5.2 et luajit 2 (fonctionnera probablement également sur d'autres versions 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

Et quelques tests amusants pour confirmer que cela fonctionne ( autant qu'il est censé le faire ):

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

J'ai ajouté deux autres cas de test "Fin" et "I". Le correctif consistait à comparer les chaînes de manière insensible à la casse. Ping'ing @ joe-basirico et tihamer au cas où ils souffriraient du même problème et souhaiteraient mettre à jour leurs fonctions.
josefnpat

@tihamer American est de 4 syllabes!
josefnpat

2

Je ne trouvais pas de moyen adéquat pour compter les syllabes, j'ai donc conçu une méthode moi-même.

Vous pouvez voir ma méthode ici: https://stackoverflow.com/a/32784041/2734752

J'utilise une combinaison d'un dictionnaire et d'une méthode d'algorithme pour compter les syllabes.

Vous pouvez consulter ma bibliothèque ici: https://github.com/troywatson/Lawrence-Style-Checker

Je viens de tester mon algorithme et j'ai eu un taux de grève de 99,4%!

Lawrence lawrence = new Lawrence();

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

Production:

4
3


Voir Mise en évidence de la syntaxe . Il y a un bouton d'aide (point d'interrogation) dans l'éditeur SO qui vous mènera à la page liée.
IKavanagh le

0

Après avoir fait beaucoup de tests et essayé des packages de césure, j'ai écrit le mien basé sur un certain nombre d'exemples. J'ai également essayé les packages pyhyphenet pyphenqui s'interfacent avec les dictionnaires de césure, mais ils produisent le mauvais nombre de syllabes dans de nombreux cas. Le nltkpackage était tout simplement trop lent pour ce cas d'utilisation.

Mon implémentation en Python fait partie d'une classe que j'ai écrite, et la routine de comptage de syllabes est collée ci-dessous. Il surestime un peu le nombre de syllabes car je n'ai toujours pas trouvé de bon moyen de rendre compte des fins de mots silencieuses.

La fonction renvoie le rapport de syllabes par mot tel qu'il est utilisé pour un score de lisibilité Flesch-Kincaid. Le nombre n'a pas besoin d'être exact, juste assez proche pour une estimation.

Sur mon processeur i7 de 7e génération, cette fonction prenait 1,1 à 1,2 millisecondes pour un exemple de texte de 759 mots.

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

J'ai utilisé jsoup pour le faire une fois. Voici un exemple d'analyseur de syllabe:

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

En quoi est-ce un analyseur de syllabe générique? Il semble que ce code ne recherche que des syllabes dans un dictionnaire
Nico Haase
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.