-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathParseTimebankPT.py
11394 lines (9025 loc) · 464 KB
/
ParseTimebankPT.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
'''
Autor: Dárcio Santos Rocha®
e-mail: darcio.rocha@ufba.br
Mestrando em Ciências da Computação - UFBA
Agosto/2021
Importa tags do corpus TimebankPT para a estrutura do spaCy.
Identifica tipos de relações temporais.
'''
#----------------------------------------------------------
#INSTALAÇÕES
#conda install -c conda-forge spacy
#conda install tabulate
#python -m spacy download pt_core_news_lg
# ou
#spacy.cli.download("pt_core_news_lg")
#!python -m spacy info
#----------------------------------------------------------
'''
TODO:
. Verificar a importância de noun_chunks: [(p.text, p.label_) for p in tb.doc[0].noun_chunks]
. verificar se \b é melhor que \W na func search -> reg = "(\W|^)(" + tratar_palavras_busca(palavras) + ")(\W|$)"
. The last feature in this category is the Temporal Relation between the Document Creation Time and the Temporal Expression in the target sentence.
. The value of this feature could be “greater than”, “less than”, “equal”, or “none”.
. ele primeiro ordena expressões temporais anotadas de acordo com seu valor normalizado (por exemplo, a data 1989-09-29 é ordenada como precedendo 1989-10-02).
Ou seja, exploramos as anotações timex3 a fim de enriquecer o conjunto de relações temporais com as quais trabalhamos, e mais especificamente fazemos uso do atributo de valor dos elementos TIMEX3.
. TRABALHOS FUTUROS
. implementar junção das relações frequentemente classificadas como incorretas com o dataset de features, verificar padrões
. Experimentar utilizar Tempo Verbal e POS das anotações do corpus ao invés do spaCy.
. Falar sobre as relações que nenhum algoritmo acertou (tb.tr.relations_incorrect_class)
. Falar sobre as 40 regras duplicadas geradas por mais de um algoritmo, 2 ou mais algoritmos geraram a mesma regra.
. Rotular 55% das relações event-time não rotuladas no Corpus
. Implementar task B: relações entre eventos e DT
. implementar taxk C: relações entre pares de eventos
. CONCLUSÕES PARA A DISSERTAÇÃO:
. Regras geradas por CN2 obteve alta cobertura, o fechamento temporal não geral nenhum par event-time que já não houvesse sido predito pelas regras.
. As regras RIPPER e IDS geraram muitas regras repetidas durante os loops
TODO IDS:
. Cria regras de alta qualidade, mas baixa cobertura.
. [ok] Retirar instâncias cobertas, e aplicar algoritmo nas instâncias descobertas
. [ok] Adicionar novo conjunto de regras abaixo do conjunto existente
. [ok] Testar outros algoritmos de optimização além do SLS, testar o DSL
. [ok] Testar outros lambdas (dinâmico)
. [ok] Gerar novamente regras com classes raras
TODO RIPPER:
. [ok] Semelhante ao IDS, repetir processamento nas instâncias não cobertas
. experimentar em ordem de relType, classe menor frequente na frente, mais frequente no final
. [ok] tentar gerar regras para as classes: BEFORE-OR-OVERLAP, OVERLAP-OR-AFTER e VAGUE
. tentar GridSearch em cada iteração
TODO CN2:
. [ok] Testar retirando classificação default da classe majoritária
. [ok] Submeter as instância não classificadas ao mesmo algoritmos (PIOROU)
#SALVAR SAIDA DO DISPLACY EM ARQUIVO HTML
html = spacy.displacy.render(doc, style='ent', jupyter=False, page=True)
f = open("teste.html", "w", encoding='utf-8')
f.write(html)
f.close()
'''
verbosy = False
import time
if verbosy: ini_total = time.time()
if verbosy: ini = time.time()
import numpy as np
import pandas as pd
from pandas.core.frame import DataFrame
#from pandas import Series
pd.set_option('display.max_columns', None)
pd.set_option('display.max_colwidth', 200)
#pd.set_option('max_rows', 40)
#pd.set_option("colheader_justify", "left") # Não funcionou
if verbosy: print((time.time() - ini) / 60, 'min -> numpy, pandas')
if verbosy: ini = time.time()
import matplotlib.pyplot as plt
import seaborn as sns
#%matplotlib inline
if verbosy: print((time.time() - ini) / 60, 'min -> plots')
if verbosy: ini = time.time()
import spacy
from spacy.language import Language
from spacy.tokens import Token, Span, Doc
if verbosy: print((time.time() - ini) / 60, 'min -> spaCy')
if verbosy: ini = time.time()
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score, f1_score, precision_score, recall_score
from sklearn.model_selection import train_test_split
import pickle #save model
from sklearn.preprocessing import LabelEncoder
from sklearn.feature_selection import SelectKBest, chi2 #, mutual_info_classif
from sklearn.feature_selection import RFECV
from sklearn.model_selection import StratifiedKFold
#Visualização Decision Tree
from IPython.display import Image
from sklearn.tree import export_graphviz
#!pip install pydotplus
#!conda install graphviz
import pydotplus
from tabulate import tabulate
#!pip install treelib
from treelib import Node, Tree
if verbosy: print((time.time() - ini) / 60, 'min -> sklearn')
if verbosy: ini = time.time()
import os
import io
import math
import random
import xml.etree.ElementTree as et
import glob
import re
import html
from itertools import product, combinations, groupby
from collections import defaultdict, Counter
import types
from typing import List, Literal, Dict, Tuple, Union
import inspect
import copy
from IPython.display import display
if verbosy: print((time.time() - ini) / 60, 'min -> outros módulos')
if verbosy: print((time.time() - ini_total) / 60, 'min -> Tempo total')
class Functions:
'''
Funções genéricas utilizadas em outras classes
'''
def explicar_spacy(self, elemento):
'''
Retorna descrição explicativa sobre elementos POS e DEP do spaCy.
'''
__explain_outros = {'pass': 'passive voice', 'foreign': 'foreign name', 'name': 'proper name'}
if not elemento:
return ''
__list_elem = elemento.split(':')
__list_elem_explain = [spacy.explain(e) for e in elemento.split(':')]
if len(__list_elem) > 1:
if __list_elem_explain[1] == None:
if __list_elem[1] in __explain_outros.keys():
__list_elem_explain[1] = __explain_outros[__list_elem[1]]
else:
__list_elem_explain[1] = 'None'
if __list_elem_explain[0] == None:
return ''
return ', '.join(__list_elem_explain)
def nbor(self, token: Token, n: int) -> Token:
'''
Retorna o token n vizinho de 'token'.
n negativo: vizinho a esquerda.
n positivos: vizinho a direita.
'''
min = -token.i
max = len(token.doc) - token.i - 1
n = 0 if n < min else n
n = 0 if n > max else n
return None if n == 0 else token.nbor(n)
#-------------------------------
def __get_class(self, tipo, obj = None):
if obj:
self = obj
classname = self.__class__
components = dir(classname)
features = list(filter(lambda attr: (type(getattr(classname, attr)) is tipo) and attr[0] != '_', components))
return features
def __get_properties(self, obj = None):
'''
Retorna propriedades de um objeto.
'''
return self.__get_class(property, obj)
def __get_functions(self, obj = None):
'''
Retorna funções de um objeto.
'''
return self.__get_class(types.FunctionType, obj)
def __get_types(self, obj = None):
'''
Retorna tipos de um objeto.
'''
return self.__get_class(type, obj)
def get_class_list(self, obj = None):
'''
Retorna lista com as propriedades, funções e tipos presentes no objeto atual.
'''
return {'properties': self.__get_properties(obj), 'functions': self.__get_functions(obj), 'types': self.__get_types(obj)}
#----------------------------------------------
# '##::: ##:'########:'##:::::'##:::::'######::'##::::::::::'###:::::'######:::'######:: #
# ###:: ##: ##.....:: ##:'##: ##::::'##... ##: ##:::::::::'## ##:::'##... ##:'##... ##: #
# ####: ##: ##::::::: ##: ##: ##:::: ##:::..:: ##::::::::'##:. ##:: ##:::..:: ##:::..:: #
# ## ## ##: ######::: ##: ##: ##:::: ##::::::: ##:::::::'##:::. ##:. ######::. ######:: #
# ##. ####: ##...:::: ##: ##: ##:::: ##::::::: ##::::::: #########::..... ##::..... ##: #
# ##:. ###: ##::::::: ##: ##: ##:::: ##::: ##: ##::::::: ##.... ##:'##::: ##:'##::: ##: #
# ##::. ##: ########:. ###. ###:::::. ######:: ########: ##:::: ##:. ######::. ######:: #
# ..::::..::........:::...::...:::::::......:::........::..:::::..:::......::::......::: #
#---------------------------------------------------------------------
# CLASSE SIGLAS
#--------------------------------------------------------------------
class Siglas(Functions):
'''
Fornece estrutura de dados contendo as siglas de POS, DEP, CLASSE DE EVENT e TIPO DO TIMEX3
'''
def __init__(self):
'''
pos : classe gramatical
dep : árvore de dependência
classe : classe do evento <EVENT> anotada no corpus
tipo : tipo da expressão temporal <TIME3> anotada no corpus
'''
self.siglas = {
'pos': {0:'adj', 1:'adp', 2:'adv', 3:'aux', 4:'cconj', 5:'det', 6:'intj', 7:'noun', 8:'num', 9:'pron', 10:'propn', 11:'punct', 12:'sconj', 13:'sym', 14:'verb', 15:'x'},
'dep': {0:'acl', 1:'acl:relcl', 2:'advcl', 3:'advmod', 4:'amod', 5:'appos', 6:'aux', 7:'aux:pass', 8:'case', 9:'cc', 10:'ccomp', 11:'compound', 12:'conj', 13:'cop', 14:'csubj', 15:'dep', 16:'det', 17:'discourse', 18:'expl', 19:'fixed', 20:'flat', 21:'flat:foreign', 22:'flat:name', 23:'iobj', 24:'mark', 25:'nmod', 26:'nsubj', 27:'nsubj:pass', 28:'nummod', 29:'obj', 30:'obl', 31:'obl:agent', 32:'parataxis', 33:'punct', 34:'root', 35:'xcomp'},
'classe': {0:'aspectual', 1:'i_action', 2:'i_state', 3:'occurrence', 4:'perception', 5:'reporting', 6:'state'},
'morph_key': {0:'case', 1:'definite', 2:'degree', 3:'foreign', 4:'gender', 5:'numtype', 6:'polarity', 7:'prontype', 8:'reflex', 9:'tense', 10:'mood', 11:'verbform', 12:'person', 13:'number', 14:'voice'},
'tipo': {0: 'date', 1:'time', 2:'duration', 3:'set'},
}
#Features em ordem de frequência para testar o formato da árvore
#self.siglas = {
# 'pos': {0: 'verb', 1: 'noun', 2: 'adp', 3: 'adj', 4: 'adv', 5: 'num', 6: 'aux', 7: 'cconj', 8: 'det', 9: 'intj', 10: 'pron', 11: 'propn', 12: 'punct', 13: 'sconj', 14: 'sym', 15: 'x'},
# 'dep': {0: 'root', 1: 'obj', 2: 'ccomp', 3: 'advcl', 4: 'xcomp', 5: 'obl', 6: 'nmod', 7: 'acl:relcl', 8: 'conj', 9: 'acl', 10: 'nsubj', 11: 'advmod', 12: 'amod', 13: 'appos', 14: 'aux', 15: 'aux:pass', 16: 'case', 17: 'cc', 18: 'compound', 19: 'cop', 20: 'csubj', 21: 'dep', 22: 'det', 23: 'discourse', 24: 'expl', 25: 'fixed', 26: 'flat', 27: 'flat:foreign', 28: 'flat:name', 29: 'iobj', 30: 'mark', 31: 'nsubj:pass', 32: 'nummod', 33: 'obl:agent', 34: 'parataxis', 35: 'punct'},
# 'classe': {0: 'occurrence', 1: 'reporting', 2: 'state', 3: 'i_state', 4: 'i_action', 5: 'aspectual', 6: 'perception'},
#}
def __valida_tipo_siglas(self, tipo_siglas: str) -> bool:
'''
Verifica se o tipo da sigla é válido
Args:
tipo_siglas: 'pos', 'dep', 'classe', morph_key e tipo (timex3)
Return:
bool
'''
if tipo_siglas in self.get_tipo_siglas():
return True
else:
print("Tipo de siglas válidos: " + str(self.get_tipo_siglas()))
return False
def __list_tipo_siglas(self, tipo_siglas: str, valor: float, sinal: str):
if not self.__valida_tipo_siglas(tipo_siglas):
return []
lista = []
siglas = self.siglas[tipo_siglas]
for k in siglas:
if eval(str(k) + sinal + str(valor)):
lista.append(siglas[k].upper())
return lista
def __le(self, tipo_siglas: str, valor: float):
'''
le = menor ou igual a
'''
return self.__list_tipo_siglas(tipo_siglas, valor, '<=')
def __gt(self, tipo_siglas: str, valor: float):
'''
gt = maior que
'''
return self.__list_tipo_siglas(tipo_siglas, valor, '>')
def pos_le(self, valor: float):
'''
Return POS tag menor ou igual a 'valor' da classe Siglas().
'''
return self.__le('pos', valor)
def dep_le(self, valor: float):
'''
Return DEP menor ou igual a 'valor' da classe Siglas().
'''
return self.__le('dep', valor)
def classe_le(self, valor: float):
'''
Return Classe do evento menor ou igual a 'valor' da classe Siglas().
'''
return self.__le('classe', valor)
def pos_gt(self, valor: float):
'''
Return POS tag maior que 'valor' da classe Siglas().
'''
return self.__gt('pos', valor)
def dep_gt(self, valor: float):
'''
Return DEP maior que 'valor' da classe Siglas().
'''
return self.__gt('dep', valor)
def classe_gt(self, valor: float):
'''
Return Classe do evento maior que 'valor' da classe Siglas().
'''
return self.__gt('classe', valor)
def get_key(self, valor: str, tipo_siglas: str):
'''
Obtém a chave do dicionário 'tipo_siglas' passando o valor como argumento.
Args:
valor: valor da chave que deseja buscar
tipo_siglas: 'pos', 'dep', 'classe', morph_key e tipo (timex3)
'''
valor = valor.lower()
tipo_siglas = tipo_siglas.lower()
if not self.__valida_tipo_siglas(tipo_siglas):
return False
for key, value in self.siglas[tipo_siglas].items():
if valor == value:
return key
return False
def get_value(self, key: int, tipo_siglas: str):
'''
Obtém o valor da chave do dicionário 'tipo_siglas' passando a chave como argumento.
Args:
key: número inteiro que representa a chave
tipo_siglas: pode ser 'pos', 'dep', 'classe', morph_key e tipo (timex3)
'''
tipo_siglas = tipo_siglas.lower()
if not self.__valida_tipo_siglas(tipo_siglas):
return False
if key not in self.siglas[tipo_siglas].keys():
return False
return self.siglas[tipo_siglas][key]
def get_tipo_siglas(self) -> list:
'''
Retorna os tipos de dicionários de siglas disponíveis
'''
return list(self.siglas.keys())
def get_desc(self, valor: str):
'''
Retorna a descrição de cada DEP ou POS disponíveis no spaCy.
Args:
valor: pode ser valores de POS e DEP.
'''
return self.explicar_spacy(valor)
def print_dep(self):
'''
Imprime todos DEP na tela com suas descrições.
'''
siglas_desc = {}
for key, sigla in self.siglas['dep'].items():
siglas_desc[sigla.upper()] = self.explicar_spacy(sigla)
return siglas_desc
def print_pos(self):
'''
Imprime todos POS na tela e suas descrições.
'''
siglas_desc = {}
for key, sigla in self.siglas['pos'].items():
siglas_desc[key] = sigla.upper() + ': ' + self.explicar_spacy(sigla.upper())
return siglas_desc
def print_classe(self):
'''
Imprime todas as classes de EVENT.
'''
siglas_desc = {}
for key, sigla in self.siglas['classe'].items():
siglas_desc[key] = sigla.upper()
return siglas_desc
def get_list(self, tipo_siglas: str):
'''
Retorna 'tipo_siglas' em formato de lista.
Args:
tipo_siglas: 'pos', 'dep', 'classe', morph_key e tipo (timex3)
'''
if not self.__valida_tipo_siglas(tipo_siglas):
return False
lista = self.siglas[tipo_siglas].values()
return list(map(str.upper, lista))
#---------------------------------------------------------------------
# FIM CLASSE SIGLAS
#--------------------------------------------------------------------
#=========================================================================================================================
#=========================================================================================================================
# '##::: ##:'########:'##:::::'##:::::'######::'##::::::::::'###:::::'######:::'######:: #
# ###:: ##: ##.....:: ##:'##: ##::::'##... ##: ##:::::::::'## ##:::'##... ##:'##... ##: #
# ####: ##: ##::::::: ##: ##: ##:::: ##:::..:: ##::::::::'##:. ##:: ##:::..:: ##:::..:: #
# ## ## ##: ######::: ##: ##: ##:::: ##::::::: ##:::::::'##:::. ##:. ######::. ######:: #
# ##. ####: ##...:::: ##: ##: ##:::: ##::::::: ##::::::: #########::..... ##::..... ##: #
# ##:. ###: ##::::::: ##: ##: ##:::: ##::: ##: ##::::::: ##.... ##:'##::: ##:'##::: ##: #
# ##::. ##: ########:. ###. ###:::::. ######:: ########: ##:::: ##:. ######::. ######:: #
# ..::::..::........:::...::...:::::::......:::........::..:::::..:::......::::......::: #
#=========================================================================================================================
#=========================================================================================================================
class TimebankPT(Functions):
'''
Importa dados do corpus TimebankPT e fornece vários métodos para manipular o conteúdo do corpus.
Se existir o arquivo 'dataset/corpus.pickle', o carregamento rápido do corpus é acionado.
Se não existir, o corpus existente na pasta 'path_tml' é processado, o carregamento é mais lento.
O arquivo 'dataset/corpus.pickle' é salvo pelo método:
TimebankPT.df.save_corpus(tb.path_corpus_pickle).
Se existir o arquivo 'dataset/data_pipe_tb.pickle', o carregamento rápido dos dados do pipeline é acionado.
Se não existir, os dados para o pipeline são processados na inicialização de Timebank.df.
O arquivo 'dataset/data_pipe_tb.pickle' é salvo pelo método:
TimebankPT.save_data_pipe_tb(tb.path_data_pipe_pickle).
Args:
path_tml: caminho do corpus TimebankPT no formato: 'c:\diretorio\*\*.txt'
add_timebank: adiciona tags (EVENT, TIMEX3 e TLINK) do corpus TimebankPT ao pipeline do spaCy. Default é True
dev: Se True, os dados de treino são divididos em 'train' e 'train_test'. 'test' não deve ser utilizado.
Se False, todo dado de treino é 'train' e 'test' é utilizado.
ignore_load_corpus: Se True, carrega o corpus previamente salvo.
Se False, processa o carragamento do curpus a partir dos arquivo .tml
'''
def __init__(self, path_tml, add_timebank = True, lang = 'pt', dev: bool = False, ignore_load_corpus = False):
self.path_corpus_pickle = 'dataset/corpus.pickle'
self.path_data_pipe_pickle = 'dataset/data_pipe_tb.pickle'
self.ignore_load_corpus = ignore_load_corpus
if not os.path.exists(self.path_corpus_pickle):
if not os.path.exists(os.path.dirname(path_tml).replace('*', '')):
print('ERROR: Path dos arquivos .tml não existe.\n' + path_tml)
return
self.path_tml = path_tml
self.dev = dev
#O self do parâmetro é para passar a class TimebankPT para Df, Print e MyTlink
self.df = self.Df(self)
self.print = self.Print(self)
self.my_tlink = self.MyTlink(self)
#TemporalRelation deve ser anterior a FeaturesToDataset
self.tr = TemporalRelation(self)
self.features = FeaturesToDataset(self)
self.siglas = Siglas()
self.__dados_pipe = None
self.__id_sentenca = []
self.__sentenca_texto = []
self.__nome_doc = None
self.__dct_doc = None
self.__lingua = 'PORTUGUÊS'
#objeto Doc do spaCy
self.__doc = None
#Carrega linguagem pt do spaCy
if lang == 'pt':
self.nlp = spacy.load('pt_core_news_lg')
else:
self.nlp = spacy.load('en_core_web_sm')
if add_timebank and lang == 'pt':
#Adiciona o pipe_timebank antes do merge_entities
self.add_pipe_timebank()
#Junta em um unico token entidades com mais de um token
if not self.nlp.has_pipe("merge_entities"):
self.nlp.add_pipe("merge_entities")
#Verificar se este pipe é útil para alguma situação. Junta substantivos em pequenas frases.
#self.nlp.add_pipe("merge_noun_chunks")
#define variável com nome da linguagem utilizada
if lang == 'pt':
self.__lingua = 'PORTUGUÊS'
else:
self.__lingua = 'INGLÊS'
def __str__(self):
'''
Exibe as quantidades dos objetos do TimebankPT
'''
return "QUANTIDADES:\n . Sentenças: " + str(self.df.quant_sentenca_total) + \
"\n . Documentos: " + str(self.df.quant_doc_total) + \
"\n . Events: " + str(self.df.quant_event_total) + \
"\n . Timex3: " + str(self.df.quant_timex3_total) + \
"\n . TLink: " + str(self.df.quant_tlink_total ) + \
"\n\nSENTENÇAS: " + str(self.df.sentenca_completo.value_counts('train_test'))
#----------------------------------------
#----- PÚBLICO - class Timebank -----
#----------------------------------------
def print_pipes(self):
'''
Imprime a sequência dos pipelines executados
'''
print('SEQUÊNCIA PIPELINE: ' + self.__lingua)
for i, pipe in enumerate(self.nlp.pipe_names):
print(' ' + str(i + 1) + ' -> ' + pipe)
print('\n')
@property
def dados_pipe(self):
return self.__dados_pipe
def add_pipe_timebank(self):
'''
Adiciona o pipe que adiciona tags dos timebankPT ao Doc no spaCy
'''
if not self.nlp.has_pipe("pipe_timebankpt"):
insere_antes = 'merge_entities'
if not self.nlp.has_pipe(insere_antes):
self.nlp.add_pipe(insere_antes)
#Recupera dados do TimebankPT para serem fornecidos ao pipeline
if os.path.exists(self.path_data_pipe_pickle) and not self.ignore_load_corpus:
print(f"Arquivo '{self.path_data_pipe_pickle}' encontrado. \nAcionado carregamento rápido dos dados do TimebankPT para pipeline do spaCy.")
self.__dados_pipe = self.load_data_pipe_tb(self.path_data_pipe_pickle)
else:
if not self.__dados_pipe:
self.__dados_pipe = self.df.dados_pipe
self.nlp.add_pipe("pipe_timebankpt", before = insere_antes, config={'tb_dict': self.__dados_pipe})
#atualiza Doc com tags do timebankpt (EVENT e TIMEX3)
self.__set_doc()
else:
print('pipe_timebankpt já está adicionado.')
self.print_pipes()
def remove_pipe_timebank(self):
'''
Remove o pipe_timabankpt. Retira as tag dos timabankpt (EVENT e TIMEX3)
'''
if self.nlp.has_pipe("pipe_timebankpt"):
self.nlp.remove_pipe('pipe_timebankpt')
#atualiza Doc retirando as tags do timebankpt (EVENT e TIMEX3)
self.__set_doc()
else:
print('pipe_timebankpt ainda não foi adicionado.')
self.print_pipes()
def save_data_pipe_tb(self, nome_arquivo: str):
'''
Salva dados do corpus carregado em arquivo físico.
'''
nome_arquivo = self.check_filename(nome_arquivo, 'pickle')
try:
if not self.__dados_pipe:
self.__dados_pipe = self.df.dados_pipe
with open(nome_arquivo, 'wb') as arquivo:
pickle.dump(self.__dados_pipe, arquivo)
except Exception as e:
print(f'Erro ao salvar dados do pipe no arquivo {nome_arquivo}. \nERRO: {e}')
else:
print(f'Dados do pipe salvo com sucesso no arquivo {nome_arquivo}.')
def load_data_pipe_tb(self, nome_arquivo: str) -> dict:
'''
Retorna objeto que representa os dados do pipe salvo pelo método 'save_data_pipe_tb(nome_arquivo)'.
Os objetos retornados são: event, timex3, tlink, sentenca, documento
'''
nome_arquivo = self.check_filename(nome_arquivo, 'pickle', check_if_exist=True)
with open(nome_arquivo, 'rb') as arquivo:
return pickle.load(arquivo)
#---------------------------------------
# DESCRITORES
def set_id_sentenca(self, *id_sentenca):
'''
Atribui as id_sentenca para as instâncias da classe e atribui valores a campos que dependem de id_sentenca
Args:
id_sentenca: Lista de id_sentença. O id_senteca não conta nos arquivos TimeML, foram criados na função timeml_to_df para facilitar o acesso.
id_sentenca pode ser vários inteiros, várias strings, lista de ambos ou strings separadas por virgulas.
'''
lista_id_sentenca = []
lista_nome_doc = []
lista_dct_doc = []
lista_sentenca_texto = []
id_sentencas = self.trata_lista(*id_sentenca)
if id_sentencas:
for id_sent in id_sentencas:
#Considera apenas id_sentenca existentes
if self.__is_id_sentenca(id_sent):
lista_id_sentenca.append(id_sent)
nome_doc = self.get_nome_doc(id_sent)
lista_nome_doc.append(nome_doc)
lista_dct_doc.append(self.get_dct_doc(nome_doc))
lista_sentenca_texto.extend(self.get_sentenca_texto(id_sent))
self.__id_sentenca = lista_id_sentenca
self.__nome_doc = lista_nome_doc
self.__dct_doc = lista_dct_doc
self.__sentenca_texto = lista_sentenca_texto
self.__set_doc()
self.df.atualizar_filtros()
def set_sentenca_texto(self, sentenca_texto):
'''
Permite atribuir sentenças que não estão no TimebankPT para submetê-las ao pipeline do spaCy.
Caso a sentenca passada exista no TimebankPT, atribui a id_sentenca à classe com set_id_sentenca()
Args:
sentenca_texto: Lista de sentenças ou sentença única.
'''
self.id_sentenca = []
self.__nome_doc = []
self.__dct_doc = []
if type(sentenca_texto) == int:
sentenca_texto = str(sentenca_texto)
#se sentenca_texto for str, converte em lista de um elemento
#if type(sentenca_texto) == str:
# tmp = []
# tmp.append(sentenca_texto)
# sentenca_texto = tmp
#Verifica se a sentença existe
achou_id_sentenca = self.get_id_sentenca(sentenca_texto)
if achou_id_sentenca: #se sim, passa ela para a instância da classe
self.id_sentenca = achou_id_sentenca
else: # se não, o pipe processa a sentença passada e atualiza o objeto Doc
self.__sentenca_texto = sentenca_texto
self.__set_doc()
self.df.atualizar_filtros()
def __set_doc(self):
'''
Atribui objeto Doc do spaCy baseado na id_sentenca passada para a instancia da classe
Return:
list de Doc
'''
if len(self.id_sentenca) == 0:
self.__doc = []
sentenca_texto = self.sentenca_texto #self.sentenca_texto é atribuido em set_id_sentenca() ou em set_sentenca_texto()
if type(sentenca_texto) == list:
self.__doc = list(self.nlp.pipe(sentenca_texto))
elif type(sentenca_texto) == str:
doc = self.nlp(sentenca_texto)
lista = []
lista.append(doc)
doc = lista
self.__doc = doc
else:
print('ERRO: sentenca_texto deve ser do tipo list ou str')
# FIM DESCRITORES
#---------------------------------------
def get_id_sentenca(self, texto_sentenca = None):
'''
Retorna as id_sentenca setadas em set_id_sentenca() se texto_sentenca não for informado
Se texto_sentenca for informado:
Busca a id_sentenca correspondente à texto_sentenca no TimeBankPt, e a retorna.
Args:
texto_sentenca: Texto da sentença a ser procurada em TimeBankPt
Return:
list id_sentenca
'''
if texto_sentenca:
id_sentenca_list = []
if (type(texto_sentenca) == list):
texto_sentenca = texto_sentenca[0]
if (type(texto_sentenca) == str):
#escapa as aspas duplas do texto, uma vez que a string texto_sentenca está entre aspas duplas
texto_sentenca = texto_sentenca.replace('"','\\"')
df = self.df.sentenca_completo.query('sentenca == "' + texto_sentenca + '"')['isentenca']
if not df.empty:
id_sentenca_list.append(df.tolist()[0])
return id_sentenca_list
else:
return self.__id_sentenca
def get_id_sentenca_unica(self):
'''
Retorna lista com a primeira id_sentenca da lista de sentenças setadas em set_id_sentenca()
'''
id_sentenca = self.id_sentenca
if len(id_sentenca) == 0:
return []
id_sentenca_list = []
id_sentenca_list.append(id_sentenca[0])
id_sentenca = id_sentenca_list
return id_sentenca
def get_eventID(self, id_sentenca = None):
'''
Retorna lista de eventID de id_sentenca
'''
if id_sentenca is None:
id_sentenca = self.id_sentenca
else:
id_sentenca = self.trata_lista(id_sentenca)
return self.df.event_completo[['eid', 'isentenca']].query("isentenca == " + str(id_sentenca))['eid'].values.tolist()
def get_nome_documento(self, id_sentenca = None):
'''
Retorna nome do documento de 'id_sentenca'.
Args:
id_sentenca: pode ser list ou int
se não for informado, retorna o nome do documento de id_sentenca da classe.
se lista, retorno o documento do primeiro id_sentenca
'''
if id_sentenca is None:
id_sentenca = self.id_sentenca
else:
id_sentenca = self.trata_lista(id_sentenca)
return self.df.sentenca_completo.query("isentenca == " + str(id_sentenca[0]))['doc'].values[0]
def get_id_sentenca_do_doc(self, id: str, nome_documento: str) -> int:
'''
Retorna id_sentenca do doc e eid/tid.
Args:
id: pode ser o id do evento ou do timex3
nome_documento: nome do arquivo que representa um documento do corpus
'''
df_event = self.df.event_completo[['eid', 'doc', 'isentenca']].rename(columns={'eid':'id'}).query("doc == '" + nome_documento + "'")
df_timex3 = self.df.timex3_completo[['tid', 'doc', 'isentenca']].rename(columns={'tid':'id'}).query("doc == '" + nome_documento + "'")
df_timex3 = df_timex3[~df_timex3['isentenca'].isna()]
df_event_timex3 = pd.concat([df_event, df_timex3])
df_event_timex3 = df_event_timex3.query("id == '" + id + "'")
if df_event_timex3.empty:
return ''
return int(df_event_timex3['isentenca'].values.tolist()[0])
def get_id_sentencas_doc(self):
'''
Retorna lista de todas id_sentenca do primeiro documento da lista de documentos atual.
O nome do documento é o nome do arquivo do TimeBankPT.
'''
#if self.df.sentenca_doc.empty:
# print('ERROR: É necessário atribuir id_sentenca à instancia da class TimebankPT.')
# return
return self.df.sentenca_doc['isentenca'].tolist()
def get_id_sentencas_dep(self):
'''
Retorna também lista de id_sentenca dependentes da sentenca do documento atual.
'''
if not self.id_sentenca:
print('ERROR: É necessário atribuir id_sentenca à instancia da class TimebankPT.')
return
recursivo_atual = self.df.recursivo
if recursivo_atual == False:
self.df.recursivo = True
df = self.df.sentenca['isentenca'].tolist()
if self.df.recursivo != recursivo_atual:
self.df.recursivo = recursivo_atual
return df
def query_filtro_task(self, task: str):
'''
Retorna query que filtra sentenças conforme task
'''
if not task:
print('Task ainda não foi informada.')
return
task = task.upper()
if task == 'A':
q = "and isentenca == isentenca_rt"
elif task == 'B':
q = ''
elif task == 'C':
q = "and isentenca != isentenca_re"
else:
print("ERROR: Task '{0}' inválida.".format(task))
return ''
return "task == '" + task + "' " + q
def id_sentencas_task(self, task: str):
'''
Retorna id_sentenca contempladas pela tarefa 'task'
Args:
task: filtrar conforme task
filter
'''
if not task:
print('Task ainda não foi informada.')
return
query = self.query_filtro_task(task)
return self.df.tlink_join_completo.query(query)['isentenca'].unique().tolist()
def __get_sentenca_texto_helper(self, id_sentenca = None, todas_do_documento = False, com_tags = False):
'''
Retorna lista contendo texto da sentença, conforme id_sentenca setadas em set_id_sentenca().
Args:
id_sentenca: sobrepõe id_sentenca atribuída em set_id_sentenca()
todas_do_documento: Se True, retorna o texto de todas as sentenças do documento de get_id_sentenca(). Se False, retorna apenas o texto da sentença de get_id_sentenca()
Se True e id_sentenca for uma lista com sentenças pertencentes a mais de um documento, considera apenas as sentenças do documento do primeiro item da lista id_sentenca
com_tags: Se True, retorna o texto da sentença com as tags TimeML. Se False, retorna texto puro.
Return:
lista de sentenças
'''
lista_sentenca = []
#DOCUMENTO
if todas_do_documento:
id_sentenca = self.id_sentencas_doc
else:
if id_sentenca is None:
id_sentenca = self.id_sentenca
else:
id_sentenca = self.trata_lista(id_sentenca)
if com_tags:
campo_retorno = 'sentenca_tag'
else:
campo_retorno = 'sentenca'
if (id_sentenca is None) or len(id_sentenca) == 0:
return []
df = self.df.sentenca_completo.query("isentenca in " + str(id_sentenca))[campo_retorno]
if not df.empty:
lista_sentenca = df.tolist()
return lista_sentenca
def get_sentenca_texto(self, id_sentenca = None):
'''
Retorna lista contendo texto da sentença com as tag TimeML, conforme id_sentenca setadas em set_id_sentenca() ou informado no parâmetro id_sentenca
'''
if id_sentenca is None:
return self.__sentenca_texto
else:
return self.__get_sentenca_texto_helper(id_sentenca, todas_do_documento = False, com_tags = False)
def get_sentenca_texto_tag(self, id_sentenca = None):
'''
Retorna lista contendo texto da sentença com as tag TimeML, conforme id_sentenca setadas em set_id_sentenca().
'''
return self.__get_sentenca_texto_helper(id_sentenca, todas_do_documento = False, com_tags = True)
def get_sentenca_texto_doc(self):
'''
Retorna lista de texto de todas as sentenças do documento da primeira sentença de get_id_sentenca().
'''
return self.__get_sentenca_texto_helper(todas_do_documento = True, com_tags = False)
def pesquisa_sentenca_texto(self, lista_termos = '', formato_dataframe = False):
'''
Retorna DataFrame com resultado pesquisa dos termos
Args:
lista_termos: lista de palavras a ser pesquisada em sentenças
formato_dataframe: se True, retorna o dataframe filtrado por lista_termos, se não, retorna lista de sentenças que atendem ao critério de pesquisa
'''
df = self.df.sentenca_completo
lista_termos = self.trata_lista(lista_termos, tipo_lista = str)
df_filtrado = df[df['sentenca'].str.contains('|'.join(map(re.escape, lista_termos)))]
if formato_dataframe:
return df_filtrado
else:
return df_filtrado['sentenca'].tolist()
def pesquisa_id_sentenca(self, lista_termos, formato_dataframe = False):
'''
Retorna DataFrame com resultado pesquisa dos termos
Args:
lista_termos: lista de palavras a ser pesquisada em sentenças
formato_dataframe: se True, retorna o dataframe filtrado por lista_termos, se não, retorna lista de id_sentenca que atendem ao critério de pesquisa
'''
df = self.pesquisa_sentenca_texto(lista_termos, formato_dataframe = True)
if formato_dataframe:
return df
else:
return df['isentenca'].tolist()
def get_doc(self, id_sentenca = None):
'''
Retorna lista de objetos Doc do spaCy