Commit 1d486c406f1d88238e01ec77ad61b83bc7d5d7e2
1 parent
ada59211
Exists in
master
and in
1 other branch
Altera alexp para um classe
Showing
3 changed files
with
169 additions
and
161 deletions
Show diff stats
| ... | ... | @@ -0,0 +1,165 @@ |
| 1 | +#! /usr/bin/env python2.6 | |
| 2 | +# -*- coding: utf-8 -*- | |
| 3 | + | |
| 4 | +#--------------------------------- | |
| 5 | + | |
| 6 | +# Editado: | |
| 7 | + | |
| 8 | +#Autor: Erickson Silva | |
| 9 | +#Email: <erickson.silva@lavid.ufpb.br> <ericksonsilva@live.com> | |
| 10 | + | |
| 11 | +#LAViD - Laboratório de Aplicações de Vídeo Digital | |
| 12 | + | |
| 13 | +#--------------------------------- | |
| 14 | + | |
| 15 | + | |
| 16 | +# Donatus Brazilian Portuguese Parser | |
| 17 | +# | |
| 18 | +# Copyright (C) 2010-2013 Leonel F. de Alencar | |
| 19 | +# | |
| 20 | +# Author: Leonel F. de Alencar <leonel.de.alencar@ufc.br> | |
| 21 | +# Homepage: <http://www.leonel.profusehost.net/> | |
| 22 | +# | |
| 23 | +# Project's URL: <http://sourceforge.net/projects/donatus/> | |
| 24 | +# For license information, see LICENSE.TXT | |
| 25 | +# | |
| 26 | +# $Id: alexp.py $ | |
| 27 | + | |
| 28 | +"""Este módulo contém funções que permitem utilizar o Aelius para etiquetar uma sentença, construindo entradas lexicais com base nas etiquetas atribuídas às palavras da sentença. Essas entradas lexicais são integradas em uma gramática CFG dada, que é transformada em um parser, utilizado para gerar uma árvore de estrutura sintagmática da sentença. | |
| 29 | +""" | |
| 30 | +import re,nltk, time, random | |
| 31 | +from os.path import expanduser | |
| 32 | +from os import environ, path | |
| 33 | +from Aelius.Extras import carrega | |
| 34 | +from Aelius import AnotaCorpus, Toqueniza | |
| 35 | +from unicodedata import normalize | |
| 36 | + | |
| 37 | + | |
| 38 | +class ClassificaSentencas(object): | |
| 39 | + | |
| 40 | + def __init__(self): | |
| 41 | + self.sentenca_anotada = "" | |
| 42 | + self.sleep_times = [0.1,0.2] | |
| 43 | + | |
| 44 | + def toqueniza(self, s): | |
| 45 | + """Decodifica string utilizando utf-8, retornando uma lista de tokens em unicode. | |
| 46 | + """ | |
| 47 | + regex = re.compile('[%s]' % re.escape('“”')) | |
| 48 | + regex2 = re.compile('[%s]' % re.escape('«»')) | |
| 49 | + try: | |
| 50 | + decodificada = regex2.sub('',regex.sub('"',s.replace("–", "-").replace("—", "-"))).decode("utf-8") | |
| 51 | + except: | |
| 52 | + decodificada = s.decode("utf-8") | |
| 53 | + return Toqueniza.TOK_PORT.tokenize(decodificada) | |
| 54 | + | |
| 55 | + def obter_classificacao_morfologica(self): | |
| 56 | + return self.sentenca_anotada | |
| 57 | + | |
| 58 | + def etiqueta_sentenca(self, s): | |
| 59 | + """Aplica um dos etiquetadores do Aelius na etiquetagem da sentença dada como lista de tokens. | |
| 60 | + """ | |
| 61 | + etiquetador = carrega("AeliusHunPos") | |
| 62 | + anotada = AnotaCorpus.anota_sentencas([s],etiquetador,"hunpos")[0] | |
| 63 | + while (anotada[0][1] is None): | |
| 64 | + time.sleep(random.choice(sleep_times)) | |
| 65 | + anotada = AnotaCorpus.anota_sentencas([s],etiquetador,"hunpos")[0] | |
| 66 | + regex = re.compile('[%s]' % re.escape('!"#&\'()*+,-./:;<=>?@[\\]^_`{|}~')) | |
| 67 | + tag_punctuation = [".",",","QT","("] | |
| 68 | + anotada_corrigida = [] | |
| 69 | + for x in anotada: | |
| 70 | + if x[1] not in tag_punctuation: | |
| 71 | + if x[1] == "NUM": | |
| 72 | + try: | |
| 73 | + float(x[0].replace(',', '.')) | |
| 74 | + anotada_corrigida.append(x) | |
| 75 | + continue | |
| 76 | + except: | |
| 77 | + pass | |
| 78 | + | |
| 79 | + tupla = [regex.sub('',x[0]).lower(),x[1]] | |
| 80 | + if tupla[0] != "": anotada_corrigida.append(tupla) | |
| 81 | + else: | |
| 82 | + if x[0] == ".": | |
| 83 | + anotada_corrigida.append(["[ponto]".decode("utf-8"),"SPT"]) | |
| 84 | + elif x[0] == "?": | |
| 85 | + anotada_corrigida.append(["[interrogacao]".decode("utf-8"),"SPT"]) | |
| 86 | + elif x[0] == "!": | |
| 87 | + anotada_corrigida.append(["[exclamacao]".decode("utf-8"),"SPT"]) | |
| 88 | + return anotada_corrigida | |
| 89 | + | |
| 90 | + def gera_entradas_lexicais(self, lista): | |
| 91 | + """Gera entradas lexicais no formato CFG do NLTK a partir de lista de pares constituídos de tokens e suas etiquetas. | |
| 92 | + """ | |
| 93 | + entradas=[] | |
| 94 | + for e in lista: | |
| 95 | + # é necessário substituir símbolos como "-" e "+" do CHPTB | |
| 96 | + # que não são aceitos pelo NLTK como símbolos não terminais | |
| 97 | + c=re.sub(r"[-+]","_",e[1]) | |
| 98 | + c=re.sub(r"\$","_S",c) | |
| 99 | + entradas.append("%s -> '%s'" % (c, self.remove_acento(e[0]))) | |
| 100 | + return entradas | |
| 101 | + | |
| 102 | + def corrige_anotacao(self, lista): | |
| 103 | + """Esta função deverá corrigir alguns dos erros de anotação mais comuns do Aelius. No momento, apenas é corrigida VB-AN depois de TR. | |
| 104 | + """ | |
| 105 | + i=1 | |
| 106 | + while i < len(lista): | |
| 107 | + if lista[i][1] == "VB-AN" and lista[i-1][1].startswith("TR"): | |
| 108 | + lista[i]=(lista[i][0],"VB-PP") | |
| 109 | + i+=1 | |
| 110 | + | |
| 111 | + def encontra_arquivo(self): | |
| 112 | + """Encontra arquivo na pasta vlibras-translate. | |
| 113 | + """ | |
| 114 | + if "TRANSLATE_DATA" in environ: | |
| 115 | + return path.join(environ.get("TRANSLATE_DATA"), "cfg.syn.nltk") | |
| 116 | + return expanduser("~") + "/vlibras-translate/data/cfg.syn.nltk" | |
| 117 | + | |
| 118 | + def extrai_sintaxe(self): | |
| 119 | + """Extrai gramática armazenada em arquivo cujo caminho é definido relativamente ao diretório nltk_data. | |
| 120 | + """ | |
| 121 | + arquivo = self.encontra_arquivo() | |
| 122 | + if arquivo: | |
| 123 | + f=open(arquivo,"rU") | |
| 124 | + sintaxe=f.read() | |
| 125 | + f.close() | |
| 126 | + return sintaxe | |
| 127 | + else: | |
| 128 | + print "Arquivo %s não encontrado em nenhum dos diretórios de dados do NLTK:\n%s" % (caminho,"\n".join(nltk.data.path)) | |
| 129 | + | |
| 130 | + def analisa_sentenca(self, sentenca): | |
| 131 | + """Retorna lista de árvores de estrutura sintagmática para a sentença dada sob a forma de uma lista de tokens, com base na gramática CFG cujo caminho é especificado como segundo argumento da função. Esse caminho é relativo à pasta nltk_data da instalação local do NLTK. A partir da etiquetagem morfossintática da sentença são geradas entradas lexicais que passam a integrar a gramática CFG. O caminho da gramática e o parser gerado são armazenados como tupla na variável ANALISADORES. | |
| 132 | + """ | |
| 133 | + parser = self.constroi_analisador(sentenca) | |
| 134 | + codificada=[] | |
| 135 | + for t in self.sentenca_anotada: | |
| 136 | + if t[1] != "SPT": | |
| 137 | + codificada.append(self.remove_acento(t[0]).encode("utf-8")) | |
| 138 | + trees=parser.parse_one(codificada) | |
| 139 | + return trees | |
| 140 | + | |
| 141 | + def constroi_analisador(self, s): | |
| 142 | + """Constrói analisador a partir de uma única sentença não anotada, dada como lista de tokens, e uma lista de regras sintáticas no formato CFG, armazenadas em arquivo. Esta função tem um bug, causado pela maneira como o Aelius etiqueta sentenças usando o módulo ProcessaNomesProprios: quando a sentença se inicia por paravra com inicial minúscula, essa palavra não é incorporada ao léxico, mas a versão com inicial maiúscula. | |
| 143 | + """ | |
| 144 | + self.sentenca_anotada = self.etiqueta_sentenca(s) | |
| 145 | + self.corrige_anotacao(self.sentenca_anotada) | |
| 146 | + entradas = self.gera_entradas_lexicais(self.sentenca_anotada) | |
| 147 | + lexico="\n".join(entradas) | |
| 148 | + gramatica="%s\n%s" % (self.extrai_sintaxe().strip(),lexico) | |
| 149 | + cfg=nltk.CFG.fromstring(gramatica) | |
| 150 | + return nltk.ChartParser(cfg) | |
| 151 | + | |
| 152 | + def remove_acento(self, texto): | |
| 153 | + try: | |
| 154 | + return normalize('NFKD', texto.encode('utf-8').decode('utf-8')).encode('ASCII', 'ignore') | |
| 155 | + except: | |
| 156 | + return normalize('NFKD', texto.encode('iso-8859-1').decode('iso-8859-1')).encode('ASCII','ignore') | |
| 157 | + | |
| 158 | + def exibe_arvores(self, arvores): | |
| 159 | + """Função 'wrapper' para a função de exibição de árvores do NLTK""" | |
| 160 | + nltk.draw.draw_trees(*arvores) | |
| 161 | + | |
| 162 | + def iniciar_classificacao(self, sentenca): | |
| 163 | + tokens = self.toqueniza(sentenca) | |
| 164 | + tree = self.analisa_sentenca(tokens) | |
| 165 | + return tree | |
| 0 | 166 | \ No newline at end of file | ... | ... |
src/TraduzSentencas.py
| ... | ... | @@ -6,7 +6,7 @@ |
| 6 | 6 | |
| 7 | 7 | #LAViD - Laboratório de Aplicações de Vídeo Digital |
| 8 | 8 | |
| 9 | -import alexp | |
| 9 | +from ClassificaSentencas import * | |
| 10 | 10 | from AplicaRegras import * |
| 11 | 11 | from AplicaSinonimos import * |
| 12 | 12 | import logging |
| ... | ... | @@ -24,6 +24,7 @@ class TraduzSentencas(object): |
| 24 | 24 | def __init__(self): |
| 25 | 25 | '''Instancia os aplicadores de regras e sinônimos. |
| 26 | 26 | ''' |
| 27 | + self.classificador = ClassificaSentencas() | |
| 27 | 28 | self.aplic_regras = AplicaRegras() |
| 28 | 29 | self.aplic_sin = AplicaSinonimos() |
| 29 | 30 | self.check_level() |
| ... | ... | @@ -33,13 +34,13 @@ class TraduzSentencas(object): |
| 33 | 34 | ''' |
| 34 | 35 | try: |
| 35 | 36 | has_sintatica = True |
| 36 | - analise_sintatica = alexp.run(sentenca) | |
| 37 | + analise_sintatica = self.classificador.iniciar_classificacao(sentenca) | |
| 37 | 38 | except Exception as ex: |
| 38 | 39 | self.salvar_log(str(traceback.format_exc())) |
| 39 | 40 | analise_sintatica = None |
| 40 | 41 | has_sintatica = False |
| 41 | 42 | |
| 42 | - analise_morfologica = alexp.getAnaliseMorfologica() | |
| 43 | + analise_morfologica = self.classificador.obter_classificacao_morfologica() | |
| 43 | 44 | |
| 44 | 45 | if (isinstance(analise_sintatica,type(None))): |
| 45 | 46 | regras_aplicadas = self.aplic_regras.aplicar_regras_morfo(analise_morfologica) | ... | ... |
src/alexp.py
| ... | ... | @@ -1,158 +0,0 @@ |
| 1 | -#! /usr/bin/env python2.6 | |
| 2 | -# -*- coding: utf-8 -*- | |
| 3 | - | |
| 4 | -#--------------------------------- | |
| 5 | - | |
| 6 | -# Editado: | |
| 7 | - | |
| 8 | -#Autor: Erickson Silva | |
| 9 | -#Email: <erickson.silva@lavid.ufpb.br> <ericksonsilva@live.com> | |
| 10 | - | |
| 11 | -#LAViD - Laboratório de Aplicações de Vídeo Digital | |
| 12 | - | |
| 13 | -#--------------------------------- | |
| 14 | - | |
| 15 | - | |
| 16 | -# Donatus Brazilian Portuguese Parser | |
| 17 | -# | |
| 18 | -# Copyright (C) 2010-2013 Leonel F. de Alencar | |
| 19 | -# | |
| 20 | -# Author: Leonel F. de Alencar <leonel.de.alencar@ufc.br> | |
| 21 | -# Homepage: <http://www.leonel.profusehost.net/> | |
| 22 | -# | |
| 23 | -# Project's URL: <http://sourceforge.net/projects/donatus/> | |
| 24 | -# For license information, see LICENSE.TXT | |
| 25 | -# | |
| 26 | -# $Id: alexp.py $ | |
| 27 | - | |
| 28 | -"""Este módulo contém funções que permitem utilizar o Aelius para etiquetar uma sentença, construindo entradas lexicais com base nas etiquetas atribuídas às palavras da sentença. Essas entradas lexicais são integradas em uma gramática CFG dada, que é transformada em um parser, utilizado para gerar uma árvore de estrutura sintagmática da sentença. | |
| 29 | -""" | |
| 30 | -import re,nltk, time, random | |
| 31 | -from os.path import expanduser | |
| 32 | -from os import environ, path | |
| 33 | -from Aelius.Extras import carrega | |
| 34 | -from Aelius import AnotaCorpus, Toqueniza | |
| 35 | -from unicodedata import normalize | |
| 36 | - | |
| 37 | -sentenca_anotada="" | |
| 38 | -sleep_times=[0.1,0.2] | |
| 39 | - | |
| 40 | -def toqueniza(s): | |
| 41 | - """Decodifica string utilizando utf-8, retornando uma lista de tokens em unicode. | |
| 42 | - """ | |
| 43 | - regex = re.compile('[%s]' % re.escape('“”')) | |
| 44 | - decodificada=regex.sub('"',s.replace("–", "-").replace("—", "-")).decode("utf-8") | |
| 45 | - return Toqueniza.TOK_PORT.tokenize(decodificada) | |
| 46 | - | |
| 47 | -def getAnaliseMorfologica(): | |
| 48 | - return sentenca_anotada | |
| 49 | - | |
| 50 | -def etiquetaSentenca(s): | |
| 51 | - """Aplica um dos etiquetadores do Aelius na etiquetagem da sentença dada como lista de tokens. | |
| 52 | - """ | |
| 53 | - etiquetador = carrega("AeliusHunPos") | |
| 54 | - anotada = AnotaCorpus.anota_sentencas([s],etiquetador,"hunpos")[0] | |
| 55 | - while (anotada[0][1] is None): | |
| 56 | - time.sleep(random.choice(sleep_times)) | |
| 57 | - anotada = AnotaCorpus.anota_sentencas([s],etiquetador,"hunpos")[0] | |
| 58 | - regex = re.compile('[%s]' % re.escape('!"#&\'()*+,-./:;<=>?@[\\]^_`{|}~')) | |
| 59 | - tag_punctuation = [".",",","QT","("] | |
| 60 | - anotada_corrigida = [] | |
| 61 | - for x in anotada: | |
| 62 | - if x[1] not in tag_punctuation: | |
| 63 | - if x[1] == "NUM": | |
| 64 | - try: | |
| 65 | - float(x[0].replace(',', '.')) | |
| 66 | - anotada_corrigida.append(x) | |
| 67 | - continue | |
| 68 | - except: | |
| 69 | - pass | |
| 70 | - | |
| 71 | - tupla = [regex.sub('',x[0]).lower(),x[1]] | |
| 72 | - if tupla[0] != "": anotada_corrigida.append(tupla) | |
| 73 | - else: | |
| 74 | - if x[0] == ".": | |
| 75 | - anotada_corrigida.append(["[ponto]".decode("utf-8"),"SPT"]) | |
| 76 | - elif x[0] == "?": | |
| 77 | - anotada_corrigida.append(["[interrogacao]".decode("utf-8"),"SPT"]) | |
| 78 | - elif x[0] == "!": | |
| 79 | - anotada_corrigida.append(["[exclamacao]".decode("utf-8"),"SPT"]) | |
| 80 | - return anotada_corrigida | |
| 81 | - | |
| 82 | -def geraEntradasLexicais(lista): | |
| 83 | - """Gera entradas lexicais no formato CFG do NLTK a partir de lista de pares constituídos de tokens e suas etiquetas. | |
| 84 | - """ | |
| 85 | - entradas=[] | |
| 86 | - for e in lista: | |
| 87 | - # é necessário substituir símbolos como "-" e "+" do CHPTB | |
| 88 | - # que não são aceitos pelo NLTK como símbolos não terminais | |
| 89 | - c=re.sub(r"[-+]","_",e[1]) | |
| 90 | - c=re.sub(r"\$","_S",c) | |
| 91 | - entradas.append("%s -> '%s'" % (c, removeAcento(e[0]))) | |
| 92 | - return entradas | |
| 93 | - | |
| 94 | -def corrigeAnotacao(lista): | |
| 95 | - """Esta função deverá corrigir alguns dos erros de anotação mais comuns do Aelius. No momento, apenas é corrigida VB-AN depois de TR. | |
| 96 | - """ | |
| 97 | - i=1 | |
| 98 | - while i < len(lista): | |
| 99 | - if lista[i][1] == "VB-AN" and lista[i-1][1].startswith("TR"): | |
| 100 | - lista[i]=(lista[i][0],"VB-PP") | |
| 101 | - i+=1 | |
| 102 | - | |
| 103 | -def encontraArquivo(): | |
| 104 | - """Encontra arquivo na pasta vlibras-translate. | |
| 105 | - """ | |
| 106 | - if "TRANSLATE_DATA" in environ: | |
| 107 | - return path.join(environ.get("TRANSLATE_DATA"), "cfg.syn.nltk") | |
| 108 | - return expanduser("~") + "/vlibras-translate/data/cfg.syn.nltk" | |
| 109 | - | |
| 110 | -def extraiSintaxe(): | |
| 111 | - """Extrai gramática armazenada em arquivo cujo caminho é definido relativamente ao diretório nltk_data. | |
| 112 | - """ | |
| 113 | - arquivo=encontraArquivo() | |
| 114 | - if arquivo: | |
| 115 | - f=open(arquivo,"rU") | |
| 116 | - sintaxe=f.read() | |
| 117 | - f.close() | |
| 118 | - return sintaxe | |
| 119 | - else: | |
| 120 | - print "Arquivo %s não encontrado em nenhum dos diretórios de dados do NLTK:\n%s" % (caminho,"\n".join(nltk.data.path)) | |
| 121 | - | |
| 122 | -def analisaSentenca(sentenca): | |
| 123 | - """Retorna lista de árvores de estrutura sintagmática para a sentença dada sob a forma de uma lista de tokens, com base na gramática CFG cujo caminho é especificado como segundo argumento da função. Esse caminho é relativo à pasta nltk_data da instalação local do NLTK. A partir da etiquetagem morfossintática da sentença são geradas entradas lexicais que passam a integrar a gramática CFG. O caminho da gramática e o parser gerado são armazenados como tupla na variável ANALISADORES. | |
| 124 | - """ | |
| 125 | - parser=constroiAnalisador(sentenca) | |
| 126 | - codificada=[] | |
| 127 | - for t in sentenca_anotada: | |
| 128 | - if t[1] != "SPT": | |
| 129 | - codificada.append(removeAcento(t[0]).encode("utf-8")) | |
| 130 | - trees=parser.parse_one(codificada) | |
| 131 | - return trees | |
| 132 | - | |
| 133 | -def constroiAnalisador(s): | |
| 134 | - """Constrói analisador a partir de uma única sentença não anotada, dada como lista de tokens, e uma lista de regras sintáticas no formato CFG, armazenadas em arquivo. Esta função tem um bug, causado pela maneira como o Aelius etiqueta sentenças usando o módulo ProcessaNomesProprios: quando a sentença se inicia por paravra com inicial minúscula, essa palavra não é incorporada ao léxico, mas a versão com inicial maiúscula. | |
| 135 | - """ | |
| 136 | - global sentenca_anotada | |
| 137 | - sentenca_anotada=etiquetaSentenca(s) | |
| 138 | - corrigeAnotacao(sentenca_anotada) | |
| 139 | - entradas=geraEntradasLexicais(sentenca_anotada) | |
| 140 | - lexico="\n".join(entradas) | |
| 141 | - gramatica="%s\n%s" % (extraiSintaxe().strip(),lexico) | |
| 142 | - cfg=nltk.CFG.fromstring(gramatica) | |
| 143 | - return nltk.ChartParser(cfg) | |
| 144 | - | |
| 145 | -def removeAcento(texto): | |
| 146 | - try: | |
| 147 | - return normalize('NFKD', texto.encode('utf-8').decode('utf-8')).encode('ASCII', 'ignore') | |
| 148 | - except: | |
| 149 | - return normalize('NFKD', texto.encode('iso-8859-1').decode('iso-8859-1')).encode('ASCII','ignore') | |
| 150 | - | |
| 151 | -def exibeArvores(arvores): | |
| 152 | - """Função 'wrapper' para a função de exibição de árvores do NLTK""" | |
| 153 | - nltk.draw.draw_trees(*arvores) | |
| 154 | - | |
| 155 | -def run(sentenca): | |
| 156 | - tokens=toqueniza(sentenca) | |
| 157 | - tree=analisaSentenca(tokens) | |
| 158 | - return tree | |
| 159 | 0 | \ No newline at end of file |