Simhash相似哈希算法

前言

最近在阅读吴军博士的<<数学之美>>这门书,得到了很多的启发和思考,里面提到了一个概念---信息指纹。一般正常人提到这个概念,第一个想到的词应该是哈希映射算法,将任何对象都映射成一个独立的变量,一般这个变量是一个独有的数字,当然也不排除哈希碰撞的可能行。论单个对象,用哈希算法做一次映射,比较对象是否一致,这固然是可以的,但是如果想用哈希算法做一些文章之间的相似度计算的时候,可能传统的哈希算法就不见得是最佳的选择了,如果把整篇文章都作为一个超长字符串的去计算,准确率无法保证,因为字符串中的任何字符的变化,也许都会造成后面的值变得差异很大。那么如果单个单个去计算的话,如何用一个有效的标准去衡量这些看似没有关联的哈希值呢。还好我们是站在巨人的肩膀上的,有一种叫Simhash算法正好巧妙的解决了我们的问题。

Simhash的背景

Simhash当初比较早些的时候用在了Google的网页爬虫中,用于重复网页的去重。可以说,Simhash就是拿来计算网页的信息指纹。Simhash是在2002年的时候由Moses Chariker 提出的。

Simhash算法原理

Simhash的设计非常的巧妙,这里举一个实际的使用场景,来使大家直观的感受这个神奇的算法。比如比较2个网页的相似度,那么比较的关键就是里面的各种词,假设有t1,t2.t3等等,每个词都会自己的信息指纹,最简单的就是字符串的hashcode值,而Simhash的作用就是把这些值进行一个汇总,得到一个整个网页的哈希值。下面是2个大的模块。

一.扩展。将上个步骤中的各个词的哈希值取余数,做一个位数的限制,比如最终的展现需要为8位的二进制数,就要把哈希值取2的8次方就是256的余数,方便后面的加减权重操作。然后初始化一个8位数组,默认每个位上的值都为0。下面的步骤是第一个模块的关键步骤了。

因为刚刚有t1,t2,t3等等这些词语的哈希值,然后取余数后就是1个个的8位的二进制整数,下面是遍历操作,取第一个词的信息指纹,假设是10000010,(随便假设的),此时总的哈希值为:

r1=1   0+w1

r2=0   0-w1

r3=0  0-w1

r4=0  0-w1

r5=0  0-w1

r6=0  0-w1

r7=1  0+w1

r8=0  0-w1

规则很简单,就是根据二进制位,根据1加0减的规则,做相应的权重操作,w指的是该词语在网页中的权重,这个可以用分词系统来做,然后统计词频,用词频作为权重值来算,当然也可以默认权重都相同,都为1,。然后是遍历第二个词,操作的过程一样,只是要在t1的基础上进行权重的操作,而不是初始值。

二、收缩。将最后的由权重值计算得到的网页总哈希数组,进行规约化操作,如果某个位的值大于0,则此位置上的值设置为1,否则设置为0。比如下面这个经过各个词的权重的加加减减,最后的结果是

0.1, 0.1,0.1,0.1,-0.8,-0.3,0.4,0.5

那么最后的结果就会是

11110011

最后的相似度比较就可以用最好的哈希对应位置上的值的相同个数做比较。如果2个网页相同,则相似哈希值必定相同,如果存在极个别少量的权重低的词不同,他们的相似哈希值也可能会相同。

算法的代码实现

给出一个算法的主要实现类,全部代码链接以及测试数据:https://github.com/linyiqun/lyq-algorithms-lib/tree/master/Simhash

package Simhash;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

/**
 * 相似哈希算法工具类
 *
 * @author lyq
 *
 */
public class SimHashTool {
	// 二进制哈希位数
	private int hashBitNum;
	// 相同位数最小阈值
	private double minSupportValue;

	public SimHashTool(int hashBitNum, double minSupportValue) {
		this.hashBitNum = hashBitNum;
		this.minSupportValue = minSupportValue;
	}

	/**
	 * 比较文章的相似度
	 *
	 * @param news1
	 *            文章路径1
	 * @param news2
	 *            文章路径2
	 */
	public void compareArticals(String newsPath1, String newsPath2) {
		String content1;
		String content2;
		int sameNum;
		int[] hashArray1;
		int[] hashArray2;

		// 读取分词结果
		content1 = readDataFile(newsPath1);
		content2 = readDataFile(newsPath2);
		hashArray1 = calSimHashValue(content1);
		hashArray2 = calSimHashValue(content2);

		// 比较哈希位数相同个数
		sameNum = 0;
		for (int i = 0; i < hashBitNum; i++) {
			if (hashArray1[i] == hashArray2[i]) {
				sameNum++;
			}
		}

		// 与最小阈值进行比较
		if (sameNum > this.hashBitNum * this.minSupportValue) {
			System.out.println(String.format("相似度为%s,超过阈值%s,所以新闻1与新闻2是相似的",
					sameNum * 1.0 / hashBitNum, minSupportValue));
		} else {
			System.out.println(String.format("相似度为%s,小于阈值%s,所以新闻1与新闻2不是相似的",
					sameNum * 1.0 / hashBitNum, minSupportValue));
		}
	}

	/**
	 * 计算文本的相似哈希值
	 *
	 * @param content
	 *            新闻内容数据
	 * @return
	 */
	private int[] calSimHashValue(String content) {
		int index;
		long hashValue;
		double weight;
		int[] binaryArray;
		int[] resultValue;
		double[] hashArray;
		String w;
		String[] words;
		News news;

		news = new News(content);
		news.statWords();
		hashArray = new double[hashBitNum];
		resultValue = new int[hashBitNum];

		words = content.split(" ");
		for (String str : words) {
			index = str.indexOf(‘/‘);
			if (index == -1) {
				continue;
			}
			w = str.substring(0, index);

			// 获取权重值,根据词频所得
			weight = news.getWordFrequentValue(w);
			if(weight == -1){
				continue;
			}
			// 进行哈希值的计算
			hashValue = BKDRHash(w);
			// 取余把位数变为n位
			hashValue %= Math.pow(2, hashBitNum);

			// 转为二进制的形式
			binaryArray = new int[hashBitNum];
			numToBinaryArray(binaryArray, (int) hashValue);

			for (int i = 0; i < binaryArray.length; i++) {
				// 如果此位置上为1,加权重
				if (binaryArray[i] == 1) {
					hashArray[i] += weight;
				} else {
					// 为0则减权重操作
					hashArray[i] -= weight;
				}
			}
		}

		// 进行数组收缩操作,根据值的正负号,重新改为二进制数据形式
		for (int i = 0; i < hashArray.length; i++) {
			if (hashArray[i] > 0) {
				resultValue[i] = 1;
			} else {
				resultValue[i] = 0;
			}
		}

		return resultValue;
	}

	/**
	 * 数字转为二进制形式
	 *
	 * @param binaryArray
	 *            转化后的二进制数组形式
	 * @param num
	 *            待转化数字
	 */
	private void numToBinaryArray(int[] binaryArray, int num) {
		int index = 0;
		int temp = 0;
		while (num != 0) {
			binaryArray[index] = num % 2;
			index++;
			num /= 2;
		}

		// 进行数组前和尾部的调换
		for (int i = 0; i < binaryArray.length / 2; i++) {
			temp = binaryArray[i];
			binaryArray[i] = binaryArray[binaryArray.length - 1 - i];
			binaryArray[binaryArray.length - 1 - i] = temp;
		}
	}

	/**
	 * BKDR字符哈希算法
	 *
	 * @param str
	 * @return
	 */
	public static long BKDRHash(String str) {
		int seed = 31; /* 31 131 1313 13131 131313 etc.. */
		long hash = 0;
		int i = 0;

		for (i = 0; i < str.length(); i++) {
			hash = (hash * seed) + (str.charAt(i));
		}

		hash = Math.abs(hash);
		return hash;
	}

	/**
	 * 从文件中读取数据
	 */
	private String readDataFile(String filePath) {
		File file = new File(filePath);
		StringBuilder strBuilder = null;

		try {
			BufferedReader in = new BufferedReader(new FileReader(file));
			String str;
			strBuilder = new StringBuilder();
			while ((str = in.readLine()) != null) {
				strBuilder.append(str);
			}
			in.close();
		} catch (IOException e) {
			e.getStackTrace();
		}

		return strBuilder.toString();
	}

	/**
	 * 利用分词系统进行新闻内容的分词
	 *
	 * @param srcPath
	 *            新闻文件路径
	 */
	private void parseNewsContent(String srcPath) {
		// TODO Auto-generated method stub
		int index;
		String dirApi;
		String desPath;

		dirApi = System.getProperty("user.dir") + "\\lib";
		// 组装输出路径值
		index = srcPath.indexOf(‘.‘);
		desPath = srcPath.substring(0, index) + "-split.txt";

		try {
			ICTCLAS50 testICTCLAS50 = new ICTCLAS50();
			// 分词所需库的路径、初始化
			if (testICTCLAS50.ICTCLAS_Init(dirApi.getBytes("GB2312")) == false) {
				System.out.println("Init Fail!");
				return;
			}
			// 将文件名string类型转为byte类型
			byte[] Inputfilenameb = srcPath.getBytes();

			// 分词处理后输出文件名、将文件名string类型转为byte类型
			byte[] Outputfilenameb = desPath.getBytes();

			// 文件分词(第一个参数为输入文件的名,第二个参数为文件编码类型,第三个参数为是否标记词性集1 yes,0
			// no,第四个参数为输出文件名)
			testICTCLAS50.ICTCLAS_FileProcess(Inputfilenameb, 0, 1,
					Outputfilenameb);
			// 退出分词器
			testICTCLAS50.ICTCLAS_Exit();
		} catch (Exception ex) {
			ex.printStackTrace();
		}

	}

}

结果输出:

相似度为0.75,超过阈值0.5,所以新闻1与新闻2是相似的
相似度为0.875,超过阈值0.5,所以新闻1与新闻2是相似的

参考文献

百度百科

<<数学之美>>第二版-吴军博士

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-06 03:01:33

Simhash相似哈希算法的相关文章

R语言实现︱局部敏感哈希算法(LSH)解决文本机械相似性的问题(二,textreuse介绍)

上一篇(R语言实现︱局部敏感哈希算法(LSH)解决文本机械相似性的问题(一,基本原理))讲解了LSH的基本原理,笔者在想这么牛气冲天的方法在R语言中能不能实现得了呢? 于是在网上搜索了一下,真的发现了一个叫textreuse的包可以实现这样的功能,而且该包较为完整,可以很好地满足要求. 现在的版本是 0.1.3,最近的更新的时间为 2016-03-28. 国内貌似比较少的用这个包来实现这个功能,毕竟R语言在运行大规模数据的性能比较差,而LSH又是处理大规模数据的办法,所以可能国内比较少的用R来执

转(一致性哈希算法(consistent hashing))

转自:http://blog.csdn.net/cywosp/article/details/23397179 一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用的简 单哈希算法带来的问题,使得分布式哈希(DHT)可以在P2P环境中真正得到应用. 一致性hash算法提出了在动态变化的Cache环境中,判定哈希算法好坏的四个定义: 1.平衡性(Balance)

一致性哈希算法原理

一致性Hash算法背景 一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用的简单哈希算法带来的问题,使得DHT可以在P2P环境中真正得到应用. 但现在一致性hash算法在分布式系统中也得到了广泛应用,研究过memcached缓存数据库的人都知道,memcached服务器端本身不提供分布式cache的一致性,而是由客户端来提供,具体在计算一致性has

一致性哈希算法(consistent hashing)(转)

原文链接:每天进步一点点——五分钟理解一致性哈希算法(consistent hashing) 一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用的简 单哈希算法带来的问题,使得分布式哈希(DHT)可以在P2P环境中真正得到应用. 一致性hash算法提出了在动态变化的Cache环境中,判定哈希算法好坏的四个定义: 1.平衡性(Balance):平衡性是指哈希的

MySQL中InnoDB存储引擎中的哈希算法

InnoDB存储引擎使用哈希算法来对字典进行查找,其冲突机制采用链表方式,哈希函数采用除法散列方式.对于缓冲池页的哈希表来说,在缓冲池中的Page页都有一个chain指针.它指向相同哈希函数值的页的.而对于除法散列,m的取值略大于2倍的缓冲池页数量的质数.例如:当前参数innodb_buffer_pool_size的大小为10M,则共有640个16kb的页.对于缓冲池页内存的哈希表来说,需要分配640*2=1280个槽,但是由于1280不是质数,需要取比1280略大的一个质数,应该是1399,所

一致性哈希算法

tencent2012笔试题附加题    问题描述: 例如手机朋友网有n个服务器,为了方便用户的访问会在服务器上缓存数据,因此用户每次访问的时候最好能保持同一台服务器.已有的做法是根据ServerIPIndex[QQNUM%n]得到请求的服务器,这种方法很方便将用户分到不同的服务器上去.但是如果一台服务器死掉了,那么n就变为了n-1,那么ServerIPIndex[QQNUM%n]与ServerIPIndex[QQNUM%(n-1)]基本上都不一样了,所以大多数用户的请求都会转到其他服务器,这样

一致性哈希算法及其在分布式系统中的应用(转)

原文:http://blog.codinglabs.org/articles/consistent-hashing.html 本文将会从实际应用场景出发,介绍一致性哈希算法(Consistent Hashing)及其在分布式系统中的应用.首先本文会描述一个在日常开发中经常会遇到的问题场景,借此介绍一致性哈希算法以及这个算法如何解决此问题:接下来会对这个算法进行相对详细的描述,并讨论一些如虚拟节点等与此算法应用相关的话题. 分布式缓存问题 假设我们有一个网站,最近发现随着流量增加,服务器压力越来越

使用再哈希算法查找元素

使用再哈希算法查找元素: /* hash search, using rehash method if find k, return if not find, d=(d+step)%m, rehash find */ int SearchHash(HashTable H, KeyType k) { int d, d1, m; m = H.tableSize; d = d1 = k%m; while(H.data[d].key != -1) { if(H.data[d].key == k) //h

五分钟理解一致性哈希算法(consistent hashing)

转载请说明出处:http://blog.csdn.net/cywosp/article/details/23397179 一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用的简 单哈希算法带来的问题,使得分布式哈希(DHT)可以在P2P环境中真正得到应用. 一致性hash算法提出了在动态变化的Cache环境中,判定哈希算法好坏的四个定义: 1.平衡性(Bal