Skip to content

Essa é uma proposta de pesquisa textual implementada com recursos em python puro com o uso de dicionário de sinônimos e distância entre termos pesquisados. Inclui um serviço teste para rotular textos baseando-se nas regras e/ou em expressões regulares.

License

Notifications You must be signed in to change notification settings

luizanisio/PesquisaTextualBR

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pesquisa textual em documentos

Essa é uma proposta de pesquisa textual implementada em python puro com o uso de dicionário de sinônimos e distância entre termos pesquisados.

  • É uma pesquisa que tenta ir além do que pesquisas comuns fazem, pois não tem o objetivo de trazer grandes volumes de resultados, mas resultados precisos.
  • Implementada em python para uso em pesquisa textual avançada com foco no Português, permitindo busca em campos textuais e critérios de proximidade textual.
  • O objetivo é refinar pesquisas textuais com frameworks comuns de mercado (MemSQL/SingleStore, ElasticSearch) em volume muito grande de dados, ou pode ser usada para pesquisa completa em textos carregados de arquivos ou em memória. Pode-se também realizar uma análise em tempo real avaliando se determinados critérios são atendidos dentro do texto (regras de texto para mudança de fluxo de trabalho).
  • Essa ideia não é nova, conheci ao longo dos últimos 20 anos vários sistemas que faziam algo parecido. Não há pretensão em competir com qualquer um desses produtos, mas ter algo simples e operacional para quem tiver interesse em personalizar uma busca textual da forma que precisar.
  • Uma aplicação muito útil dos critérios de pesquisa, alé de encontrar textos, é identificar rótulos que são aplicáveis a um texto ao testar um conjunto de regras pré-definidas com seus rótulos correspondentes, simulando um classificador multilabel só que no lugar do modelo, tem-se um conjunto de regras textuais. Daí pode-se identificar fluxos automáticos para sistemas, definir alertas, etc.
  • O uso do componente é exemplificado no serviço de regras, clique para baixar e testar.

Estão disponíveis nesse repositório:

  • Classe python PesquisaBR que recebe um documento e um critério de pesquisa e retorna o resultado da avaliação.

  • Classe python RegrasPesquisaBR que recebe um conjunto de regras e seus rótulos e aplica as regras em um documento, identificando que rótulos são aplicáveis a ele. Simula um modelo multilabel mas com regras no lugar de um modelo treinado.

  • Serviço avaliador de regras: Um exemplo simples de serviço que simula um classificador multilabel que funciona por regras no lugar de um modelo treinado.

  • Testes da classe código que permitem validar todos os critérios e funcionalidades implementadas

  • Conversor de pesquisas método que converte critérios avançados para critérios simples AND OR NOT aceitos pelo MemSQL

  • Classe experimental PesquisaBRMemSQL : classe que permite combinar a análise de pesquisa da classe PesquisaBR com o poder de pesquisa textual nativo do MemSQL. Agora o MemSQL chama-se SingleStore. Veja também a classe PesquisaElasticFacil que converte os critérios avançados de proximidade de termos em pesquisa nativa do ElasticSearch.

  • Manual com os operadores de pesquisa

Uso simples da classe:

pb = PesquisaBR(texto = 'A casa de papel é um seriado muito legal', criterios='casa adj2 papel adj5 seriado')
print('Retorno: ', pb.retorno())

print(pb.print_resumo())

Console

Retorno:  True
RESUMO DA PESQUISA: retorno = True
 - texto: a casa de papel e um seriado muito legal
 - tokens: ['a', 'casa', 'de', 'papel', 'e', 'um', 'seriado', 'muito', 'legal']
 - tokens_unicos: {'papel', 'a', 'um', 'muito', 'de', 'e', 'seriado', 'legal', 'casa'}
 - criterios: ['casa', 'adj2', 'papel', 'adj5', 'seriado']
 - mapa: {'a': {'t': [0], 'p': [0], 'c': ['']}, 'casa': {'t': [1], 'p': [0], 'c': ['']}, 'de': {'t': [2], 'p': [0], 'c': ['']}, 'papel': {'t': [3], 'p': [0], 'c': ['']}, 'e': {'t': [4], 'p': [0], 'c': ['']}, 'um': {'t': [5], 'p': [0], 'c': ['']}, 'seriado': {'t': [6], 'p': [0], 'c': ['']}, 'muito': {'t': [7], 'p': [0], 'c': ['']}, 'legal': {'t': [8], 'p': [0], 'c': ['']}}

Uso simples da classe de regras:

regras = [{'grupo' : 'receitas_bolo', 'rotulo': 'Receita de Bolo', 'regra': 'receita ADJ10 bolo'},
          {'grupo' : 'receitas_bolo', 'rotulo': 'Receita de Bolo', 'regra': 'aprenda ADJ5 fazer ADJ10 bolo'},
          {'grupo' : 'receitas_pao', 'rotulo': 'Receita de Pão', 'regra': 'receita PROX15 pao'},
          {'grupo' : 'grupo teste', 'rotulo': 'teste', 'regra': 'teste'}]
# receita de bolo
texto = 'nessa receita você vai aprender a fazer bolos incríveis'
pbr = RegrasPesquisaBR(regras = regras, print_debug=False)
rotulos = pbr.aplicar_regras(texto = texto)
print(f'Rótulos encontrados para o texto: "{texto}" >> ', rotulos)

Console

Rótulos encontrados para o texto: "nessa receita você vai aprender a fazer bolos incríveis" >>  ['Receita de Bolo']

Extraindo dados

dados = RegrasPesquisaBR.aplicar_criterios(texto = "esse teste é simples 123,45 123.123 simples",
                                           detalhar =1, extrair = 1, grifar = 1, 
                                           criterios = "r:(esse)|(teste)|(simples)")
print(dados)

Console

{ "criterios":"(esse)|(teste)|(simples)",
  "criterios_analise":"r:(esse)|(teste)|(simples)",
  "extracao":[{"fim":4,"inicio":0,"texto":"esse"},{"fim":10,"inicio":5,"texto":"teste"},
              {"fim":20,"inicio":13,"texto":"simples"},{"fim":43,"inicio":36,"texto":"simples"}],   
  "retorno": True,
  "texto": "esse teste e simples 123,45 123.123 simples",
  "texto_grifado": "<mark>esse</mark> <mark>teste</mark> e <mark>simples</mark> 123,45 123.123 <mark>simples</mark>"
}

Testes básicos da classe

Estão disponíveis diversos textos e pesquisas que são testados para garantir o funcionamento da classe durante o desenvolvimento.

python pesquisabr_testes.py

Sobre a pesquisa textual avançada

Implementei aqui um conjunto de operadores de pesquisa por proximidade dos termos e outros operadores para refinamento de pesquisa. Esses tipos de operadores tornam-se importantes para refinar pesquisas em grande volume de dados, onde não é importante trazer muito resultado, mas um resultado o mais próximo possível do que é procurado. Ferramentas comuns de busca como ElasticSearch e o próprio MemSQL não trazem nativamente esses tipos de operadores. O ElasticSearch tem o operador slop que trabalha com proximidade de termos, a classe PesquisaElasticFacil permite converter parte dos critérios de pesquisa em pesquisas nativas do elastic.

Esse tipo de pesquisa permite o uso de dicionário de sinônimos em qualquer língua, inclusive o uso de recursos fonéticos. O texto de entrada é pré-processado para evitar não encontrar termos com grafia incorreta de acentos ou termos no singular/plural, bem como números com pontuação ou sem. Por padrão o texto é pesquisado no singular, removendo pronomes oblíquos, mas é possível localizar o termo real usando aspas (exceto acentos que sempre são desconsiderados).

A pesquisa também permite localizar termos pelo dicionário de sinônimos. Ao pesquisar a palavra "genitor", o sistema pesquisa também "pai". A tabela de sinônimos é flexível e facilmente atualizável, permitindo incluir termos em outras línguas se desejado. O uso de sinônimos pode ser ativado ou desativado a cada pesquisa. Ao pesquisar termos entre aspas, o sinônimo é desativado para o termo ou conjunto de termos entre aspas enquanto os outros termos podem ser pesquisados com o uso dos sinônimos.

O pré-processamento envolve:

  • retirada de acentos
  • redução a um pseudosingular ou singular estimado: não é um português perfeito, mas uma singularização para a máquina localizar termos com maior flexibilidade

Conectores ou operadores de pesquisa

Conectores ou operadores de pesquisa são termos especiais utilizados em sistemas de pesquisa para indicar a relação desejada entre os termos pesquisados. Por exemplo, se é desejado encontrar documentos com a palavra casa e a palavra papel, pode-se escrever o critério de pesquisa como casa papel ou pode-se escrever casa E papel. O operador E está subentendido quando nenhum operador é informado. Para usar termos que são iguais aos operadores, é necessário colocar o termo entre aspas. Ex.: amor e ódio deveria ser escrito como amor "e" ódio para garantir que os três termos existem no texto. Ou também "amor e ódio" para que os termos sejam exigidos nessa sequência, um seguido do outro.

  • E: conector padrão, exige a existência do termo no documento
  • NÃO: nega a existência de um termo no documento
  • OU entre termos: indica que um ou outro termo podem ser encontrados para satisfazer a pesquisa ex.: | "fazer" OU "feito" E casa | realiza uma pesquisa que encontre (fazer ou feito literalmente) e também (casa ou termos que no singular sejam escritos como casa)
  • OU com parênteses: permite realizar pesquisas mais complexas. Ex.: | (casa ADJ5 papel) ou (casa ADJ5 moeda) |. Nesse caso a pesquisa poderia ser simplificada como | casa ADJ5 papel ou moeda |
  • ADJn: permite localizar termos que estejam até n termos a frente do primeiro termo. Ex.: | casa ADJ3 papel | vai localizar textos que contenham "casa de papel", "casa papel", "casa feita de papel", mas não localizaria "casa feita de muito papel".
  • ADJCn: equivalente ao ADJ padrão, mas obriga que os dois termos estejam presentes no mesmo parágrafo. Não necessariamente a mesma sentença, mas o mesmo parágrafo considerando a quebra /n no texto
  • PROXn: semelhante ao ADJ, mas localiza termos posteriores ou anteriores ao primeiro termo pesquisado. Ex.: | casa PROX3 papel | vai localizar textos que contenham "casa de papel", "papel na casa", "papel colado na casa", "casa feita de papel", mas não localizaria "casa feita de muito papel" ou "papel desenhado e colado na casa".
  • PROXCn: equivalente ao PROX padrão, mas obriga que os dois termos estejam presentes no mesmo parágrafo. Não necessariamente a mesma sentença, mas o mesmo parágrafo considerando a quebra /n no texto
  • COMn: obriga que os dois termos pesquisados estejam presentes em um mesmo parágrafo, independente da distância e da ordem. Ex.: | casa COM papel | avalia se o texto contém "casa" e papel em um mesmo parágrafo, em qualquer ordem e distância. Opcionalmente pode-se informar o número de parágrafos. COM1 avalia se os termos estão no mesmo parágrafo, COM2 avalia no parágrafo e o seguinte, e assim por diante.
  • MESMO: os documentos podem ser indexados com um tipo único, ou com tipos independentes como, por exemplo: resumo, dados textuais complementares e o texto original. O operador MESMO permite que o documento seja encontrado apenas se os termos pesquisados estiverem em um mesmo tipo do documento. Sem o operador MESMO, o texto será localizado se tiver um termo em um tipo (resumo por exemplo) e outro termo em outro tipo (índice remissivo, por exemplo). O operador MESMO funciona apenas como substituição do operador E, pois os operdores ADJ, ADJC, PROX, PROXC e COM subentendem o uso do operador MESMO por usarem recrusos de distância entre termos. Ex.: | casa MESMO papel | vai localizar textos que contenham "casa" E "papel" no mesmo tipo de documento, caso o termo "casa" esteja no resumo e "papel" esteja no índice, o texto não será localizado.

Curingas

  • $: permite o uso de partes do termo no critério de pesquisa. Por exemplo: cas$ vai encontrar casa, casinha, casamento...
  • ?: permite a existência ou não de um caracter no lugar do símbolo "?". Por exemplo: cas? vai encontrar cas, casa, caso, case... Pode estar no meio do termo tamém: ca?a vai encontrar caa, casa, cata, cala ...

Operador especial remover(....)

Esse operador foi criado para remover trechos do texto antes da análise da regra, para o caso de existirem trechos conhecidos que podem resultar em faso positivo para a regra, como cabeçalhos, citações, dentre outros. Pode-se usar quantos remover(...) forem necessários dentro do critério de pesquisa.

Como usar o operador remover(texto):

  • $ ou * - de 0 a 100 caracteres quaisquer
  • ? - um caractere de letra ou número opcional
  • & - um caractere de letra ou número obrigatório
  • # - um a 10 caracteres que não sejam letra nem número (pontuação, início ou final de texto, espaço, etc)
  • *# - caracteres até um símbolo (pontuação, início ou final de texto, espaço, etc)
  • *## - caracteres até uma quebra de linha
  • % - aspas, parênteses, chaves ou colchetes (citações/explicações em geral)
  • " - aspas normal

Exemplos de uso do remover:

  • remover(aspas): remove todo o conteúdo do texto entre aspas ou parênteses, com o objetivo de remoção de citações
  • remover(termo1 termo2 termo3): remove o trecho do texto termo1 termo2 termo3 conforme está escrito dentro dos parênteses do remover(...)
  • remover(termo&): remove qualquer trecho que contenha termo seguido de um número ou letra obrigatória
  • remover(termo?): remove qualquer trecho que contenha termo podendo ou não estar seguido de um número ou letra
  • remover(contab*#): remove todo o texto iniciado por contab até encontrar o final da palavra
  • remover(conforme exemplos*##): remove todo o texto iniciado por conforme exemplos até encontrar uma quebra de linha

Exemplos de uso dentro dos critérios de pesquisa:

  • `casa adj2 papel remover(termo1) remover(teste)'
  • Ao ser aplicado o critério no texto o seriado casa termo1 de teste papel, a avaliação será verdadeira já que os termos termo1 e teste serão removidos antes da análise.

Operador especial recortar(....)

Esse operador foi criado para recortar o texto (manter o texto) entre dois termos ou conjuntos de termos. Pode-se usar quantos recortar(...) forem necessários dentro do critério de pesquisa. Os trechos recortados serão concatenados com \n

Como usar o operador recortar(texto_inicial;texto_final):

  • $ - início/fim de palavra ou texto
  • # - quebra de linha ou início/fim de texto
  • pode-se usar o ? ao final do delimitador para indicar que ele é opcional.

Exemplos de uso do recortar:

  • recortar(#resumo#;#metodologia#): mantém apenas o texto entre \nresumo\n e \nmetodologia\n
  • recortar(#formulario$;): mantém apenas o texto após \nformulario até o final do texto`
  • recortar(;#resumo#): mantém apenas o texto do início até o texto \nresumo\n
  • recortar(#resumo#?;#metodologia#): mantém apenas o texto entre \nresumo\n e \nmetodologia\n. Não encontrando \nresumo\n, mantém o texto do início até \nmetodologia\n

Exemplos de uso dentro dos critérios de pesquisa:

  • `seriado casa adj2 papel recortar($inicio$;$fim$)'
  • Ao ser aplicado o critério no texto o seriado inicio casa de papel fim qualquer coisa, a avaliação será falsa já que o texto analisado será inicio casa de papel fim.

Exemplo de configuração de sinônimos

  • ao encontrar um termo no texto analisado, os sinônimos são mapeados como se fossem esse termo ** sinônimos compostos são analisados apenas para termos entre aspas nos critérios de pesquisa
  • Sinônimos: {'alegre': ['feliz','sorridente'], 'feliz':['alegre','sorridente'], 'sorridente':['alegre','feliz'], 'casa':['apartamento'] }
  • Sinônimos compostos: {'casa_de_papel':['la casa de papel','a casa de papel'], "inss" : ['instituto nacional de seguridade social'], 'instituto_nacional_de_seguridade_social':['inss']}

Com esse mapeamento, se o critério de pesquisa estiver escrito alegre é o mesmo que pesquisar (alegre ou feliz ou sorridente). Se estiver escrito "alegre" entre aspas, os sinônimos não serão pesquisados. Os sinônimos compostos possuem um comportamento peculiar, permitem o mapeamento de expressões, siglas, etc. Se o critério de pesquisa estiver escrito "inss" é o mesmo que pesquisar (inss ou "instituto nacional de seguridade social"). Mas se no critério de pesquisa estiver escrito inss sem aspas, somente será pesquisada a palavra inss ou sinônimos simples dela .

Exemplo de texto e texto com campos

  • esses textos serão usados mais abaixo
  • Texto único: A casa de papel é um seriado muito interessante
  • Texto composto: {'texto' : 'A casa de papel é um seriado muito interessante', 'tipo' : 'seriado', 'ano': '2017', 'comentario': 'seriado muito bom'}

Exemplo de pesquisas simples

  • o operador E é padrão para pesquisas sem operadores entre termos
  • ao pesquisar "papeis", a pesquisa vai localizar no texto o termo "papel", pois o texto estará singularizado e o critério de pesquisa também
  • Termos simples: casa papel
  • Termos simples com curingas: casa? E papeis
  • Termos simples com operadores: casa E papel E seriado
  • Termos simples com operadores e parênteses: (casa E papel) ou (papel E seriado)
  • Termos literias: "casa de papel" E seriado
  • Termos próximos: casa ADJ2 papel ADJ5 seriado
  • Termos próximos em qualquer ordem: papel PROX2 casa ADJ10 seriado
  • Termos no mesmo parágrafo: papel PROX2 casa COM seriado

Exemplo de pesquisas em campos

  • operadores especiais alteram o comportamento da pesquisa. Ao colocar um termo no critério de pesquisa seguido de .nomo_campo., o critério será analisado apenas no campo informado. ** um conjunto de critérios pode ser analisado no campo colocando (termo1 E termo2).nome_campo. ** combinações mais complexas podem ser feitas em conjuntos de critérios (termo1.campo1. E termo2 E termo3).campo2. - operadores de campos internos serão avaliados no lugar dos externos quando existirem.
  • critérios por campo: (papel PROX2 casa).texto. E 2017.ano=.
  • campo ANO>=2017: papel PROX2 casa E 2017.ano>=.
  • critérios por campo: (papel PROX2 casa).texto. E 2017.ano=.
  • critérios por campo (escrita alternativa): (papel PROX2 casa).texto. E @ano=2017
  • critérios por campos diferentes: (papel PROX2 casa).texto. E 2017.ano=. E "muito bom".comentario.

Exemplo de pesquisas simples com sinônimos

  • palavras simples são analisadas como se fossem seus sinônimos. Os sinônimos simples são desativados em termos entre aspas.
  • os sinônimos compostos são analisados apenas em palavras entre aspas no critério de pesquisa
  • apartamento = casa: papel PROX2 apartamento ADJ10 seriado
  • Sinônimos: papel PROX2 apartamento ADJ10 seriado

Usando a classe Python

Exemplos disponíveis no arquivo testes_exemplos.py e testes_exemplos_sem_db.py Para uso da classe PesquisaBRMemSQL (experimental) é necessário ter instalado o MemSQL (pode ser o container de exemplo). E criar as tabelas e funções do database pesquisabr. Scripts disponívels: db_funcoes.sql e db_tabelas.sql

TODOs

  • opção para usar regex multi line
  • opção para analisar operadores multi line

About

Essa é uma proposta de pesquisa textual implementada com recursos em python puro com o uso de dicionário de sinônimos e distância entre termos pesquisados. Inclui um serviço teste para rotular textos baseando-se nas regras e/ou em expressões regulares.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published