MinHash 原理
前言
MinHash 用于比较集合的相似度。
在数据挖掘中,一个最基本的问题就是比较两个集合的相似度。通常通过遍历这两个集合中的所有元素,统计这两个集合中相同元素的个数,来表示集合的相似度;这一步也可以看成特征向量间相似度的计算(欧氏距离,余弦相似度)。
当这两个集合里的元素数量异常大(特征空间维数很大),同时又有很多个集合需要判断两两间的相似度时,传统方法会变得十分耗时,最小哈希(minHash)可以用来解决该问题。
Jaccard 相似度
Jaccard 相似度:通过计算交集的相对大小来获得集合之间的相似度。
J
a
c
c
a
r
d
(
A
,
B
)
=
∣
A
∩
B
∣
∣
A
∪
B
∣
Jaccard(A,B) = \frac{|A \cap B|}{|A \cup B|}
Jaccard(A,B)=∣A∪B∣∣A∩B∣
Jaccard相似度在如下问题取得较好效果:在大的语料库中寻找文本内容相似的文档,这里主要指字面上的相似,而非语义上的相似。为了识别字面上相似的文档,需要将文档表示为文档中的短字符串集合,Shingling 是构建表示文中的短字符串集合的方法。
- 文档的 k-shingle 就是文档中出现过的长度为 k 的字符串。
- 一篇文档表示成 k-shingle 的集合,如文档为字符串”abcdabd“,k = 2,则所有 2-shingle 组成的集合为 {ab,bc,cd,da,bd}。
- 如果 k 取得太小,大部分长度为 k 的字符串会出现在大部分的文档中,这就会导致相似度较高。对于论文来说,k = 9较为合适。
Shingle 集合非常大,一个 4-shingle 组成的集合也是原始文件的 4 倍,所以集合相似度计算代价很大,MinHash 可以解决这个问题。
MinHash值计算
假设现在有4个集合,分别为S1,S2,S3,S4,其中 S1={a,d}, S2={c}, S3={b,d,e}, S4={a,c,d},所以全集U={a,b,c,d,e}。我们可以构造如下0-1矩阵(特征矩阵),矩阵的列表示各个集合,行表示所有可能的元素:
元素是什么对最后的结果没有什么影响。所以索性用行号来代表Item,行号跟Item是一一相应的,如下:
行号 | S1 | S2 | S3 | S4 |
---|---|---|---|---|
0 | 1 | 0 | 0 | 1 |
1 | 0 | 0 | 1 | 0 |
2 | 0 | 1 | 0 | 1 |
3 | 1 | 0 | 1 | 1 |
4 | 0 | 0 | 1 | 0 |
为了得到各集合的最小哈希值,首先对矩阵进行随机行打乱,则某个集合的最小哈希值就等于打乱后的这一列的第一个值为1的行所在的行号(即置换后哪个元素在最前),举例说明,定义一个最小哈希函数 h,用于模拟对矩阵进行随机行打乱,打乱后的0-1矩阵(特征矩阵)为:
如图所示,h(S1)=a,h(S2)=c,h(S3)=b,h(S4)=a。
变换后也可以表示为行号的形式:
行号 | S1 | S2 | S3 | S4 |
---|---|---|---|---|
1 | 0 | 0 | 1 | 0 |
4 | 0 | 0 | 1 | 0 |
0 | 1 | 0 | 0 | 1 |
3 | 1 | 0 | 1 | 1 |
2 | 0 | 1 | 0 | 1 |
如图所示,h(S1)=0,h(S2)=2,h(S3)=1,h(S4)=0。
行号也可以是固定不变的,其实是等价的,只不过是一个标志而已,而且一般说行号的话,都是从上往下递增的:
行号 | S1 | S2 | S3 | S4 |
---|---|---|---|---|
0 | 0 | 0 | 1 | 0 |
1 | 0 | 0 | 1 | 0 |
2 | 1 | 0 | 0 | 1 |
3 | 1 | 0 | 1 | 1 |
4 | 0 | 1 | 0 | 1 |
这样的话,h(S1)=2,h(S2)=4,h(S3)=0,h(S4)=2。下面都采用这种行号固定的方式。
可以看出,无论是用元素作为第一列,还是用变换的行号作为第一列,还是用固定的行号作为第一列,结果是等价的。因为无论行号是否固定,只是一个标志而已。
在经过随机行打乱后,两个集合的最小哈希值相等的概率等于这两个集合的Jaccard相似度。证明如下:
- S1和S2的值都为1,记为X;
- 只有一个值为1,另一个值为0,记为Y;
- S1和S2的值都为0,记为Z;
- S1和S2交集的元素个数为x,并集的元素个数为x+y,所以sim(S1,S2) = Jaccard(S1,S2) = x/(x+y)。接下来计算h(S1)=h(S2)的概率,经过随机行打乱后,从上往下扫描,在碰到Y行之前碰到X行的概率为x/(x+y),即h(S1)=h(S2)的概率为x/(x+y)。
MinHash签名及签名矩阵
用一个单词来代表一个文档偶然性会比较大,那么这个时候我们的想法是,能够随机地产生多次变换,取出多个单词来进行比较,也就是进行多次行打乱。
对于每个行打乱方式,每一个集合(文档)都有一个最小哈希值。如果有n个行打乱方式(n一般是一百到数百),每个集合就能产生 n 个最小哈希值,把这些哈希值写成一个列向量,也称为MinHash签名。
每个集合(文档)的列向量组合在一起,写成一个矩阵,称为MinHash签名矩阵。
当特征矩阵很大,打乱很耗时且需要多次打乱。为了解决这个问题,可以通过一些随机哈希函数来模拟打乱的效果(哈希函数用h1,h2,……,hn表示,一个哈希函数为每个集合产生一个 MinHash 值,所有的 n 个 MinHash 值组成的就是这个集合的 MinHash 签名)。
之前计算 MinHash 值的时候,都是变换矩阵的内容而行号不变,但我们知道运动是相对的,也可以不变矩阵,仅仅变换行号,这样计算量就少了很多,模拟打乱效果时,就采用的这种思想。
模拟打乱效果,生成 MinHash签名矩阵的处理过程如下:
- 令 S I G ( i , c ) SIG(i,c) SIG(i,c) 表示签名矩阵中第 i 个哈希函数在第 c 列上的元素。
- 开始时,将所有的
S
I
G
(
i
,
c
)
SIG(i,c)
SIG(i,c) 都初始化为
I
n
f
Inf
Inf,然后对每一行进行如下处理:
- 计算 h1®,h2®,……,hn®(这是关于行号的函数).
- 对于每一列c(每一列对应一个集合):
- 如果 c 所在的第 r 行为0,则什么都不做;
- 如果 c 所在的第 r 行不为1,则对于每个 i=1,2,……,n,将 S I G ( i , c ) SIG(i,c) SIG(i,c)置为 m i n { S I G ( i , c ) , h i ( r ) } min\{SIG(i,c), h_i(r)\} min{SIG(i,c),hi(r)}.
举例说明:
这里使用两个哈希函数, x + 1 m o d 5 x + 1 \ mod \ 5 x+1 mod 5 以及 3 x + 1 m o d 5 3x+1\ mod\ 5 3x+1 mod 5.
行号 | S1 | S2 | S3 | S4 | x + 1 m o d 5 x + 1 \ mod \ 5 x+1 mod 5 | 3 x + 1 m o d 5 3x+1\ mod\ 5 3x+1 mod 5 |
---|---|---|---|---|---|---|
0 | 1 | 0 | 0 | 1 | 1 | 1 |
1 | 0 | 0 | 1 | 0 | 2 | 4 |
2 | 0 | 1 | 0 | 1 | 3 | 2 |
3 | 1 | 0 | 1 | 1 | 4 | 0 |
4 | 0 | 0 | 1 | 0 | 0 | 3 |
初始化:
哈希函数 | S1 | S2 | S3 | S4 |
---|---|---|---|---|
h 1 h_1 h1 | ∞ \infty ∞ | ∞ \infty ∞ | ∞ \infty ∞ | ∞ \infty ∞ |
h 2 h_2 h2 | ∞ \infty ∞ | ∞ \infty ∞ | ∞ \infty ∞ | ∞ \infty ∞ |
处理第 0 行,其中列 S1 以及 S4 是 1,例如 S I G ( h 1 , S 1 ) > h 0 ( 0 ) SIG(h_1,S1)>h_0(0) SIG(h1,S1)>h0(0),将 S I G ( h 1 , S 1 ) SIG(h_1,S1) SIG(h1,S1)置为 h 0 ( 0 ) = 1 h_0(0)=1 h0(0)=1:
哈希函数 | S1 | S2 | S3 | S4 |
---|---|---|---|---|
h 1 h_1 h1 | 1 1 1 | ∞ \infty ∞ | ∞ \infty ∞ | 1 1 1 |
h 2 h_2 h2 | 1 1 1 | ∞ \infty ∞ | ∞ \infty ∞ | 1 1 1 |
处理第 1 行:
哈希函数 | S1 | S2 | S3 | S4 |
---|---|---|---|---|
h 1 h_1 h1 | 1 1 1 | ∞ \infty ∞ | 2 2 2 | 1 1 1 |
h 2 h_2 h2 | 1 1 1 | ∞ \infty ∞ | 4 4 4 | 1 1 1 |
处理第 2 行:
哈希函数 | S1 | S2 | S3 | S4 |
---|---|---|---|---|
h 1 h_1 h1 | 1 1 1 | 3 3 3 | 2 2 2 | 1 1 1 |
h 2 h_2 h2 | 1 1 1 | 2 2 2 | 4 4 4 | 1 1 1 |
处理第 3 行:
哈希函数 | S1 | S2 | S3 | S4 |
---|---|---|---|---|
h 1 h_1 h1 | 1 1 1 | 3 3 3 | 2 2 2 | 1 1 1 |
h 2 h_2 h2 | 0 0 0 | 2 2 2 | 0 0 0 | 0 0 0 |
处理第 4 行:
哈希函数 | S1 | S2 | S3 | S4 |
---|---|---|---|---|
h 1 h_1 h1 | 1 1 1 | 3 3 3 | 0 0 0 | 1 1 1 |
h 2 h_2 h2 | 0 0 0 | 2 2 2 | 0 0 0 | 0 0 0 |
这就是最终的签名矩阵(MinHash签名矩阵),里面每一列都代表对应集合(文档)的哈希签名(MinHash签名),可以计算集合之间的相似度。
S i m ( S 1 , S 3 ) = 0.5 , S i m ( S 1 , S 4 ) = 1 Sim(S1,S3)=0.5,Sim(S1,S4)=1 Sim(S1,S3)=0.5,Sim(S1,S4)=1
局部敏感哈希算法(LSH)
问题来源
这里主要说的是判断文档(集合)相似性中的LSH。
对于集合中元素个数很多,而且有很多集合需要判断相似性的情况,MinHash解决了元素个数很多的集合判断相似性的问题,但是还剩下集合个数很多的问题没有解决,那么给定一组文档,还有MinHash签名矩阵,不去一一计算Hash签名的相似性,怎么快速地寻找与一个查询文档(最)相似的文档呢?
LSH 基本思想
Locality Sensitive Hashing(LSH)。
即使可以使用 MinHash 将大文档压缩成小的哈希签名,当文档数目太大时寻找相似文档对仍然是不可能的,因为需要计算出所有文档的哈希签名,然后两两计算相似度。那么我们能不能只比较那些相似度可能很高的文档,而忽略那些相似性很低的文档之间的比较?
实际上,只需要寻找那些相似性可能很高的文档对,而不是全部文档对。这就是局部敏感哈希。
假设文档先表示成 Shingle 集合,通过 MinHash 处理,变成最小哈希签名,所有文档的最小哈希签名组成签名矩阵。基本想法:
- 把签名矩阵分成子矩阵(行条),使用多次哈希函数。
- 具有相同部分(某行条中的值相等)的列将被哈希到同一个桶中。
- 只考察那些哈希到同一个桶里面的列的相似性(这样相似的可能性较大)。
Banding for LSH
把签名矩阵分成 b 个行条(band),每个行条由 r 行组成(假设签名矩阵中有 n 行,那么n = br)。对于每个行条,存在一个哈希函数能够将行条中的每 r 个整数组成的列向量(行条中的每一列)映射到某个桶中,可以对所有行条使用相同的哈希函数,但是每个行条都有独立的桶数组。
只要两个集合在某个行条中有落在同一个桶数组中的列,这两个集合就认为可能相似度比较高,就作为后续计算的候选对。
例:现在有一个12行签名矩阵,把这个矩阵分为4个行条,每个行条有3行;为了方便,这里只写出行条1的内容(n = 12, b = 4, r = 3)。
可以看出,行条1中第2列和第4列的内容都为[0,2,1],所以这两列会落在行条1下的相同桶中,因此不论在剩下的3个行条中这两列是否有落在相同桶中,这两个集合都会成为候选对。在行条1中不相等的两列还有另外的3次机会成为候选对,因为他们只需在剩下的3个行条中有一次相等即可。
对于S2,我们仅需要寻找那些桶相同的集合来计算相似度,例如:
我们仅需要计算sim(S2, S3),sim(S2, S4),sim(S2, S5),因为这些集合出现过与S2桶相同的情况。