阅读更多
第三章 查找相似的Items
数据挖掘的一个基本问题是检测相似的Items.比如网页去重,从网页集合中找到近似重复的网页,这类网页通常是内容相同,但是有一些是关于不同站点和镜像的内容。
这章从集合中找到交集,交集和集合大小的相对比率表示相似度,展开介绍相似度。我们会介绍怎样把文本文档的相似性表示为集合问题,这种技术被称为指纹方式。然后介绍了minhashing,他可以将大的集合压缩,并从压缩后的版本导出原来集合的相似度。其他的一些相似度算法在3.9进行了介绍。
一个非常重要的问题是,我们要搜索相似的Items,我们需要比较两两比较,这需要比较太多
的次数,即使每一对比较都非常的简单,但是仍然需要非常大的计算量。使用"locality-senitive hashing"可以解决这个问题,他只搜索可能相似的pair。
最后,介绍了不通过集合交集来表达的形似度计算方法,然后介绍了LSH通用的框架,可以使用其他定义的相似度算法。
3.1 临近搜索应用
3.1.1
Jaccard 相似度表示为: 两个集合S和T,
SIM(S,T) = |S ∩ T |/|S ∪ T |.
3.1.2 文档相似度
从文档比如网页或者新闻文章集中找出文本相似的文档,Jaccard相似度可以很好的解决。
文本相似度具有很大的用处,比如找出重复或者近似重复的文档,测试两个文档完全重复是很好做的事情,但是有一些应用中,他们不是完全一样,而是共享很大一部分内容。有以下应用:
文章抄袭
查找文档的抄袭可以检测我们查找文档相似度的能力。抄袭者可能只有一部分时他自己的,他可能修改了措辞或者调整了句子的顺序,但是仍有50%是抄袭别人的。简单的逐字的对比来是检测不出复杂的抄袭。
镜像网页
一些流行的网站会把网页镜像到不同的域中,以此来均衡服务器压力。网站的镜像中的网页非常相似,但是并不是完全相同,他们可能会包含自己的域的信息,链接到其他的镜像而不是他们自身。能够检测出重复网页是一个很重要的应用,比如搜索引擎应该避免近似相同的两个网页同时出现在搜索结果的第一页。
同一个来源的文章
一个记者写的一篇新闻,可能被多个媒体使用,每一个可能只是将这篇文章做了一些修改,
比如删除掉了一些段落,添加了他们自己的内容,内容周边环绕了他们的logo,广告,链接到他们自己网站的其他文章。Google新闻应该能够找到这个文章的所有版本,并只显示出一个,这需要查找两篇文章的文本相似度。
3.1.3 基于相似集合的协同过滤
另一类使用集合相似度的应用是协同过滤,协同过滤描述了把具有相似口味的用户的items推荐给其他用户。
在线购物
比如在亚马逊有上百万的用户和物品,记录了哪些用户买了哪些物品,如果两各个人购买的物品集合具有很高的Jaccard相似度,那么这两个客户也是相似的。
除了根据Jaccard相似度,还需要一些其他的工具,比如两个客户都喜欢看科幻小说,但是他们购买了不同的小说,具有很少的重复,那么我们还需要通过组合相似度和聚类来做协同过滤。
电影分级评分
NetFlix记录了哪些客户租了哪些电影,已经他们对这些电影的评分,我们可以认为如果相同的顾客对不同电影评分比较高的,它们具有很大的相似性。
bag相似度:
{a, a, a, b} 与{a, a, b, b, c} 的相似度为 1/3.
3.2 文档指纹签名
用文档的一个短的字符串集合来描述文档词法上的相似度,是非常有效的方法。这种方式,文档公用一些短的句子或者短语,这样这些文档的字符串集合会有很多的共同元素,即使这些句子出现的顺序不同。
3.2.1 k-shingles
一个文档是由字符组成的字符串,k-shingles表示文档中任意长度为k的字串集合。
我们还可以将他们出现的次数关联上去。比如文档D为abcdabd字符串,那么2-shingles为
{ab,bc,cd,da,bd}。ab出现了两次但是没有在2-shingles出现过两次。也可以采用bag的方式,对出现的个数进行技术。
对于空白字符的处理,最好的办法是将所有的连续空白都替换为单个空格。
3.2.2如何选择k
我们可以取k为任意常数,然而,如果我们选择k太小,则会有非常多的k-shingles出现在大多数的文章中。那么他们的指纹集合的Jaccard相似度就会很高。如果我们取k=1,那么大多数的网页中都含有非常多的共同的字符,很少的其他字符,那么所有的网页都是相似的。
k的长度应该根据文档的长度和字符集的大小来选定,但是最重要的是:k应该选择让任意的指纹在任意的文档中都应该是很低的概率
如果我们的语料是emails,那么选择k是5是一个很好的选择。对于大的文档,比如研究论文,可以选择k=9.
3.2.3 哈希指纹
除了使用子串进行指纹签名,我们还可以使用hash函数,他可以把长度为k的字符串映射到
一些桶中,将桶的数字作为指纹。这样一个文档的集合可以表示为一个或者多个k-shingles出现桶的数字。我们可以为文档构建9-shingles,然后将每一个9-shingles映射到0~2^32-1之间桶的标号中。这样每一个指纹只有4字节而不是9个了。这样不仅空间被压缩了,而且操作是单个机器字的操作。
我们可以使用9-shingles,然后将它hash成4个字节,这样文档的区分度就会别使用4-shingles要高,虽然在空间消耗上是一样的。
3.2.4 根据单词构建指纹
一个好的方式是,首先定义文中的停顿词,然后取出停顿词后面的两个单词,这样可以组成很好的指纹信息。这个在网页去重中非常有用,因为导航、边框都是很短的很少停顿词的句子,而正文中有很多的停顿词,这样可以去除非正文部分的干扰,并且获得比较好的效果。
3.3 保持相似性的集合摘要(Similarity-Preserving Summaries of Sets)
指纹集合是很大的,即使把他们hash为4个字节,它们所占的空间也是原来文档大小的4倍。如果我们数以百万的文档集合,那么在内存中存储所有的指纹集合是不可能的。
我们本节的目标是将很大的集合用很小的签名表示。签名的重要属性必须能够通过比较签名就能够估计其所表示集合的Jaccard 相似度。虽然签名不能得到精确的相似度,但是签名集合越大,估计出来的值越准确。
3.3.1 基于矩阵的集合表示法
在我们解释如何从大的集合构建小的签名之前,一个非常有用的方法将一组集合可视化为特征矩阵。矩阵的列和集合对应,行和所有集合的元素对应。如果第r行的元素出现在第c个集合,那么第r行c列的元素值为1,否则为0.
特征矩阵很容易可视化数据,但是不适合存储,因为矩阵一般是稀疏矩阵。
例如:元素集合{a,b,c,d,e},其中s1={a,d},s2={c},s3={b,d,e},s4={a,c,d}
那么这四个集合的矩阵表示为:
3.3.2 Minhashing
在本节,我们首先学习计算minhash的原理,然后在下面的章节我们介绍在实际中如何近似计算minhash的值。要minhash一个由特征矩阵的一列表示的集合,从这些行的全排列中选择一种排列。任何一列的minhash值是排列的次序中数字第一行是1的行号。
例子:比如我们要从前面矩阵中选择beadc次序的行,选择的这个排列定义了minhash函数h,他将集合映射到行。我们根据h计算minhash的值,我们发现当一个不是零的行是行a,因此
h(S1) = a,同样可以得到h(S2) = c, h(S3) = b, h(S4) = a.
虽然对一个很大特征矩阵进行全排列是不实际的,但minhash函数隐式的重新调整矩阵的行序。
3.3.3 Minhash和Jaccard相似度
minhash和其集合的Jaccard相似度有着重要的联系:两个集合的随机的一个行排列的minhash值相等的概率和两个集合的Jaccard相似度相等。
为什么呢?如果我们限制于集合s1和s2,那么行可以分成三类:
1.类型X 两列值都是1
2.类型Y 一列是1,另一列是0
3.类型Z 两列都是0
由于矩阵是稀疏的,所以大多数的行是类型Z。类型X和类型Y的函数既决定了SIM(S1,S2)
又决定了h(s1)=h(s2).我们假设有x行是类型X的,y行是类型y的。那么SIM(S1,S2) = x/(x+y).因为x是S1 ∩ S2的大小,而x + y是S1 ∪ S2大小.
现在我们考虑h(s1) = h(s2)的概率。如果我们假设我们随机排列行,那么我们从上到下,
在遇到类型Y之前遇到类型X的概率是x/(x+y)。如果我们从上往下遇到的除了Z类型的第一行
是X类型的行,那么h(s1) = h(s2).
另一方面,如果除了类型Z,我们第一次遇到的类型是类型Y,那么是1的集合,其对应行数,就是minhash值。是0的集合仍然需要向下才能找到为1的行,因此如果我们第一次遇到的是类型Y那么h(s1) <> h(s2)。所以h(s1) = h(s2)的概率是 x / (x + y),这也是s1和s2的Jaccard相似度。
3.3.4 Minhash签名
我们重新考虑一组集合,有特征矩阵M表示,为了代表这些集合,我们从M行的排列中选择n个
,可能是100或者几百个排列。有这些排列组成了minhash函数h1,h2,...,hn。
从由列表示的集合S,构建其minhash签名,组成向量[h1(s),h2(s),...,hn(s)]。我们把这些
hash值作为列,因此我们从矩阵M中得到了签名矩阵,矩阵M的第i个列被第i列的minhash签名所代替。
这个签名矩阵具有和原来矩阵一样的列M,但是只有n行,所以远远小于原来的矩阵大小。
3.3.5 计算Minhash签名
显式的计算一个很大的特征矩阵的排列是不可行的,即使从几百万或者几十亿的行中选择一个随机的排列也是非常耗时的。排列矩阵在概念上可以,但是不可以在实际中实现。
幸运的是,通过一个随机hash函数将行号和具有和行数相同的桶进行映射,可以有效的模拟随机排列。一个hash函数将0...k-1的数字映射到0...k-1的桶中,典型的可能出现不同的数字会映射到相同的桶中,然后其他的一些桶没有被映射。然而只要k足够大,并且没有太多的冲突,这个不同是不重要得,我们可以维护一个hansh函数h,他排列行r到h(r)的位置上。
方法一.行hash
选择k个hash函数,计算行号的hash值,然后按照值进行排序,得到随机的排列。
方法二 One-pass的实现
这样我们可以不去选择n个随机的排列,而是选择n个随机函数h1,h2...,hn,我们可以通过现在的顺序来构建签名矩阵,假设SIG(i,c)是签名矩阵的第i个hash函数和第c列,最初,我们设置所有的SIG(i,c)为无穷大,我们按照下面的方法来处理行r
1.计算h1(r),h2(r),...,hn(r)
2.对于所有的列c:
(a)如果(c,r)是0,不做任何操作
(b)如果(c,r)是1,那么对于i=1,2,...,n设置SIG(i,c)为当前的SIG(i,c)和hi(r)的最小值。