Detectando sílabas em uma palavra

138

Preciso encontrar uma maneira bastante eficiente de detectar sílabas em uma palavra. Por exemplo,

Invisível -> in-vi-sib-le

Existem algumas regras de silabificação que podem ser usadas:

V CV VC CVC CCV CCCV CVCC

* onde V é uma vogal e C é uma consoante. Por exemplo,

Pronúncia (5 Pro-freira-cação; CV-CVC-CV-V-CVC)

Eu tentei alguns métodos, entre os quais estavam usando regex (que ajuda apenas se você quiser contar sílabas) ou definição de regra codificada (uma abordagem de força bruta que se mostra muito ineficiente) e finalmente usando um autômato de estado finito (que fez não resultar em nada útil).

O objetivo do meu aplicativo é criar um dicionário de todas as sílabas em um determinado idioma. Este dicionário será usado posteriormente para aplicativos de verificação ortográfica (usando classificadores bayesianos) e síntese de texto para fala.

Eu apreciaria se alguém pudesse me dar dicas de uma maneira alternativa de resolver esse problema, além das abordagens anteriores.

Eu trabalho em Java, mas qualquer dica em C / C ++, C #, Python, Perl ... funcionaria para mim.

user50705
fonte
Deseja realmente os pontos de divisão reais ou apenas o número de sílabas em uma palavra? Nesse último caso, considere procurar as palavras em um dicionário de conversão de texto em fala e conte os fonemas que codificam os sons das vogais.
Adrian McCarthy
A maneira mais eficiente (em termos de computação; não em termos de armazenamento), eu acho que seria apenas ter um dicionário Python com palavras como chaves e o número de sílabas como valores. No entanto, você ainda precisará de um substituto para palavras que não foram incluídas no dicionário. Deixe-me saber se você já encontrou esse dicionário!
Brōtsyorfuzthrāx 29/07

Respostas:

120

Leia sobre a abordagem TeX para esse problema para fins de hifenização. Veja, em especial, a dissertação da tese de Frank Liang, Word Hy-phen-a-by, do autor . Seu algoritmo é muito preciso e inclui um pequeno dicionário de exceções para casos em que o algoritmo não funciona.

Jason
fonte
52
Gosto que você tenha citado uma dissertação de tese sobre o assunto, é uma pequena dica para o pôster original de que essa pode não ser uma pergunta fácil.
Karl
Sim, estou ciente de que essa não é uma pergunta simples, embora não tenha trabalhado muito nisso. No entanto, subestimei o problema, pensei em trabalhar em outras partes do meu aplicativo e depois voltar a esse problema "simples". Tolo me :)
user50705
Eu li o documento de dissertação e achei muito útil. O problema com a abordagem era que eu não tinha nenhum padrão para o idioma albanês, apesar de ter encontrado algumas ferramentas que poderiam gerar esses padrões. De qualquer forma, para o meu propósito, escrevi um aplicativo baseado em regras, que resolveu o problema ...
user50705
10
Observe que o algoritmo TeX é para encontrar pontos de hifenização legítimos, que não são exatamente o mesmo que divisões de sílabas. É verdade que os pontos de hifenização se enquadram nas divisões de sílabas, mas nem todas as divisões de sílabas são pontos de hifenização válidos. Por exemplo, hífens (geralmente) não são usados ​​dentro de uma letra ou duas das extremidades de uma palavra. Eu também acredito que os padrões TeX foram ajustados para trocar falsos negativos por falsos positivos (nunca coloque um hífen no lugar em que não pertence, mesmo que isso signifique perder algumas oportunidades legítimas de hifenização).
Adrian McCarthy
1
Também não acredito que a hifenização seja a resposta.
Ezequiel
46

Eu tropecei nesta página procurando a mesma coisa e encontrei algumas implementações do documento de Liang aqui: https://github.com/mnater/hyphenator ou o sucessor: https://github.com/mnater/Hyphenopoly

Ou seja, a menos que você goste de ler uma tese de 60 páginas em vez de adaptar o código disponível gratuitamente para problemas não exclusivos. :)

Sean
fonte
concordou - muito mais conveniente usar apenas um implmentation existente
Hoju
41

Aqui está uma solução usando o 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
fonte
Ei, obrigado pequeno erro de bebê na função deve ser def nsyl (word): return [len (list (y para y em x se y [-1] .isdigit ())) para x em d [word.lower ()] ]
Gourneau
6
O que você sugeriria como substituto de palavras que não estão nesse corpus?
Dan Gayle
4
@Pureferret cmudict é um dicionário de pronúncia para palavras em inglês da América do Norte. divide as palavras em fonemas, que são mais curtos que as sílabas (por exemplo, a palavra 'gato' é dividida em três fonemas: K - AE - T). mas as vogais também têm um "marcador de estresse": 0, 1 ou 2, dependendo da pronúncia da palavra (para que o EA em 'gato' se torne AE1). o código na resposta conta os marcadores de estresse e, portanto, o número de vogais - o que efetivamente fornece o número de sílabas (observe como nos exemplos do OP cada sílaba tem exatamente uma vogal).
precisa saber é o seguinte
1
Isso retorna o número de sílabas, não a silabificação.
Adam Michael Wood
19

Estou tentando resolver esse problema em um programa que calcula a pontuação de leitura de um bloco de texto flesch-kincaid e flesch. Meu algoritmo usa o que encontrei neste site: http://www.howmanysyllables.com/howtocountsyllables.html e fica razoavelmente próximo. Ele ainda tem problemas com palavras complicadas, como invisível e hifenização, mas descobri que isso entra no campo dos meus propósitos.

Ele tem a vantagem de ser fácil de implementar. Eu descobri que os "es" podem ser silábicos ou não. É uma aposta, mas decidi remover os es no meu 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;
    }
Joe Basirico
fonte
Para o meu cenário simples de encontrar sílabas em nomes próprios, isso parece estar funcionando bem o suficiente. Obrigado por publicá-lo aqui.
Norman H
5

Por que calculá-lo? Todo dicionário on-line tem essa informação. http://dictionary.reference.com/browse/invisible em · vis · i · ble

Cerin
fonte
3
Talvez tenha que funcionar para palavras que não aparecem nos dicionários, como nomes?
Wouter Lievens
4
@WouterLievens: Eu não acho que os nomes estejam perto de se comportar o suficiente para a análise automática de sílabas. Um analisador de sílabas para nomes em inglês falharia miseravelmente em nomes de origem galesa ou escocesa, sem falar em nomes de origem indiana e nigeriana, mas você pode encontrar tudo isso em uma única sala em algum lugar, por exemplo, em Londres.
Jean-François Corbett
É preciso ter em mente que não é razoável esperar um desempenho melhor do que um humano poderia oferecer, considerando que essa é uma abordagem puramente heurística para um domínio superficial.
Darren Ringer
5

Obrigado Joe Basirico, por compartilhar sua implementação rápida e suja em C #. Eu usei as grandes bibliotecas, e elas funcionam, mas geralmente são um pouco lentas e, para projetos rápidos, seu método funciona bem.

Aqui está o seu código em Java, junto com os casos de teste:

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

O resultado foi o esperado (funciona suficientemente bem 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
Tihamer
fonte
5

Bumping @Tihamer e @ joe-basirico. Função muito útil, não perfeita , mas boa para a maioria dos projetos de pequeno a médio porte. Joe, reescrevi uma implementação do seu código em 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 alguém ache isso útil!

Tersosauros
fonte
4

Perl tem o Lingua :: Fonologia :: Sílaba módulo . Você pode tentar isso ou tentar analisar seu algoritmo. Também vi outros módulos antigos lá.

Não entendo por que uma expressão regular fornece apenas uma contagem de sílabas. Você deve conseguir as sílabas usando parênteses de captura. Supondo que você possa construir uma expressão regular que funcione, ou seja.

skiphoppy
fonte
4

Hoje encontrei essa implementação em Java do algoritmo de hifenização de Frank Liang com padrão para inglês ou alemão, que funciona muito bem e está disponível no Maven Central.

Cave: É importante remover as últimas linhas dos .texarquivos de padrão, pois, caso contrário, esses arquivos não poderão ser carregados com a versão atual no Maven Central.

Para carregar e usar o hyphenator, você pode usar o seguinte snippet de código Java. texTableé o nome dos .texarquivos que contêm os padrões necessários. Esses arquivos estão disponíveis no site do projeto 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;
    }

Depois o Hyphenatorestá pronto para uso. Para detectar sílabas, a idéia básica é dividir o termo nos hífens fornecidos.

    String hyphenedTerm = hyphenator.hyphenate(term);

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

    int syllables = hyphens.length;

Você precisa se separar "\u00AD", pois a API não retorna um normal "-".

Essa abordagem supera a resposta de Joe Basirico, uma vez que suporta muitos idiomas diferentes e detecta a hifenização alemã mais precisa.

rzo
fonte
4

Encontrei exatamente esse mesmo problema há pouco tempo.

Acabei usando o Dicionário de Pronúncia da CMU para pesquisas rápidas e precisas da maioria das palavras. Para palavras que não estão no dicionário, voltei a um modelo de aprendizado de máquina com ~ 98% de precisão na previsão da contagem de sílabas.

Eu envolvi tudo em um módulo python fácil de usar aqui: https://github.com/repp/big-phoney

Instalar: pip install big-phoney

Contagem de sílabas:

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

Se você não está usando Python e deseja tentar a abordagem baseada no modelo ML, escrevi bastante detalhadamente como o modelo de contagem de sílabas funciona no Kaggle .

Ryan Epp
fonte
Isso é super legal. Alguém teve sorte em converter o modelo Keras resultante em um modelo CoreML para uso no iOS?
Alexsander Akers
2

Obrigado @ joe-basirico e @tihamer. Portei o código do @ tihamer para Lua 5.1, 5.2 e luajit 2 ( provavelmente também será executado em outras versões do 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

E alguns testes divertidos para confirmar que funciona ( tanto quanto deveria ):

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
fonte
Eu adicionei mais dois casos de teste "End" e "I". A correção foi comparar maiúsculas e minúsculas sem diferenciação. Ping'ing @ joe-basirico e tihamer, caso sofram do mesmo problema e desejem atualizar suas funções.
josefnpat
@tihamer American são 4 sílabas!
precisa saber é o seguinte
2

Não consegui encontrar uma maneira adequada de contar sílabas, então eu mesmo projetei um método.

Você pode ver meu método aqui: https://stackoverflow.com/a/32784041/2734752

Eu uso uma combinação de um método de dicionário e algoritmo para contar sílabas.

Você pode ver minha biblioteca aqui: https://github.com/troywatson/Lawrence-Style-Checker

Acabei de testar meu algoritmo e tinha uma taxa de ataque de 99,4%!

Lawrence lawrence = new Lawrence();

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

Resultado:

4
3
Troy
fonte
1
Geralmente, os links para uma ferramenta ou biblioteca devem ser acompanhados de notas de uso, uma explicação específica de como o recurso vinculado é aplicável ao problema, ou algum código de amostra ou, se possível, todos os itens acima.
IKavanagh # 25/15
Consulte Realce de sintaxe . Há um botão de ajuda (ponto de interrogação) no editor de SO que o levará à página vinculada.
IKavanagh # 25/15
0

Depois de fazer muitos testes e experimentar pacotes de hifenização, escrevi os meus com base em vários exemplos. Eu também tentei os pacotes pyhyphene pyphenque fazem interface com os dicionários de hifenização, mas eles produzem o número errado de sílabas em muitos casos. O nltkpacote estava muito lento para este caso de uso.

Minha implementação em Python faz parte de uma classe que escrevi e a rotina de contagem de sílabas é colada abaixo. Superestima um pouco o número de sílabas, pois ainda não encontrei uma boa maneira de explicar as terminações de palavras silenciosas.

A função retorna a proporção de sílabas por palavra, conforme é usada para uma pontuação de legibilidade de Flesch-Kincaid. O número não precisa ser exato, apenas perto o suficiente para uma estimativa.

Na minha CPU i7 de 7ª geração, essa função levou 1,1-1,2 milissegundos para um texto de amostra de 759 palavras.

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
fonte
-1

Eu usei o jsoup para fazer isso uma vez. Aqui está um exemplo de analisador de sílaba:

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
fonte
Como isso é um analisador de sílaba genérico? Parece que esse código está pesquisando apenas sílabas em um dicionário
Nico Haase