推荐系统-找相似

前言

最近做推荐,召回其中的一个策略是采用协同过滤的算法,本来觉得不就是简单的一通计算相似度取topK相似用户做推荐就好嘛!但是真正做起来就要踩“大数据”的坑。

传统的基于协同过滤,核心在于计算相似度找到topK相似用户/物品,无论哪种计算相似度的时间复杂度都会随着用户/物品规模的扩大而呈平方增长,对于实际应用中,用户/物品往往达到千万甚至更多,因此暴力计算相似度显然不可行,这时就需要采用近似计算的方法,损失一部分精度来换取计算的效率。

最开始准备采用Facebook的Faiss框架来计算相似度,但是由于准备用go做服务,Faiss没有官方提供go的接口需要手动编译,而且计算用户相似度时,数据是动态的,用起来不那么方便,因此弃用Faiss转向LSH来计算用户的相似度。下面详述Min hashing和LSH(local sensitive hashing)原理。

Hash Functions

大家应该经常能听到哈希,也用过java类或相似的包来计算哈希值,哈希函数根据hash-key可以产生一个bucket number作为其存储位置,因此可以快速查询。hash table就是根据key-value通过hash function(记为h)得到映射的值进行寻址直接访问的数据结构。

  1. 对于不同的key得到同样的散列地址,即$k_{1}≠ k_{2}$但$h_{1} = h_{2}$,我们称之为碰撞。
  2. 若对于所有的key经过hash function的映射到每一个地址的概率是相等的,则成为均匀散列函数。

Min Hashing

Jaccard相似度

Jaccard index用于计算相似度,是距离的一种度量方式,假设有向量$S_{1}$和$S_{2}$,我们定义

$Jaccard(S_{1}, S_{2})=\frac{S_{1}∩S_{2}}{S_{1}∪S_{2}}$

Minhash

Min Hashing是LSH的一种,可以用于快速估计两个向量的相似度。Min Hashing和Jaccard相似度有很大的关系:

对两个向量进行Min Hashing,产生的哈希值相同的概率等于两个向量的Jaccard相似度
                                                            -- (1)

通过MinHash得到映射分两步:

  • 首先对row进行permutation,每个向量做同样的操作
  • 向量的MinHash值对应permutation后,取值为非零的第一行的row index

考虑为什么通过Minhash可以达到上述(1)所说的效果呢?我们举例如下,有四个向量,每个向量有5行(维)(我们可以把列看作User,行看作Item,即User$S_{1}$购买过2,3商品…):

表1:

row $S_{1}$ $S_{2}$ $S_{3}$ $S_{4}$
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

我们看$S_{1}$和$S_{2}$,每一行的取值分三种情况:

  1. $S_{1}$和$S_{2}$同为1
  2. $S_{1}$和$S_{2}$一个为0一个为1
  3. $S_{1}$和$S_{2}$同为0

由于矩阵通常很稀疏(考虑实际中用户与购买物品的矩阵),因此大部分为情况3,但是1和2决定来Jaccard($S_{1}$,$S_{2}$)和$h\left(S_{1}\right)=h\left(S_{2}\right)$概率,假设情况1有x行,情况2有y行,则$\operatorname{Jaccard}\left(S_{1}, S_{2}\right)=\frac{x}{x+y}$(x相当于$S_{1} \cap S_{2}$,y相当于$S_{1} \cup S_{2}$),若Permutation是随机的,则$S_{1}$和$S_{2}$从上向下找第一个非零属于情况1的概括为$\frac{x}{x+y}$。

Min Hashing signatures

如何通过Min Hashing产生签名呢?

对向量做m次permutation(m一般为几百或更小,小于原向量长度n),每一次经过permutation得到minhash记为$h_{1}, h_{2}, \ldots, h_{m}$, 则向量获得的签名可以表示为:

$\operatorname{sig}(A)=\left[h_{1}(A), h_{2}(A), \ldots, h_{m}(A)\right]$

对于一个高维度的向量进行permutation也是非常耗时的,因此可以随机选择m个哈希函数代替permutation。具体做法如下:

  1. 取m个针对row index的哈希函数$h_{1},h_{2}, \ldots, h_{m}$将原始0,1…n-1映射到0,1…n-1上
  2. 记Sig(i,c)为第c列在第i个哈希函数下的MinHash值,初始值记做$\infty$
  3. 对于每一行r
    • 计算$h_{1}(r),h_{2}(r),\ldots,h_{m}(r)$
    • 对于每列c:
      • 如果c在r行取值为0,忽略
      • 如果c在r行取值为1,则对$i=1,2,…,m$计算Sig(i,c) = Min(Sig(i,c),$h_{i}(r)$)

对表1所示数据,我们举例说明Min Hashing签名生成过程,假设我们选择两个哈希函数对row index生成MinHash,如下表2所示:$h_{1}(x) = x+1 \mod 5$, $h_{2}(x) = 3x+1 \mod 5$

row $S_{1}$ $S_{2}$ $S_{3}$ $S_{4}$ x+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 5 3

初始时,哈希签名如下:

row $S_{1}$ $S_{2}$ $S_{3}$ $S_{4}$
$h_{1}$ $\infty$ $\infty$ $\infty$ $\infty$
$h_{2}$ $\infty$ $\infty$ $\infty$ $\infty$

对于第0行,只有$S_{1}$ 和$S_{4}$为非零,且$h_{1}$和$h_{2}$均为1,则当前签名矩阵变为

row $S_{1}$ $S_{2}$ $S_{3}$ $S_{4}$
$h_{1}$ 1 $\infty$ $\infty$ 1
$h_{2}$ 1 $\infty$ $\infty$ 1

对于第1行,$S_{3}$为1,对于$h_{1}$和$h_{2}$为2和4,则签名矩阵变为:

row $S_{1}$ $S_{2}$ $S_{3}$ $S_{4}$
$h_{1}$ 1 $\infty$ 2 1
$h_{2}$ 1 $\infty$ 4 1

继续至第2行,$S_{2}$ 和$S_{4}$为非零,对应$h_{1}$和$h_{2}为3和2,$S_{4}$目前的值均为1,小于$h_{1}$,$h_{2}$对应的3和2,则签名矩阵变为:

row $S_{1}$ $S_{2}$ $S_{3}$ $S_{4}$
$h_{1}$ 1 3 2 1
$h_{2}$ 1 2 4 1

最后扫描完第4行,最后得到签名矩阵为:

row $S_{1}$ $S_{2}$ $S_{3}$ $S_{4}$
$h_{1}$ 1 3 0 1
$h_{2}$ 0 2 0 0

通过上述过程可以看到,实际的$Jaccard(S_{1},S_{4})=2/3$,但通过签名计算得到的$Jaccard(S_{1},S_{4})=1$,上述结果是由于示例只选择了两个哈希函数,在实际应用中,会选择更多的哈希函数并且向量数往往在百万千万级,这时就会比较相近了。

Local Sensitive Hashing(LSH)

经过Min Hashing,我们可以实现数据降维并保证降维后的数据保持原空间的相似度,降低了在物品维度很高时计算相似度的时间复杂度,但是当用户数巨大时,计算两两相似度仍旧需要很高的时间开销,如何进一步降低寻找相似用户的复杂度,就需要LSH这样一个近似算法来实现。

LSH常见的做法是经过多次哈希将相似的向量散列到相同的桶中,检索时,我们只需要考虑相同桶中的任意对作为候选集即可。我们称不相似的向量被散列到相同的桶样本为false positive,相似的向量被散列到不同到桶中的样本称为false negetive。

在有来Min Hashing签名的基础上,一个有效的办法是将签名分段,我们称之为band。如下图所示:
1

每个签名都被分成4段,向量的签名散列到对应的band上,如果band相同的越多,则其相似度越高。我们可以通过band获取candidate,计算candidate相似用户的相似度就好了。

如何选择band数?

假设我们有b和band,每个band有r行,即总的Min Hashing签名长度为b*r,假设两个向量的Jaccard相似度为s,则:

  1. 对于某一个band,所有行都相等的概率为:$s^r$
  2. 对于某一个band,至少有一行不同的概率为:$1 - s^r$
  3. 对所有band,至少有一行不同的概率为: $(1 - s^r)^b$
  4. 至少有一个band的所有行都相等的概率为: $1- (1 - s^r)^b$,即两个用户成为candidate的概率

上述概率在r,b不同值时为一个S曲线,如当r=6,b=100时的曲线如下图:

2

我们定义超过0.5的概率就有可能成为candidate,此时对应的相似度称为threshold:

$threshold \approx (\frac{1}{b})^{(\frac{1}{r})}$

在实际应用中,我们需要先确定最小相似度,即相似度大于多少我们认为可以作为candidate,然后确定哈希签名的长度进而可以确定b和r。我们需要考虑的是:

  • 我们想要尽可能少出现false negative,我们需要选择b和r使得thresh< 我们定义的最小相似度。
  • 如果要保证计算速度快并尽可能少出现false positive,我们需要选择b和r使得thresh>最小相似度。

参考文献:

  1. Mining of massive datasets; Jeffrey D. Ullman Anand Rajaraman, Jure Leskovec.
  2. 大规模数据的相似度计算:LSH算法