Java实现标题相似度计算,文本内容相似度匹配,Java通过SimHash计算标题文本内容相似度

2023-11-15

 目录

一、前言

二、关于SimHash

补充知识

一)、什么是海明距离

二)、海明距离的应用

三)、什么是编辑距离

三、SimHash算法的几何意义和原理

一)、SimHash算法的几何意义

二)、SimHash的计算原理

 三)、文本的相似度计算

四、Java通过SimHash计算文本内容相似度代码示例

一)、新增依赖包

二)、过滤特殊字符

三)、计算单个分词的Hash值

四)、分词计算向量

五)、获取标题内容的海明距离

六)、获取标题内容的相似度

七)、测试

八)、示例效果图

九)、完整代码示例

五、结束语



一、前言

在项目开发过程中,遇到了一个需求,要从数量庞大的文本中,计算出文本的相似度并做分类,一般做法都是两两比较相似度,但是这对相似度计算算法的耗时就比较长了,一旦数据量庞大,就能立竿见影。呃,两两相比这个可能不太适合做项目或产品使用,玩玩还可以。如果遇到数量庞大的文本,既要快速且高效的计算出文本的相似度并做分类,如何才能实现呢?这,就是本文博主需要表达的。
通过本文的学习,将很容易理解并实现大量文本之间的相似度计算,在本文中博主通过SimHash算法并结合实际的代码来实现了问帮相似度的计算,当然分类的话就需要根据具体的需求来进行划分了,只要计算出相似度了,剩下的都简单。

二、关于SimHash

SimHash算法是Google在2007年发表的论文《Detecting Near-Duplicates for Web Crawling》中提到的一种指纹生成算法,被应用在Google搜索引擎网页去重的工作之中。SimHash值不但提供了原始值是否相等这一信息,还能通过该值计算出内容的差异程度。

简单的说,SimHash算法主要的工作就是将文本进行降维,生成一个SimHash值,也就是论文中所提及的“指纹”,通过对不同文本的SimHash值进而比较“海明距离”,从而判断两个文本的相似度。

对于文本相似度的问题,常见的解决办法有欧式距离、编辑距离、最长公共子串、余弦算法、Jaccard相似度等方法。但是这些方法并不能对海量数据高效的处理。

比如说,在搜索引擎中,会有很多相似的关键词,用户所需要获取的内容是相似的,但是搜索的关键词却是不同的,例如:
“有哪些是养猫必须知道的冷知识?”
“有那些冷知识是养猫必须知道的?”
以上两个可以等价的关键词,然而通过普通的hash计算,会产生两个相差甚远的hash串。而通过SimHash计算得到的Hash串会非常的相近,从而可以判断两个文本的相似程度。

补充知识

一)、什么是海明距离

海明距离是编辑距离其中之一,在信息编码中,两个合法代码对应位上编码不同的位数称为码距,又称海明距离。即两个码字的对应比特取值不同的比特数称为这两个码字的海明距离。一个有效编码集中任意两个码字的海明距离的最小值称为该编码集的海明距离。例如: 10101 和 00110 从第一位开始依次有第一位、第四、第五位不同,则海明距离为 3。
N位的码字可以用N维空间的超立方体的一个顶点来表示,两个码字之间的海明距离就是超立方体两个顶点之间的一条边,而且是这两个顶点之间的最短距离。

二)、海明距离的应用

海明距离应用最多的是在海量短文、文本去重上,以其性能优的特点。海明距离主要就是对文本进行向量化,或者说是把文本的特征抽取出来映射成编码,然后再对编码进行异或计算出海明距离。

三)、什么是编辑距离

编辑距离又称Levenshtein距离(也叫做Edit Distance),是指两个字串之间,由一个转成另一个所需的最少编辑操作次数,许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。

三、SimHash算法的几何意义和原理

一)、SimHash算法的几何意义

它首先将每一个特征映射为f维空间的一个向量,这个映射规则具体是怎样并不重要,只要对很多不同的特征来说,它们对所对应的向量是均匀随机分布的,并且对相同的特征来说对应的向量是唯一的就行。比如一个特征的4位Hash签名的二进制表示为1010,那么这个特征对应的4维向量就是(1,-1,1,-1)T,即Hash签名的某一位为1,映射到的向量的对应位就为1,否则为-1。然后,将一个文档中所包含的各个特征对应的向量加权求和,加权的系数等于该特征的权重。得到的和向量即表征了这个文档,我们可以用向量之间的夹角来衡量对应文档之间的相似度。最后,为了得到一个f位的签名,需要进一步将其压缩,如果和向量的某一维大于0,则最终签名的对应位为1,否则为0。这样的压缩相当于只留下了和向量所在的象限这个信息,而64位的签名可以表示多达264个象限,因此只保存所在象限的信息也足够表征一个文档了。

明确了SimHash算法的几何意义,使这个算法直观上看来是合理的。但是,为何最终得到的签名相近的程度,即可以衡量原始文档的相似程度呢。在SimHash的发明人Charikar的论文中并没有给出具体的SimHash算法和证明,以下列出证明思路:

SimHash是由随机超平面Hash算法演变而来的,随机超平面Hash算法非常简单,对于一个n维向量v,要得到一个f位的签名(f<<n),算法如下: 
1、随机产生f个n维的向量r1,…rf;
2、对每一个向量ri,如果v与ri的点积大于0,则最终签名的第i位为1,否则为0;

这个算法相当于随机产生了f个n维超平面,每个超平面将向量v所在的空间一分为二,v在这个超平面上方则得到一个1,否则得到一个0,然后将得到的 f个0或1组合起来成为一个f维的签名。如果两个向量u,v的夹角为θ,则一个随机超平面将它们分开的概率为θ/π,因此u, v的签名的对应位不同的概率等于θ/π。所以,我们可以用两个向量的签名的不同的对应位的数量,即海明距离,来衡量这两个向量的差异程度。

SimHash算法与随机超平面Hash如何联系起来?在SimHash算法中,并没有直接产生用于分割空间的随机向量,而是间接产生->第 k个特征的Hash签名的第i位拿出来,如果为0,则改为-1,如果为1则不变,作为第i个随机向量的第k维。由于hash签名是f位的,因此这样能产生 f个随机向量,对应f个随机超平面。下面举个例子: 
假设用5个特征w1,…,w5来表示所有文档,现要得到任意文档的一个3维签名。
假设这5个特征对应的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的签名,先要计算向量
m = 1*h(w1) + 2*h(w2) + 0*h(w3) + 3*h(w4) + 0*h(w5) = (-4, -2, 6) T,
然后根据SimHash算法的步骤3,得到最终的签名s=001。 

上面的计算步骤其实相当于->先得到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;

按随机超平面算法的步骤2,分别求向量d与r1,r2,r3的点积: 
d T r1=-4 < 0,所以s1=0; 
d T r2=-2 < 0,所以s2=0; 
d T r3=6 > 0,所以s3=1;
故最终的签名s=001,与SimHash算法产生的结果是一致的。 

从上面的计算过程可以看出,SimHash算法其实与随机超平面Hash算法是相同的,SimHash算法得到的两个签名的海明距离,可以用来衡量原始向量的夹角。这其实是一种降维技术,将高维的向量用较低维度的签名来表征。衡量两个内容相似度,需要计算海明距离,这对给定签名查找相似内容的应用来说带来了一些计算上的困难。

SimHash算法的输入是一个向量,输出是一个 f 位的签名值。为了表述方便,假设输入的是一个文档的特征集合,每个特征有一定的权重,比如特征可以是文档中的词,其权重可以是这个词出现的次数。

二)、SimHash的计算原理

SimHash算法主要分为五个过程:分词、Hash、加权、合并、降维。如图实例[图示-01]
1、分词
对给定的一段文本进行分词,产生n个特征词,并赋予每个特征词一个权重。

2.Hash
通过hash函数对每一个词向量进行映射,产生一个n位二进制串。

3.加权
经过前面的计算已经得到了每个词向量的Hash串和该词向量对应的权重,第三步计算权重向量W=hash*weight。

4.合并
对于一个文本,计算出了文本分词之后每一个特征词的权重向量,在合并这个阶段,把文本所有词向量的权重向量相累加,得到一个新的权重向量。

5.降维
对于前面合并后得到的文本的权重向量,大于0的位置1,小于等于0的位置0,就可以得到该文本的SimHash值。

SimHash算法步骤如下: 
1、将一个 f 维的向量 V 初始化为 0 ;f 位的二进制数 S 初始化为 0;
2、对每一个特征用传统的 Hash 算法对该特征产生一个 f 位的签名 b。对 i=1 到 f : 
如果b 的第 i 位为 1 ,则 V 的第 i 个元素加上该特征的权重; 
否则,V 的第 i 个元素减去该特征的权重;
3、如果 V 的第 i 个元素大于 0 ,则 S 的第 i 位为 1,否则为 0 ; 
4,输出 S 作为签名;

Simhash算法原理图如下:

 三)、文本的相似度计算

对每条文本根据SimHash 算出签名后,再计算两个签名的海明距离(两个二进制异或后1的个数)即可。根据经验值,对64位的SimHash,海明距离在3以内的可以认为相似度比较高。
假设对64位的SimHash,查找海明距离在3以内的所有签名。可以把64位的二进制签名均分成4块,每块16位。根据鸽巢原理(也称抽屉原理,见组合数学),如果两个签名的海明距离在3以内,它们必有一块完全相同。
把上面分成的4块中的每一个块分别作为前16位来进行查找。建立倒排索引。

相似度算法原理图如下:

如果库中有2^34个(大概10亿)签名,那么匹配上每个块的结果最多有2^(34-16)=262144个候选结果(假设数据是均匀分布,16位的数据,产生的像限为2^16个,则平均每个像限分布的文档数则2^34/2^16=2^(34-16)),四个块返回的总结果数为4*262144(大概100万)。原本需要比较10亿次,经过索引,大概就只需要处理100万次了。由此可见,确实大大减少了计算量。、

四、Java通过SimHash计算文本内容相似度代码示例

一)、新增依赖包


        <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) {

        // 将内容转换为小写
        topicName = StringUtils.lowerCase(topicName);

        // 过来HTML标签
        topicName = Jsoup.clean(topicName, Whitelist.none());

        // 过滤特殊字符
        String[] strings = {" ", "\n", "\r", "\t", "\\r", "\\n", "\\t", "&nbsp;", "&amp;", "&lt;", "&gt;", "&quot;", "&qpos;"};
        for (String string : strings) {
            topicName = topicName.replaceAll(string, "");
        }

        return topicName;
    }

三)、计算单个分词的Hash值

    /**
     * Description:[计算单个分词的hash值]
     *
     * @return BigInteger
     * @date 2020-04-01
     * @author huazai
     */
    private BigInteger getWordHash(String word) {

        if (StringUtils.isEmpty(word)) {

            // 如果分词为null,则默认hash为0
            return 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;
        }
    }

四)、分词计算向量

    /**
     * Description:[分词计算向量]
     *
     * @return BigInteger
     * @date 2020-04-01
     * @author huazai
     */
    private BigInteger simHash() {

        // 清除特殊字符
        this.topicName = this.clearSpecialCharacters(this.topicName);
        int[] hashArray = new int[this.hashCount];

        // 对内容进行分词处理
        List<Term> terms = StandardTokenizer.segment(this.topicName);

        // 配置词性权重
        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.getWordHash(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;
    }

五)、获取标题内容的海明距离

    /**
     * Description:[获取标题内容的海明距离]
     *
     * @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;
    }

六)、获取标题内容的相似度

    /**
     * Description:[获取标题内容的相似度]
     *
     * @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) {
        // 准备测试标题内容数据
        List<String> titleList = new ArrayList<>();
        titleList.add("有哪些养猫必须知道的冷知识");
        titleList.add("有哪些养猫必须知道的冷");
        titleList.add("有哪些养猫必须知道");
        titleList.add("有哪些养猫");
        titleList.add("有哪些");

        // 原始标题内容数据
        String originalTitle = "有哪些养猫必须知道的冷知识?";

        Map<String, Double> simHashMap = new HashMap<>(16, 0.75F);

        System.out.println("======================================");
        long startTime = System.currentTimeMillis();
        System.out.println("原始标题:" + originalTitle);

        // 计算相似度
        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);
        });

        // 打印测试结果到控制台
        /* simHashMap.forEach((title, similarity) -> {
            System.out.println("标题:"+title+"-----------相似度:"+similarity);
        });*/

        // 按相标题内容排序输出控制台
        Set<String> titleSet = simHashMap.keySet();
        Object[] titleArrays = titleSet.toArray();
        Arrays.sort(titleArrays, Collections.reverseOrder());

        System.out.println("-------------------------------------");
        for (Object title : titleArrays) {
            System.out.println("标题:" + title + "-----------相似度:" + simHashMap.get(title));
        }

        // 求得运算时长(单位:毫秒)
        long endTime = System.currentTimeMillis();
        long totalTime = endTime - startTime;
        System.out.println("\n本次运算总耗时" + 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论坛系统
 * Department:  研发一组
 * Title:       [aiyou-bbs — SimHashUtil 模块]
 * Description: [SimHash 标题内容相似度算法工具类]
 * Created on:  2020-04-01
 * Contacts:    [who.seek.me@java98k.vip]
 *
 * @author huazai
 * @version V1.1.0
 */
public class SimHashUtil {

    /**
     * 标题名称
     */
    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:[分词计算向量]
     *
     * @return BigInteger
     * @date 2020-04-01
     * @author huazai
     */
    private BigInteger simHash() {

        // 清除特殊字符
        this.topicName = this.clearSpecialCharacters(this.topicName);
        int[] hashArray = new int[this.hashCount];

        // 对内容进行分词处理
        List<Term> terms = StandardTokenizer.segment(this.topicName);

        // 配置词性权重
        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.getWordHash(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;
    }

    /**
     * Description:[计算单个分词的hash值]
     *
     * @return BigInteger
     * @date 2020-04-01
     * @author huazai
     */
    private BigInteger getWordHash(String word) {

        if (StringUtils.isEmpty(word)) {

            // 如果分词为null,则默认hash为0
            return 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;
        }
    }

    /**
     * Description:[过滤特殊字符]
     *
     * @return BigInteger
     * @date 2020-04-01
     * @author huazai
     */
    private String clearSpecialCharacters(String topicName) {

        // 将内容转换为小写
        topicName = StringUtils.lowerCase(topicName);

        // 过来HTML标签
        topicName = Jsoup.clean(topicName, Whitelist.none());

        // 过滤特殊字符
        String[] strings = {" ", "\n", "\r", "\t", "\\r", "\\n", "\\t", "&nbsp;", "&amp;", "&lt;", "&gt;", "&quot;", "&qpos;"};
        for (String string : strings) {
            topicName = topicName.replaceAll(string, "");
        }

        return topicName;
    }

    /**
     * Description:[获取标题内容的相似度]
     *
     * @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:[获取标题内容的海明距离]
     *
     * @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;
    }

}

五、结束语

关于SimHash算法计算文本相似问题就写到这儿了,当然,有兴趣伙伴,还可以试下余弦算法、欧式距离、海明距离三者在文本相似度计算方面的性能,
测试性能例举:
10W文本的计算性能值比;
1000W文本的计算性能值比;
3000W文本的计算性能值比;
5000W文本的计算性能值比;

参考文献:

simhash-java


 好了,关于 Java实现标题相似度计算,文本内容相似度匹配,Java通过SimHash计算标题文本内容相似度 就写到这儿了,如果还有什么疑问或遇到什么问题欢迎扫码提问,也可以给我留言哦,我会一一详细的解答的。 
歇后语:“ 共同学习,共同进步 ”,也希望大家多多关注CSND的IT社区。


作       者: 华    仔
联系作者: who.seek.me@java98k.vip
来        源: CSDN (Chinese Software Developer Network)
原        文: https://blog.csdn.net/Hello_World_QWP/article/details/122842339
版权声明: 本文为博主原创文章,请在转载时务必注明博文出处!
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java实现标题相似度计算,文本内容相似度匹配,Java通过SimHash计算标题文本内容相似度 的相关文章

  • 不同帐户上的 Spring Boot、JmsListener 和 SQS 队列

    我正在尝试开发一个 Spring Boot 1 5 应用程序 该应用程序需要侦听来自两个不同 AWS 帐户的 SQS 队列 是否可以使用 JmsListener 注解创建监听器 我已检查权限是否正确 我可以使用 getQueueUrl 获取
  • Junit:如何测试从属性文件读取属性的方法

    嗨 我有课ReadProperty其中有一个方法ReadPropertyFile返回类型的Myclass从属性文件读取参数值并返回Myclass目的 我需要帮助来测试ReadPropertyFile方法与JUnit 如果可能的话使用模拟文件
  • 在内存中使用 byte[] 创建 zip 文件。 Zip 文件总是损坏

    我创建的 zip 文件有问题 我正在使用 Java 7 我尝试从字节数组创建一个 zip 文件 其中包含两个或多个 Excel 文件 应用程序始终完成 没有任何异常 所以 我以为一切都好 当我尝试打开 zip 文件后 Windows 7 出
  • 为什么 JTables 使 TableModel 在呈现时不可序列化?

    所以最近我正在开发一个工具 供我们配置某些应用程序 它不需要是什么真正令人敬畏的东西 只是一个具有一些 SQL 脚本生成功能并创建几个 XML 文件的基本工具 在此期间 我使用自己的 AbstractTableModel 实现创建了一系列
  • 动态选择端口号?

    在 Java 中 我需要获取端口号以在同一程序的多个实例之间进行通信 现在 我可以简单地选择一些固定的数字并使用它 但我想知道是否有一种方法可以动态选择端口号 这样我就不必打扰我的用户设置端口号 这是我的一个想法 其工作原理如下 有一个固定
  • org.apache.hadoop.security.AccessControlException:客户端无法通过以下方式进行身份验证:[TOKEN,KERBEROS] 问题

    我正在使用 java 客户端通过 Kerberos 身份验证安全访问 HDFS 我尝试打字klist在服务器上 它显示已经存在的有效票证 我收到的异常是客户端无法通过以下方式进行身份验证 TOKEN KERBEROS 帮助将不胜感激 这是一
  • 如何在 Spring 中禁用使用 @Component 注释创建 bean?

    我的项目中有一些用于重构逻辑的通用接口 它看起来大约是这样的 public interface RefactorAwareEntryPoint default boolean doRefactor if EventLogService wa
  • 如何更改javaFX中按钮的图像?

    我正在使用javaFX 我制作了一个按钮并为此设置了图像 代码是 Image playI new Image file c Users Farhad Desktop icons play2 jpg ImageView iv1 new Ima
  • 在接口中使用默认方法是否违反接口隔离原则?

    我正在学习 SOLID 原则 ISP 指出 客户端不应被迫依赖于他们所使用的接口 不使用 在接口中使用默认方法是否违反了这个原则 我见过类似的问题 但我在这里发布了一个示例 以便更清楚地了解我的示例是否违反了 ISP 假设我有这个例子 pu
  • Java 集合的并集或交集

    建立并集或交集的最简单方法是什么Set在 Java 中 我见过这个简单问题的一些奇怪的解决方案 例如手动迭代这两个集合 最简单的单行解决方案是这样的 set1 addAll set2 Union set1 retainAll set2 In
  • java.lang.IllegalStateException:提交响应后无法调用 sendRedirect()

    这两天我一直在尝试找出问题所在 我在这里读到我应该在代码中添加一个返回 我做到了 但我仍然得到 java lang IllegalStateException Cannot call sendRedirect after the respo
  • 将 MOXy 设置为 JAXB 提供程序,而在同一包中没有属性文件

    我正在尝试使用 MOXy 作为我的 JAXB 提供程序 以便将内容编组 解组到 XML JSON 中 我创建了 jaxb properties 文件 内容如下 javax xml bind context factory org eclip
  • 在 junit 测试中获取 javax.lang.model.element.Element 类

    我想测试我的实用程序类 ElementUtils 但我不知道如何将类作为元素获取 在 AnnotationProcessors 中 我使用以下代码获取元素 Set
  • 如何对不同的参数类型使用相同的java方法?

    我的问题 我有 2 个已定义的记录 创建对象请求 更新对象请求 必须通过实用方法进行验证 由于这两个对象具有相同的字段 因此可以对这两种类型应用相同的验证方法 现在我只是使用两种方法进行重载 但它很冗长 public record Crea
  • 如何访问JAR文件中的Maven资源? [复制]

    这个问题在这里已经有答案了 我有一个使用 Maven 构建的 Java 应用程序 我有一个资源文件夹com pkg resources 我需要从中访问文件 例如directory txt 我一直在查看各种教程和其他答案 但似乎没有一个对我有
  • 使用 AsyncTask 传递值

    我一直在努力解决这个问题 但我已经到了不知道该怎么办的地步 我想做的是使用一个类下载文件并将其解析为字符串 然后将该字符串发送到另一个类来解析 JSON 内容 所有部件都可以单独工作 并且我已经单独测试了所有部件 我只是不知道如何将值发送到
  • 如何使用 jUnit 将测试用例添加到套件中?

    我有 2 个测试类 都扩展了TestCase 每个类都包含一堆针对我的程序运行的单独测试 如何将这两个类 以及它们拥有的所有测试 作为同一套件的一部分执行 我正在使用 jUnit 4 8 在 jUnit4 中你有这样的东西 RunWith
  • 找不到符号 NOTIFICATION_SERVICE?

    package com test app import android app Notification import android app NotificationManager import android app PendingIn
  • 使用反射覆盖最终静态字段是否有限制?

    在我的一些单元测试中 我在最终静态字段上的反射中遇到了奇怪的行为 下面是说明我的问题的示例 我有一个基本的 Singleton 类 其中包含一个 Integer public class BasicHolder private static
  • 双枢轴快速排序和快速排序有什么区别?

    我以前从未见过双枢轴快速排序 是快速排序的升级版吗 双枢轴快速排序和快速排序有什么区别 我在 Java 文档中找到了这个 排序算法是双枢轴快速排序 作者 弗拉基米尔 雅罗斯拉夫斯基 乔恩 本特利和约书亚 布洛赫 这个算法 在许多数据集上提供

随机推荐

  • Java可变参数Object... args

    文章目录 引言 一 方法重载 二 Object args 三 Object args 3 1 定义 3 2 调用 3 3 处理 3 4 传参 3 5 泛型 3 6 重载 参考 引言 因为Java要求实参 Arguments 和形参 Para
  • 打造高大上的Canvas粒子动画

    首先来看下我们准备要做的粒子动画效果是怎么样的 是这样 或者是这样 甚至是这样 很酷炫 那如何去实现类似上面的粒子动画甚至根据自己的喜好去做更多其他轨迹的动画呢 请看下面详细的讲解 技术选择 因为粒子数量很多 而且涉及到图像像素处理 所以这
  • Linux终端与SSH

    1 终端 当在我们需要操作服务器时要接上显示器和键盘 在键盘上输入 可以在显示器上看到 我们把 显示器 键盘 鼠标等这些统称为 输入 输出的终端设备 或者把显示器和键盘这些统称作 终端 当我们通过终端设备连接到服务器上并打开这些设备 如显示
  • ES6 新增的循环方法

    在 ES6 ECMAScript 2015 中 新增了一些循环方法 这些方法可以帮助我们更方便地遍历数组 字符串 Set Map 等数据结构 本文将介绍一些常用的 ES6 循环方法 for of 循环 for of 循环是一种遍历可迭代对象
  • 防火墙安全策略①

    防火墙 基本定义 防火墙是一种隔离 非授权用户在区域间 并过滤 对受保护网络有害流量或数据包 的设备 防火墙主要是借助硬件和软件的作用于内部和外部网络的环境间产生一种保护的屏障 从而实现对计算机不安全网络因素的阻断 只有在防火墙同意情况下
  • 如何将类数组转换为真正的数组

    开发过程中 有很多时候获取到的是类似数组的对象 比如元素的集合 elementcollection nodeList 以及今天开发时发现classList也是类数组 有时我们需要类数组去调用数组的方法 怎么办 一 遍历类数组 依次将元素放入
  • vue使用高德地图获取当前经纬度

    vue使用高德地图Api获取当前经纬度信息 在utils里面新建getLocation js 封装获取经纬度的公用方法 优化加载速度动态cdn引入高德地图 由于高德Api方法获取当前经纬度比较慢 如果需求是在获取到当前经纬度数据之后请求一些
  • JDBC数据库连接MySQL5.7

    1 首先准备mysql 和eclipse环境 在环境搭建好之后 从eclipse官网下载jdbc的驱动包 下载地址http dev mysql com downloads connector j 2 从下载的文件中取出mysql conne
  • 做电商的您还在为淘宝问题订单备注分类烦恼吗?

    做电商的您还在为淘宝问题订单备注分类烦恼吗 能够自动给淘宝订单添加旗子类型与备注的电商运营机器人 实现轻松管理淘宝问题订单 支持自动给订单添加旗子类型 自动给订单添加备注 解决问题订单处理缓慢 备注修改不及时等难题 使用UiBot之前 问题
  • Python javascript操作DOM

    文档对象模型 Document Object Model DOM 是一种用于HTML和XML文档的编程接口 它给文档提供了一种结构化的表示方法 可以改变文档的内容和呈现方式 我们最为关心的是 DOM把网页和脚本以及其他的编程语言联系了起来
  • 【元宇宙】智能手机万岁

    凭借出色的新设备 我们很快就能进人元字宙 想象这样的情景是很趣的 但是 至少到21世纪20年代 元宇宙时代的大多数设备很可能是我们已经在使用的设备 AR 和 VR 设备不仅面临重大的技术 财务和体验障碍 而且它们在上市后同样会面临币场反响冷
  • Esp8266 Node Mcu 一直乱码的问题详解

    最近一直在做项目 遇到的这个问题花了我很长时间 因此在这里写出自己的经历供大家参考 喜欢的可以点个赞 比较简单的方案 在Arduino上设置Node Mcu 1 打开文件 gt 首选项 复制这样一个网址 http arduino esp82
  • 关于Cache-Control: no-cache和no-store

    在公司上班的正真上班的第一天 发现的jsp页面上 设置了response HTTP头 是设置了这三个属性 Cache Control no cache Cache Control no store Expires 这三个属性都是和网页的缓存
  • OpenMPI:介绍与an'zhuang

    前言 在跑GLowdemo时发现没装OpenMPI 因此 只有装了吧 介绍 OpenMPI 1 是一种高性能消息传递库 最初是作为融合的技术和资源从其他几个项目 FT MPI LA MPI LAM MPI 以及 PACX MPI 它是MPI
  • pytest.fixture详解

    scope分为session package module class function级别 其中function也是包含测试类的测试方法的 package指在当前contest py的package下只执行一次 如果想在某个package
  • <深度学习基础> Batch Normalization

    Batch Normalization批归一化 BN优点 减少了人为选择参数 在某些情况下可以取消dropout和L2正则项参数 或者采取更小的L2正则项约束参数 减少了对学习率的要求 现在我们可以使用初始很大的学习率或者选择了较小的学习率
  • Unity3D中Update和FixedUpdate、LateUpdate的区别

    1 MonoBehaviour Update 更新 当MonoBehaviour启用时 其Update在每一帧被调用 2 MonoBehaviour FixedUpdate 固定更新 当MonoBehaviour启用时 其 FixedUpd
  • NOI2017搞基记

    一篇很长的流水账 写的长也就是因为考得好吧 去年写NOI游记的时候 就想着快点写完就好了 以后大概会再写一篇比较矫情的回顾一下竞赛的历程的吧 虽然我这种狗逼划水语文课代表的水平 大概激励人心的效果肯定赶不上hzwer的吧 DAY 2 在家排
  • linux下sublimetext的中文输入问题解决方法

    InputHelper插件 好奇怪哦 竟然一个文本编辑器在linux平台下竟然原生不能切换并使用系统自带的输入法 所以就有了一系列插件 看到网上各种方法 我觉得还是使用inputhelper这个插件最简单 使用这个插件可以通过Package
  • Java实现标题相似度计算,文本内容相似度匹配,Java通过SimHash计算标题文本内容相似度

    目录 一 前言 二 关于SimHash 补充知识 一 什么是海明距离 二 海明距离的应用 三 什么是编辑距离 三 SimHash算法的几何意义和原理 一 SimHash算法的几何意义 二 SimHash的计算原理 三 文本的相似度计算 四