1 应用背景
最近在计算word2vec中的相似性的时候,遇到了计算量特别巨大的情况,大到计算在hadoop集群上也计算了好几个小时,在本机上初步估计了下,基本上是不可能算出来的(规模是几十万候选集,维度是100)。
在跟我组一个博士交流这个问题时,他给我推荐了LSH(局部敏感哈希)算法,用这个算法之后,发现计算量瞬间降低了近千倍(这里顺带鄙视下自己的知识面)。后来查了下资料,发现LSH在相似性计算方面,已经有大量应用了(特别是处理海量数据)。下面将详细梳理下LSH的原理及其应用。
2 LSH基本原理
2.1 LSH初探
首先来举个例子,比如说要从海量的网页中,找一个和特定网页相似的其他网页,最简单的办法就是去遍历整个数据集,一个一个页面去比较,看看哪个网页和当前页面最相似,计算相似的方法一般是cosine距离或者Jaccard距离,假设抽取的特征就是网页中出现的单词,给每个网页构造一个向量(特征),那么现在的问题转化为:给定一个向量,如何快速的从海量的向量中找到和这个特征相似的一个向量呢?这时候LSH就派上用场啦。哈希大家都知道,它的查找时间复杂度为$O(1)$,有着极高的查找性能,那什么叫做“局部敏感哈希”呢?它的意思就是:如果原来的数据相似,那么hash以后的数据也保持一定的相似性,这就是LSH。为什么说是保持一定的相似性?这主要是由于哈希冲突造成的。
先来看看通常情况下的hash,比如有一个hash函数:$f(x)=(x7)\%10$,有两个数据$x_1=123$,$x_2=124$,那么经过hash函数之后,$f(x_1)=1,f(x_2)=8$,这表明什么呢?*原来的数据很相似,然而hash之后的数据,就差得很远了!说明这个哈希函数并没有保持相似性,也就不是局部敏感哈希。
可以用一句话总结LSH的思想:它是用hash的方法把数据从原空间经过哈希映射到一个新的空间,使得在原空间相似的数据,在新的空间中也相似的概率很大,而在原始空间不相似的数据,在新的空间中相似的概率很小。
2.2 文档相似度计算
按照文本处理的术语,将一个网页当做一篇文档,度量2篇文档相似度有多重方法,欧氏距离、编辑距离、余弦距离、Jaccard距离等,这里就简单的用Jaccard距离为例,集合$S$和集合$T$的Jaccard相似度计算公式为:
当不考虑文档中重复出现的单词时,一篇文档就可以看成一个集合,集合中的元素是一个个分词之后的词:
那么,按照上面Jaccard计算公式,
2.3 文档矩阵表示
假设我们有4篇文档,分词之后的表示如下:
那么整个vocabulary集合为{我,他,要,减肥,成功},将文档表示成矩阵向量,行代表vocabulary集合中的元素,列表示文档,只有文档j出现元素i时,对应的矩阵$M[i][i]=1$,否则$M[i][i]=0$,按照这种方式将上面4篇文档组织成如下形式:
2.3 最小哈希(min-hashing)定义
我们将最小hash的定义如下:特征矩阵按行进行一个随机的排列之后,第一个列值为1的行的行号。举例说明如下,假设之前的一个特征矩阵按行进行一次随机排列之后的结果如下:
那么按照最小哈希的定义,以上4篇文档的最小哈希值分别为:
$h(S_1)=3, h(S_2)=5, h(S_3)=1, h(S_4)=3$
那么为什么要定义最小哈希呢?实际上,两列的最小哈希最就是这两列的Jaccard相似度的一个估计,换句话说,两列最小hash值相同的概率与其相似度相等,也就是$Prob(h(S_i)=h(S_j)) = sim(S_i, S_j)$。
为什么会相等呢?我们来单独考虑$S_i$和$S_j$这两列,它们形成特征矩阵对应的行所有可能的结果可以分为以下三种情况:
- A类:两列的值都为1
- B类:一列的值为1,另一列的值为0
- C类:两列的值都为0
一般情况下,特征矩阵比较稀疏,这就导致大部分的行都是属于C类,只有少数行数据A类和B类,但也只有A类和B类的行决定了$sim(S_i,S_j)$,假设A类的行有a个,B类的行有b个,那么$sim(S_i,S_j)=a/(a+b)$。
现在,只需要证明对矩阵进行随机的行变换,两个的最小哈希值相等的概率$Prob(h(S_i)=h(S_j)) = a/(a+b)$即可。因为C类对计算相似性没有用,可以将所有的C类全部删除,那么第一行不是A类就是B类,如果第一行是A类,那么$h(S_i)=h(S_j)$,因此$Prob(h(S_i)=h(S_j)) = Prob(删掉C类行之后,第一行为A类)=A类行的数目/(所有行的数目) = a/(a+b)$,这就是最小哈希的神奇之处。
2.4 最小hash签名
有了最小hash还不够,因为一次最小hash只是一次独立的随机事件,大数定理告诉我们,只有多次重复随机事件才能造就必然。选择n个随机排列作用于特征矩阵,得到n个最小哈希值,$h_1,h_2,…,h_n$,这n个最下hash值组成一个n维向量,称之为最小hash签名,两列的最小hash签名的相似度即为两列的Jaccard相似度的估计。
2.5 基于最小hash的局部敏感哈希
假设前面的工作中,我们已经将一个文档的集合转化为一个最小签名,虽然这个签名已经大大的压缩了集合的空间,但要计算两列的相似度还是需要两两计算签名矩阵两列的相似度,如果有n篇文档,两两比较的次数为$n*(n-1)/2$,当n较大时,计算的复杂度依旧很高。接下来就看局部敏感哈希怎么个做法。
我们对签名矩阵按行进行分组,将矩阵分成b组,每组由r行组成,下面的实例将一个签名矩阵分成6组,每组由3行组成:
上面每一个分组中,每一列表示一个文档经过最小hash签名之后对应的向量。分组之后,对最小签名向量的每一组进行hash,各个组设置不一样的桶空间,只要两列有一组的最小签名部分相同,那么这两列就会hash到同一个桶中而成为候选相似项。上面签名的分析我们知道,对于某个具体的行,两个签名相同的概率p=两列的相似度=$sim(S_i,S_j)$,并且有:
- 在某个组中所有行的两个签名都相同的概率是$p^r$
- 在某个组中至少有一对签名不相等的概率是$1-p^r$
- 在每一组中至少有一对签名不相等的概率是$(1-p^r)^b$
- 至少有一个组的所有对签名相等的概率是$1-(1-p^r)^b$
于是,两列称为候选相似对的概率是$1-(1-p^r)^b$,它的采样值以及曲线如下图所示:
可以看到,当两篇文档的相似度为0.8时,它们hash到同一个桶而成为候选对的概率为0.9996,而当他们的相似度为0.3时,它们成为候选对的概率为0.0475;因此,局部敏感哈希解决了让相似的对以高概率hash到同一个桶中,而不相似的项hash到不同的桶的问题。
3 在cosine距离中应用LSH
上面的章节介绍完了LSH的核心思想,但是在实际应用中,在计算距离时,cosine距离应用相对更广泛一些,那么当相似度是cosine相似性时,hash函数是什么呢?
它用了一个叫随机超平面的概念。就是随机的生成一些超平面,哈希方法是看一个特征向量对应的点,是在平面的哪一侧:
下图示例了LSH应用于cosine的一个例子:
随机生成3个超平面{y1, y2, y3},在平面上有2个点A和B,根据上面的定义,很容易知道A对应的值为[1, 1, 1],B对应的向量为[-1, -1, 1],再以A,B的向量来计算A,B之间的相似性就更简单了。
下面以一段代码来进一步说明LSH在计算cosine距离上的应用:
如上面代码所示,采用局部敏感哈希算法,将大大降低计算复杂度。