Open lucasrct opened 4 years ago
Após um bom tempo, o script rodou e foram gerados os arquivos:
entidades_correto.log (com todas as entidades que, pela nossa premissa, foram classificadas de forma correta)
entidades_errado.log (entidades que nós consideramos como tendo classificação incorreta)
ne_stats.log (as estatísticas)
error.log (alguns arquivos que deram problema para extrair o json)
Tipo: PERSON, Total: 161727, Análises corretas: 147059, Análises erradas: 14668
Tipo: ORGANIZATION, Total: 193680, Análises corretas: 186716, Análises erradas: 6964
Tipo: LOCAL, Total: 53967, Análises corretas: 52983, Análises erradas: 984
Tipo: TIME, Total: 147294, Análises corretas: 131250, Análises erradas: 16044
Tipo: LAW, Total: 6114, Análises corretas: 5538, Análises erradas: 576
Tipo: EVENT, Total: 4159, Análises corretas: 4058, Análises erradas: 101
Tipo: POL, Total: 2693, Análises corretas: 2643, Análises erradas: 50
Tipo: CURRENCY, Total: 286, Análises corretas: 286, Análises erradas: 0
Tipo: DOC, Total: 16914, Análises corretas: 16530, Análises erradas: 384
Tipo: AGE, Total: 837, Análises corretas: 837, Análises erradas: 0
Tipo: TAX, Total: 1231, Análises corretas: 1159, Análises erradas: 72
Tipo: AWARD, Total: 12, Análises corretas: 12, Análises erradas: 0
Os tipos Organization, Time e Person são os que mais possuem análises erradas.
Gostei destas descobertas, bastante promissor. Considerando os números, vejo dois pontos de vista para pesquisarmos o que pode existir na literatura:
correção das árvores a partir das anotações. A desvantagem é que muito provavelmente, depois das analises prontas, mesmo que possamos fazer alguma correção local de uma subtree, quanto da análise como um todo seria diferente caso o sistema tivesse entendido a NE?
abordagens que informem ao parser sobre NE, evitando que um constituinte seja erroneamente quebrado. Algo como HPSG faz para restringir possiveis análises. Por exemplo,
We ⌊(⌋ saw a man ⌊)⌋ with a telescope.
bloqueia a analise de with a telescope
modificando man
.
Referência antiga. Mesmo objetivo, melhorar analise sintatica. Mas não foca em NP, nossos exemplos são NP. Restrito para bigrams, não claro como generalizar.
https://www.aclweb.org/anthology/I05-1050.pdf
Vide também citação 4
Eu concordo que corrigir as análises prontas não seria a melhor solução. Eu acho essa ideia de informar ao parser sobre NE uma ideia interessante, eu achei esse artigo:
https://nlp.stanford.edu/jrfinkel/papers/joint-parse-ner.pdf
Que trata exatamente esse problema e inclusive é uma ideia afim à que estamos lidando aqui, de tentar preservar as NE em subárvores.
Uma ideia também é adicionar uma camada a mais de informação na escolha da "melhor árvore sintática" por algum algoritmo, exigindo que a análise deva ter a NE presente na frase como uma subárvore, acho que essa é uma ideia que se aplica a uma gramática facilmente, pois ela retorna muitas árvores possíveis.
Pelo que lembro do método do Udpipe, lembro de alguma informação que dizia que era possível obter não uma única árvore, mas sim as N árvores mais prováveis.
Diante da crise instalada na área econômica, Vargas reformulou seu ministério, substituindo no dia 24 de agosto de 1953 o ministro da Fazenda por Osvaldo Aranha e o presidente do Banco do Brasil por Marcos de Sousa Dantas.
Usando o parser em http://lindat.mff.cuni.cz/services/udpipe/ a analise não é tão errada como acima.
Em troca do apoio político e eventualmente militar do Brasil aos EUA, o ministro das Relações Exteriores João Neves da Fontoura pleiteou junto ao governo norte-americano, além da construção de fábricas de material bélico no Brasil, a obtenção de créditos bancários a médio e longo prazo para um programa de industrialização e obras públicas.
O UDPipe online erra, mas se trocarmos [ministro das Relações Exteriores] por [ministro] e [João Neves da Fontoura] por [João] resolvemos o parser nos daria uma boa árvore que depois poderia ser recompletada facilmente.
O que estou sugerindo aqui é uma variação da abordagem (2) acima onde poderíamos pré-processar as sentenças antes de passar para o parser. Se tivermos bons resultados de NER.
Pelo que lembro do método do Udpipe, lembro de alguma informação que dizia que era possível obter não uma única árvore, mas sim as N árvores mais prováveis.
Mas https://github.com/ufal/udpipe/issues/122 disse que não. O paper acima é promissor. Eu acho que explorar tentativas de pos-procesar arvores ou pre-processar textos e/ou combinar as duas coisas. considerando anotações de entidades... pode ser um caminho de trabalho antes de próximas etapas.
Pelo que lembro do método do Udpipe, lembro de alguma informação que dizia que era possível obter não uma única árvore, mas sim as N árvores mais prováveis.
Mas ufal/udpipe#122 disse que não.
Mas ele indica exatamente onde no código é feito o descarte das árvores menos prováveis. Ele gera 5 por padrão.
O paper acima é promissor. Eu acho que explorar tentativas de pos-procesar arvores ou pre-processar textos e/ou combinar as duas coisas. considerando anotações de entidades... pode ser um caminho de trabalho antes de próximas etapas.
Eu acho que pós-processar pode adicionar mais ruído no output. Quanto ao pré-processamento, uma ideia de fato é reduzir as entidades a talvez um único token, para os casos cabíveis claro (não vejo uma maneira simples de fazer isso com data) e após a análise sintática com o parser, expandirmos de novo a árvore com os nomes originais, vejo algumas questões que precisam ser olhadas de perto:
Fazer a análise sintática das entidades de forma correta, não acho que seja uma task que com algumas regras a gente consiga englobar todos os casos. Uma ideia é analisar somente a entidade pelo udpipe. Como não existirão outras palavras para "atrapalhar" o parser, talvez ele acerte mais.
Dado que temos as análises sintáticas das entidades, temos que elaborar um algoritmo para recolocá-las nas árvores onde aparecem.
Obs: Usei o nosso modelo mais recente do udpipe para parsear o nome "João Neves da Fontoura":
# text = João Neves da Fontoura
1 João João PROPN _ Gender=Masc|Number=Sing 0 root __
2 Neves Neves PROPN _ Number=Sing 1 flat:name __
3-4 da _ _ _ _ _ _ _ _
3 de de ADP _ _ 5 case _ _
4 a o DET _ Definite=Def|Gender=Fem|Number=Sing|PronType=Art 5 det _ _
5 Fontoura Fontouro NOUN _ Gender=Fem|Number=Sing 1 nmod _ SpacesAfter=\n
Uma análise quase correta. O último token está com a POS errada, lemma errado e aponta pro João como nmod (essa útlima não é necessariamente um erro né?). Mas "topologicamente" está o que gostaríamos, diferente da primeira árvore desse issue.
Cruzando os dados do MorphoBr e do output do udpipe, gerei um arquivo com ocorrências do lema do HEAD da frase não estar no morphobr. (scrpit: analysis.py)
O arquivo se encontra em src/head_not_in_morpho.dhbb (como nem todos os heads são verbos, eu os filtrei e coloquei em head_not_in_morpho.verb)
O parser muitas vezes erra por colocar o lema em letra maiúscula, mas descobri algo interessante, que consta nos arquivos: Ele sempre faz isso quando o token é o primeiro token da frase.
Quanto aos verbos, para analisar possíveis erros de análise sintática, vou remover os casos de HEAD verbo onde a divergência foi por capitalização ou quando o token se encontra no morphobr (ou seja, o parser só errou na lematização). Assim temos um pouco mais de confiaça que a análise sintática da árvore estará errada, por exemplo, no arquivo 113.conllu, frase no. 14, ele considera vitoriosa como um verbo. De qualquer maneira, ainda é algo complicado como estimar erros nas análises sintáticas, visto que ainda não é claro para mim a conexão entre POS tagging/lematização errada e erro na análise sintática.
@arademaker Separei um subset de frases que tinham um token root erroneamente taggeado como VERB
Notei um padrão, quase a totalidade das frases que peguei para corrigir, eram frases que de fato não tinham um verbo como root. O único verbo da frase na verdade era um verbo auxiliar. E isso confundiu o parser. Mas em geral, a topologia da árvore não mudou. O root continuava sendo a palavra que foi dada como verbo. Um exemplo
# text = Seu pai foi prefeito de Tefé (AM).
# sent_id = 3
1 Seu seu DET _ Gender=Masc|Number=Sing|PronType=Prs 2 det _ _
2 pai pai NOUN _ Gender=Masc|Number=Sing 4 nsubj:pass _ _
3 foi ser AUX _ Mood=Ind|Number=Sing|Person=3|Tense=Past|VerbForm=Fin 4 aux:pass _ _
4 prefeito prefeger VERB _ Gender=Masc|Number=Sing|VerbForm=Part|Voice=Pass 0 root _ _
5 de de ADP _ _ 6 case _ _
6 Tefé Tefé PROPN _ Gender=Masc|Number=Sing 4 obj _ _
7 ( ( PUNCT _ _ 8 punct _ SpaceAfter=No
8 AM AM PROPN _ Gender=Fem|Number=Sing 6 parataxis _ SpaceAfter=No
9 ) ) PUNCT _ _ 8 punct _ SpaceAfter=No
10 . . PUNCT _ _ 4 punct _ SpacesAfter=\n
Prefeito na verdade é um nome, deveria ter sido taggeado como NOUN, mas note que este continua sendo o root da frase. Outro exemplo:
# text = Nas duas eleições, não foi bem-sucedido.
# sent_id = 32
1-2 Nas _ _ _ _ _ _ _ _
1 Em em ADP _ _ 4 case _ _
2 as o DET _ Definite=Def|Gender=Fem|Number=Plur|PronType=Art 4 det _ _
3 duas dois NUM _ NumType=Card 4 nummod _ _
4 eleições eleição NOUN _ Gender=Fem|Number=Plur 8 obl _ SpaceAfter=No
5 , , PUNCT _ _ 4 punct _ _
6 não não ADV _ Polarity=Neg 8 advmod _ _
7 foi ser AUX _ Mood=Ind|Number=Sing|Person=3|Tense=Past|VerbForm=Fin 8 aux:pass _ _
8 bem-sucedido bem-sucedir VERB _ Gender=Masc|Number=Sing|VerbForm=Part|Voice=Pass 0 root _ SpaceAfter=No
9 . . PUNCT _ _ 8 punct _ SpacesAfter=\n
Todos os exemplos de frases que corrigi tinham essa mesma característica. O processo então foi:
Criei um arquivo gold.conllu
com as frases corrigidas (tanto o pos tagging quanto a análise de dependências)
Criei outro arquivo wrong.conllu
com as frases do jeito inicial
Um terceiro "parsed.conllu" com as frases parseadas pelo udp, somente com o pos corrigido.
Os resultados do LAS e UAS score então foram...
Comparação entre o gold e o parsed:
UAS (razão das palavras com o mesmo head): 0.921
LAS (razão das palavras com o mesmo head e dprel): 0.836
Comparação entre o gold e o wrong:
UAS: 0.918
LAS :0.788
Ou seja, de fato melhorou os resultados. O interessante é que UAS mudou muito pouco, isso se da ao fato de os roots não terem mudado, e isso fez com que as outras relações não mudassem seus heads, mas mudou bastante o deprel.
Eu agora peguei frases sem verbos AUX para ver se isso faz com que a topologia da árvore mude mais quando a gente corrige os erros no pos tagging.
Só um adendo, nessa busca por frases com head verbo errado e sem aux, acabei encontrando algumas sentenças segmentadas erroneamente e outras que parecem ter sido segmentadas erroneamente (não conferi todas), são elas:
# text = Valepar.
# sent_id = 349
# file = 5789.conllu
# text = «FATOS
# sent_id = 1
# newpar
# file = 5868.conllu
# text = Shafer opina:
# sent_id = 211
# file = 6045.conllu
# text = (Termochar).
# sent_id = 76
# file = 5846.conllu
# text = «Belíndia 2.0:
# sent_id = 87
# file = 443.conllu
# text = (BNDESPar).
# sent_id = 173
# file = 5747.conllu
# text = Bradespar, 21,21%;
# sent_id = 420
# file = 5789.conllu
# text = Lei nº 2.004).
# sent_id = 9
# file = 6293.conllu
# text = Almeida para sua campanha eleitoral.
# sent_id = 36
# file = 3296.conllu
# text = Ademar contra a legalidade”.
# sent_id = 79
# file = 5752.conllu
# text = Oscar Weinschenck (um voto);
# sent_id = 205
# file = 5740.conllu
**# text = Dutra para uma intervenção militar contra Getúlio.
# sent_id = 2431
# file = 5458.conllu**
# text = Sá (vice-presidente), sem concorrentes.
# sent_id = 75
# file = 1090.conllu
**# text = Dutra para o Ministério da Guerra.
# sent_id = 72
# file = 2362.conllu**
**# text = Dutra a continuação do Estado Novo.
# sent_id = 2757
# file = 5458.conllu**
# text = Sallinger, e de cinco romances de Vladimir Nabokov.
# sent_id = 44
# file = 1674.conllu
**# text = Dutra para a formação do corpo expedicionário.
# sent_id = 2189
# file = 5458.conllu**
**# text = Dutra e tornasse possível a convocação de uma assembléia nacional constituinte.
# sent_id = 643
# file = 1205.conllu**
**# text = Dutra o apoio de Minas Gerais à “revolução”.
# sent_id = 1455
# file = 5458.conllu**
# text = Indústrias Alimentícias, empresa nordestina pioneira na industrialização de carnes.
# sent_id = 6
# file = 3333.conllu
**# text = Dutra a presidência da República, retornando ao STF.
# sent_id = 99
# file = 2922.conllu**
# text = Aldovrando de Vasconcelos, Luís Dias Alvarenga, Nélson Maia e Roberto Furquim Werneck.
# sent_id = 57
# file = 8875.conllu
# text = Ademar, na impossibilidade de indicar um candidato autenticamente pessepista, resolveu “cristianizar”
# sent_id = 109
# file = 6246.conllu
Pode ser do seu interesse @igorcj
Notem que frases com Dutra aparecem várias vezes nessa lista, eu vi pelo menos 2 casos de segmentação sem a presença de um EOS character, o que é estranho dado como o opennlp funciona... esses casos estão entre asteriscos
Fiz as correções das frases com tokens eroneamente colocados como VERB e os resultados foram muito bons, as correções foram bem mais difíceis, então acho que seria legal vocẽ @arademaker dar uma conferida. O gold encontra-se em src/sintatic_tree_experiment/gold_noaux.conllu.
UAS entre GOLD e o WRONG (output normal do udp com as POS erradas): 0.697 LAS entre GOLD e WRONG: 0.639
UAS entre GOLD e o PARSED (output do udpipe com as POS corrigidas): 0.977 LAS entre GOLD e o PARSED: 0.961
Como disse, seria bom vc pegar aleatoriamente umas 4-5 frases do gold e dar uma olhada, pois achei bem difícil corrigi-las. Acredito que em termos de UAS (somente a topologia do grafo), esteja bem acurado, agora junto com as deprels, acho que posso ter cometido alguns erros.
Mas de qualquer maneira, isso já justifica uma abordagem de pós processamento das POS tags com o MorphoBR, não?
@arademaker Mais atualizações.
Fiz o script para corrigir os POS tagging a partir do morpho e fiz o seguinte experimento:
Peguei o conjunto de teste do bosque e parseei ele pelo UDP com o nosso modelo mais recente. (fiz o processo de TAG e PARSE)
Rodei o script de correção nesse output e ele conseguiu corigir 10 frases que tinham verbo errado. Então eu peguei as árvores com o POS corrigido e passei o udpipe de novo, apenas parseando. Daí obtive os conjuntos:
wrong - o output do udpipe sem alteração treated - output do udpipe com o pos corrigido e re-parseado (agora somente analise de dependencia) gold - test set do bosque
Calculando as métricas eu obtive:
UAS wrong e gold: 0.75 LAS wrong e gold: 0.68
UAS treated e gold: 0.81 LAS treated e gold: 0.75
Foi uma melhora expressiva.
Este issue precisa ser organizado, possivelmente desmembrado em outros. Começamos falando de NE agora estamos falando de POS tagging.
Acho que não podemos continuar criando novos arquivos, precisamos evoluir na anotação de camadas nos arquivos conllu. Provavelmente usando extensões do formato conllu. Já falamos disso.
No mais, bom progresso, estamos no caminho certo e precisamos começar a escrever sobre o que foi feito, onde queremos chegar e quais nossa hipóteses.
As sentenças revisadas estão no diretório udp marcadas como tal?
Vou abrir outro Issue então, até pois vou começar a trabalhar nas NER de novo. Vou tentar fazer aquele procedimento de remover os nomes, deixando apenas 1 (isso pode ser feito direto na frase eu acho, antes da criação do conllu), reparsear e re-inserir os nomes de novo. Existirão alguns problemas que eu estava mapeando já, que teremos que lidar, como por exemplo, outros filhos ligados ao 'head' do nome. Na hora de recolocar, pode dar problema.
Quanto às sentenças revisadas, eu ainda não coloquei no udp, estou pensando como fazer, pois temos sentenças revisadas em momentos e com intenções diferentes, acho que vale a pena a gente explicitar isso, até para facilitar as correções. Por exemplo, se você quiser verificar a correção dessas novas sentenças, como saberia quais foram tais sentenças corrigidas? Dado que temos outras também. Estava pensando em criar "batches" e daí documentar no README a origem de cada "batch" de sentenças gold.
Nesse FDS fiz o mesmo esquema de correção com o corpus GSD, mas os resultados não foram tão bons. A correção pelo morphoBR melhorou em 3% o LAS e 1% o UAS.
Vou começar também a escrever sobre o que foi feito até agora.
Fiz um script (src/compare_ne.py) que lê as entidades extraídas dos textos do dhbb que se encontram nos arquivos json do diretório dhbb-json e analisa sua ocorrência na análise sintática gerada pelo udp. A premissa é que uma NE deve gerar uma subárvore. Os casos em que a entidade reconhecida não gerou uma subárvore estão sendo contabilizados, bem como identificados para análise.
O script vai demorar um pouco a rodar, mas uma prévia do log: arquivos onde a análise da NE não gerou uma subárvore:
Note que existem algumas entidades estranhas como "novembro do mesmo ano", mas que ainda assim deveriam formar uma subárvore.
Analisando:
token="João Neves da Fontoura" file=2694.conllu sent_id=65 tipo=PERSON status=OK
Vemos que João Neves não forma uma subárvore, eles estão ligados a Relações.
Já o exemplo
token="24 de agosto de 1953" file=2694.conllu sent_id=96 tipo=TIME status=OK
Temos
A análise da data está completamente errada.
Será que conseguimos encontrar um padrão em alguns erros? Isso ajudaria a encontrar exemplos que podem causar muito impacto nas análises sintáticas ao corrigirmos um subset.