Commit 1d486c406f1d88238e01ec77ad61b83bc7d5d7e2

Authored by Erickson Silva
1 parent ada59211
Exists in master and in 1 other branch devel

Altera alexp para um classe

src/ClassificaSentencas.py 0 → 100644
... ... @@ -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