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 @@ | @@ -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 | \ No newline at end of file | 166 | \ No newline at end of file |
src/TraduzSentencas.py
@@ -6,7 +6,7 @@ | @@ -6,7 +6,7 @@ | ||
6 | 6 | ||
7 | #LAViD - Laboratório de Aplicações de Vídeo Digital | 7 | #LAViD - Laboratório de Aplicações de Vídeo Digital |
8 | 8 | ||
9 | -import alexp | 9 | +from ClassificaSentencas import * |
10 | from AplicaRegras import * | 10 | from AplicaRegras import * |
11 | from AplicaSinonimos import * | 11 | from AplicaSinonimos import * |
12 | import logging | 12 | import logging |
@@ -24,6 +24,7 @@ class TraduzSentencas(object): | @@ -24,6 +24,7 @@ class TraduzSentencas(object): | ||
24 | def __init__(self): | 24 | def __init__(self): |
25 | '''Instancia os aplicadores de regras e sinônimos. | 25 | '''Instancia os aplicadores de regras e sinônimos. |
26 | ''' | 26 | ''' |
27 | + self.classificador = ClassificaSentencas() | ||
27 | self.aplic_regras = AplicaRegras() | 28 | self.aplic_regras = AplicaRegras() |
28 | self.aplic_sin = AplicaSinonimos() | 29 | self.aplic_sin = AplicaSinonimos() |
29 | self.check_level() | 30 | self.check_level() |
@@ -33,13 +34,13 @@ class TraduzSentencas(object): | @@ -33,13 +34,13 @@ class TraduzSentencas(object): | ||
33 | ''' | 34 | ''' |
34 | try: | 35 | try: |
35 | has_sintatica = True | 36 | has_sintatica = True |
36 | - analise_sintatica = alexp.run(sentenca) | 37 | + analise_sintatica = self.classificador.iniciar_classificacao(sentenca) |
37 | except Exception as ex: | 38 | except Exception as ex: |
38 | self.salvar_log(str(traceback.format_exc())) | 39 | self.salvar_log(str(traceback.format_exc())) |
39 | analise_sintatica = None | 40 | analise_sintatica = None |
40 | has_sintatica = False | 41 | has_sintatica = False |
41 | 42 | ||
42 | - analise_morfologica = alexp.getAnaliseMorfologica() | 43 | + analise_morfologica = self.classificador.obter_classificacao_morfologica() |
43 | 44 | ||
44 | if (isinstance(analise_sintatica,type(None))): | 45 | if (isinstance(analise_sintatica,type(None))): |
45 | regras_aplicadas = self.aplic_regras.aplicar_regras_morfo(analise_morfologica) | 46 | regras_aplicadas = self.aplic_regras.aplicar_regras_morfo(analise_morfologica) |
src/alexp.py
@@ -1,158 +0,0 @@ | @@ -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 | \ No newline at end of file | 0 | \ No newline at end of file |