Silben in einem Wort erkennen

137

Ich muss einen ziemlich effizienten Weg finden, um Silben in einem Wort zu erkennen. Z.B,

Unsichtbar -> in-vi-sib-le

Es gibt einige Silbenregeln, die verwendet werden könnten:

V CV VC CVC CCV CCCV CVCC

* wobei V ein Vokal und C ein Konsonant ist. Z.B,

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

Ich habe nur wenige Methoden ausprobiert, darunter die Verwendung von Regex (was nur hilft, wenn Sie Silben zählen möchten) oder einer fest codierten Regeldefinition (ein Brute-Force-Ansatz, der sich als sehr ineffizient herausstellt) und schließlich die Verwendung von Automaten mit endlichen Zuständen (was der Fall war) nicht mit irgendetwas Nützlichem resultieren).

Der Zweck meiner Anwendung ist es, ein Wörterbuch aller Silben in einer bestimmten Sprache zu erstellen. Dieses Wörterbuch wird später für Rechtschreibprüfungsanwendungen (unter Verwendung von Bayes'schen Klassifikatoren) und für die Text-zu-Sprache-Synthese verwendet.

Ich würde mich freuen, wenn man mir neben meinen bisherigen Ansätzen Tipps geben könnte, wie man dieses Problem auf andere Weise lösen kann.

Ich arbeite in Java, aber jeder Tipp in C / C ++, C #, Python, Perl ... würde für mich funktionieren.

user50705
quelle
Möchten Sie tatsächlich die tatsächlichen Teilungspunkte oder nur die Anzahl der Silben in einem Wort? In letzterem Fall sollten Sie die Wörter in einem Text-zu-Sprache-Wörterbuch nachschlagen und die Phoneme zählen, die Vokale codieren.
Adrian McCarthy
Der effizienteste Weg (rechnerisch; nicht speichertechnisch) wäre wohl, ein Python-Wörterbuch mit Wörtern als Schlüssel und der Anzahl der Silben als Werte zu haben. Sie benötigen jedoch immer noch einen Fallback für Wörter, die es nicht in das Wörterbuch geschafft haben. Lassen Sie mich wissen, wenn Sie jemals ein solches Wörterbuch finden!
Brōtsyorfuzthrāx

Antworten:

119

Lesen Sie mehr über den TeX-Ansatz für dieses Problem zum Zwecke der Silbentrennung. Siehe insbesondere Frank Liangs Dissertation Word Hy-phen-a-tion von Computer . Sein Algorithmus ist sehr genau und enthält dann ein kleines Ausnahmewörterbuch für Fälle, in denen der Algorithmus nicht funktioniert.

Jason
quelle
52
Ich finde es gut, dass Sie eine Dissertation zu diesem Thema zitiert haben. Es ist ein kleiner Hinweis auf das Originalplakat, dass dies möglicherweise keine einfache Frage ist.
Karl
Ja, mir ist bewusst, dass dies keine einfache Frage ist, obwohl ich nicht viel daran gearbeitet habe. Ich habe das Problem jedoch unterschätzt und dachte, ich würde an anderen Teilen meiner App arbeiten und später zu diesem „einfachen“ Problem zurückkehren.
Blöd
Ich habe das Dissertationspapier gelesen und fand es sehr hilfreich. Das Problem mit dem Ansatz war, dass ich keine Muster für die albanische Sprache hatte, obwohl ich einige Werkzeuge gefunden habe, die diese Muster erzeugen könnten. Jedenfalls habe ich für meinen Zweck eine regelbasierte App geschrieben, die das Problem gelöst hat ...
user50705
10
Beachten Sie, dass der TeX-Algorithmus zum Auffinden legitimer Silbentrennungspunkte dient, was nicht genau mit Silbentrennungen identisch ist. Es ist wahr, dass Silbentrennungspunkte auf Silbentrennungen fallen, aber nicht alle Silbentrennungen sind gültige Silbentrennungspunkte. Beispielsweise werden Bindestriche (normalerweise) nicht in einem oder zwei Buchstaben eines Wortendes verwendet. Ich glaube auch, dass die TeX-Muster so abgestimmt wurden, dass falsche Negative gegen falsch positive ausgetauscht werden (setzen Sie niemals einen Bindestrich dort ein, wo er nicht hingehört, auch wenn dies bedeutet, dass einige legitime Silbentrennungsmöglichkeiten fehlen).
Adrian McCarthy
1
Ich glaube auch nicht, dass Silbentrennung die Antwort ist.
Ezequiel
46

Ich bin über diese Seite gestolpert und habe nach einigen Implementierungen des Liang-Papiers gesucht: https://github.com/mnater/hyphenator oder der Nachfolger: https://github.com/mnater/Hyphenopoly

Es sei denn, Sie sind der Typ, der gerne eine 60-seitige Arbeit liest, anstatt frei verfügbaren Code für nicht eindeutige Probleme anzupassen. :) :)

Sean
quelle
vereinbart - viel bequemer, nur eine vorhandene Implikation zu verwenden
hoju
41

Hier ist eine Lösung mit 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()]] 
hoju
quelle
Hey danke winziger Babyfehler in der sollte Funktion def nsyl (Wort) sein: return [len (Liste (y für y in x wenn y [-1] .isdigit ())) für x in d [word.lower ()] ]
Gourneau
6
Was würden Sie als Ersatz für Wörter vorschlagen, die nicht in diesem Korpus enthalten sind?
Dan Gayle
4
@Pureferret cmudict ist ein Aussprachewörterbuch für nordamerikanische englische Wörter. Es teilt Wörter in Phoneme auf, die kürzer als Silben sind (z. B. wird das Wort 'Katze' in drei Phoneme aufgeteilt: K - AE - T). Vokale haben aber auch einen "Stressmarker": entweder 0, 1 oder 2, abhängig von der Aussprache des Wortes (so wird AE in 'cat' zu AE1). Der Code in der Antwort zählt die Spannungsmarkierungen und damit die Anzahl der Vokale - was effektiv die Anzahl der Silben angibt (beachten Sie, dass in den Beispielen von OP jede Silbe genau einen Vokal hat).
billy_chapters
1
Dies gibt die Anzahl der Silben zurück, nicht die Silbenbildung.
Adam Michael Wood
19

Ich versuche, dieses Problem für ein Programm anzugehen, das die Flesch-Kincaid- und Flesch-Lesewerte eines Textblocks berechnet. Mein Algorithmus verwendet das, was ich auf dieser Website gefunden habe: http://www.howmanysyllables.com/howtocountsyllables.html, und es kommt ziemlich nahe. Es hat immer noch Probleme mit komplizierten Wörtern wie unsichtbar und Silbentrennung, aber ich habe festgestellt, dass es für meine Zwecke in den Ballpark gelangt.

Es hat den Vorteil, dass es einfach zu implementieren ist. Ich fand, dass die "es" entweder Silben sein können oder nicht. Es ist ein Glücksspiel, aber ich habe beschlossen, die es in meinem Algorithmus zu entfernen.

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;
    }
Joe Basirico
quelle
Für mein einfaches Szenario, Silben in Eigennamen zu finden, scheint dies zunächst gut genug zu funktionieren. Danke, dass du es hier veröffentlicht hast.
Norman H
5

Vielen Dank an Joe Basirico, der Ihre schnelle und schmutzige Implementierung in C # geteilt hat. Ich habe die großen Bibliotheken verwendet und sie funktionieren, aber normalerweise sind sie etwas langsam, und für schnelle Projekte funktioniert Ihre Methode einwandfrei.

Hier ist Ihr Code in Java zusammen mit Testfällen:

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

Das Ergebnis war wie erwartet (es funktioniert gut genug für 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
Tihamer
quelle
5

@Tihamer und @ joe-basirico stoßen. Sehr nützliche Funktion, nicht perfekt , aber gut für die meisten kleinen bis mittleren Projekte. Joe, ich habe eine Implementierung Ihres Codes in Python neu geschrieben:

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

Hoffe jemand findet das nützlich!

Tersosauros
quelle
4

Warum berechnen? Jedes Online-Wörterbuch hat diese Informationen. http://dictionary.reference.com/browse/invisible in · vis · i · ble

Cerin
quelle
3
Vielleicht muss es für Wörter funktionieren, die nicht in Wörterbüchern erscheinen, wie z. B. Namen?
Wouter Lievens
4
@WouterLievens: Ich denke nicht, dass Namen annähernd gut genug sind, um automatisch Silben zu analysieren. Ein Silbenparser für englische Namen würde bei Namen walisischer oder schottischer Herkunft kläglich versagen, geschweige denn bei Namen indischer und nigerianischer Herkunft, aber all diese finden Sie möglicherweise in einem einzigen Raum irgendwo in z. B. London.
Jean-François Corbett
Man muss bedenken, dass es nicht vernünftig ist, eine bessere Leistung zu erwarten, als ein Mensch bieten könnte, wenn man bedenkt, dass dies ein rein heuristischer Ansatz für eine skizzenhafte Domäne ist.
Darren Ringer
4

Perl hat das Modul Lingua :: Phonology :: Syllable . Sie könnten das versuchen oder versuchen, seinen Algorithmus zu untersuchen. Ich habe dort auch einige andere ältere Module gesehen.

Ich verstehe nicht, warum ein regulärer Ausdruck nur eine Anzahl von Silben enthält. Sie sollten in der Lage sein, die Silben selbst in Klammern zu erfassen. Angenommen, Sie können einen regulären Ausdruck erstellen, der funktioniert.

Skiphoppy
quelle
4

Heute habe ich diese Java-Implementierung von Frank Liangs Silbentrennungsalgorithmus mit Muster für Englisch oder Deutsch gefunden, die recht gut funktioniert und auf Maven Central verfügbar ist.

Cave: Es ist wichtig, die letzten Zeilen der .texMusterdateien zu entfernen , da diese Dateien sonst nicht mit der aktuellen Version von Maven Central geladen werden können.

Zum Laden und Verwenden von hyphenatorkönnen Sie das folgende Java-Code-Snippet verwenden. texTableist der Name der .texDateien, die die benötigten Muster enthalten. Diese Dateien sind auf der Projekt-Github-Site verfügbar.

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

Danach Hyphenatorist der gebrauchsfertig. Um Silben zu erkennen, besteht die Grundidee darin, den Begriff an den bereitgestellten Bindestrichen aufzuteilen.

    String hyphenedTerm = hyphenator.hyphenate(term);

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

    int syllables = hyphens.length;

Sie müssen auf "\u00AD" teilen ", da die API kein normales zurückgibt "-".

Dieser Ansatz übertrifft die Antwort von Joe Basirico, da er viele verschiedene Sprachen unterstützt und die deutsche Silbentrennung genauer erkennt.

rzo
quelle
3

Ich bin vor einiger Zeit auf genau dasselbe Problem gestoßen.

Am Ende habe ich das CMU-Aussprachewörterbuch verwendet, um die meisten Wörter schnell und genau nachzuschlagen. Für Wörter, die nicht im Wörterbuch enthalten sind, habe ich auf ein Modell für maschinelles Lernen zurückgegriffen, das bei der Vorhersage von Silbenzahlen zu ~ 98% genau ist.

Ich habe das Ganze hier in einem benutzerfreundlichen Python-Modul zusammengefasst: https://github.com/repp/big-phoney

Installieren: pip install big-phoney

Silben zählen:

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

Wenn Sie Python nicht verwenden und den ML-Modell-basierten Ansatz ausprobieren möchten, habe ich ziemlich ausführlich beschrieben , wie das Silbenzählmodell bei Kaggle funktioniert .

Ryan Epp
quelle
Das ist super cool. Hat jemand Glück gehabt, das resultierende Keras-Modell in ein CoreML-Modell für iOS umzuwandeln?
Alexsander Akers
2

Danke @ joe-basirico und @tihamer. Ich habe den Code von @ tihamer auf Lua 5.1, 5.2 und Luajit 2 portiert (wird höchstwahrscheinlich auch auf anderen Versionen von Lua ausgeführt ):

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

Und einige lustige Tests, um zu bestätigen, dass es funktioniert ( so viel es soll ):

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.")
josefnpat
quelle
Ich habe zwei weitere Testfälle "End" und "I" hinzugefügt. Die Lösung bestand darin, die Zeichenfolge unabhängig von Groß- und Kleinschreibung zu vergleichen. Ping'ing @ joe-basirico und tihamer, falls sie unter demselben Problem leiden und ihre Funktionen aktualisieren möchten.
Josefnpat
@tihamer Amerikaner ist 4 Silben!
Josefnpat
2

Ich konnte keinen adäquaten Weg finden, um Silben zu zählen, deshalb habe ich selbst eine Methode entworfen.

Sie können meine Methode hier anzeigen: https://stackoverflow.com/a/32784041/2734752

Ich benutze eine Kombination aus einem Wörterbuch und einer Algorithmusmethode, um Silben zu zählen.

Sie können meine Bibliothek hier anzeigen: https://github.com/troywatson/Lawrence-Style-Checker

Ich habe gerade meinen Algorithmus getestet und hatte eine Trefferquote von 99,4%!

Lawrence lawrence = new Lawrence();

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

Ausgabe:

4
3
troy
quelle
1
Im Allgemeinen sollten Links zu einem Tool oder einer Bibliothek mit Verwendungshinweisen, einer spezifischen Erläuterung der Anwendbarkeit der verknüpften Ressource auf das Problem oder einem Beispielcode oder, wenn möglich, allen oben genannten Elementen versehen sein.
IKavanagh
Siehe Syntaxhervorhebung . Im SO-Editor befindet sich eine Hilfeschaltfläche (Fragezeichen), mit der Sie zur verlinkten Seite gelangen.
IKavanagh
0

Nachdem ich viele Tests durchgeführt und auch Silbentrennungspakete ausprobiert hatte, schrieb ich meine eigenen anhand einer Reihe von Beispielen. Ich habe auch die Pakete pyhyphenund ausprobiert, die pyphenmit Silbentrennungswörterbüchern kompatibel sind, aber sie erzeugen in vielen Fällen die falsche Anzahl von Silben. Das nltkPaket war für diesen Anwendungsfall einfach zu langsam.

Meine Implementierung in Python ist Teil einer Klasse, die ich geschrieben habe, und die Silbenzählroutine wird unten eingefügt. Es überschätzt die Anzahl der Silben ein wenig, da ich immer noch keinen guten Weg gefunden habe, um stille Wortendungen zu erklären.

Die Funktion gibt das Verhältnis der Silben pro Wort zurück, wie es für eine Flesch-Kincaid-Lesbarkeitsbewertung verwendet wird. Die Zahl muss nicht genau sein, gerade nah genug für eine Schätzung.

Auf meiner i7-CPU der 7. Generation dauerte diese Funktion 1,1 bis 1,2 Millisekunden für einen Beispieltext mit 759 Wörtern.

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)
Jadzia626
quelle
-1

Ich habe jsoup verwendet, um dies einmal zu tun. Hier ist ein Beispiel für einen Silbenparser:

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;
    }
Itamar Fiorino
quelle
Wie ist das ein generischer Silbenparser? Es sieht so aus, als würde dieser Code nur Silben in einem Wörterbuch nachschlagen
Nico Haase