Vitor Pamplona

Innovation on Vision: Imaging , Enhancement and Simulation

Introdução ao Apache Lucene

Criado por Doug Cutting em 2000, o Lucene é uma das mais famosas e mais usadas bibliotecas para indexação e consulta de textos, disponível em código aberto. Sob o domínio da Apache Foundation, a biblioteca, escrita em java, pode ser utilizada em qualquer aplicativo J2SE ou J2EE, de código aberto ou não. Outras linguagens como Delphi, Perl, C#, C++, Python, Ruby e PHP devem usar os ports do Lucene para as referidas linguagens.

A biblioteca é composta por duas etapas principais: indexação e pesquisa (Figura 3.1). A indexação processa os dados originais gerando uma estrutura de dados inter-relacionada eficiente para a pesquisa baseada em palavras-chave. A pesquisa, por sua vez, consulta o índice pelas palavras digitadas em uma consulta e organiza os resultados pela similaridade do texto com a consulta.

Figura 3.1: Arquitetura do Apache Lucene. Imagem do Lucene in Action 2004.

O Lucene oferece um agradável nível de abstração para um conjunto poderoso de técnicas baseadas no modelo Vetorial e Booleano. O desenvolvedor não precisa conhecer rotinas e algoritmos de indexação, nem de consulta. Basta utilizá-los através da API da biblioteca. Apesar da facilidade de uso, o Lucene não implementa um webcrawler ou parsers de HTML / XML. Este processamento, se necessário, deve ser feito pela aplicação e repassado à biblioteca por meio de instâncias da classe Document .

A classe Document representa o mecanismo escolhido pelo Lucene para troca de informações entre a aplicação e a biblioteca. Um documento é composto por campos e estes campos por informações. Cada campo possui sua utilidade para a aplicação e sua importância na pesquisa. Por exemplo, campos comuns são: o ID, que não será indexado, mas será importante para que a aplicação localize o documento retornado pela pesquisa; e o TEXT que conterá a cadeia de caracteres a ser indexado e analisado pela biblioteca. O resultado da consulta é um conjunto de documentos ordenado por relevância, ou seja, os itens mais similares a consulta aparecem primeiro.

O Lucene implementa uma linguagem para consulta que proporciona pesquisas booleanas (e.g. Luis AND Inacio AND Silva OR Lula), restritivas (e.g. + George + Rice - eat - pudding), por campos (e.g. animal: monkey AND food: banana), por expressões regulares (e.g. test *, te? t), consultas ponderadas (e.g. jakarta ^ 4 apache) e em range (e.g. date: [20020101 TO 20030101]). Além disso, ainda permite ao desenvolvedor criar dois tipos de consultas anvançadas: (i) a fuzzy , que utiliza a distância de Levenshtein ao avaliar a proximidade entre a consulta e o documento, e permite informar o nível de similaridade mínimo, como, por exemplo, na consulta " roam ~ 0.8 "; (ii) a busca por proximidade entre palavras, como, por exemplo, a consulta " jakarta apache " ~ 10, que buscará todos os documentos que possuem " jakarta apache " com no máximo 10 palavras entre estas duas da consulta.

Os índices podem ser criados em ambientes distribuídos, aumentando a performance e a escalabilidade da ferramenta. Calcula-se que o Lucene consiga indexar cerca de 20MB de texto por minuto em um computador com um único core de 1.5Ghz. Os arquivos de índices comprimidos ocupam cerca de 25% do tamanho sem compressão.

Instalação

O download da biblioteca deve ser feito pelo endereço http://www.apache.org/dyn/closer.cgi/lucene/java/. Após o download, basta descompactar o arquivo ZIP e colocar o lucene-core-X.Y.Z.jar dentro da pasta de bibliotecas externas de seu projeto java.

Lucene em 5 minutos

Crie um projeto Java e uma classe principal com um método main , onde deverão ser colocados todos os códigos aqui apresentados. Este resumo foi baseado no tutorial Lucene in 5 minutes.

Indexando uma base

Para criar os arquivos de índice, basta instanciar o IndexWriter, adicionar os documentos de sua base a ele, e fechar o arquivo. Como este é um caso simples, usaremos o índice em memória.

	// Cria o analyzador
StandardAnalyzer analyzer = new StandardAnalyzer();

// Diretório virtual para o índice
Directory indexDirectory = new RAMDirectory();

// Cria o arquivo com tamanho ilimitado.
IndexWriter w = new IndexWriter(indexDirectory, analyzer, true,
IndexWriter.MaxFieldLength.UNLIMITED);

// Adiciona 4 documentos.
addDoc(w, "Lucene in Action");
addDoc(w, "Lucene for Dummies");
addDoc(w, "Managing Gigabytes");
addDoc(w, "The Art of Computer Science");

// Fecha o arquivo.
w.close();
O método addDoc cria um novo documento, adiciona um texto (text) como título deste documento e configura este título para ser armazenado e analizado.
	private static void addDoc(IndexWriter w, String text) throws IOException {
Document doc = new Document();
doc.add(new Field("title", text, Field.Store.YES, Field.Index.ANALYZED));
w.addDocument(doc);
}

Criando a Consulta

Agora que o índice está pronto, podemos criar uma consulta no campo de título com o texto “ Lucene ”. Neste caso, a biblioteca deve retornar os documentos 1 e 2 como possíveis respostas a esta consulta.

	// Faz o parse da consulta e cria uma query do lucene.
Query q = new QueryParser("title", analyzer).parse("Lucene");

Repare que usamos o mesmo analyzer na criação do índice e na consulta. É importante que seja o mesmo analisador nos dois processos, ou seja, se o analizador criado para o índice processa, por exemplo, stopwords , o analisador da consulta também deve processar. Caso contrario a resposta do Lucene para as consultas pode não ser boa.

O objeto query criado anteriormente deve então ser repassado a um IndexSearcher:

	int maxHits = 10;

// Cria o acesso ao índice
IndexSearcher searcher = new IndexSearcher(indexDirectory);

// Prepara a coleção de resultado
TopDocCollector collector = new TopDocCollector(maxHits);

// Faz a pesquisa
searcher.search(q, collector);

// Separa os 10 itens mais relevantes para a consulta.
ScoreDoc[] hits = collector.topDocs().scoreDocs;

Depois da consulta realizada, o TopDocCollector conterá os 10 textos mais relevantes, ordenados por similaridade em relação a consulta. Para imprimir o resultado, basta varrer o array hits:

	// Imprime os documentos retornados.
System.out.println("Found " + hits.length + " hits.");
for (int i = 0; i < hits.length; ++i) {
int docId = hits[i].doc;
Document d = searcher.doc(docId);
System.out.println((i + 1) + ". " + d.get("title"));
}

Pronto. O sistema está em funcionamento.

Stopwords, Stemming e Sinônimos

Remoção de Stopwords e Stemming são dois procedimentos comuns em qualquer sistema de recuperação de informações. Stopwords é o conjunto de palavras que não são consideradas na busca. Normalmente são palavras muito comuns do idioma ou do domínio pesquisado. Stemming é processo de reduzir as palavras à sua raiz. A raiz de uma palavra é o conjunto de caracteres que está presente em todas as suas derivações.

Para utilizar stopwords é necessário implementar um analisador. O analisador é uma cadeia de filtros onde cada palavra passará, seja na criação do índice ou na consulta. A classe abaixo é um analisador que filtra as stopwords importadas de um arquivo e efetua o Porter stemming como um filtro que retorna apenas os radicais das palavras válidas.

	public class TextAnalyzer extends Analyzer {
private final Set<String> stopWords;
private final boolean usePorterStemming;

public TextAnalyzer(Set<String> stopWords, boolean usePorterStemming) {
this.stopWords = stopWords;
this.usePorterStemming = usePorterStemming;
}

public TokenStream tokenStream(String fieldName, Reader reader) {
TokenStream result = new LowerCaseTokenizer(reader);
if (!stopWords.isEmpty()) {
result = new StopFilter(result, stopWords, true);
}
if (usePorterStemming) {
result = new PorterStemFilter(result);
}
return result;
}
}

A inclusão de sinônimos na consulta do usuário é um processo menos comum, mas que pode aumentar muito a eficiência do sistema de RI. Trata-se de encontrar documentos que utilizam termos não utilizados pelo cliente da consulta, mas que tem o mesmo significado semântico. Para tal, nós baixamos o script para sinônimos feito em Prolog da versão 2.0 da Wordnet (http://www.cogsci.princeton.edu/2.0/WNprolog-2.0.tar.gz) que cria um hash map thread-safe na memória próprio para efetuar pesquisas de sinônimos para qualquer palavra.

O script armazena os sinônimos em um grafo não direcionado. Ou seja, se B é sinônimo de A, A também será sinônimo de B. No entanto, o script não garante que, se A é sinônimo de B e B é sinônimo de C, A seja sinônimo de C. A carga do arquivo leva cerca de 1,5 segundo e consome cerca de 10MB de RAM, mas só precisa ser feito uma vez por execução do programa. Depois de carregado, o método getSynonyms (String) retorna os sinônimos de uma palavra em lowercase em O (1). Por exemplo, a entrada “ The earth, the skies and the space ” sem stemming, sem stop words e com número máximo de 2 sinônimos por palavras resulta na saída “ the earth world globe the skies and the space quad blank ”.

A classe abaixo abre o script da WordNet e o incorpora em um analisador do Lucene. Como já foi dito, um analisador é um filtro palavra a palavra. Esta classe segue a mesma estrutura do analisador anterior para remover stopwords.

	public class QueryAnalyser extends Analyzer {
private static final String WORDNET_PL_DEFAULT_FILE_PATH = "./resources/wn_s.pl";
private static final SynonymMap synonymMap;

static {
try {
synonymMap = new SynonymMap(
new FileInputStream(WORDNET_PL_DEFAULT_FILE_PATH));
} catch (IOException e) {
throw new IllegalStateException("could not read wordnet file", e);
}
}

private final TextAnalyzer textAnalyzer;
private final int maxSynonyms;

public QueryAnalyser(TextAnalyzer textAnalyzer, int maxSynonyms) {
this.textAnalyzer = textAnalyzer;
this.maxSynonyms = maxSynonyms;
}


public TokenStream tokenStream(String fieldName, Reader reader) {
// Cria SynonimFilter do Lucene passando o mapa de sinônimos já carregado
// Retorna um stream de tokens com o original e os sinônimos.
SynonymTokenFilter tokenStream = new SynonymTokenFilter(
new LowerCaseTokenizer(reader), synonymMap, maxSynonyms);

// Trasforma a lista de tokens em String novamente.
String query = null;
try {
query = Utilities.readTokenStream(tokenStream);
} catch (IOException e) {
throw new IllegalArgumentException("problem reading given reader",
e);
}
StringReader stringReader = new StringReader(query);

// Re-passa para o removedor de Stemming e Stopwords
return textAnalyzer.tokenStream(fieldName, stringReader);
}
}

Bom, este post foi só um resumo sobre o Lucene e algumas extensões. Espero que tenham gostado.

Posted in Jul 14, 2009 by Vitor Pamplona - Edit - History

Showing Comments

Parabéns, gostei muito do seu artigo, gostei bastante dos exemplos utilizados, principalmente o de sinonimos. Que venham os proximos artigos. E tem previsão de lançar algum sobre Hibernate Search?

- - Emmanuel Silva

- - Posted in Jul 19, 2009 by 201.21.174.57

Olá Vitor Parabens pelo seu Artigo!!!
bom, estou trabalhando com um sistema onde faz esse processo d recuperação d informação!!
blz mas oq eu qero eh comparar 2 documentos para fazer uma comparação e saber se são iguais, oq seria um plagio!!
mas esto sem rumo para fazer essa comparação!!
gostaria de saber se essa api poderia me ajudar!!
e como eu poderia está fazendo!!
se puder me dar o caminho das pedras agradeceria mto!!
vlw e até +

- - David

- - Posted in Jul 24, 2009 by 189.74.177.159

Adriano

- - Adriano

- - Posted in Jul 29, 2009 by 189.65.187.142



- - bosco.barbosa @ gmail.com

- - Posted in Dec 15, 2009 by 187.88.133.194

Olá Vitor!!... estou trabalhando com o Lucene para efetuar pesquisa por palavra-chave. E o resultado da pesquisa está muito lento... chegando a 100% a CPU do servidor onde está sendo realizada a pesquisa.

Você poderia me dá umas dicas, do que poderia estar causando esta lentidão.

Desde já agradeço a atenção.

msocorroc@hotmail.com

- - Socorro Carvalho

- - Posted in Jan 7, 2010 by 200.164.100.9

Ola,

Alguem sabe onde encontrar informações sobre volumetria e infraestrutura para o Lucene?
Ou mesmo materiais em portugues..

Obrigado pessoal!!



- - René Bizelli

- - Posted in Jan 27, 2010 by 201.6.54.134

Excelente, esta introdução ao Apache Lucene será de grande valia aos estudos desta API, parabéns.

Abraço.

- - Ednei Parmigiani Júnior

- - Posted in Mar 1, 2010 by 187.2.92.112

E esse erro:


ERROR: index path not specified

Usage: java org.apache.lucene.index.CheckIndex pathToIndex [- fix] [- segment X] [- segment Y]

- fix: actually write a new segments_N file, removing any problematic segments
- segment X: only check the specified segments. This can be specified multiple
times, to check more than one segment, eg ' - segment _2 - segment _a '.
You can't use this with the - fix option

* * WARNING * *: - fix should only be used on an emergency basis as it will cause
documents (perhaps many) to be permanently removed from the index. Always make
a backup copy of your index before running this! Do not run this tool on an index
that is actively being written to. You have been warned!

Run without - fix, this tool will open the index, report version information
and report any exceptions it hits and what action it would take if - fix were
specified. With - fix, this tool will remove any segments that have issues and
write a new segments_N file. This means all documents contained in the affected
segments will be removed.

This tool exits with exit code 1 if the index cannot be opened or has any
corruption, else 0.


- - RX.Silva

- - Posted in Mar 15, 2010 by 189.3.89.194

Absolutaly Great.

Your topic is very good, I will go study this api and your topic help me of many forms.

Tks and congratulations.


- - Teixeira D.

- - Posted in Aug 17, 2010 by 200.185.17.190

Parabens!

Muito bom tutorial, já da para ter uma idéia do poder do lucene.

- - Emir

- - Posted in Sep 2, 2010 by 189.89.41.102

Esse programa é um lixo, não sei como alguém pode achá-lo bom. Ridículo esse Lucene.

- - O Justiceiro

- - Posted in Oct 25, 2010 by 201.17.198.213

Gostaria de implementar um sistema para capturar as palavras chave de um documento.... teria como fazer isso usando stopwords e stemming?

Me retorno por favor
lucas@semanticworks.com

- - Lucas Thom

- - Posted in Apr 21, 2011 by 186.212.233.132


Gostei muito do seu artigo me ajudou bastante a entender o funcionamento do Lucene,
Porem tenho uma dúvida como faço pra realizar uma pesquisa onde possuo campos vazios?

Se vc tiver alguma dica posta ai ou me manda um email
thiagogarbazza@hotmail.com

- - Thiago Garbazza

- - Posted in May 4, 2011 by 187.115.194.184

Olá. Parabéns pelo tutorial. Está muito bacana...

Seguinte. Gostaria de saber qual a release do lucene vc está usando no tutorial.. Eu baixei a versão 3.1 e não estou conseguindo rodar este código. parece que alguns métodos estão depreciados. Você pode me ajudar com isto?

Por favor, entre em contato comigo via thnardi@gmail.com

Saudações. obrigado.



- - Thiago Nardi

- - Posted in Jun 3, 2011 by 200.203.253.70

Add New Comment

Your Name:


Write the code showed above on the text below.