2009-12-04 7 views
15

Zbudowałem indeks w Lucene. Chcę bez określania zapytania, aby uzyskać wynik (podobieństwo cosinusa lub inny dystans?) Między dwoma dokumentami w indeksie.uzyskać podobieństwo kosinusów między dwoma dokumentami w Lucinie

Na przykład otrzymuję z poprzednio otwartego czytnika IndexReader dokumenty z identyfikatorami 2 i 4. Dokument d1 = ir.document (2); Dokument d2 = ir.document (4);

Jak mogę uzyskać podobieństwo cosinusa między tymi dwoma dokumentami?

Dziękuję

Odpowiedz

13

Kiedy indeksowanie, nie ma opcji, aby zapisać utrzymujące wektory częstotliwości.

W czasie wykonywania należy wyszukać wektory częstotliwości dla obu dokumentów za pomocą IndexReader.getTermFreqVector() i wyszukać dane częstotliwości dokumentów dla każdego terminu za pomocą IndexReader.docFreq(). To da ci wszystkie elementy niezbędne do obliczenia podobieństwa cosinusa między dwoma dokumentami.

Łatwiejszym sposobem może być przesłanie dokumentu A jako zapytania (dodanie wszystkich słów do zapytania jako warunków OR, zwiększenie częstotliwości każdego terminu) i poszukiwanie dokumentu B w zestawie wyników.

+0

Tak ok dla pierwszego, używam termfreqvector, aby uzyskać to, co chcę, ale chciałem sprawdzić, ile szybciej byłoby uzyskać podobieństwo z lucene. Po drugiej części odpowiedzi, sprawdziłem w javadoc, że nie ma oczywistego sposobu uzyskania wyniku podobieństwa. Ok, mogę szukać dokumentu B w zestawie wyników, ale jedyne, co mogę uzyskać, to jego pozycja w TopDocs, a nie dokładny wynik podobieństwa między tymi dwoma wektorami dokumentów, które chcę. – maiky

16

Jak Julia zaznacza Sujit Pal's example jest bardzo przydatna ale interfejs API Lucene 4 zawiera istotne zmiany. Oto wersja przepisany dla 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

Czy VecTextField został pobrany z pytania [this] (http://stackoverflow.com/questions/11945728/how-to-use-termvector-lucene-4-0)? –

+0

@o_nix - tak masz rację. Dzięki. Teraz jestem stałym –

+0

Test ten z przykładu Sujit Pal: Dokument # 0: operacja zastawek mitralnej - minimalnie inwazyjna (31825) Dokument nr 1: operacja zastawek mitralnej - open (31835) Dokument nr 2: laryngektomii (31706) ale dostałem różnicę w reuslt! czy możesz wyjaśnić, dlaczego dzięki – tiendv

2

Jest to bardzo dobre rozwiązanie Mark Butler, jednak obliczenia TF/IDF ciężarkami są złe!

Term-Frequency (tf): ile termin ten pojawił się w tym dokumencie (nie wszystkie dokumenty, jak w kodzie z termsEnum.totalTermFreq()).

Dokument częstotliwości (DF): całkowita liczba dokumentów, które termin ten pojawił się w

odwrotność częstotliwości dokumentu. IDF = log (N/DF), gdzie N jest całkowitą liczbą dokumentów.

Tf/idf waga = tf * idf, dla danego terminu i danego dokumentu.

Miałem nadzieję na wydajne obliczenia za pomocą Lucene! Nie mogę znaleźć skutecznego obliczenia dla prawidłowych ciężarów if/idf.

EDIT: Zrobiłem ten kod, aby obliczyć wagi jako ciężary tf/idf, a nie jako czystą terminę częstotliwości. Działa całkiem nieźle, ale zastanawiam się, czy istnieje skuteczniejszy sposób.

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

Dziękuję za twoją opinię, ale jak rozumiem, nie musisz obliczyć TF-IDF, aby obliczyć podobieństwo cosinusa.Możesz obliczyć miarę podobieństwa za pomocą TF-IDF, jeśli chcesz, ale to nie było celem tego kodu W szczególności używam powyższego algorytmu, aby przetestować, jak dobrze niektóre automatyczne extr Kod akcji działa przeciwko niektórym ludzkim generowanym odpowiedziom na podstawie jednego dokumentu. TF-IDF nie pomógłby w tym przypadku, dlatego go nie wykorzystałem. –

+0

Jestem również zadowolony z pracy nad optymalizacją Twojego kodu i widzę kilka podstawowych rzeczy, które mógłbyś zrobić, ale byłoby lepiej, gdybyś zamieścił to pod nowym pytaniem, jako że nie wspomniałeś o TF-IDF? Zawsze możesz zacytować to pytanie? –

+0

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

0

można znaleźć lepsze rozwiązanie @http://darakpanand.wordpress.com/2013/06/01/document-comparison-by-cosine-methodology-using-lucene/#more-53. są następujące kroki

  • kod Java, który buduje pojęcie wektora z treścią za pomocą Lucene (sprawdź: http://lucene.apache.org/core/).
  • Przy użyciu biblioteki commons-math.jar wykonywane jest obliczenie cosinusa między dwoma dokumentami.
+0

Spróbuj napisać coś więcej. Nie umieszczaj tylko linków. – WooCaSh

0

Jeśli nie trzeba przechowywać dokumenty Lucene i po prostu chcą, aby obliczyć podobieństwa między dwoma docs, tutaj jest szybszy kod (Scala, z mojego bloga 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 
} 

Tak więc, aby obliczyć podobieństwo między text1 i text2 po prostu zadzwoń similarity(extractTerms(text1), extractTerms(text2))

Powiązane problemy