一.簡單總結git
其實類似度計算方法也是老生常談,好比經常使用的有:github
1.常規方法web
a.編輯距離ide
b.Jaccardthis
c.餘弦距離spa
d.曼哈頓距離3d
e.歐氏距離code
f.皮爾遜相關係數blog
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 }