2009-12-04 8 views
15

Ich habe einen Index in Lucene gebaut. Ich möchte, ohne eine Abfrage anzugeben, nur um eine Punktzahl (Kosinusähnlichkeit oder eine andere Entfernung?) Zwischen zwei Dokumenten im Index zu erhalten.erhalten Kosinusähnlichkeit zwischen zwei Dokumenten in Lucene

Zum Beispiel bekomme ich von zuvor geöffneten IndexReader die Dokumente mit den IDs 2 und 4. Dokument d1 = ir.document (2); Dokument d2 = ir.document (4);

Wie kann ich die Kosinusähnlichkeit zwischen diesen beiden Dokumenten erhalten?

Danke

Antwort

13

Wenn Indizierung gibt eine Option Begriff Frequenzvektoren zu speichern.

Suchen Sie während der Laufzeit den Begriff Häufigkeitsvektoren für beide Dokumente mit IndexReader.getTermFreqVector(), und suchen Sie mit IndexReader.docFreq() nach Dokumenthäufigkeitsdaten für jeden Begriff. Dadurch erhalten Sie alle notwendigen Komponenten, um die Kosinusähnlichkeit zwischen den beiden Dokumenten zu berechnen.

Ein einfacherer Weg könnte darin bestehen, Dokument A als Abfrage einzureichen (alle Wörter als OR-Begriffe in die Abfrage einzufügen, jede nach Häufigkeit zu steigern) und nach Dokument B in der Ergebnismenge suchen.

+0

Ja ok für die erste, ich benutze den Termfreqvector, um zu bekommen, was ich will, aber ich wollte überprüfen, wie viel schneller wäre es die Ähnlichkeit von Lucene zu bekommen. Für den zweiten Teil Ihrer Antwort habe ich in der Javadoc überprüft, dass es keine offensichtliche Möglichkeit gibt, Ähnlichkeitswert zu erhalten. Ok, ich kann in der Ergebnismenge nach doc B suchen, aber das einzige, was ich bekommen kann, ist seine Position in den TopDocs, nicht die genaue Ähnlichkeit zwischen diesen beiden Dokumentenvektoren, die ich möchte. – maiky

16

Wie Julia weist darauf hin, Sujit Pal's example ist sehr nützlich aber die Lucene 4 API hat wesentliche Änderungen. Hier ist eine Version neu geschrieben für 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<String> 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<String, Integer> f1 = getTermFrequencies(reader, 0); 
     Map<String, Integer> 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<String, Integer> getTermFrequencies(IndexReader reader, int docId) 
      throws IOException { 
     Terms vector = reader.getTermVector(docId, CONTENT); 
     TermsEnum termsEnum = null; 
     termsEnum = vector.iterator(termsEnum); 
     Map<String, Integer> 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<String, Integer> 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()); 
    } 
} 
+1

Wurde VecTextField von [this] (http://stackoverflow.com/questions/11945728/how-to-use-termvector-lucene-4-0) Frage entnommen? –

+0

@o_nix - ja du hast recht. Vielen Dank. Jetzt –

+0

fixierte Ich bin Test dies mit Sujit Pal Beispiel: Dokument # 0: Mitralklappenoperation - minimal-invasiven (31825) Dokument # 1: Mitralklappenoperation - open (31835) Dokument # 2: Laryngektomie (31706) aber es hat einen unterschied reuslt! Kannst du erklären, warum Danke – tiendv

2

Es ist eine sehr gute Lösung von Mark Butler, aber die Berechnungen der tf/idf Gewichte falsch sind!

Term-Frequenz (tf): wie viel dieser Begriff in diesem Dokument (nicht alle Dokumente wie im Code mit termsEnum.totalTermFreq()) erschien.

Document Frequency (df): die Gesamtzahl der Dokumente, dass dieser Begriff erschien in

Inverse Document Frequency:. Idf = log (N/df), wobei N die Gesamtzahl der Dokumente ist.

Tf/idf Gewicht = tf * IDF, für einen gegebenen Begriff und ein bestimmtes Dokument.

Ich hatte auf eine effiziente Berechnung mit Lucene gehofft! Ich kann keine effiziente Berechnung für die korrekten if/idf-Gewichte finden.

BEARBEITEN: Ich machte diesen Code, um die Gewichte als tf/idf Gewichte und nicht als reine Term-Frequenz zu berechnen. Es funktioniert ziemlich gut, aber ich frage mich, ob es einen effizienteren Weg gibt.

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<String> 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<String, Double> f1 = getWieghts(reader, 0); 
     Map<String, Double> 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<String, Double> getWieghts(IndexReader reader, int docId) 
      throws IOException { 
     Terms vector = reader.getTermVector(docId, CONTENT); 
     Map<String, Integer> docFrequencies = new HashMap<>(); 
     Map<String, Integer> termFrequencies = new HashMap<>(); 
     Map<String, Double> 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<String, Double> 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<String, Integer> map) { 
     for (String key : map.keySet()) { 
      System.out.println("Term: " + key + ", value: " + map.get(key)); 
     } 
    } 

    public static void printMapDouble(Map<String, Double> map) { 
     for (String key : map.keySet()) { 
      System.out.println("Term: " + key + ", value: " + map.get(key)); 
     } 
    } 

} 
+0

Vielen Dank für Ihr Feedback, aber wie ich es verstehe, müssen Sie nicht berechnen TF-IDF Cosinus-Ähnlichkeit zu berechnen.Sie könnten eine Ähnlichkeit Metrik mit TF-IDF berechnen, wenn Sie wollen, aber das war nicht das Ziel des Codes Ich benutze den Algorithmus oben, um zu testen, wie gut automatische Extr Der Aktionscode wirkt auf einige menschliche Antworten pro Dokument. TF-IDF würde in diesem Fall nicht helfen, weshalb ich es nicht benutzt habe. –

+0

Auch ich bin glücklich, mit Ihnen zu arbeiten, Ihren Code zu optimieren, und ich kann ein paar grundlegende Dinge sehen, die Sie tun konnten, aber es wäre besser, wenn Sie es unter einer neuen Frage schrieben, weil dieses TF-IDF nicht erwähnte? Sie könnten diese Frage immer anführen? –

+0

Siehe http://stackoverflow.com/questions/6255835/cosine-similarity-and-tf-idf –

0

können Sie eine bessere Lösung @http://darakpanand.wordpress.com/2013/06/01/document-comparison-by-cosine-methodology-using-lucene/#more-53 finden. folgenden sind die Schritte

  • Java-Code, der von Inhalt mit Hilfe von Lucene Begriff Vektor baut (Check: http://lucene.apache.org/core/).
  • Mit commons-math.jar Bibliothek Cosinus Berechnung zwischen zwei Dokumenten durchgeführt wird.
+0

Versuchen Sie, etwas mehr zu schreiben. Setze nicht nur Links. – WooCaSh

0

Wenn Sie nicht speichern Dokumente zu Lucene brauchen und wollen nur Ähnlichkeit zwischen zwei docs zu berechnen, hier ist der schnellere Code (Scala, aus meinem Blog http://chepurnoy.org/blog/2014/03/faster-cosine-similarity-between-two-dicuments-with-scala-and-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 
} 

So zu berechnen Ähnlichkeit zwischen Text1 und Text2 rufen Sie einfach similarity(extractTerms(text1), extractTerms(text2))