转自:
http://www.open-open.com/lib/view/open1375690611500.html
关于 罗刚 老师 搜索解密中的 SimHash算法 、 TITS算法 、标准Trie树、三叉Trie树 java实现 下载地址
http://download.csdn.net/detail/zhuhongming123/8175135
(mkse/simHash目录下)
传统的 hash 算法只负责将原始内容尽量均匀随机地映射为一个签名值,原理上相当于伪随机数产生算法。产生的两个签名,如果相等,说明原始内容在一定概 率 下是相等的;如果不相等,除了说明原始内容不相等外,不再提供任何信息,因为即使原始内容只相差一个字节,所产生的签名也很可能差别极大。从这个意义 上来 说,要设计一个 hash 算法,对相似的内容产生的签名也相近,是更为艰难的任务,因为它的签名值除了提供原始内容是否相等的信息外,还能额外提供不相等的 原始内容的差异程度的信息。
而 Google 的 simhash 算法产生的签名,可以满足上述要求。出人意料,这个算法并不深奥,其思想是非常清澈美妙的。
明确了算法了几何意义,使这个算法直观上看来是合理的。但是,为何最终得到的签名相近的程度,可以衡量原始文档的相似程度呢?这需要一个清晰的思路和证明。在simhash的发明人Charikar的论文中[2]并没有给出具体的simhash算法和证明,以下列出我自己得出的证明思路。
Simhash是由随机超平面hash算法演变而来的,随机超平面hash算法非常简单,对于一个n维向量v,要得到一个f位的签名(f<
这个算法相当于随机产生了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个随机超平面。下面举个例子:
按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,
上面的计算步骤其实相当于,先得到3个5维的向量,第1个向量由h(w1),…,h(w5)的第1维组成:
r1=(1,-1,1,-1,1) T;
从上面的计算过程可以看出,simhash算法其实与随机超平面hash算法是相同的,simhash算法得到的两个签名的汉明距离,可以用来衡量原始向量的夹角。这其实是一种降维技术,将高维的向量用较低维度的签名来表征。衡量两个内容相似度,需要计算汉明距离,这对给定签名查找相似内容的应用来说带来了一些计算上的困难;我想,是否存在更为理想的simhash算法,原始内容的差异度,可以直接由签名值的代数差来表示呢?
异或: 只有在两个比较的位不同时其结果是1 ,否则结果为 0
对每篇文档根据SimHash 算出签名后,再计算两个签名的海明距离(两个二进制异或后 1 的个数)即可。根据经验值,对 64 位的 SimHash ,海明距离在 3 以内的可以认为相似度比较高。
如果库中有2^34 个(大概 10 亿)签名,那么匹配上每个块的结果最多有 2^(34-16)=262144 个候选结果 (假设数据是均匀分布, 16 位的数据,产生的像限为 2^16 个,则平均每个像限分布的文档数则 2^34/2^16 = 2^(34-16)) ,四个块返回的总结果数为 4* 262144 (大概 100 万)。原本需要比较 10 亿次,经过索引,大概就只需要处理 100 万次了。由此可见,确实大大减少了计算量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
|
/**
* Function: 注:该示例程序暂不支持中文
* Date: 2013-8-4 下午11:01:45
* @author june: [email protected]<script type="text/javascript">
/* <![CDATA[ */
(function(){
try
{var s,a,i,j,r,c,l,b=document.getElementsByTagName(
"script"
);l=b[b.length-
1
].previousSibling;a=l.getAttribute(
'data-cfemail'
);
if
(a){s=
''
;r=parseInt(a.substr(
0
,
2
),
16
);
for
(j=
2
;a.length-j;j+=
2
){c=parseInt(a.substr(j,
2
),
16
)^r;s+=String.fromCharCode(c);}s=document.createTextNode(s);l.parentNode.replaceChild(s,l);}}
catch
(e){}})();
/* ]]> */
</script>
*/
import
java.math.BigInteger;
import
java.util.ArrayList;
import
java.util.HashMap;
import
java.util.List;
import
java.util.StringTokenizer;
public
class
SimHash {
private
String tokens;
private
BigInteger intSimHash;
private
String strSimHash;
private
int
hashbits =
64
;
public
SimHash(String tokens) {
this
.tokens = tokens;
this
.intSimHash =
this
.simHash();
}
public
SimHash(String tokens,
int
hashbits) {
this
.tokens = tokens;
this
.hashbits = hashbits;
this
.intSimHash =
this
.simHash();
}
HashMap<string, integer=
""
> wordMap =
new
HashMap<string, integer=
""
>();
public
BigInteger simHash() {
// 定义特征向量/数组
int
[] v =
new
int
[
this
.hashbits];
// 1、将文本去掉格式后, 分词.
StringTokenizer stringTokens =
new
StringTokenizer(
this
.tokens);
while
(stringTokens.hasMoreTokens()) {
String temp = stringTokens.nextToken();
// 2、将每一个分词hash为一组固定长度的数列.比如 64bit 的一个整数.
BigInteger t =
this
.hash(temp);
for
(
int
i =
0
; i <
this
.hashbits; i++) {
BigInteger bitmask =
new
BigInteger(
"1"
).shiftLeft(i);
// 3、建立一个长度为64的整数数组(假设要生成64位的数字指纹,也可以是其它数字),
// 对每一个分词hash后的数列进行判断,如果是1000...1,那么数组的第一位和末尾一位加1,
// 中间的62位减一,也就是说,逢1加1,逢0减1.一直到把所有的分词hash数列全部判断完毕.
if
(t.and(bitmask).signum() !=
0
) {
// 这里是计算整个文档的所有特征的向量和
// 这里实际使用中需要 +- 权重,而不是简单的 +1/-1,
v[i] +=
1
;
}
else
{
v[i] -=
1
;
}
}
}
BigInteger fingerprint =
new
BigInteger(
"0"
);
StringBuffer simHashBuffer =
new
StringBuffer();
for
(
int
i =
0
; i <
this
.hashbits; i++) {
// 4、最后对数组进行判断,大于0的记为1,小于等于0的记为0,得到一个 64bit 的数字指纹/签名.
if
(v[i] >=
0
) {
fingerprint = fingerprint.add(
new
BigInteger(
"1"
).shiftLeft(i));
simHashBuffer.append(
"1"
);
}
else
{
simHashBuffer.append(
"0"
);
}
}
this
.strSimHash = simHashBuffer.toString();
System.out.println(
this
.strSimHash +
" length "
+
this
.strSimHash.length());
return
fingerprint;
}
private
BigInteger hash(String source) {
if
(source ==
null
|| source.length() ==
0
) {
return
new
BigInteger(
"0"
);
}
else
{
char
[] sourceArray = source.toCharArray();
BigInteger x = BigInteger.valueOf(((
long
) sourceArray[
0
]) <<
7
);
BigInteger m =
new
BigInteger(
"1000003"
);
BigInteger mask =
new
BigInteger(
"2"
).pow(
this
.hashbits).subtract(
new
BigInteger(
"1"
));
for
(
char
item : sourceArray) {
BigInteger temp = BigInteger.valueOf((
long
) item);
x = x.multiply(m).xor(temp).and(mask);
}
x = x.xor(
new
BigInteger(String.valueOf(source.length())));
if
(x.equals(
new
BigInteger(
"-1"
))) {
x =
new
BigInteger(
"-2"
);
}
return
x;
}
}
public
int
hammingDistance(SimHash other) {
BigInteger x =
this
.intSimHash.xor(other.intSimHash);
int
tot =
0
;
// 统计x中二进制位数为1的个数
// 我们想想,一个二进制数减去1,那么,从最后那个1(包括那个1)后面的数字全都反了,对吧,然后,n&(n-1)就相当于把后面的数字清0,
// 我们看n能做多少次这样的操作就OK了。
while
(x.signum() !=
0
) {
tot +=
1
;
x = x.and(x.subtract(
new
BigInteger(
"1"
)));
}
return
tot;
}
public
int
getDistance(String str1, String str2) {
int
distance;
if
(str1.length() != str2.length()) {
distance = -
1
;
}
else
{
distance =
0
;
for
(
int
i =
0
; i < str1.length(); i++) {
if
(str1.charAt(i) != str2.charAt(i)) {
distance++;
}
}
}
return
distance;
}
public
List subByDistance(SimHash simHash,
int
distance) {
// 分成几组来检查
int
numEach =
this
.hashbits / (distance +
1
);
List characters =
new
ArrayList();
StringBuffer buffer =
new
StringBuffer();
int
k =
0
;
for
(
int
i =
0
; i <
this
.intSimHash.bitLength(); i++) {
// 当且仅当设置了指定的位时,返回 true
boolean
sr = simHash.intSimHash.testBit(i);
if
(sr) {
buffer.append(
"1"
);
}
else
{
buffer.append(
"0"
);
}
if
((i +
1
) % numEach ==
0
) {
// 将二进制转为BigInteger
BigInteger eachValue =
new
BigInteger(buffer.toString(),
2
);
System.out.println(
"----"
+ eachValue);
buffer.delete(
0
, buffer.length());
characters.add(eachValue);
}
}
return
characters;
}
public
static
void
main(String[] args) {
String s =
"This is a test string for testing"
;
SimHash hash1 =
new
SimHash(s,
64
);
System.out.println(hash1.intSimHash +
" "
+ hash1.intSimHash.bitLength());
hash1.subByDistance(hash1,
3
);
s =
"This is a test string for testing, This is a test string for testing abcdef"
;
SimHash hash2 =
new
SimHash(s,
64
);
System.out.println(hash2.intSimHash +
" "
+ hash2.intSimHash.bitCount());
hash1.subByDistance(hash2,
3
);
s =
"This is a test string for testing als"
;
SimHash hash3 =
new
SimHash(s,
64
);
System.out.println(hash3.intSimHash +
" "
+ hash3.intSimHash.bitCount());
hash1.subByDistance(hash3,
4
);
System.out.println(
"============================"
);
int
dis = hash1.getDistance(hash1.strSimHash, hash2.strSimHash);
System.out.println(hash1.hammingDistance(hash2) +
" "
+ dis);
int
dis2 = hash1.getDistance(hash1.strSimHash, hash3.strSimHash);
System.out.println(hash1.hammingDistance(hash3) +
" "
+ dis2);
//通过Unicode编码来判断中文
/*String str =
"中国chinese"
;
for
(
int
i =
0
; i < str.length(); i++) {
System.out.println(str.substring(i, i +
1
).matches(
"[\\u4e00-\\u9fbb]+"
));
}*/
}
}</string,></string,>
|
simHash在短文本的可行性:
测试相似文本的相似度与汉明距离
测试文本:20个城市名作为词串:北京,上海,香港,深圳,广州,台北,南京,大连,苏州,青岛,无锡,佛山,重庆,宁波,杭州,成都,武汉,澳门,天津,沈阳
相似度矩阵:
simHash码:
勘误:0.667, Hm:13 是对比的msg 1与2。
可见:相似度在0.8左右的Hamming距离为7,只有相似度高到0.9412,Hamming距离才近到4,此时,反观Google对此算法的应用场景:网页近重复。
镜像网站、内容复制、嵌入广告、计数改变、少量修改。
以上原因对于长文本来说造成的相似度都会比较高,而对于短文本来说,如何处理海量数据的相似度文本更为合适的?
测试短文本(长度在8个中文字符~45个中文字符之间)相似性的误判率如下图所示:
1、simHash 简介以及java实现
http://blog.sina.com.cn/s/blog_4f27dbd501013ysm.html
2、对simhash算法的一些思考
http://2588084.blog.51cto.com/2578084/558873
3、Simhash算法原理和网页查重应用
http://blog.sina.com.cn/s/blog_72995dcc010145ti.html
4、其它
http://www.cnblogs.com/zhengyun_ustc/archive/2012/06/12/sim.html
http://tech.uc.cn/?p=1086
http://jacoxu.com/?p=369 simHash是否适合短文本的相似文本匹配
https://github.com/sing1ee/simhash-java