Back-End

8 out, 2012

Avance para os padrões de software livre no processamento de linguagem

Publicidade

Muitos projetos de software bem estabelecidos vêm usando arquivos de configuração e recursos de texto simples há vários anos sem grandes problemas. À medida que os projetos se desenvolvem e ficam mais complexos, aumenta a necessidade de maior rigor e adaptabilidade. Com o XML e o aplicativo do XML usando padrões concretos, é possível se beneficiar de compatibilidade entre projetos e plataformas, robustez e extensibilidade em áreas como o Unicode.

Ao converter os arquivos de texto simples para o padrão de software livre relevante, também é possível aumentar a flexibilidade e confiabilidade. O léxico no trabalho de reconhecimento de voz fornece um bom exemplo daquilo que é usado neste artigo. Independentemente de os seus projetos de software livre adotarem (ou não) o XML nos arquivos de recursos, é possível empregar os padrões XML no seu trabalho sem perda de funções.

Neste artigo, aprenda a alternar facilmente entre os formatos simples e de Especificação do Léxico de Pronúncia (PLS). Os exemplos mostram como armazenar léxicos customizados no formato PLS e extrair dados para o arquivo simples necessário.

Exemplo: o léxico

Léxicos são listas de palavras utilizadas nas ferramentas de reconhecimento de discurso. Contêm informações sobre a forma como a palavra deve ser impressa ou renderizada graficamente e mostram a pronúncia em forma de fonemas. O léxico que se usa regularmente com o Hidden Markov Model Toolkit (HTK) é amplamente utilizado em projetos de controle de voz (consulte Recursos). A Listagem 1 é um trecho de um léxico do VoxForge em HTK.

AGENCY  [AGENCY]        ey jh ih n s iy
AGENDA  [AGENDA]        ax jh eh n d ax
AGENT   [AGENT] ey jh ih n t
AGENTS  [AGENTS]        ey jh ih n t s
AGER    [AGER]  ey g er
AGES    [AGES]  ey jh ih z

O arquivo da Listagem 1 é formado por três campos separados por tabulação:

  • O rótulo que descreve a palavra de modo geral;
    Os colchetes retos que delimitam a palavra da forma que você quer que ela seja impressa ou mostrada na tela (o grapheme);
  • Uma sequência de fonemas separados por espaços únicos, proveniente do conjunto Arpabet (consulte Recursos) que descreve a pronúncia da palavra.

No exemplo acima, as pronúncias são do idioma inglês, que os caracteres do American Standard Code for Information Interchange (ASCII) podem abranger facilmente.

O projeto CMU Sphinx (consulte Recursos) armazena o léxico (chamado de dicionário no contexto do CMU Sphinx) de forma semelhante. A Listagem 2 mostra um trecho.

agency  EY JH AH N S IY
agenda  AH JH EH N D AH
agendas AH JH EH N D AH Z
agent   EY JH AH N T
agents  EY JH AH N T S
ager    EY JH ER

A Listagem 2 tem apenas dois campos: a palavra/grafema e a sua representação fonética. Os dois exemplos de léxico têm algumas diferenças sutis:

  • As palavras e fonemas estão em caixas (maiúsculas/minúsculas) diferentes;
    Os fonemas apresentam ligeiras diferenças;
    A pontuação (vírgula, ponto de exclamação, etc.) é tratada de forma ligeiramente diferente.

É possível ver o dicionário inteiro no arquivo cmu07a.dic no download atual do PocketSphinx (consulte Recursos).

Como o léxico descreve pronúncias específicas de palavras, talvez seja necessário editar o arquivo para adaptá-lo a pessoas ou dialetos específicos. Com o tempo, você acumula capital de conhecimento no seu próprio léxico customizado. É fácil editar o arquivo simples com um editor de texto, mas também é fácil introduzir erros, como usar um separador que não é o padrão do arquivo, inserir caracteres que não são ASCII, colocar os campos na ordem incorreta, classificar os registros equivocadamente, não usar colchetes retos onde eles são obrigatórios, etc.

Há outra desvantagem sutil dos arquivos simples. Conforme você desenvolve o seu arquivo customizado, ele continua incompatível com outros projetos de linguagem. Um léxico em um formato XML padrão, como o PLS, caso seja reconhecido por ambos os projetos, é imediatamente compatível em ambos.

Especificação do léxico de pronúncia

O PLSA tem um formato objetivo e básico, como se mostra na Listagem 3.

    <?xml version="1.0" encoding="UTF-8"?>
<lexicon version="1.0" 
      xmlns="http://www.w3.org/2005/01/pronunciation-lexicon"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xsi:schemaLocation="http://www.w3.org/2005/01/pronunciation-lexicon 
        http://www.w3.org/TR/2007/CR-pronunciation-lexicon-20071212/pls.xsd"
      alphabet="ipa" xml:lang="en-US">
  <lexeme ...>
    <grapheme>...</grapheme>
    <phoneme ...>...</phoneme>
  </lexeme>
</lexicon>

O XML descreve o elemento raiz lexicon , que pode conter vários elementos filhos lexeme . Cada lexeme pode conter vários elementos grapheme e vários elementos phoneme . A especificação permite substituir o atributo alphabet , mas não permite a substituição do atributo de idioma xml:lang . Para armazenar lexemas referentes a idiomas diferentes, é estritamente necessário separar os arquivos de léxico em PLS. O alfabeto padrão nesse léxico é ipa, que designa o sistema do Alfabeto Fonético Internacional (IPA) de representação dos sons (consulte Recursos). As representações fonéticas do IPA são caracteres Unicode multibyte. Tanto o HTK quanto o Sphinx usam códigos ASCII simples. Essa questão importante é abordada mais adiante neste artigo.

A vantagem de usar a especificação PLS são a estrutura mais rigorosa e a capacidade de armazenar mais informações, como a parte do discurso e alfabetos específicos. O detalhe da parte do discurso é importante em inglês porque algumas palavras que podem ter a mesma grafia (homógrafas) têm uma pronúncia diferente de acordo com a função gramatical. Por exemplo, perfect é pronunciado de forma diferente como adjetivo e como advérbio, porque a sílaba tônica é diferente. As informações extras armazenadas nos atributos permitem extrair registros específicos do arquivo inteiro, dependendo da necessidade. Usando esse método, é possível procurar um alfabeto específico entre vários elementos phoneme .

Considere o léxico em PLS como um banco de dados de informações sobre léxico, do qual é possível extrair detalhes relevantes para a ferramenta de voz que você usa. A Listagem 4 é um exemplo em formato PLS:

<?xml version="1.0" encoding="UTF-8"?>
<lexicon version="1.0" 
      xmlns="http://www.w3.org/2005/01/pronunciation-lexicon"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xsi:schemaLocation="http://www.w3.org/2005/01/pronunciation-lexicon 
        http://www.w3.org/TR/2007/CR-pronunciation-lexicon-20071212/pls.xsd"
      alphabet="ipa" xml:lang="en">
  <lexeme role="noun">
    <grapheme>agency</grapheme>
    <phoneme alphabet="x-htk-voxforge">ey jh ih n s iy</phoneme>
    <phoneme alphabet="x-cmusphinx">EY JH AH N S IY</phoneme>
  </lexeme>
</lexicon>

O exemplo na Listagem 4 armazena somente uma palavra que tem duas representações fonêmicas possíveis. É possível filtrar uma das cadeias de caracteres phoneme usando o atributo alphabet . O elemento lexeme mostra o atributo role de noun. Embora isso seja informativo, neste caso é redundante, já que a palavra só é usada como substantivo, sem nenhum cenário de complicação da pronúncia.
Ao colocar as representações de phoneme de duas fontes diferentes lado a lado, já é possível discernir diferenças sutis. Essas informações podem ser úteis para resolver problemas de reconhecimento de discurso.

Nem o CMU Sphinx nem o HTK podem usar um léxico em PLS diretamente, mas o frontend simon (consulte Recursos) para o kit de ferramentas do HTK pode. Se você está usando o HTK ou Sphinx direto, é necessário ter certeza de que é possível passar facilmente do texto simples para o PLS e vice-versa sem perda de informações.

As seções a seguir mostram como usar o Python para passar de um arquivo simples para o PLS e voltar para o arquivo simples. Supõe-se que as suas informações customizadas estão em um arquivo simples de léxico.

Conversão para o PLS

O código na Listagem 5 usa Python, mas há várias outras formas de fazer a mesma coisa. (Para ver um exemplo, consulte o tutorial do developerWorks sobre Transformação de Linguagem de Folha de Estilo Extensível (XSLT) em Recursos.) Algumas pessoas desejam usar bibliotecas que verificam a robustez do XML a cada pequena etapa para dar um feedback mais imediato sobre o local onde o problema está, principalmente se os arquivos de origem são grandes e propensos a conter erros e inconsistências. O exemplo abaixo deixa a verificação para a última etapa, o que implica certo nível de confiança de que os arquivos simples estão corretos.

from elementtree.ElementTree import parse
import string as str
import sys
import cgi
#
# call with 
#	python flat2pls.py vox
# or 
#	python flat2pls.py spx
#
if len(sys.argv) == 2:
  src = sys.argv[1]
else:
  exit("wrong args")
#
outfile = "mylex"+src+".pls"
print "out is "+outfile
out = open(outfile,"w")
out.write('<!--?xml version="1.0" encoding="UTF-8"?-->\n\
')
# now the lexemes
if src == "vox":
  f = open("vf.lex","r")
  for line in f:
    line = str.strip(line)
    word = str.split(line,"\t")
    #gr = str.strip(word[1],"[]")
    gr = cgi.escape(word[0])
    out.write('\n\
  \n\
    '+gr+'\n\
    '+word[2]+'\n\
  ')
else: # src is sphinx
  f = open("cmu.dic","r")
  for line in f:
    line = str.strip(line)
    word = str.split(line,"\t")
    gr = cgi.escape(word[0])
    out.write('\n\
  \n\
    '+gr+'\n\
    '+word[1]+'\n\
  ')
# ended lexemes
out.write('\n\n')
out.close()
# now check the output is ok
tree = parse(outfile)
lexicon = tree.getroot()
mylexcount = 0
for lexeme in lexicon:
  mylexcount += 1
print 'Found %(number)d lexemes' % {"number":mylexcount}

A Listagem 5 começa importando módulos da biblioteca de análise do XML elementtree (consulte Recursos) e algumas bibliotecas de apoio. A importação de ElementTree em distribuições diferentes pode envolver uma sintaxe ligeiramente diferente, dependendo da forma de instalação do módulo. O código de exemplo é do openSUSE, com o módulo instalado a partir da origem, mas o Ubuntu pode exigir from xml.etree.ElementTree import parse. O módulo str permite algumas manipulações de cadeia de caractere, sys proporciona acesso aos arquivos e cgi fornece uma rotina de escape muito importante, que é essencial para manipular dados a partir do XML. O código espera obter um argumento de interface da linha de comandos (CLI) dizendo se ele deve converter do formato CMU Sphinx ou do HTK/VoxForge. Em seguida, o código de exemplo abre o arquivo para saída e grava o prólogo de XML adequado para o PLS. Já que você não está armazenando caracteres Unicode neste estágio, basta abrir o arquivo para acessar somente ASCII simples.

Neste ponto, o código na Listagem 5:

  • Processa o arquivo de origem linha por linha, dividindo os campos em cadeias de caracteres separadas e gravando os componentes lexeme, grapheme e phoneme
  • Identifica o phoneme com o atributo alphabet=”x-htk-voxforge” se os dados recebidos são do léxico VoxForge e com alphabet=”x-cmusphinx” se os dados são do Sphinx
  • Retém as maiúsculas e minúsculas dos fonemas

Quando o primeiro campo é importado, ele pode conter caracteres, como “e” comercial (&), que causam problemas no XML, a não ser que sejam caracteres de escape como cgi.escape().

Finalmente, o código:

  • Grava as tags de fechamento;
  • Fecha o arquivo PLS e, em seguida, recarrega-o como um arquivo XML;
  • Lê o arquivo contando os elementos lexeme;
  • Relata a contagem de lexemas.

Se a contagem é relatada, o XML aparenta ser robusto e bem formado.

A Listagem 6 é um trecho do resultado do léxico VoxForge HTK.

...
<lexeme>
  <grapheme>AGENDA</grapheme>
  <phoneme alphabet="x-htk-voxforge">ax jh eh n d ax</phoneme>
</lexeme>
<lexeme> 
  <grapheme>AGENT</grapheme>
  <phoneme alphabet="x-htk-voxforge">ey jh ih n t</phoneme>
</lexeme>
...

Conversão do PLS

É importante saber que é possível voltar facilmente do formato PLS para um arquivo simples. O código na Listagem 7 supõe que o léxico está armazenado em um arquivo formatado como PLS e que o seu projeto de reconhecimento de discurso só pode usar arquivos simples no formato HTK ou CMU Sphinx.

from elementtree.ElementTree import parse
import string as str
import sys
#
# call with 
#	python pls2flat.py x-htk-voxforge > mylexicon
# or 
#	python pls2flat.py x-cmusphinx > mylexicon.dic
#
if len(sys.argv) > 1:
  alpha = sys.argv[1]
#
if alpha == "x-htk-voxforge":
  tree = parse("mylexvox.pls")
else:
  tree = parse("mylexspx.pls")
lexicon = tree.getroot()
for lexeme in lexicon:
  for child in lexeme:
    #print child.tag
    if child.tag[-8:] == 'grapheme':
      if alpha == 'x-htk-voxforge':
	gr = str.upper(child.text)
	print gr,"\t","["+gr+"]","\t",
      else:
	gr = child.text
	print gr,"\t",
    if child.tag[-7:] == 'phoneme':
      if child.get('alphabet') == alpha:
	print child.text

Esse script curto usa a biblioteca elementtree para analisar o arquivo XML em PLS. Ele estabelece o elemento-raiz e, em seguida, itera nos lexemas filho, procurando grapheme e phoneme, e grava os valores em um arquivo de texto no formato relevante. O script pede os oito últimos caracteres na tag em relação ao grapheme , já que haverá um prefixo de namespace retornado com a tag. Ele cria novamente o formato de três campos para o HTK e dois campos para o CMU Sphinx.

Mesclando e lidando com Unicode

O script na Listagem 8 usa dois arquivos PLS para criar um arquivo PLS comum que contém informações referentes aos dois arquivos originais. Também converte a cadeia de caracteres phoneme do VoxForge para o Unicode e armazena a versão em Unicode em um elemento phoneme separado, identificado com o atributo alphabet=”ipa” .

#! /usr/bin/python -u
# -*- coding: utf-8 -*-
#
# challenge is to merge two pls files
# given two pls files, merge them into one
# 
import elementtree.ElementTree as ET
from elementtree.ElementTree import parse
import string as str
import codecs
import cgi
#
treevox = ET.parse("mylexvox.pls")
treespx = ET.parse("mylexspx.pls")
#
lexvox = treevox.getroot()
lexspx = treespx.getroot()
#
phons = { 'aa':u'ɑ','ae':u'æ','ah':u'ʌ','ao':u'ɔ','ar':u'ɛr','aw':u'aʊ',
'ax':u'ə','ay':u'aɪ','b':u'b','ch':u'tʃ','d':u'd','dh':u'ð','eh':u'ɛ',
'el':u'ɔl','en':u'ɑn','er':u'ər','ey':u'eɪ','f':u'f',
'g':u'ɡ','hh':u'h','ih':u'ɪ','ir':u'ɪr','iy':u'i','jh':u'dʒ','k':u'k','l':u'l',
'm':u'm','n':u'n','ng':u'ŋ','ow':u'oʊ','oy':u'ɔɪ','p':u'p','r':u'r','s':u's',
'sh':u'ʃ','t':u't','th':u'θ','uh':u'ʊ','ur':u'ʊr','uw':u'u','v':u'v',
'w':u'w','y':u'j','z':u'z','zh':u'ʒ','sil':'' }
#
def to_utf(s):
  myp = str.split(s)
  myipa = []
  for p in myp:
    myipa.append(phons[p])
  return str.join(myipa,'')
#
outfile = "my2lexmrg.pls"
out = codecs.open(outfile, encoding='utf-8', mode='w')
#
out.write('<!--?xml version="1.0" encoding="UTF-8"?-->\n\
')
#
# scan the two pls, create dictionary
voxdict = {}
for lexeme in lexvox:
  gr = str.lower(lexeme[0].text)
  ph = lexeme[1].text
  voxdict[gr] = {ph,}
#
for lexeme in lexspx:
  gr = lexeme[0].text
  ph = lexeme[1].text
  if gr in voxdict:
    voxdict[gr].add(ph)
  else:
    voxdict[gr] = {ph,}
#
for gr in sorted(voxdict.iterkeys()):
  out.write('\n\
  \n\
    '+cgi.escape(gr)+'')
  #print "%s: %s" % (key, voxdict[key])
  for ph in sorted(voxdict[gr]):
    alph = 'x-htk-voxforge' if ph.islower() else 'x-cmusphinx'
    out.write('\n\
    '+ph+'')
    if ph.islower():
      phipa = to_utf(ph)
      out.write(u'\n\
    '+phipa+'')
  out.write('\n\
  ')
# done, close files
out.write('\n')
out.close()
# now check the output is ok
tree = parse(outfile)
lexicon = tree.getroot()
mylexcount = 0
for lexeme in lexicon:
  mylexcount += 1
print 'Found %(number)d lexemes' % {"number":mylexcount}

Você começa com uma expressão hashbang (#!) seguida por um indicador especial para o interpretador do Python, na segunda linha, de que esse código contém caracteres Unicode. Em seguida, o script importa vários módulos, como elementtree, codecs e cgi, que são úteis para lidar com o Unicode. Você diz ao interpretador onde os dois arquivos PLS estão e aponta para os elementos raiz deles.

A variável phons armazena um dicionário especial que contém um mapeamento dos códigos CMU Arpabet para uma combinação equivalente no Unicode. Esse dicionário converte as cadeias de caracteres phoneme já existentes para uma versão em Unicode. Fique à vontade para modificar o mapeamento de acordo com os seus objetivos— por exemplo, é possível considerar que o equivalente a 'aa' no Unicode é u'ɑ:', que prolonga o som de a .

Uma única função definida, to_utf(), que converte uma cadeia de caracteres em ASCII Arpabet para o Unicode. A última parte da base é abrir um arquivo para armazenar a saída, certificando-se de que esse arquivo saiba que deve estar preparado para aceitar Unicode e gravar o prólogo de PLS nele.

Agora você está pronto para processar os arquivos criando dois dicionários especiais internos de Python, um de cada arquivo PLS, varrendo-os com a biblioteca elementtree . Supõe-se que grapheme será o primeiro filho e phoneme , o segundo filho do lexeme. O script inclui todos os registros do primeiro arquivo para um novo dicionário de mesclagem. Ao varrer o segundo arquivo, se uma chave já existe no novo dicionário de mesclagem, você inclui em seu conjunto de fonemas. Do contrário, você cria um novo item de chave no dicionário mesclado. No final do loop, o novo dicionário mesclado contém chaves dos dois arquivos originais e um conjunto associado de uma ou duas cadeias de caracteres de phoneme.

Grave o novo arquivo PLS a partir da mesclagem que você acabou de criar. Conforme você varre o arquivo, você inclui o atributo alphabet para distinguir um phoneme de outro. Depois de gravar os fonemas já existentes, você cria uma nova cadeia de caracteres phoneme que é o equivalente no Unicode da cadeia de caracteres em CMU Arpabet, que é possível obter a partir da versão em HTK ou Sphinx (ou ambas) de acordo com as suas necessidades.

Finalmente, feche o elemento raiz, feche o arquivo e analise-o novamente, como você fez antes, para se certificar de que esteja bem formado.

O resultado deve ser semelhante à Listagem 9.

...
  <lexeme>
    <grapheme>agenda</grapheme>
    <phoneme alphabet="x-cmusphinx">AH JH EH N D AH</phoneme>
    <phoneme alphabet="x-htk-voxforge">ax jh eh n d ax</phoneme>
    <phoneme alphabet="ipa">ədʒɛndə</phoneme>
  </lexeme>
  <lexeme> 
    <grapheme>agendas</grapheme>
    <phoneme alphabet="x-cmusphinx">AH JH EH N D AH Z</phoneme>
  </lexeme>
  <lexeme> 
    <grapheme>agent</grapheme>
    <phoneme alphabet="x-cmusphinx">EY JH AH N T</phoneme>
    <phoneme alphabet="x-htk-voxforge">ey jh ih n t</phoneme> 
    <phoneme alphabet="ipa">eɪdʒɪnt</phoneme>
  </lexeme>
  <lexeme>
    <grapheme>agent's</grapheme>
    <phoneme alphabet="x-cmusphinx">EY JH AH N T S</phoneme>
  </lexeme>
...

Com o dicionário de PLS mesclado, é possível aplicar a Linguagem de Folha de Estilo Extensível (XSL) ou qualquer outro procedimento para gerar os resultados necessários, seja um arquivo simples ou um novo arquivo específico em PLS. Teoricamente, também é possível armazenar cadeias de caracteres phoneme neste arquivo, até mesmo de outros idiomas. Entretanto, esse uso não é o padrão da especificação PLS.

Kai Schott vem trabalhando muito com o PLS e tem vários arquivos já preparados para download em diversos idiomas, particularmente o alemão (consulte Recursos).

Problemas não resolvidos

Embora seja possível obter muitas informações a partir dos arquivos simples, os problemas a seguir permanecem sem solução.

Selecionando a partir de vários grafemas
Ocasionalmente, é necessário lidar com várias grafias da mesma palavra em um idioma. A função e o fonema são idênticos em ambas as grafias, portanto, há vários grafemas no mesmo lexema. No entanto, não há nada no PLS que permite incluir um atributo ao elemento grapheme , como acontece com o atributo alphabet do elemento phoneme .
Acrônimos
Os léxicos frequentemente contêm acrônimos. O PLS lida com eles em um elemento filho de lexeme chamado de <alias>. Para desenvolver um PLS automaticamente a partir de um arquivo simples, é necessária uma forma de distinguir os acrônimos das palavras propriamente ditas do léxico. Os arquivos simples não têm necessariamente essas informações.
Função/parte do discurso
Assim como acontece com os acrônimos, as informações sobre a parte do discurso não estão disponíveis a partir dos arquivos simples para integrar atributos role ao PLS.

Conclusão

Neste artigo, você aprendeu a passar facilmente do formato simples para o PLS e vice-versa. O armazenamento de léxicos no formato PLS oferece possíveis vantagens. Os projetos de software livre podem adotar o XML nos arquivos de recursos ou não. Essa decisão cabe aos gerentes de projetos — só eles podem avaliar os recursos e aplicá-los da melhor forma que seja amplamente aceita em suas comunidades.

Por enquanto, é possível aplicar padrões XML ao seu próprio trabalho sem perda de funções. Em relação aos léxicos e dicionários usados no trabalho de reconhecimento de voz, é possível aumentar a adaptabilidade entre projetos, robustez e utilidade armazenando léxicos customizados no formato PLS. É possível extrair os dados facilmente para o arquivo simples necessário conforme a necessidade.

***

Descubra a flexibilidade de sistemas integrados especiais e o valor ao utilizar padrões comprovados e implementáveis de especialistas, através da oferta abaixo exclusiva para desenvolvedores. Faça o download do Virtual Pattern Kit for Developers para alavancar ou estender os padrões IBM localmente em seu próprio sistema!

 Recursos

Aprender

Obter produtos e tecnologias

Discutir

***

Sobre o autor: Colin Beckingham é pesquisador freelance, escritor e programador. Ele mora na região leste de Ontário, no Canadá. Com diplomas da Universidade de Queen, em Kingston, e da Universidade de Windsor, atuou em uma grande variedade de áreas, incluindo o setor financeiro, horticultura, corrida de cavalos, ensino, serviço público e viagens e turismo. Autor de aplicativos de banco de dados, inúmeros jornais, revistas e artigos online. Seu interesse em pesquisa inclui programação de software livre, VoIP e aplicativos por controle de voz no Linux. É possível entrar em contato com Colin através do email colbec@start.ca.

***

Artigo original disponível em: http://www.ibm.com/developerworks/br/library/x-flatfilespython/index.html