2009-12-04 19 views
15

Я построил индекс в Lucene. Я хочу, не указав запрос, просто чтобы получить оценку (косинус подобие или другое расстояние?) Между двумя документами в индексе.получить косинус сходство между двумя документами в lucene

Например, я получаю от ранее открытого IndexReader и документов с идентификаторами 2 и 4. Документ d1 = ir.document (2); Документ d2 = ir.document (4);

Как я могу получить сходство косинусов между этими двумя документами?

Спасибо

ответ

13

При индексации, есть возможность для хранения долгосрочных векторов частот.

Во время выполнения найдите выражение для векторов сроков для обоих документов с помощью IndexReader.getTermFreqVector() и найдите данные частоты документа для каждого термина, используя IndexReader.docFreq(). Это даст вам все компоненты, необходимые для вычисления сходства косинусов между двумя документами.

Более простым способом может быть отправка документа A в виде запроса (добавление всех слов к запросу в виде OR, повышение каждой по срочной частоте) и поиск документа B в результирующем наборе.

+0

Да, для первого, я использую termfreqvector, чтобы получить то, что хочу, но я хотел проверить, насколько быстрее было бы получить сходство от lucene. Для второй части вашего ответа я проверил в javadoc, что нет очевидного способа получить оценку подобия. Хорошо, я могу найти doc B в результирующем наборе, но единственное, что я могу получить, это его позиция в TopDocs, а не точная оценка сходства между этими двумя векторами документов, которые я хочу. – maiky

16

Как Юли указывает Sujit Pal's example очень полезно но API Lucene 4 имеет существенные изменения. Вот версия переписан для 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

Был ли VecTextField выбран из [this] (http://stackoverflow.com/questions/11945728/how-to-use-termvector-lucene-4-0) вопроса? –

+0

@o_nix - да, вы правы. Благодарю. Теперь это исправлено –

+0

я тест это с примером Sujit Pal: Документ # 0: митрального клапана хирургии - малоинвазивная (31825) Документ № 1: митральный клапан хирургии - открыт (31835) Документ № 2: Ларингэктомия (31706) но он получил разницу! вы можете объяснить, почему спасибо – tiendv

2

Это очень хорошее решение, Марк Батлер, однако расчеты ТФ/IDF весов ошибаетесь!

Term-Frequency (tf): насколько этот термин появился в этом документе (не все документы, как в коде с терминамиEnum.totalTermFreq()).

частота документа (DF): общее количество документов, что этот термин появился в

обратной частота документа:. IDF = журнал (N/DF), где N представляет собой общее количество документов.

Tf/idf weight = tf * idf, для данного термина и данного документа.

Я надеялся на эффективный расчет с использованием Lucene! Я не могу найти эффективный расчет правильных весов if/idf.

EDIT: Я сделал этот код для вычисления весов как веса tf/idf, а не как чистую термическую частоту. Он работает очень хорошо, но мне интересно, есть ли более эффективный способ.

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

Спасибо за ваши отзывы, но, как я понимаю, вам не нужно вычислять TF-IDF для вычисления сходства косинусов. Вы можете рассчитать показатель подобия с использованием TF-IDF, если хотите, но это не было целью кода выше. В частности, я использую алгоритм выше, чтобы проверить, насколько хорошо какой-то автоматический extr код действия работает против некоторых человеческих ответов на основе документа. TF-IDF не помог бы в этом случае, поэтому я не использовал его. –

+0

Также я рад работать с вами, оптимизируя ваш код, и я вижу несколько основных вещей, которые вы могли бы сделать, но было бы лучше, если бы вы разместили его по новому вопросу, поскольку в этом не упоминалось TF-IDF? Вы всегда можете ссылаться на этот вопрос? –

+0

См. Http://stackoverflow.com/questions/6255835/cosine-similarity-and-tf-idf –

0

вы можете найти лучшее решение @http://darakpanand.wordpress.com/2013/06/01/document-comparison-by-cosine-methodology-using-lucene/#more-53. следующие шаги

  • Java код, который создает термин вектор от содержания с помощью Lucene (проверьте: http://lucene.apache.org/core/).
  • С помощью библиотеки commons-math.jar выполняется расчет косинуса между двумя документами.
+0

Попробуйте написать что-то еще. Не ставьте только ссылки. – WooCaSh

0

Если вам не нужно хранить документы в Lucene и просто хотите, чтобы вычислить сходство между двумя Docs, вот быстрый код (Scala, из моего блога 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 
} 

Итак, для расчета сходство между текстом1 и текстом2 просто вызывает similarity(extractTerms(text1), extractTerms(text2))

1

Расчет сходства косинуса в Lucene версии 4.x отличается от такового от 3.x. Следующий пост содержит подробное объяснение со всем необходимым кодом для вычисления сходства косинусов в Lucene 4.10.2. ComputerGodzilla: Calculated Cosine Similarity in Lucene!