使用Lucene计算文档相似度的更好方法

时间:2012-05-18 09:17:47

标签: java lucene similarity

我通过在索引时指定TermVector来使用Lucene索引文档集合。 然后我通过读取索引并计算每个文档的TF-IDF得分向量来检索术语及其频率。然后,使用TF-IDF向量,我使用Wikipedia's cosine similarity equation计算文档之间的成对余弦相似度。

这是我的问题:假设我在这个集合中有两个相同的文档“A”和“B”(A和B有超过​​200个句子)。如果我计算A和B之间的成对余弦相似度,它给出余弦值= 1,这是完全可以的。但是如果我从Doc“B”中删除一个句子,它会给出这两个文档之间0.85左右的余弦相似度值。文件几乎相似,但余弦值不是。我理解问题在于我正在使用的等式。

我可以使用更好的方法/方程来计算文档之间的余弦相似度吗?

被修改

这是我计算余弦相似度的方法,doc1[]doc2[]是相应文档的TF-IDF向量。向量仅包含scores,但不包含words

private double cosineSimBetweenTwoDocs(float doc1[], float doc2[]) {
    double temp;
    int doc1Len = doc1.length;
    int doc2Len = doc2.length;
    float numerator = 0;
    float temSumDoc1 = 0;
    float temSumDoc2 = 0;
    double equlideanNormOfDoc1 = 0;
    double equlideanNormOfDoc2 = 0;
    if (doc1Len > doc2Len) {
        for (int i = 0; i < doc2Len; i++) {
            numerator += doc1[i] * doc2[i];
            temSumDoc1 += doc1[i] * doc1[i];
            temSumDoc2 += doc2[i] * doc2[i];
        }
        equlideanNormOfDoc1=Math.sqrt(temSumDoc1);
         equlideanNormOfDoc2=Math.sqrt(temSumDoc2);
    } else {
        for (int i = 0; i < doc1Len; i++) {
            numerator += doc1[i] * doc2[i];
            temSumDoc1 += doc1[i] * doc1[i];
            temSumDoc2 += doc2[i] * doc2[i];
        }
         equlideanNormOfDoc1=Math.sqrt(temSumDoc1);
         equlideanNormOfDoc2=Math.sqrt(temSumDoc2);
    }

    temp = numerator / (equlideanNormOfDoc1 * equlideanNormOfDoc2);
    return temp;
} 

1 个答案:

答案 0 :(得分:4)

正如我在评论中告诉你的那样,我认为你在某处犯了一个错误。 向量实际上包含<word,frequency>对,而不仅仅是words。 因此,当您删除该句子时,只有相应单词的频率减1(后面的单词不会移位)。 请考虑以下示例:

记录a:

A B C A A B C. D D E A B. D A B C B A.

文件b:

A B C A A B C. D A B C B A.

矢量a:

A:6, B:5, C:3, D:3, E:1

矢量b:

A:5, B:4, C:3, D:1, E:0

导致以下相似性度量:

(6*5+5*4+3*3+3*1+1*0)/(Sqrt(6^2+5^2+3^2+3^2+1^2) Sqrt(5^2+4^2+3^2+1^2+0^2))=
62/(8.94427*7.14143)=
0.970648

修改 我认为您的源代码不能正常工作。请考虑以下代码,该代码适用于上述示例:

import java.util.HashMap;
import java.util.Map;

public class DocumentVector {
    Map<String, Integer> wordMap = new HashMap<String, Integer>();

    public void incCount(String word) {
        Integer oldCount = wordMap.get(word);
        wordMap.put(word, oldCount == null ? 1 : oldCount + 1);
    }

    double getCosineSimilarityWith(DocumentVector otherVector) {
        double innerProduct = 0;
        for(String w: this.wordMap.keySet()) {
            innerProduct += this.getCount(w) * otherVector.getCount(w);
        }
        return innerProduct / (this.getNorm() * otherVector.getNorm());
    }

    double getNorm() {
        double sum = 0;
        for (Integer count : wordMap.values()) {
            sum += count * count;
        }
        return Math.sqrt(sum);
    }

    int getCount(String word) {
        return wordMap.containsKey(word) ? wordMap.get(word) : 0;
    }

    public static void main(String[] args) {
        String doc1 = "A B C A A B C. D D E A B. D A B C B A.";
        String doc2 = "A B C A A B C. D A B C B A.";

        DocumentVector v1 = new DocumentVector();
        for(String w:doc1.split("[^a-zA-Z]+")) {
            v1.incCount(w);
        }

        DocumentVector v2 = new DocumentVector();
        for(String w:doc2.split("[^a-zA-Z]+")) {
            v2.incCount(w);
        }

        System.out.println("Similarity = " + v1.getCosineSimilarityWith(v2));
    }

}