日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

SimHash算法原理与应用(Java版)

發布時間:2024/3/26 java 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SimHash算法原理与应用(Java版) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

引言

項目中原使用的文本對比算法是使用MD5 Hash的方法。MD5 Hash算法簡單來說是指對于任何長度的文本都可生成一段128bit長度的字符串,相同文本生成的Hash字符串是相同的,因此可用來比較文本是否相同。

但這種傳統的Hash算法,對于文本的查找效率是很低的,另外文本間的相似度計算是很困難,因為即使改動文本的一個字符,得到的Hash結果也是完全不同的。因此在新項目中考慮用新的算法去做,對此作了一些技術調研,也收獲了一些更好的方法。接下來會在系列博客中總結一些成果。

簡要介紹

通過引言,我們已經知道傳統Hash的局限性,因此,接下來引入一個名詞“局部敏感哈希”。

與傳統的Hash不同,局部敏感哈希是一種解決在海量的高維數據集中查找與查詢數據點(query data point)近似最相鄰的某個或某些數據點的方法。常用的方法包括:歐式距離、余弦距離、海明距離、Jaccard相似度等。本篇博客將介紹的SimHash算法就屬于一種局部敏感哈希算法,利用海明距離比較內容之間的相似度。

SimHash是Google用來處理海量文本去重的算法。主要思想是降維,將高維的特征向量轉化為一個f位的指紋,通過算出兩個指紋的海明距離來確定文本的相似度,海明距離越小,相似度越低。

計算原理

  • 分詞:將處理后的文本(去除特殊字符等)進行分詞,可為分詞設置權重,得到分詞向量
  • 計算:通過Hash函數計算每個分詞向量的Hash值,值為二進制串
  • 加權:計算權重向量=每個分詞的hash*該詞對應的權重weight
  • 合并:將所有分詞的權重向量累加,得到一個新的權重向量
  • 降維:對上述合并后得到的權重向量,大于0的位置為1,小于等于0的位置為0,從而得到文本的simHash值
  • 核心代碼

    1. 文本處理,過濾特殊標簽,符號統一為半角比較
    /*** 全角轉半角** @param text* @return*/ public static String toDBC(String text) {char chars[] = text.toCharArray();for (int i = 0; i < chars.length; i++) {if (chars[i] == '\u3000') {chars[i] = ' ';} else if (chars[i] > '\uFF00' && chars[i] < '\uFF5F') {chars[i] = (char) (chars[i] - 65248);}}return new String(chars); }/*** 去除特殊符號* @param text 文本內容* @return*/ private String clearCharacters(String text) {// 將內容轉換為小寫text = StringUtils.lowerCase(text);// 過來HTML標簽text = Jsoup.clean(text, Whitelist.none());// 過濾特殊字符String[] strings = {" ", "\n", "\r", "\t", "\\r", "\\n", "\\t", "&nbsp;", "&amp;", "&lt;", "&gt;", "&quot;", "&qpos;"};for (String string : strings) {text = text.replaceAll(string, "");}//符號轉換text = toDBC(text);//去空格text = StringUtils.deleteWhitespace(text);return text; }
    2. 文本分詞,配置分詞權重,計算每個分詞的Hash值,合并分詞向量,得到Hash值
    /*** 計算分詞Hash,合并分詞向量,得到文本Hash* @param word * @return*/ public BigInteger simHash() {// 對內容進行分詞處理List<Term> terms = StandardTokenizer.segment(this.text);// 配置詞性權重Map<String, Integer> weightMap = new HashMap<>(16, 0.75F);weightMap.put("n", 1);// 設置停用詞Map<String, String> stopMap = new HashMap<>(16, 0.75F);stopMap.put("w", "");// 設置超頻詞上線Integer overCount = 5;// 設置分詞統計量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;}// 計算單個分詞的Hash值BigInteger wordHash = this.countHash(word);for (int i = 0; i < this.hashCount; i++) {// 向量位移BigInteger bitMask = new BigInteger("1").shiftLeft(i);// 對每個分詞hash后的列進行判斷,例如:1000...1,則數組的第一位和末尾一位加1,中間的62位減一,也就是,逢1加1,逢0減1,一直到把所有的分詞hash列全部判斷完// 設置初始權重Integer weight = 1;if (weightMap.containsKey(nature)) {weight = weightMap.get(nature);}// 計算所有分詞的向量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; }/*** 計算每個分詞的Hash* @param word * @return*/ private BigInteger countHash(String word) {if (StringUtils.isEmpty(word)) {// 如果分詞為null,則默認hash為0return new BigInteger("0");} else {// 分詞補位,如果過短會導致Hash算法失敗while (word.length() < SimHashUtil.WORD_MIN_LENGTH) {word = word + word.charAt(0);}// 分詞位運算char[] wordArray = word.toCharArray();BigInteger x = BigInteger.valueOf(wordArray[0] << 7);BigInteger m = new BigInteger("1000003");// 初始桶pow運算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;} }
    3. 獲取文本的海明距離
    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; }
    4. 文本間海明距離的比較
    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; }

    測試結果

    對于任意一些文本,測試結果如下:

    通過多次測試結果發現,該算法對于語意相同、文本較小差異的調整,比如文字順序的修改、個別字的增加刪減,得到的相似度結果都是百分之百。因此更適用于文本比較結果不要求每一個字符都精確完全相同的場景。

    參考資料

    局部敏感哈希介紹

    使用SimHash進行海量文本去重

    總結

    以上是生活随笔為你收集整理的SimHash算法原理与应用(Java版)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。