引言

项目中原使用的文本对比算法是使用MD5 Hash的方法。MD5 Hash算法简单来说是指对于任何长度的文本都可生成一段128bit长度的字符串,相同文本生成的Hash字符串是相同的,因此可用来比较文本是否相同。

但这种传统的Hash算法,对于文本的查找效率是很低的,另外文本间的相似度计算是很困难,因为即使改动文本的一个字符,得到的Hash结果也是完全不同的。因此在新项目中考虑用新的算法去做,对此作了一些技术调研,也收获了一些更好的方法。接下来会在系列博客中总结一些成果。

简要介绍

通过引言,我们已经知道传统Hash的局限性,因此,接下来引入一个名词“局部敏感哈希”。

与传统的Hash不同,局部敏感哈希是一种解决在海量的高维数据集中查找与查询数据点(query data point)近似最相邻的某个或某些数据点的方法。常用的方法包括:欧式距离、余弦距离、海明距离、Jaccard相似度等。本篇博客将介绍的SimHash算法就属于一种局部敏感哈希算法,利用海明距离比较内容之间的相似度。

SimHash是Google用来处理海量文本去重的算法。主要思想是降维,将高维的特征向量转化为一个f位的指纹,通过算出两个指纹的海明距离来确定文本的相似度,海明距离越小,相似度越低。

计算原理

  1. 分词:将处理后的文本(去除特殊字符等)进行分词,可为分词设置权重,得到分词向量
  2. 计算:通过Hash函数计算每个分词向量的Hash值,值为二进制串
  3. 加权:计算权重向量=每个分词的hash*该词对应的权重weight
  4. 合并:将所有分词的权重向量累加,得到一个新的权重向量
  5. 降维:对上述合并后得到的权重向量,大于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版)相关推荐

  1. java排序算法原理_排序算法原理与实现(java)

    排序算法原理与实现(java) Java程序员必知的8大排序 [来源:本站 | 日期:2012年12月24日 | 浏览173 次] 字体:[大 中 小] 8种排序之间的关系: 1, 直接插入排序 (1 ...

  2. java 堆排序算法_堆排序算法的讲解及Java版实现

    这篇文章主要介绍了堆排序算法的讲解及Java版实现,堆排序基于堆这种数据结构,在本文中对堆的概念也有补充介绍,需要的朋友可以参考下 堆是数据结构中的一种重要结构,了解了"堆"的概念 ...

  3. 算法--猫扑素数--java版

    算法–猫扑素数–java版 简介 猫扑素数: 形如以 2 开头, 之后跟任意多个 3 的十进制整数如果是个素数, 则它是猫扑素数. 如 2, 23, 233, 2333, 23333 都是猫扑素数, ...

  4. long 雪花算法_雪花算法(SnowFlake)Java版

    算法原理 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截, ...

  5. simhash算法原理

    背景 如何设计一个比较两篇文章相似度的算法?可能你会回答几个比较传统点的思路: 一种方案是先将两篇文章分别进行分词,得到一系列特征向量,然后计算特征向量之间的距离(可以计算它们之间的欧氏距离.海明距离 ...

  6. Google的S2算法原理以及使用Java版本--部分参考自《高效的多维空间点索引算法》

    文章目录 相关资料 1.S2算法是什么? 2.为什么要使用S2算法? 3.S2的原理是什么? 1)球面坐标变换 2)球面坐标转平面坐标(降维) remark: 3)球面矩形投影修正 4)点与坐标轴点相 ...

  7. 【图】Dijkstra(迪杰特斯拉)算法、左神Java版

    什么是Dijkstra 给定一个图,从某点出发到达某点给出最短的路径 比如上述图,从A出发,到其余点的最短路径,返回这样的表 思路 我们先用一个表格记录A到其余点的距离,初始值是A到A的距离为0,与其 ...

  8. 图论算法—图的拓扑排序介绍和Kahn算法原理解析以及Java代码的实现

    详细介绍了图的拓扑排序的概念,然后介绍了求拓扑序列的算法:Kahn算法的原理,最后提供了基于邻接矩阵和邻接表的图对该算法的Java实现. 阅读本文需要一定的图的基础,如果对于图不是太明白的可以看看这篇 ...

  9. 排序算法---选择排序(java版)

    简单选择排序 原理 选择排序(Selection Sort)的原理有点类似插入排序,也分已排序区间和未排序区间.但是选择排序每次会从排序区间中找到最小的元素,将其放到已排序区间的末尾. 简单选择排序执 ...

最新文章

  1. MIT喊你来上课,深度学习课程,免费的那种 | 资源
  2. 数据结构经典书籍--数据结构与算法分析
  3. C语言(rand函数)
  4. html文件设置成mac屏保,Mac怎么设置屏幕保护?如何设置Mac屏幕保护程序?
  5. George and Job(动态规划)
  6. 创建文件夹 java_java怎么建文件夹
  7. UI标签库专题六:JEECG智能开发平台 Autocomplete(自动补全标签 )
  8. 解决Python中sum函数出现的TypeError: unsupported operand type(s) for +: 'int' and 'list'错误问题
  9. cube station下载_Cube Station
  10. mysql二进制安装方法
  11. Python学习入门基础教程(learning Python)--6 Python下的list数据类型
  12. https://www.runoob.com/python/python-variable-types.html
  13. navicat premium使用教程 Navicat Premium mac的基本使用
  14. 人人开源-renren-generator的基本使用
  15. VS2010设置快捷键
  16. Electron客户端的自动升级方案-2022版
  17. 学习笔记(二十一)—— 使用SMTP发送电子邮件
  18. 美国大通胀:谁来扛旗?
  19. 一切还要从副总裁在朋友圈卖内裤说起
  20. 分享:大讲台在线学习平台怎么样,靠谱吗?

热门文章

  1. 利用echart和echart-gl绘制江苏省的地图之一
  2. 分布式系统和数据同步要点
  3. 中国电信计算机专业面试的云计算问题,中国电信企业信息化类面试题和笔试题库(社会招聘和内部竞聘用题)...
  4. DVWA问题2:Could not connect to the MySQL service.
  5. PPT 、word 、pdf、 txt 格式转换
  6. Python通过手肘法实现k_means聚类
  7. shell获取明天、上周、上个月时间
  8. mac系统卸载亚信安全助手
  9. 同步磁阻电机SynRM高频注入无感 FOC 采用高频注入法实现SynRM零低速下无位置传感器起动运行
  10. 通过Shell实现小火车效果