web-dev-qa-db-fra.com

Détecter des syllabes dans un mot

Je dois 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 peuvent être utilisées:

V CV VC CVC CCV CCCV CVCC

* où V est une voyelle et C est une consonne . 

Prononciation (5 voix contre; CV-CVC-CV-V-CVC)

J'ai essayé peu de méthodes, parmi lesquelles on utilisait regex (ce qui n'est utile que si l'on veut compter les syllabes) ou la définition de règle codée en dur (une approche de force brute qui s'avère très peu efficace) et enfin l'utilisation d'un automate à états finis (qui 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é par la suite pour des applications de vérification orthographique (utilisant des classificateurs bayésiens) et de synthèse de texte à parole. 

Je vous serais reconnaissant de bien vouloir me donner des conseils sur un autre moyen 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.

124
user50705

Lisez à propos de l'approche TeX de ce problème aux fins de la césure. Voir en particulier Frank Liang mémoire de thèse Parole Hy-phen-a-tion de Com-put-er . Son algorithme est très précis et inclut ensuite un dictionnaire de petites exceptions pour les cas où l’algorithme ne fonctionne pas.

114
jason

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

C’est à moins que vous ne soyez du genre à apprécier la lecture d’une thèse de 60 pages au lieu d’adapter du code librement disponible à un problème non unique. :)

42
Sean

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

J'essaie de résoudre ce problème pour un programme qui calcule les scores de lecture flesch-kincaid et flesch d'un bloc de texte. Mon algorithme utilise ce que j'ai trouvé sur ce site: http://www.howmanysyllables.com/howtocountsyllables.html et cela devient assez proche. Il a toujours du mal à utiliser des mots compliqués comme invisible et la césure, mais je trouve que ça entre dans le stade pour mes besoins. 

Il a l'avantage d'être facile à mettre en œuvre. J'ai trouvé le "es" peut être syllabique ou non. C'est un pari mais j'ai décidé de supprimer l'es de 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;
    }
18
Joe Basirico

C'est un problème particulièrement difficile qui n'est pas complètement résolu par l'algorithme de césure de LaTeX. Vous trouverez un bon résumé de certaines des méthodes disponibles et des problèmes rencontrés dans le document Evaluation des algorithmes de syllabification automatique en anglais (Marchand, Adsett et Damper 2007).

7
Chris

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, avec 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
Tihamer

Bumping @Tihamer et @ joe-basirico. Fonction très utile, pas parfaite, mais bonne pour la plupart des projets de petite à moyenne taille. 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 trouve cela utile!

5
Tersosauros

Aujourd'hui, j'ai trouvé ceci une 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 fichiers de modèle .tex, sinon ces fichiers ne peuvent pas être chargés avec la version actuelle sur Maven Central.

Pour charger et utiliser la hyphenator, vous pouvez utiliser l'extrait de code Java suivant. texTable est le nom des fichiers .tex 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, la Hyphenator est prête à être utilisée. Pour détecter les syllabes, l’idée de base est de scinder le terme au niveau des tirets fournis.

    String hyphenedTerm = hyphenator.hyphenate(term);

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

    int syllables = hyphens.length;

Vous devez diviser en "\u00AD ", car l’API ne renvoie 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 la césure allemande plus précisément.

4
rzo

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

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

4
skiphoppy

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

3
Cerin

Je ne pouvais pas trouver un moyen adéquat pour compter les syllabes, alors j'ai conçu moi-même une méthode. 

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 voir ma bibliothèque ici: https://github.com/troywatson/Lawrence-Style-Checker

Je viens de tester mon algorithme et j'avais un taux de collision de 99,4%! 

Lawrence lawrence = new Lawrence();

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

Sortie:

4
3
2
troy

Merci @ joe-basirico et @tihamer. J'ai porté le code de @ tihamer sur Lua 5.1, 5.2 et luajit 2 ( fonctionnera probablement 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 que cela est supposé ):

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
josefnpat

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

J'ai fini par utiliser le Dictionnaire de prononciation CMU pour une recherche rapide et précise de la plupart des mots. Pour les mots ne figurant pas dans le dictionnaire, je suis revenu à un modèle d’apprentissage automatique précis à environ 98% pour prédire le nombre de syllabes.

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

Installer: pip install big-phoney

Comptez 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 un modèle ML, j'ai écrit de manière assez détaillée sur le fonctionnement du modèle de comptage de syllabes sous Kaggle .

0
Ryan Epp

Après avoir beaucoup testé et essayé plusieurs paquets de césure, j'ai écrit le mien à partir d'un certain nombre d'exemples. J'ai également essayé les packages pyhyphen et pyphen qui s'interfacent avec les dictionnaires de césure, mais ils produisent souvent un nombre incorrect de syllabes. Le package nltk é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 des syllabes est collée ci-dessous. Il surestime un peu le nombre de syllabes, car je n’ai toujours pas trouvé le moyen de rendre compte de la fin silencieuse de Word.

La fonction renvoie le rapport des syllabes par mot utilisé pour un score de lisibilité de Flesch-Kincaid. Le nombre n'a pas besoin d'être exact, mais 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 Word.

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
Jadzia626