Executando consultas por frases

A partir de um índice de termos/documentos só é possível efetuar consultas de ocorrência de termos e filtros com operadores AND, OR e NOT. Entretanto, o que é preciso para executar uma consulta por presidente do Brasil? A forma mais simples, é converter essa consulta em presidente AND do AND Brasil (o do pode ser removido se quiser remover stop words). O detalhe é que essa consulta vai retornar qualquer documento que contenha presidente e Brasil, mas que não não fale necessariamento do presidente do Brasil. Um exemplo seria: O presidente do conselho está trabalhando para aumentar os empregos no Brasil. Esse documento é retornado pela consulta presidente AND do AND Brasil, mas não tem nada a ver com presidente do Brasil. Portanto, é necessário melhorar a estrutura de índice para obter melhores resultados pelas consultas.

Antes de prosseguir, é importante notar que esse tipo de consulta normalmente é feito com ". Portanto, pesquisar por presidente do Brasil é o equivalente à presidente AND do AND Brasil. Para efetuar a consulta com a phrase query é necessário adicionar ", assim “presidente do Brasil” faz a consulta pela frase. Claro que é possível adicionar algum mecanismo que interpréte presidente do Brasil como uma phrase query, sem a necessidade de pedir ao usuário que coloque ".

índice bi-word

Uma forma de resolver a consulta por frases é criar um índice de k-palavras. Sendo 2-palavras um número bom, visto que $k \gt 2$ começa a requisitar mais espaço de armazenamento para todas as combinações de três ou mais palavras. Portanto, dados os documentos de exemplo usado no índice invertido, o índice bi-word (2-palavras) seria.

Documentos:

  • Uma casa à venda em Blumenau: [casa, venda, Blumenau]: [(casa, venda), (venda, Blumenau)]
  • Vendo terreno em Gaspar: [venda, terreno, Gaspar]: [(venda, terreno), (terreno, Gaspar)]
  • Alugo apartamento em Indaial: [alugo, apartamento, Indaial]: [(alugo, apartamento), (apartamento, Indaial)]

Observação: As stop words foram removidas para simplificar o exemplo.

# índice bi-word
I = {
    '__alldocs__': [1, 2, 3],
    ('alugo', 'apartamento'): [3],
    ('apartamento', 'Indaial'): [3],
    ('casa', 'venda'): [1],
    ('terreno', 'Gaspar'): [2],
    ('venda', 'Blumenau'): [1],
    ('venda', 'terreno'): [2],
}

Como é possível notar no exemplo acima, a diferença fica que as chaves do dicionário são tuplas (poderia ser uma string concatenada também) de palavras em sequência. É necessário atentar na hora de efetuar a consulta. Nesse momento, uma consulta terreno em Gaspar não se torna terreno AND Gaspar, mas sim terreno Gaspar.

Voltando ao exemplo da consulta presidente do Brasil. Com a condição de um índice de duas palavras, a consulta seria presidente do AND do Brasil. Note que a consulta foi separada em um AND com as sequências de k-palavras possíveis.

Um exemplo de como montar um índice bi-word em python.

import nltk
from nltk.corpus import brown
from itertools import chain
import string
from collections import defaultdict


nltk.download('brown')


def filtrar_pontuacao(doc):
    return (p for p in doc if p not in string.punctuation)


DOCUMENTOS = brown.paras()
DOCUMENTOS = [list(chain.from_iterable(p)) for p in DOCUMENTOS]
documentos = (filtrar_pontuacao(p) for p in DOCUMENTOS)
documentos = [list(p) for p in documentos]

bi_word_inverted_index = defaultdict(list)

for (doc_id, doc) in enumerate(documentos):
    # termo especial para queries com NOT
    bi_word_inverted_index['__alldocs__'].append(doc_id)
    for (t1, t2) in zip(doc[:-1], doc[1:]):
        bi_word_inverted_index[(t1, t2)].append(doc_id)

Agora é possível comparar uma consulta nesse índice bi-word, contra o índice de termos. O teste será de uma consulta para Texas House of Representatives.

No índice de termos

print('texas AND house AND representatives')
texas = inverted_index['texas']
house = inverted_index['house']
representatives = inverted_index['representatives']
doc_ids = set(texas) & set(house) & set(representatives)
for doc_id in doc_ids:
    print('> ', ' '.join(DOCUMENTOS[doc_id]))

Com resultados:

  • Under Formby’s plan , an appointee would be selected by a board composed of the governor , lieutenant governor , speaker of the House , attorney general and chief justice of the Texas Supreme Court . Austin , Texas – State representatives decided Thursday against taking a poll on what kind of taxes Texans would prefer to pay .

  • Rep. James Cotten of Weatherford insisted that a water development bill passed by the Texas House of Representatives was an effort by big cities like Dallas and Fort Worth to cover up places like Paradise , a Wise County hamlet of 250 people .

Note que o primeiro resultado contém todos os termos, mas não na sequência desejada. Já o segundo documento é um resultado esperado para a consulta.

A mesma consulta no índice bi-word

print('texas house AND house of AND of representatives')
texas_house = bi_word_inverted_index[('Texas', 'House')]
house_of = inverted_index[('House', 'of')]
of_representatives = inverted_index[('of', 'Representatives')]
doc_ids = set(texas_house) & set(house_of) & set(of_representatives)
for doc_id in doc_ids:
    print('> ', ' '.join(DOCUMENTOS[doc_id]))

Com resultado:

Rep. James Cotten of Weatherford insisted that a water development bill passed by the Texas House of Representatives was an effort by big cities like Dallas and Fort Worth to cover up places like Paradise , a Wise County hamlet of 250 people .

Nesse caso, o único documento retornado foi o esperado. Obviamente esses foram exemplos simples e a ideia é combinar o índice de termos com um índice bi-word para ter um conjunto melhor de resultados.

Entretanto, ainda existe um problema no índice bi-word. Nesse exemplo, em inglês, tirado de Introduction to Information Retrieval, fica claro. Indexar renegotiation of the constitution seria: [(renegotiation, of), (of, the), (the, constitution)]. Note que esses índices de duas palavras não agregam muito e, provavelmente, retornariam resultado irrelevantes em uma consulta. A solução é pensar em aumentar de 2-palavras para 3-palavras ou 4-palavras. O problema é o tamanho do índice, que começa a ficar muito grande, considerando as várias combinações de 3, ou 4, palavras. Outra possibilidade é considerar um pouco da estrutura do idioma. Por exemplo, of the funcionam como palavras auxiliares, stop words até certo ponto, e poderiam ser agregadas ao montar o índice. Para isso, é necessário uma lista de stop words ou usar técnicas de Processamento de Linguagem Natural para fazer o Part-of-Speech tagging, que indica a função (verbo, substantivo, artigo, …) de cada palavra em uma frase. Uma vez que se sabe a função das palavras é possível montar o índice ignorando palavras de estrutura (artigos, preposições, …) que aparecem entre substantivos/verbos. Portanto, dado que renegotiation of the constitution seria anotado como N X X N (N: substantivo, X: palavra auxiliar). Uma estrutura de índice poderia ser montada com NX*N, ou seja, um substantivo (N) seguido por zero ou mais palavras auxiliares (X*) seguido por mais um substantivo (N). Essa alternativa para considerar os casos especiais onde é necessário indexar mais que duas palavras em sequência é chamada de phrase index, visto que frases completas podem ser indexadas e não apenas termos ou sequências fixas de k-termos.

O índice bi-word é uma forma para resolver o problema de consultas por frases. Mas tem a limitação de espaço, visto que indexar sequências muito grandes pode ser proibitivo (em espaço) e também resultar em uma consulta demorada.

Referências