Information Retrieval Evaluation con Lucene

In questo tutorial attraverso i tools di Lucene, indicizzeremo ed eseguiremo il recupero su tutte e tre le collezioni della Confusion Trec.
Lucene è un motore di ricerca interamente implementato in Java. Per sfruttare le potenzialità di Lucene sono state importate nel progetto Java le seguenti librerie:

  • lucene-analyzers-common-4.3.1.jar
  • lucene-core-4.3.1.jar
  • lucene-queryparser-4.3.1.jar

Lucene può essere reperito al seguente collegamento ipertestuale: .
In questa sede Lucene sarà utilizzato per la manipolazione di indici su delle collezioni di TREC (Text Retrieval Conference).
Questo progetto è stato sviluppato in Java ed esso è capace di indicizzare le collezioni di documenti della Trec Confusion e di effettuare query, dando in input il file dei topic (queries.11-20).
Di seguito si discuteranno gli step che descrivono il progetto Java in questione.

Lucene

Motore di Information Retrieval


1 STEP: INDICIZZAZIONE
Per la fase di indicizzazione è stata prevista la classe IndexTREC.java la quale può essere lanciata sia da console sia da riga di comando utilizzando la seguente istruzione: java org.apache.lucene.demo.IndexFiles -index /Path/To/Index -docs /Path/To/Collection Qualora invece si voglia lanciarlo da console occorre modificare le seguenti righe di codice:

String indexPath = "C:/lucene/indice";
String docsPath = "C:/lucene/confusion_track/degrade5";

Tale step crea una cartella che contiene gli indici per la collezione desiderata. IndexTREC sfrutta la classe di utility TrecDocIterator.java, capace di iterare sui dei documenti. Di seguito si riporta parte del codice sorgente della classe IndexTREC.

package it.univaq.mwt.webmining;
 
import java.io.File;
import java.io.IOException;
import java.util.Date;
 
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
 
/**
 * Indicizzazione delle collezioni di Trec.
 *
 * @author Gianluca
 * @author Alessandro
 */
public class IndexTREC {
 
    private IndexTREC() {
    }
 
    public static void main(String[] args) {
        String strUtilizzo = "java org.apache.lucene.demo.IndexFiles"
                + " [-index INDEX_PATH] [-docs DOCS_PATH] [-update]\n\n"
                + "Creazione dell'indice DOCS_PATH in INDEX_PATH.\n\n";
        System.out.println(strUtilizzo);
 
        String indexPath = "C:/lucene/indice";
        String docsPath = "C:/lucene/confusion_track/degrade5";
 
        boolean create = true;
 
        /*
         * Completamento di eventuali parametri non 
         * settati nelle proprietà del main.
         */
        for (int i = 0; i < args.length; i++) {
            if ("-index".equals(args[i])) {
                indexPath = args[i + 1];
                i++;
            } else if ("-docs".equals(args[i])) {
                docsPath = args[i + 1];
                i++;
            } else if ("-update".equals(args[i])) {
                create = false;
            }
        }
 
        if (docsPath == null) {
            System.err.println("Utilizzo: " + strUtilizzo);
            System.exit(1);
        }
 
        final File docDir = new File(docsPath);
        if (!docDir.exists() || !docDir.canRead()) {
            System.out.println("La directory dei documenti '" + docDir.getAbsolutePath()
                    + "' o non esiste o non è accessibile, verificare il percorso.");
            System.exit(1);
        }
 
        Date start = new Date();
        try {
            System.out.println("Sto creando l'indice nella directory: '" 
                    + indexPath + "'...");
 
            Directory dir = FSDirectory.open(new File(indexPath));
            Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_43);
            IndexWriterConfig iwc = new IndexWriterConfig(
                    Version.LUCENE_43, analyzer);
 
            if (create) {
                // Crea un nuovo indice nella cartella, eliminando i documenti
                // indicizzati precedentemente.
                iwc.setOpenMode(OpenMode.CREATE);
            } else {
                // Aggiungi nuovi documenti all'indice creato in precedenza.
                iwc.setOpenMode(OpenMode.CREATE_OR_APPEND);
            }
 
            // Miglioramento delle prestazioni sfruttando il 
            // buffering della RAM.
            iwc.setRAMBufferSizeMB(3070.0);
 
            IndexWriter writer = new IndexWriter(dir, iwc);
            indexDocs(writer, docDir);
 
            writer.close();
 
            Date end = new Date();
            System.out.println(end.getTime() - start.getTime() + " total milliseconds");
 
        } catch (IOException e) {
            System.out.println("La classe " + e.getClass()
                    + "\n ha generato una eccezione con il seguente messaggio:"
                    + e.getMessage());
        }
    }
 
    /**
     * Indicizzazioni dei documenti contenuti in una specifica directory.
     *
     * @param writer che conterrà l'indice da immagazzinare.
     * @param file file da indicizzare.
     * @throws IOException eccezione nel caso si riscontra un errore di I/O.
     */
    private static void indexDocs(IndexWriter writer, File file)
            throws IOException {
        if (file.canRead()) {
            if (file.isDirectory()) {
                String[] files = file.list();
                if (files != null) {
                    for (int i = 0; i < files.length; i++) {
                        indexDocs(writer, new File(file, files[i]));
                    }
                }
            } else {
                TrecDocIterator docs = new TrecDocIterator(file);
                Document doc;
                while (docs.hasNext()) {
                    doc = docs.next();
                    if (doc != null && doc.getField("contents") != null) {
                        writer.addDocument(doc);
                    }
                }
            }
        }
    }
}

2 STEP: INTERROGAZIONE
Per l’interrogazione dell’indice è stata prevista la classe BatchSearch.java la quale può essere lanciata sia da console sia da riga di comando utilizzando la seguente istruzione: tjava BatchSearch -index /Path/To/Index -field contents -queries /Path/To/Topics -simfn nomeModello.

I modelli previsti per l’analisi sono:

  • 1. default: modello di default di Lucene, esso corrisponde al TF-IDF.
  • 2. bm25: modello di valutazione probabilistico BM25.
  • 3. dfr: Divergence from Randomness, paradigma di standardizzazione di Harter’s 2-poisson.
  • 4. lm: modello di valutazione linguistico con Dirichlet smoothing, anche detto Language Model.

Per lanciare invece il programma da console occorrerà modificare le seguenti righe di codice:

        // Percorso della cartella indice.
        String strPathIndice = "C:/lucene/indice";
 
        // Percorso per il fileOutStream delle query.
        String strPathQuery = "C:/lucene/topics.confusion.cf1-cf50";
 
        String strField = "contents";
 
        // Modello scelto per la query.
        String strModello = "dfr";

Si riporta di seguito parte del codice della classe BatchSearch.java.

package it.univaq.mwt.webmining;
 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
 
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.similarities.*;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
 
/**
 * Ricerca mediante la tecnologia Batch di Lucene.
 *
 * @author Gianluca
 * @author Alessandro
 */
public class BatchSearch {
 
    private static String strQueryTmp;
 
    public static void main(String[] args) throws IOException {
        String strUtilizzo =
            "Utilizzo:\tjava BatchSearch [-index dir] [-simfn similarity] [-field f] [-queries file]";
        System.out.println(strUtilizzo);
 
        // Percorso della cartella indice.
        String strPathIndice = "C:/lucene/indice";
 
        // Percorso per il fileOutStream delle query.
        String strPathQuery = "C:/lucene/topics.confusion.cf1-cf50";
 
        String strField = "contents";
 
        // Modello scelto per la query.
        String strModello = "dfr";
 
        /*
         * Completamento dei parametri qualora non
         * vengono specificati tra le proprietà.
         */
        for (int i = 0; i < args.length; i++) {
            if ("-index".equals(args[i])) {
                strPathIndice = args[i + 1];
                i++;
            } else if ("-field".equals(args[i])) {
                strField = args[i + 1];
                i++;
            } else if ("-queries".equals(args[i])) {
                strPathQuery = args[i + 1];
                i++;
            } else if ("-simfn".equals(args[i])) {
                strModello = args[i + 1];
                i++;
            }
        }
 
        Similarity simfn = null;
        if ("default".equals(strModello)) {
            simfn = new DefaultSimilarity();
        } else if ("bm25".equals(strModello)) {
            simfn = new BM25Similarity();
        } else if ("dfr".equals(strModello)) {
            simfn = new DFRSimilarity(new BasicModelP(), new AfterEffectL(), new NormalizationH2());
        } else if ("lm".equals(strModello)) {
            simfn = new LMDirichletSimilarity();
        }
        if (simfn == null) {
            System.out.println(strUtilizzo);
            System.out.println("Funzionalità di similarità supportate:\ndefault: tfidf");
            System.out.println("\nbm25: BM25Similarity (standard parameters)");
            System.out.println("\ndfr: Divergence from Randomness model");
            System.out.println("\nlm: Language model, Dirichlet smoothing");
            System.exit(0);
        }
 
        /*
         * File dei risultati.
         */
        FileOutputStream fileOutStream = new FileOutputStream("C:/lucene/resultBatchQrels.out");
        PrintStream printStreamResult = new PrintStream(fileOutStream);
 
        /*
         * Processo di Query sull'indice.
         */
        IndexReader reader = DirectoryReader.open(FSDirectory.open(
                new File(strPathIndice)));
        IndexSearcher searcher = new IndexSearcher(reader);
        searcher.setSimilarity(simfn);
        Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_43);
        BufferedReader bufferReader = null;
        if (strPathQuery != null) {
            bufferReader = new BufferedReader(new InputStreamReader(
                    new FileInputStream(strPathQuery), "UTF-8"));
        } else {
            bufferReader = new BufferedReader(new InputStreamReader(
                    new FileInputStream("queries"), "UTF-8"));
        }
        QueryParser parser = new QueryParser(
                Version.LUCENE_43, strField, analyzer);
 
        while (true) {
            String strLine = bufferReader.readLine();
            if (strLine == null || strLine.length() == -1) {
                break;
            }
            strLine = strLine.trim();
            if (strLine.length() == 0) {
                break;
            }
            try {
                if (strLine.substring(0, 5).equalsIgnoreCase("<top>")) {
                    continue;
                } else if (strLine.substring(0, 5).equalsIgnoreCase("<num>")){
                    String[] strSplitNum = strLine.split(" ", 3);
                    strQueryTmp = strSplitNum[2];
                    continue;
                } else if (strLine.substring(0, 6).equalsIgnoreCase("<desc>")){
                    strLine = bufferReader.readLine();
                    Query query = parser.parse(strLine);
                    doBatchSearch(bufferReader, searcher, strQueryTmp,
                            query, strModello, printStreamResult);
                } else {
                    continue;
                }
            } catch (StringIndexOutOfBoundsException ex) {
                System.out.println(ex.getMessage());
                continue;
            } catch (ParseException ex) {
                Logger.getLogger(BatchSearch.class.getName()).
                        log(Level.SEVERE, null, ex);
            }
        }
        reader.close();
    }
 
    /**
     * Esecuzione della query.
     *
     * @param bufferReader BufferedReader.
     * @param searcher IndexSearcher per l'indice.
     * @param strQueryId id della query.
     * @param query query sotto forma di stringa.
     * @param strModello modello scelto per l'esecuzione della query.
     * @throws IOException
     */
    public static void doBatchSearch(BufferedReader bufferedReader, 
            IndexSearcher searcher, String strQueryId, Query query, 
            String strModello, PrintStream printStreamResult)
            throws IOException {
 
        TopDocs resultsTopDocs = searcher.search(query, 1000);
        ScoreDoc[] hits = resultsTopDocs.scoreDocs;
        HashMap<String, String> seen = new HashMap<String, String>(1000);
        int numTotalHits = resultsTopDocs.totalHits;
 
        int intStart = 0;
        int intEnd = Math.min(numTotalHits, 1000);
 
        for (int i = intStart; i < intEnd; i++) {
            Document doc = searcher.doc(hits[i].doc);
 
            String docno = doc.get("docno");
 
            // Se non è presente il docno, continue.
            if (seen.containsKey(docno)) {
                continue;
            }
 
            seen.put(docno, docno);
            String strResult = strQueryId + " Q0 " + docno + " " + i +
                    " " + hits[i].score + " " + strModello;
            System.out.println(strResult);
            printStreamResult.println(strResult);
        }
    }
}

Il main si occuperà della lettura dei campi del file queries.11-20. Dopo la lettura del topic sarà effettuata la query grazie al metodo

1
2
3
4
5
6
public static void doBatchSearch(BufferedReader bufferedReader, 
            IndexSearcher searcher, String strQueryId, Query query, 
            String strModello, PrintStream printStreamResult)
            throws IOException {
...
}

L’esecuzione dell’intero processo di query restituisce in output il file resultBatchQrels.out così formattato:
numero_query Q0 i-esimo_DOCNO score_doc nome_modello_utilizzato
dove per ogni query sono specificati al massimo 1000 documenti ordinati in maniera decrescente di score_doc, ponendo in testa il documento più rilevante.
Il file resultBatchQrels.out sarà poi interessante oggetto di valutazione per trec_eval.
L’intero progetto è stato realizzato utilizzando l’IDE NetBeans, ed esso può essere scaricato al seguente link: Progetto Lucene

Per ulteriori varianti e/o delucidazioni è possibile consultare il seguente link: GitHub Lucene.

Information Retrieval Evaluation con Lucene ultima modidfica: 2014-01-01T12:48:32+01:00 da Gianluca Di Vincenzo
Posted in: Java

By on 1 Gennaio 2014

Tagged: , , , , ,