基于熵的方法计算query与docs相似度

一.简单总结

  其实相似度计算方法也是老生常谈,比如常用的有:

  1.常规方法

    a.编辑距离

    b.Jaccard

    c.余弦距离

    d.曼哈顿距离

    e.欧氏距离

    f.皮尔逊相关系数

  2.语义方法

    a.LSA

    b.Doc2Vec

    c.DSSM

  ......

二.利用熵计算相似度

  关于什么是熵、相对熵、交叉熵的概念,网上有很多,这里就不总结了。本篇主要关注工程方面,即怎么用代码实现,参考的论文来自《Content-based relevance estimation on the web using inter-document similarities》(2012-CIKM)。

  利用熵计算query与文档相似度并排序的步骤分为召回和重排序,比如先从大规模文档中召回小部分子集再进行重排序。召回部分可以用一些简单的效率高的方法快速确定候选子集,再将这些子集进行重排序。本篇关注如何利用熵重排序相关文档。

  召回后的排序公式如下:

    

  说明:

  (1).H(d)表示文档d的熵

    

    其中=|w|/|d|,分子是词w个数,分母为文档d中的总词数

  (2).文档间的相似度

    

    

    其中表示query的top-k个相关文档;利用交叉熵计算文档间的相似度,这里面的文档去除了query中的词。

    表示语言模型Dirichlet-smoothed,常见的平滑方法如下:

    

    其中Dirichlet 方法:
    a.首先计算最基本的最大的似然估计w|d 单词在单个文档出现的频率(有可能为0,所以就需要平滑,将所有f(w|d1), f(w|d2)....f(w|dn) 的所有频率加总
    b.设定u值,根据实证研究: Dirichlet 方法的u值在100-200之间是最理想 ,但论文中给出的是1000,0为不使用平滑
    c. 计算P(w|C)的概率

    (3).sim(q,d)表示query与doc的相似度,可以使用其它方法计算,也可以使用如(2)中的方法计算

 三.程序

  完整程序https://github.com/jiangnanboy/entropy_sim

  核心程序:

 1 /**
 2      * 结合交叉熵和狄里克雷平滑语言方法计算相关度
 3      * @param queryTerms
 4      * @return
 5      */
 6     private Map<String, Double> queryDocScore(List<String> queryTerms) {
 7         //统计查询中的词频
 8         Map<String, Long> queryTermsCount = queryTerms
 9                 .stream()
10                 .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
11         //查询中的总词频
12         long queryTermsSize = queryTermsCount
13                 .values()
14                 .stream()
15                 .mapToLong(word -> word)
16                 .sum();
17 
18         //文档集中的词频
19         Map<String, Long> collectionTermsCount = corpusTerms
20                 .stream()
21                 .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
22         //文档集中的总词频
23         long collectionTermsSize = collectionTermsCount
24                 .values()
25                 .stream()
26                 .mapToLong(word -> word)
27                 .sum();
28 
29         Map<String, Double> scoredDocument = new HashMap<>();
30         documentList.forEach(docTerms -> {
31             //文档中的词频
32             Map<String, Long> docTermsCount = docTerms
33                     .stream()
34                     .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
35             //文档中的总词频
36             long docTermsSize = docTermsCount
37                     .values()
38                     .stream()
39                     .mapToLong(word -> word)
40                     .sum();
41 
42             //计算交叉熵(或者相对熵)
43             OptionalDouble score = queryTerms
44                     .stream()
45                     .mapToDouble(queryTerm -> {
46                         //queryTerm的似然
47                         double queryCE = (double)queryTermsCount.get(queryTerm) / queryTermsSize;
48                         //经过Dirichlet smooth的term weight
49                         double docCE = (1.0 + docTermsCount.getOrDefault(queryTerm, 0L) +
50                                 this.lambda * (collectionTermsCount.getOrDefault(queryTerm, 0L) / collectionTermsSize)) /
51                                 (docTermsSize + this.lambda);
52                         return queryCE * Math.log(1 / docCE);//交叉熵
53                         //return queryCE * Math.log(queryCE / docCE);//相对熵
54                     })
55                     .reduce(Double::sum);
56             String docID = corpusHashMap.get(docTerms);
57             scoredDocument.put(docID, Math.exp(-score.getAsDouble()));
58         });
59         return scoredDocument;
60     }
原文地址:https://www.cnblogs.com/little-horse/p/11253030.html