Java--SimHash实现文本标题内容相似度计算
Java--SimHash實(shí)現(xiàn)文本標(biāo)題內(nèi)容相似度計(jì)算
- 一 .關(guān)于SimHash
- 一)、什么是海明距離
- 二)、海明距離的應(yīng)用
- 三)、什么是編輯距離
- 二、SimHash算法的幾何意義和原理
- 一)、SimHash算法的幾何意義
- 二)、SimHash的計(jì)算原理
- 三)、文本的相似度計(jì)算
- 三、Java通過SimHash計(jì)算文本內(nèi)容相似度代碼示例
- 一)、新增依賴包
- 二)、過濾特殊字符
- 三)、計(jì)算單個分詞的Hash值
- 四)、分詞計(jì)算向量
- 五)、獲取標(biāo)題內(nèi)容的海明距離
- 六)、獲取標(biāo)題內(nèi)容的相似度
- 七)、測試
一 .關(guān)于SimHash
SimHash算法是Google在2007年發(fā)表的論文《Detecting Near-Duplicates for Web Crawling》中提到的一種指紋生成算法,被應(yīng)用在Google搜索引擎網(wǎng)頁去重的工作之中。SimHash值不但提供了原始值是否相等這一信息,還能通過該值計(jì)算出內(nèi)容的差異程度。
簡單的說,SimHash算法主要的工作就是將文本進(jìn)行降維,生成一個SimHash值,也就是論文中所提及的“指紋”,通過對不同文本的SimHash值進(jìn)而比較“海明距離”,從而判斷兩個文本的相似度。
對于文本相似度的問題,常見的解決辦法有歐式距離、編輯距離、最長公共子串、余弦算法、Jaccard相似度等方法。但是這些方法并不能對海量數(shù)據(jù)高效的處理。
比如說,在搜索引擎中,會有很多相似的關(guān)鍵詞,用戶所需要獲取的內(nèi)容是相似的,但是搜索的關(guān)鍵詞卻是不同的,例如:
“年齡大于30歲的自然人?”
“年齡大于50歲的人?”
以上兩個可以等價(jià)的關(guān)鍵詞,然而通過普通的hash計(jì)算,會產(chǎn)生兩個相差甚遠(yuǎn)的hash串。而通過SimHash計(jì)算得到的Hash串會非常的相近,從而可以判斷兩個文本的相似程度。
一)、什么是海明距離
海明距離是編輯距離其中之一,在信息編碼中,兩個合法代碼對應(yīng)位上編碼不同的位數(shù)稱為碼距,又稱海明距離。即兩個碼字的對應(yīng)比特取值不同的比特?cái)?shù)稱為這兩個碼字的海明距離。一個有效編碼集中任意兩個碼字的海明距離的最小值稱為該編碼集的海明距離。例如: 10101 和 00110 從第一位開始依次有第一位、第四、第五位不同,則海明距離為 3。
N位的碼字可以用N維空間的超立方體的一個頂點(diǎn)來表示,兩個碼字之間的海明距離就是超立方體兩個頂點(diǎn)之間的一條邊,而且是這兩個頂點(diǎn)之間的最短距離。
二)、海明距離的應(yīng)用
海明距離應(yīng)用最多的是在海量短文、文本去重上,以其性能優(yōu)的特點(diǎn)。海明距離主要就是對文本進(jìn)行向量化,或者說是把文本的特征抽取出來映射成編碼,然后再對編碼進(jìn)行異或計(jì)算出海明距離。
三)、什么是編輯距離
編輯距離又稱Levenshtein距離(也叫做Edit Distance),是指兩個字串之間,由一個轉(zhuǎn)成另一個所需的最少編輯操作次數(shù),許可的編輯操作包括將一個字符替換成另一個字符,插入一個字符,刪除一個字符。
二、SimHash算法的幾何意義和原理
一)、SimHash算法的幾何意義
它首先將每一個特征映射為f維空間的一個向量,這個映射規(guī)則具體是怎樣并不重要,只要對很多不同的特征來說,它們對所對應(yīng)的向量是均勻隨機(jī)分布的,并且對相同的特征來說對應(yīng)的向量是唯一的就行。比如一個特征的4位Hash簽名的二進(jìn)制表示為1010,那么這個特征對應(yīng)的4維向量就是(1,-1,1,-1)T,即Hash簽名的某一位為1,映射到的向量的對應(yīng)位就為1,否則為-1。然后,將一個文檔中所包含的各個特征對應(yīng)的向量加權(quán)求和,加權(quán)的系數(shù)等于該特征的權(quán)重。得到的和向量即表征了這個文檔,我們可以用向量之間的夾角來衡量對應(yīng)文檔之間的相似度。最后,為了得到一個f位的簽名,需要進(jìn)一步將其壓縮,如果和向量的某一維大于0,則最終簽名的對應(yīng)位為1,否則為0。這樣的壓縮相當(dāng)于只留下了和向量所在的象限這個信息,而64位的簽名可以表示多達(dá)264個象限,因此只保存所在象限的信息也足夠表征一個文檔了。
明確了SimHash算法的幾何意義,使這個算法直觀上看來是合理的。但是,為何最終得到的簽名相近的程度,即可以衡量原始文檔的相似程度呢。在SimHash的發(fā)明人Charikar的論文中并沒有給出具體的SimHash算法和證明,以下列出證明思路:
SimHash是由隨機(jī)超平面Hash算法演變而來的,隨機(jī)超平面Hash算法非常簡單,對于一個n維向量v,要得到一個f位的簽名(f<<n),算法如下:
1、隨機(jī)產(chǎn)生f個n維的向量r1,…rf;
2、對每一個向量ri,如果v與ri的點(diǎn)積大于0,則最終簽名的第i位為1,否則為0;
這個算法相當(dāng)于隨機(jī)產(chǎn)生了f個n維超平面,每個超平面將向量v所在的空間一分為二,v在這個超平面上方則得到一個1,否則得到一個0,然后將得到的 f個0或1組合起來成為一個f維的簽名。如果兩個向量u,v的夾角為θ,則一個隨機(jī)超平面將它們分開的概率為θ/π,因此u, v的簽名的對應(yīng)位不同的概率等于θ/π。所以,我們可以用兩個向量的簽名的不同的對應(yīng)位的數(shù)量,即海明距離,來衡量這兩個向量的差異程度。
SimHash算法與隨機(jī)超平面Hash如何聯(lián)系起來?在SimHash算法中,并沒有直接產(chǎn)生用于分割空間的隨機(jī)向量,而是間接產(chǎn)生->第 k個特征的Hash簽名的第i位拿出來,如果為0,則改為-1,如果為1則不變,作為第i個隨機(jī)向量的第k維。由于hash簽名是f位的,因此這樣能產(chǎn)生 f個隨機(jī)向量,對應(yīng)f個隨機(jī)超平面。下面舉個例子:
假設(shè)用5個特征w1,…,w5來表示所有文檔,現(xiàn)要得到任意文檔的一個3維簽名。
假設(shè)這5個特征對應(yīng)的3維向量分別為:
h(w1) = (1, -1, 1)T
h(w2) = (-1, 1, 1)T
h(w3) = (1, -1, -1)T
h(w4) = (-1, -1, 1)T
h(w5) = (1, 1, -1)T
按SimHash算法,要得到一個文檔向量
d=(w1=1, w2=2, w3=0, w4=3, w5=0) T的簽名,先要計(jì)算向量
m = 1h(w1) + 2h(w2) + 0h(w3) + 3h(w4) + 0*h(w5) = (-4, -2, 6) T,
然后根據(jù)SimHash算法的步驟3,得到最終的簽名s=001。
上面的計(jì)算步驟其實(shí)相當(dāng)于->先得到3個5維的向量:
第1個5維向量由h(w1),…,h(w5)的第1維組成:r1=(1,-1,1,-1,1) T;
第2個5維向量由h(w1),…,h(w5)的第2維組成:r2=(-1,1,-1,-1,1) T;
第3個5維向量由h(w1),…,h(w5)的第3維組成:r3=(1,1,-1,1,-1) T;
按隨機(jī)超平面算法的步驟2,分別求向量d與r1,r2,r3的點(diǎn)積:
d T r1=-4 < 0,所以s1=0;
d T r2=-2 < 0,所以s2=0;
d T r3=6 > 0,所以s3=1;
故最終的簽名s=001,與SimHash算法產(chǎn)生的結(jié)果是一致的。
從上面的計(jì)算過程可以看出,SimHash算法其實(shí)與隨機(jī)超平面Hash算法是相同的,SimHash算法得到的兩個簽名的海明距離,可以用來衡量原始向量的夾角。這其實(shí)是一種降維技術(shù),將高維的向量用較低維度的簽名來表征。衡量兩個內(nèi)容相似度,需要計(jì)算海明距離,這對給定簽名查找相似內(nèi)容的應(yīng)用來說帶來了一些計(jì)算上的困難。
SimHash算法的輸入是一個向量,輸出是一個 f 位的簽名值。為了表述方便,假設(shè)輸入的是一個文檔的特征集合,每個特征有一定的權(quán)重,比如特征可以是文檔中的詞,其權(quán)重可以是這個詞出現(xiàn)的次數(shù)。
二)、SimHash的計(jì)算原理
SimHash算法主要分為五個過程:分詞、Hash、加權(quán)、合并、降維。如圖實(shí)例[圖示-01]
1、分詞
對給定的一段文本進(jìn)行分詞,產(chǎn)生n個特征詞,并賦予每個特征詞一個權(quán)重。
2.Hash
通過hash函數(shù)對每一個詞向量進(jìn)行映射,產(chǎn)生一個n位二進(jìn)制串。
3.加權(quán)
經(jīng)過前面的計(jì)算已經(jīng)得到了每個詞向量的Hash串和該詞向量對應(yīng)的權(quán)重,第三步計(jì)算權(quán)重向量W=hash*weight。
4.合并
對于一個文本,計(jì)算出了文本分詞之后每一個特征詞的權(quán)重向量,在合并這個階段,把文本所有詞向量的權(quán)重向量相累加,得到一個新的權(quán)重向量。
5.降維
對于前面合并后得到的文本的權(quán)重向量,大于0的位置1,小于等于0的位置0,就可以得到該文本的SimHash值。
SimHash算法步驟如下:
1、將一個 f 維的向量 V 初始化為 0 ;f 位的二進(jìn)制數(shù) S 初始化為 0;
2、對每一個特征用傳統(tǒng)的 Hash 算法對該特征產(chǎn)生一個 f 位的簽名 b。對 i=1 到 f :
如果b 的第 i 位為 1 ,則 V 的第 i 個元素加上該特征的權(quán)重;
否則,V 的第 i 個元素減去該特征的權(quán)重;
3、如果 V 的第 i 個元素大于 0 ,則 S 的第 i 位為 1,否則為 0 ;
4,輸出 S 作為簽名;
Simhash算法原理圖如下:
三)、文本的相似度計(jì)算
對每條文本根據(jù)SimHash 算出簽名后,再計(jì)算兩個簽名的海明距離(兩個二進(jìn)制異或后1的個數(shù))即可。根據(jù)經(jīng)驗(yàn)值,對64位的SimHash,海明距離在3以內(nèi)的可以認(rèn)為相似度比較高。
假設(shè)對64位的SimHash,查找海明距離在3以內(nèi)的所有簽名。可以把64位的二進(jìn)制簽名均分成4塊,每塊16位。根據(jù)鴿巢原理(也稱抽屜原理,見組合數(shù)學(xué)),如果兩個簽名的海明距離在3以內(nèi),它們必有一塊完全相同。
把上面分成的4塊中的每一個塊分別作為前16位來進(jìn)行查找。建立倒排索引。
相似度算法原理圖如下:
如果庫中有234個(大概10億)簽名,那么匹配上每個塊的結(jié)果最多有2(34-16)=262144個候選結(jié)果(假設(shè)數(shù)據(jù)是均勻分布,16位的數(shù)據(jù),產(chǎn)生的像限為216個,則平均每個像限分布的文檔數(shù)則234/216=2(34-16)),四個塊返回的總結(jié)果數(shù)為4*262144(大概100萬)。原本需要比較10億次,經(jīng)過索引,大概就只需要處理100萬次了。由此可見,確實(shí)大大減少了計(jì)算量。、
三、Java通過SimHash計(jì)算文本內(nèi)容相似度代碼示例
一)、新增依賴包
<dependency><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>1.11.3</version> </dependency><dependency><groupId>com.hankcs</groupId><artifactId>hanlp</artifactId><version>portable-1.8.1</version> </dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.11</version> </dependency>二)、過濾特殊字符
/*** Description:[過濾特殊字符]** @return BigInteger* @date 2020-04-01* @author huazai*/private String clearSpecialCharacters(String topicName) {// 將內(nèi)容轉(zhuǎn)換為小寫topicName = StringUtils.lowerCase(topicName);// 過來HTML標(biāo)簽topicName = Jsoup.clean(topicName, Whitelist.none());// 過濾特殊字符String[] strings = {" ", "\n", "\r", "\t", "\\r", "\\n", "\\t", " ", "&", "<", ">", """, "&qpos;"};for (String string : strings) {topicName = topicName.replaceAll(string, "");}return topicName;}三)、計(jì)算單個分詞的Hash值
/*** Description:[計(jì)算單個分詞的hash值]** @return BigInteger* @date 2020-04-01* @author huazai*/private BigInteger getWordHash(String word) {if (StringUtils.isEmpty(word)) {// 如果分詞為null,則默認(rèn)hash為0return new BigInteger("0");} else {// 分詞補(bǔ)位,如果過短會導(dǎo)致Hash算法失敗while (word.length() < SimHashUtil.WORD_MIN_LENGTH) {word = word + word.charAt(0);}// 分詞位運(yùn)算char[] wordArray = word.toCharArray();BigInteger x = BigInteger.valueOf(wordArray[0] << 7);BigInteger m = new BigInteger("1000003");// 初始桶pow運(yùn)算BigInteger mask = new BigInteger("2").pow(this.hashCount).subtract(new BigInteger("1"));for (char item : wordArray) {BigInteger temp = BigInteger.valueOf(item);x = x.multiply(m).xor(temp).and(mask);}x = x.xor(new BigInteger(String.valueOf(word.length())));if (x.equals(ILLEGAL_X)) {x = new BigInteger("-2");}return x;}}四)、分詞計(jì)算向量
/*** Description:[分詞計(jì)算向量]** @return BigInteger* @date 2020-04-01* @author huazai*/private BigInteger simHash() {// 清除特殊字符this.topicName = this.clearSpecialCharacters(this.topicName);int[] hashArray = new int[this.hashCount];// 對內(nèi)容進(jìn)行分詞處理List<Term> terms = StandardTokenizer.segment(this.topicName);// 配置詞性權(quán)重Map<String, Integer> weightMap = new HashMap<>(16, 0.75F);weightMap.put("n", 1);// 設(shè)置停用詞Map<String, String> stopMap = new HashMap<>(16, 0.75F);stopMap.put("w", "");// 設(shè)置超頻詞上線Integer overCount = 5;// 設(shè)置分詞統(tǒng)計(jì)量Map<String, Integer> wordMap = new HashMap<>(16, 0.75F);for (Term term : terms) {// 獲取分詞字符串String word = term.word;// 獲取分詞詞性String nature = term.nature.toString();// 過濾超頻詞if (wordMap.containsKey(word)) {Integer count = wordMap.get(word);if (count > overCount) {continue;} else {wordMap.put(word, count + 1);}} else {wordMap.put(word, 1);}// 過濾停用詞if (stopMap.containsKey(nature)) {continue;}// 計(jì)算單個分詞的Hash值BigInteger wordHash = this.getWordHash(word);for (int i = 0; i < this.hashCount; i++) {// 向量位移BigInteger bitMask = new BigInteger("1").shiftLeft(i);// 對每個分詞hash后的列進(jìn)行判斷,例如:1000...1,則數(shù)組的第一位和末尾一位加1,中間的62位減一,也就是,逢1加1,逢0減1,一直到把所有的分詞hash列全部判斷完// 設(shè)置初始權(quán)重Integer weight = 1;if (weightMap.containsKey(nature)) {weight = weightMap.get(nature);}// 計(jì)算所有分詞的向量if (wordHash.and(bitMask).signum() != 0) {hashArray[i] += weight;} else {hashArray[i] -= weight;}}}// 生成指紋BigInteger fingerPrint = new BigInteger("0");for (int i = 0; i < this.hashCount; i++) {if (hashArray[i] >= 0) {fingerPrint = fingerPrint.add(new BigInteger("1").shiftLeft(i));}}return fingerPrint;}五)、獲取標(biāo)題內(nèi)容的海明距離
/*** Description:[獲取標(biāo)題內(nèi)容的海明距離]** @return Double* @date 2020-04-01* @author huazai*/private int getHammingDistance(SimHashUtil simHashUtil) {// 求差集BigInteger subtract = new BigInteger("1").shiftLeft(this.hashCount).subtract(new BigInteger("1"));// 求異或BigInteger xor = this.bigSimHash.xor(simHashUtil.bigSimHash).and(subtract);int total = 0;while (xor.signum() != 0) {total += 1;xor = xor.and(xor.subtract(new BigInteger("1")));}return total;}六)、獲取標(biāo)題內(nèi)容的相似度
/*** Description:[獲取標(biāo)題內(nèi)容的相似度]** @return Double* @date 2020-04-01* @author huazai*/public Double getSimilar(SimHashUtil simHashUtil) {// 獲取海明距離Double hammingDistance = (double) this.getHammingDistance(simHashUtil);// 求得海明距離百分比Double scale = (1 - hammingDistance / this.hashCount) * 100;Double formatScale = Double.parseDouble(String.format("%.2f", scale));return formatScale;}七)、測試
public static void main(String[] args) {// 準(zhǔn)備測試標(biāo)題內(nèi)容數(shù)據(jù)List<String> titleList = new ArrayList<>();titleList.add("有哪些養(yǎng)貓必須知道的冷知識");titleList.add("有哪些養(yǎng)貓必須知道的冷");titleList.add("有哪些養(yǎng)貓必須知道");titleList.add("有哪些養(yǎng)貓");titleList.add("有哪些");// 原始標(biāo)題內(nèi)容數(shù)據(jù)String originalTitle = "有哪些養(yǎng)貓必須知道的冷知識?";Map<String, Double> simHashMap = new HashMap<>(16, 0.75F);System.out.println("======================================");long startTime = System.currentTimeMillis();System.out.println("原始標(biāo)題:" + originalTitle);// 計(jì)算相似度titleList.forEach(title -> {SimHashUtil mySimHash_1 = new SimHashUtil(title, 64);SimHashUtil mySimHash_2 = new SimHashUtil(originalTitle, 64);Double similar = mySimHash_1.getSimilar(mySimHash_2);simHashMap.put(title, similar);});// 打印測試結(jié)果到控制臺/* simHashMap.forEach((title, similarity) -> {System.out.println("標(biāo)題:"+title+"-----------相似度:"+similarity);});*/// 按相標(biāo)題內(nèi)容排序輸出控制臺Set<String> titleSet = simHashMap.keySet();Object[] titleArrays = titleSet.toArray();Arrays.sort(titleArrays, Collections.reverseOrder());System.out.println("-------------------------------------");for (Object title : titleArrays) {System.out.println("標(biāo)題:" + title + "-----------相似度:" + simHashMap.get(title));}// 求得運(yùn)算時(shí)長(單位:毫秒)long endTime = System.currentTimeMillis();long totalTime = endTime - startTime;System.out.println("\n本次運(yùn)算總耗時(shí)" + totalTime + "毫秒");System.out.println("======================================");}八)、示例效果圖
九)、完整代碼示例
package com.b2c.aiyou.bbs.common.utils.hanlp;import com.hankcs.hanlp.seg.common.Term; import com.hankcs.hanlp.tokenizer.StandardTokenizer; import org.apache.commons.lang3.StringUtils; import org.jsoup.Jsoup; import org.jsoup.safety.Whitelist;import java.math.BigInteger; import java.util.*;/*** System: BBS論壇系統(tǒng)* Department: 研發(fā)一組* Title: [aiyou-bbs — SimHashUtil 模塊]* Description: [SimHash 標(biāo)題內(nèi)容相似度算法工具類]* Created on: 2020-04-01* Contacts: [who.seek.me@java98k.vip]** @author huazai* @version V1.1.0*/ public class SimHashUtil {/*** 標(biāo)題名稱*/private String topicName;/*** 分詞向量*/private BigInteger bigSimHash;/*** 初始桶大小*/private Integer hashCount = 64;/*** 分詞最小長度限制*/private static final Integer WORD_MIN_LENGTH = 3;private static final BigInteger ILLEGAL_X = new BigInteger("-1");public SimHashUtil(String topicName, Integer hashCount) {this.topicName = topicName;this.bigSimHash = this.simHash();this.hashCount = hashCount;}/*** Description:[分詞計(jì)算向量]** @return BigInteger* @date 2020-04-01* @author huazai*/private BigInteger simHash() {// 清除特殊字符this.topicName = this.clearSpecialCharacters(this.topicName);int[] hashArray = new int[this.hashCount];// 對內(nèi)容進(jìn)行分詞處理List<Term> terms = StandardTokenizer.segment(this.topicName);// 配置詞性權(quán)重Map<String, Integer> weightMap = new HashMap<>(16, 0.75F);weightMap.put("n", 1);// 設(shè)置停用詞Map<String, String> stopMap = new HashMap<>(16, 0.75F);stopMap.put("w", "");// 設(shè)置超頻詞上線Integer overCount = 5;// 設(shè)置分詞統(tǒng)計(jì)量Map<String, Integer> wordMap = new HashMap<>(16, 0.75F);for (Term term : terms) {// 獲取分詞字符串String word = term.word;// 獲取分詞詞性String nature = term.nature.toString();// 過濾超頻詞if (wordMap.containsKey(word)) {Integer count = wordMap.get(word);if (count > overCount) {continue;} else {wordMap.put(word, count + 1);}} else {wordMap.put(word, 1);}// 過濾停用詞if (stopMap.containsKey(nature)) {continue;}// 計(jì)算單個分詞的Hash值BigInteger wordHash = this.getWordHash(word);for (int i = 0; i < this.hashCount; i++) {// 向量位移BigInteger bitMask = new BigInteger("1").shiftLeft(i);// 對每個分詞hash后的列進(jìn)行判斷,例如:1000...1,則數(shù)組的第一位和末尾一位加1,中間的62位減一,也就是,逢1加1,逢0減1,一直到把所有的分詞hash列全部判斷完// 設(shè)置初始權(quán)重Integer weight = 1;if (weightMap.containsKey(nature)) {weight = weightMap.get(nature);}// 計(jì)算所有分詞的向量if (wordHash.and(bitMask).signum() != 0) {hashArray[i] += weight;} else {hashArray[i] -= weight;}}}// 生成指紋BigInteger fingerPrint = new BigInteger("0");for (int i = 0; i < this.hashCount; i++) {if (hashArray[i] >= 0) {fingerPrint = fingerPrint.add(new BigInteger("1").shiftLeft(i));}}return fingerPrint;}/*** Description:[計(jì)算單個分詞的hash值]** @return BigInteger* @date 2020-04-01* @author huazai*/private BigInteger getWordHash(String word) {if (StringUtils.isEmpty(word)) {// 如果分詞為null,則默認(rèn)hash為0return new BigInteger("0");} else {// 分詞補(bǔ)位,如果過短會導(dǎo)致Hash算法失敗while (word.length() < SimHashUtil.WORD_MIN_LENGTH) {word = word + word.charAt(0);}// 分詞位運(yùn)算char[] wordArray = word.toCharArray();BigInteger x = BigInteger.valueOf(wordArray[0] << 7);BigInteger m = new BigInteger("1000003");// 初始桶pow運(yùn)算BigInteger mask = new BigInteger("2").pow(this.hashCount).subtract(new BigInteger("1"));for (char item : wordArray) {BigInteger temp = BigInteger.valueOf(item);x = x.multiply(m).xor(temp).and(mask);}x = x.xor(new BigInteger(String.valueOf(word.length())));if (x.equals(ILLEGAL_X)) {x = new BigInteger("-2");}return x;}}/*** Description:[過濾特殊字符]** @return BigInteger* @date 2020-04-01* @author huazai*/private String clearSpecialCharacters(String topicName) {// 將內(nèi)容轉(zhuǎn)換為小寫topicName = StringUtils.lowerCase(topicName);// 過來HTML標(biāo)簽topicName = Jsoup.clean(topicName, Whitelist.none());// 過濾特殊字符String[] strings = {" ", "\n", "\r", "\t", "\\r", "\\n", "\\t", " ", "&", "<", ">", """, "&qpos;"};for (String string : strings) {topicName = topicName.replaceAll(string, "");}return topicName;}/*** Description:[獲取標(biāo)題內(nèi)容的相似度]** @return Double* @date 2020-04-01* @author huazai*/public Double getSimilar(SimHashUtil simHashUtil) {// 獲取海明距離Double hammingDistance = (double) this.getHammingDistance(simHashUtil);// 求得海明距離百分比Double scale = (1 - hammingDistance / this.hashCount) * 100;Double formatScale = Double.parseDouble(String.format("%.2f", scale));return formatScale;}/*** Description:[獲取標(biāo)題內(nèi)容的海明距離]** @return Double* @date 2020-04-01* @author huazai*/private int getHammingDistance(SimHashUtil simHashUtil) {// 求差集BigInteger subtract = new BigInteger("1").shiftLeft(this.hashCount).subtract(new BigInteger("1"));// 求異或BigInteger xor = this.bigSimHash.xor(simHashUtil.bigSimHash).and(subtract);int total = 0;while (xor.signum() != 0) {total += 1;xor = xor.and(xor.subtract(new BigInteger("1")));}return total;}}總結(jié)
以上是生活随笔為你收集整理的Java--SimHash实现文本标题内容相似度计算的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: qt界面和python怎么交互_pyth
- 下一篇: 【Java语言基础】1.3 Java补充