Détection de syllabes dans un mot
je dois trouver un moyen assez efficace de détecter les syllabes dans un mot. Par exemple,
Invisible -> en-vi-sib-le
il y a des règles de syllabification qui pourraient être utilisées:
V CV VC CVC CCV CCCV CVCC
* où V est une voyelle et C une consonne. Par exemple,
prononciation (5 Pro-nun-ci-a-tion; CV-CVC-CV-V-CVC)
j'ai essayé peu de méthodes, parmi qui utilisaient regex (qui aide seulement si vous voulez compter des syllabes) ou la définition de la règle codée dure (une approche de la force brute qui s'avère être très inefficace) et finalement en utilisant un automata d'état fini (qui n'a pas donné de résultat avec quelque chose 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 plus tard utilisé pour des applications de vérification orthographique (en utilisant des classificateurs bayésiens) et de synthèse textuelle.
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 n'importe quel conseil en C/C++, C#, Python, Perl... pourrait fonctionner pour moi.
15 réponses
lisez à propos de l'approche TeX à ce problème pour les besoins de la hyphénation. Surtout voir Frank Liang thèse Mot Hy-phen-a-tion par Com-mis-er . Son algorithme est très précis, et comprend ensuite un petit dictionnaire d'exceptions pour les cas où l'algorithme ne fonctionne pas.
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
qui est à moins que vous êtes le type qui aime lire une thèse de 60 pages au lieu d'adapter le code librement disponible pour le problème non-unique. :)
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: http://www.howmanysyllables.com/howtocountsyllables.html et il devient raisonnablement proche. Il a encore des problèmes sur les mots compliqués comme invisible et le trait d'Union, mais je l'ai trouvé obtient dans le stade pour mes buts.
Il a l'avantage d'être facile mettre. J'ai trouvé que le "es" peut être syllabique ou pas. C'est un pari, mais j'ai décidé de supprimer l'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;
}
il s'agit d'un problème particulièrement difficile qui n'est pas complètement résolu par l'algorithme D'hyphénation du LaTeX. Un bon résumé de quelques méthodes disponibles et les défis impliqués peut être trouvé dans le document Evaluating Automatic Syllabification Algorithms for English (Marchand, Adsett, and Damper 2007).
Merci Joe Basirico, pour partager votre mise en œuvre 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 très bien.
Voici votre code en Java, ainsi que des 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
Bumping @Tihamer et @joe-basirico. Fonction très utile, pas parfait , mais bon pour la plupart des projets de petite à moyenne. 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
Espère que quelqu'un trouve cette pratique!
Perl a Lingua::Phonologie::Syllabe module. Vous pourriez essayer ça, ou chercher dans son algorithme. J'ai vu quelques autres anciens modules.
Je ne comprends pas pourquoi une expression régulière ne donne qu'un nombre de syllabes. Vous devriez être en mesure d'obtenir les syllabes elles-mêmes en utilisant les parenthèses de capture. En supposant que vous pouvez construire une expression régulière qui fonctionne, ce qui est.
Aujourd'hui j'ai trouvé cette implémentation Java de L'algorithme d'hyphénation de Frank Liang avec 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 motif .tex
, car autrement ces fichiers ne peuvent pas être chargés avec la version actuelle sur Maven Central.
Pour charger et utiliser le hyphenator
, vous pouvez utiliser le suivant code Java snippet. texTable
est le nom des fichiers .tex
contenant les motifs nécessaires. Ces fichiers sont disponibles sur le site du projet 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;
}
après le Hyphenator
est prêt à l'emploi. Pour détecter les syllabes, l'idée de base est de diviser le terme par les traits d'Union fournis.
String hyphenedTerm = hyphenator.hyphenate(term);
String hyphens[] = hyphenedTerm.split("\u00AD");
int syllables = hyphens.length;
vous devez vous séparer sur "\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 les traits d'Union allemands plus précis.
pourquoi le calculer? Chaque dictionnaire en ligne a cette information. http://dictionary.reference.com/browse/invisible ·vis·i·ble
Merci @joe-basirico et @tihamer. J'ai porté le code de @tihamer sur Lua 5.1, 5.2 et luajit 2 ( sera très probablement exécuté sur d'autres versions de lua ainsi que ):
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 qu'il fonctionne ( autant que c'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.")
Je n'ai pas pu trouver un moyen adéquat pour compter les syllabes, donc j'ai 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
I viens de tester mon algorithme et avait une de 99,4% taux de grève!
Lawrence lawrence = new Lawrence();
System.out.println(lawrence.getSyllable("hyphenation"));
System.out.println(lawrence.getSyllable("computer"));
sortie:
4
3
j'ai rencontré ce même numéro il y a peu.
j'ai fini par utiliser le CMU prononciation Dictionary pour la recherche rapide et précise de la plupart des mots. Pour les mots qui ne sont pas dans le dictionnaire, je suis retombé sur un modèle d'apprentissage automatique qui est ~98% précis pour prédire les nombres de syllabes.
j'ai tout emballé dans un module python facile à utiliser ici: https://github.com/repp/big-phoney
installer:
pip install big-phoney
Count Syllabes:
from big_phoney import BigPhoney
phoney = BigPhoney()
phoney.count_syllables('triceratops') # --> 4
si vous n'utilisez pas Python et que vous voulez essayer L'approche basée sur le modèle ML, j'ai fait un assez détaillé sur la façon dont le modèle de comptage de syllabes fonctionne sur Kaggle .
Après avoir fait beaucoup de tests et d'essayer de césure paquets ainsi, j'ai écrit ma propre fondée sur un certain nombre d'exemples. J'ai aussi essayé les paquets pyhyphen
et pyphen
qui sont en interface avec les dictionnaires de typographie, mais ils produisent le mauvais nombre de syllabes dans de nombreux cas. Le nltk
paquet était tout simplement trop lent pour ce cas d'utilisation.
mon implémentation en Python fait partie d'un cours que j'ai écrit, et la routine de comptage de syllabes est collée dessous. Il surestime un peu le nombre de syllabes, car je n'ai toujours pas trouvé de bonne façon de tenir compte des fins muettes.
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 CPU i7 de 7ème génération, cette fonction a pris 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)
j'ai utilisé jsoup pour faire ça une fois. Voici un exemple d'analyseur de syllabes:
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;
}