obtener similitud coseno entre dos documentos en lucene

He construido un índice en Lucene. Quiero sin especificar una consulta, solo para obtener una puntuación (similitud coseno u otra distancia?) Entre dos documentos en el índice.

Por ejemplo, estoy obteniendo de IndexReader abierto previamente los documentos con ids 2 y 4. Documento d1 = ir.document (2); Documento d2 = ir.document (4);

¿Cómo puedo obtener la similitud del coseno entre estos dos documentos?

Gracias

Al indexar, hay una opción para almacenar vectores de frecuencia de términos.

Durante el tiempo de ejecución, busque los vectores de frecuencia de término para ambos documentos usando IndexReader.getTermFreqVector (), y busque datos de frecuencia de documento para cada término usando IndexReader.docFreq (). Eso le dará todos los componentes necesarios para calcular la similitud del coseno entre los dos documentos.

Una forma más fácil podría ser enviar el documento A como una consulta (agregar todas las palabras a la consulta como términos OR, boost cada una por frecuencia de término) y buscar el documento B en el conjunto de resultados.

Como señala Julia, el ejemplo de Sujit Pal es muy útil, pero el API de Lucene 4 tiene cambios sustanciales. Aquí hay una versión reescrita para Lucene 4.

import java.io.IOException; import java.util.*; import org.apache.commons.math3.linear.*; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.core.SimpleAnalyzer; import org.apache.lucene.document.*; import org.apache.lucene.document.Field.Store; import org.apache.lucene.index.*; import org.apache.lucene.store.*; import org.apache.lucene.util.*; public class CosineDocumentSimilarity { public static final String CONTENT = "Content"; private final Set terms = new HashSet<>(); private final RealVector v1; private final RealVector v2; CosineDocumentSimilarity(String s1, String s2) throws IOException { Directory directory = createIndex(s1, s2); IndexReader reader = DirectoryReader.open(directory); Map f1 = getTermFrequencies(reader, 0); Map f2 = getTermFrequencies(reader, 1); reader.close(); v1 = toRealVector(f1); v2 = toRealVector(f2); } Directory createIndex(String s1, String s2) throws IOException { Directory directory = new RAMDirectory(); Analyzer analyzer = new SimpleAnalyzer(Version.LUCENE_CURRENT); IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_CURRENT, analyzer); IndexWriter writer = new IndexWriter(directory, iwc); addDocument(writer, s1); addDocument(writer, s2); writer.close(); return directory; } /* Indexed, tokenized, stored. */ public static final FieldType TYPE_STORED = new FieldType(); static { TYPE_STORED.setIndexed(true); TYPE_STORED.setTokenized(true); TYPE_STORED.setStored(true); TYPE_STORED.setStoreTermVectors(true); TYPE_STORED.setStoreTermVectorPositions(true); TYPE_STORED.freeze(); } void addDocument(IndexWriter writer, String content) throws IOException { Document doc = new Document(); Field field = new Field(CONTENT, content, TYPE_STORED); doc.add(field); writer.addDocument(doc); } double getCosineSimilarity() { return (v1.dotProduct(v2)) / (v1.getNorm() * v2.getNorm()); } public static double getCosineSimilarity(String s1, String s2) throws IOException { return new CosineDocumentSimilarity(s1, s2).getCosineSimilarity(); } Map getTermFrequencies(IndexReader reader, int docId) throws IOException { Terms vector = reader.getTermVector(docId, CONTENT); TermsEnum termsEnum = null; termsEnum = vector.iterator(termsEnum); Map frequencies = new HashMap<>(); BytesRef text = null; while ((text = termsEnum.next()) != null) { String term = text.utf8ToString(); int freq = (int) termsEnum.totalTermFreq(); frequencies.put(term, freq); terms.add(term); } return frequencies; } RealVector toRealVector(Map map) { RealVector vector = new ArrayRealVector(terms.size()); int i = 0; for (String term : terms) { int value = map.containsKey(term) ? map.get(term) : 0; vector.setEntry(i++, value); } return (RealVector) vector.mapDivide(vector.getL1Norm()); } } 

Sé que la pregunta ha sido respondida, pero para las personas que podrían venir aquí en el futuro, un buen ejemplo de la solución se puede encontrar aquí:

http://sujitpal.blogspot.ch/2011/10/computing-document-similarity-using.html

¡Es una solución muy buena por Mark Butler, sin embargo, los cálculos de los pesos de tf / idf son INCORRECTOS!

Término-Frecuencia (tf): cuánto este término apareció en este documento (NO todos los documentos como en el código con termsEnum.totalTermFreq ()).

Frecuencia del documento (df): el número total de documentos en los que apareció este término.

Frecuencia inversa del documento: idf = log (N / df), donde N es el número total de documentos.

Tf / idf peso = tf * idf, para un término dado y un documento dado.

¡Estaba esperando un cálculo eficiente usando Lucene! No puedo encontrar un cálculo eficiente para los pesos correctos de if / idf.

EDITAR : hice este código para calcular los pesos como pesos tf / idf y no como términos de frecuencia puros. Funciona bastante bien, pero me pregunto si hay una manera más eficiente.

 import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.commons.math3.linear.ArrayRealVector; import org.apache.commons.math3.linear.RealVector; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.core.SimpleAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.FieldType; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.DocsEnum; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.Term; import org.apache.lucene.index.Terms; import org.apache.lucene.index.TermsEnum; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.store.Directory; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.Version; public class CosineSimeTest { public static void main(String[] args) { try { CosineSimeTest cosSim = new CosineSimeTest( "This is good", "This is good" ); System.out.println( cosSim.getCosineSimilarity() ); } catch (IOException e) { e.printStackTrace(); } } public static final String CONTENT = "Content"; public static final int N = 2;//Total number of documents private final Set terms = new HashSet<>(); private final RealVector v1; private final RealVector v2; CosineSimeTest(String s1, String s2) throws IOException { Directory directory = createIndex(s1, s2); IndexReader reader = DirectoryReader.open(directory); Map f1 = getWieghts(reader, 0); Map f2 = getWieghts(reader, 1); reader.close(); v1 = toRealVector(f1); System.out.println( "V1: " +v1 ); v2 = toRealVector(f2); System.out.println( "V2: " +v2 ); } Directory createIndex(String s1, String s2) throws IOException { Directory directory = new RAMDirectory(); Analyzer analyzer = new SimpleAnalyzer(Version.LUCENE_CURRENT); IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_CURRENT, analyzer); IndexWriter writer = new IndexWriter(directory, iwc); addDocument(writer, s1); addDocument(writer, s2); writer.close(); return directory; } /* Indexed, tokenized, stored. */ public static final FieldType TYPE_STORED = new FieldType(); static { TYPE_STORED.setIndexed(true); TYPE_STORED.setTokenized(true); TYPE_STORED.setStored(true); TYPE_STORED.setStoreTermVectors(true); TYPE_STORED.setStoreTermVectorPositions(true); TYPE_STORED.freeze(); } void addDocument(IndexWriter writer, String content) throws IOException { Document doc = new Document(); Field field = new Field(CONTENT, content, TYPE_STORED); doc.add(field); writer.addDocument(doc); } double getCosineSimilarity() { double dotProduct = v1.dotProduct(v2); System.out.println( "Dot: " + dotProduct); System.out.println( "V1_norm: " + v1.getNorm() + ", V2_norm: " + v2.getNorm() ); double normalization = (v1.getNorm() * v2.getNorm()); System.out.println( "Norm: " + normalization); return dotProduct / normalization; } Map getWieghts(IndexReader reader, int docId) throws IOException { Terms vector = reader.getTermVector(docId, CONTENT); Map docFrequencies = new HashMap<>(); Map termFrequencies = new HashMap<>(); Map tf_Idf_Weights = new HashMap<>(); TermsEnum termsEnum = null; DocsEnum docsEnum = null; termsEnum = vector.iterator(termsEnum); BytesRef text = null; while ((text = termsEnum.next()) != null) { String term = text.utf8ToString(); int docFreq = termsEnum.docFreq(); docFrequencies.put(term, reader.docFreq( new Term( CONTENT, term ) )); docsEnum = termsEnum.docs(null, null); while (docsEnum.nextDoc() != DocIdSetIterator.NO_MORE_DOCS) { termFrequencies.put(term, docsEnum.freq()); } terms.add(term); } for ( String term : docFrequencies.keySet() ) { int tf = termFrequencies.get(term); int df = docFrequencies.get(term); double idf = ( 1 + Math.log(N) - Math.log(df) ); double w = tf * idf; tf_Idf_Weights.put(term, w); //System.out.printf("Term: %s - tf: %d, df: %d, idf: %f, w: %f\n", term, tf, df, idf, w); } System.out.println( "Printing docFrequencies:" ); printMap(docFrequencies); System.out.println( "Printing termFrequencies:" ); printMap(termFrequencies); System.out.println( "Printing if/idf weights:" ); printMapDouble(tf_Idf_Weights); return tf_Idf_Weights; } RealVector toRealVector(Map map) { RealVector vector = new ArrayRealVector(terms.size()); int i = 0; double value = 0; for (String term : terms) { if ( map.containsKey(term) ) { value = map.get(term); } else { value = 0; } vector.setEntry(i++, value); } return vector; } public static void printMap(Map map) { for ( String key : map.keySet() ) { System.out.println( "Term: " + key + ", value: " + map.get(key) ); } } public static void printMapDouble(Map map) { for ( String key : map.keySet() ) { System.out.println( "Term: " + key + ", value: " + map.get(key) ); } } } 

Cálculo de la similitud del coseno en la versión 4.x de Lucene es diferente de los de 3.x. La siguiente publicación tiene una explicación detallada con todo el código necesario para calcular la similitud del coseno en Lucene 4.10.2. ComputerGodzilla: ¡Similitud coseno calculada en Lucene !

puede encontrar una mejor solución @ http://darakpanand.wordpress.com/2013/06/01/document-comparison-by-cosine-methodology-using-lucene/#more-53 . siguientes son los pasos

  • código Java que construye un vector de términos a partir del contenido con la ayuda de Lucene (ver: http://lucene.apache.org/core/ ).
  • Mediante el uso de la biblioteca commons-math.jar, se realiza el cálculo del coseno entre dos documentos.

Si no necesita almacenar documentos en Lucene y solo desea calcular la similitud entre dos documentos, este es el código más rápido (Scala, de mi blog http://chepurnoy.org/blog/2014/03/faster-cosine-similarity -entre-dos-dicumentos-con-scala-y-lucene / )

 def extractTerms(content: String): Map[String, Int] = { val analyzer = new StopAnalyzer(Version.LUCENE_46) val ts = new EnglishMinimalStemFilter(analyzer.tokenStream("c", content)) val charTermAttribute = ts.addAttribute(classOf[CharTermAttribute]) val m = scala.collection.mutable.Map[String, Int]() ts.reset() while (ts.incrementToken()) { val term = charTermAttribute.toString val newCount = m.get(term).map(_ + 1).getOrElse(1) m += term -> newCount } m.toMap } def similarity(t1: Map[String, Int], t2: Map[String, Int]): Double = { //word, t1 freq, t2 freq val m = scala.collection.mutable.HashMap[String, (Int, Int)]() val sum1 = t1.foldLeft(0d) {case (sum, (word, freq)) => m += word ->(freq, 0) sum + freq } val sum2 = t2.foldLeft(0d) {case (sum, (word, freq)) => m.get(word) match { case Some((freq1, _)) => m += word ->(freq1, freq) case None => m += word ->(0, freq) } sum + freq } val (p1, p2, p3) = m.foldLeft((0d, 0d, 0d)) {case ((s1, s2, s3), e) => val fs = e._2 val f1 = fs._1 / sum1 val f2 = fs._2 / sum2 (s1 + f1 * f2, s2 + f1 * f1, s3 + f2 * f2) } val cos = p1 / (Math.sqrt(p2) * Math.sqrt(p3)) cos } 

Por lo tanto, para calcular la similitud entre text1 y text2 simplemente llama similarity(extractTerms(text1), extractTerms(text2))