快速相似性检测

6
我有一大批对象,需要找出它们之间的相似性。

具体来说:给定两个对象,我可以将它们的不相似性计算为一个数字,即度量 - 更高的值意味着更少的相似性,0意味着对象具有相同的内容。计算此数字的成本与较小对象的大小成比例(每个对象都有特定大小)。

我需要快速查找与某个对象相似的对象集。

具体来说:我需要生成一个数据结构,将任何对象o映射到与o不相似度超过d的对象集,其中d是某个不相似度值,列出集合中的对象所需的时间不超过数组或链接列表中的时间(也许它们实际上就是)。通常,集合将远小于对象总数,因此执行此计算真正值得。如果数据结构假设固定的d,那么它已经足够好了,但如果它适用于任意d,那就更好了。

您以前见过这个问题或类似的问题吗?有什么好的解决方案吗?

具体来说:一种直接的解决方案涉及计算所有对象对之间的不相似度,但这很慢 - O(n2),其中n是对象数量。是否有更低复杂度的通用解决方案?


请提供几个对象的示例,并附上您的评论。 - Misha
8个回答

2
我需要创建一个数据结构,将任何对象o映射到与o的不相似度不超过d的对象集合。
当小计大于d时,最快的方法可能是放弃相似性计算。例如,如果您的相似性基于余弦或豪斯多夫距离,这可以很容易地实现。
PS:如果无法这样做,您的问题可能与k最近邻问题有关(更准确地说是具有阈值邻域的最近邻问题)。您应该寻找能够找到附近成员而不计算所有距离的算法(也许使用三角不等式的某些东西)。维基百科应该帮助您探索合适的算法。

我可能漏掉了什么,但我不明白k最近邻算法如何适用。它似乎是一种分类算法,假设距离已知,而不是一种快速计算这些距离的方法。 - Dan Hook
有一类knn算法可以在不计算所有成对距离的情况下找到最近的邻居。这取决于您的度量空间以及您可以采取多少假设。 - akuhn
@Adrian:请提供一个链接以便澄清。 - Misha
例如,可以看看kd-tree,但是它是否适用取决于OP问题的空间。所以你很好地要求OP提供示例。 - akuhn
谢谢提到kd树和k最近邻问题。我的问题与3D空间或其他我能想到的空间的距离无关。 - reinierpost
如果你有一个度量,那么你就有了一个空间。有许多奇怪的空间,它们与传统意义上的“空间”完全不同。http://en.wikipedia.org/wiki/Metric_space - Dan Hook

1

我认为解决方案取决于更多关于您问题性质的详细信息。

  1. 你需要多次查找相似的对象,还是只需要一次?如果需要多次,那么创建一个数据结构,在每对对象之间计算差异一次,然后将对象连接到相似的对象,以便可以快速检索列表而无需重新计算,这可能是非常有用的性能增强。

  2. 计算的性质是什么?在极端情况下,如果差异的性质是例如两个人之间的身高差异,那么通过按身高排序维护列表将让您非常快速地找到相似的对象。我假设实际问题比这更复杂,但是遵循这种逻辑,如果差异是几个线性量的总和,则可以创建一个多维数组,然后在概念上将相似对象集合想象为围绕参考对象的n维球体(即圆形、球体、超球体等),并直接找到它们。实际上,我想到了一个方法,如果半径计算过于复杂或运行时间太长,一个好的近似值是在参考对象周围创建一个n维立方体(即正方形、立方体、四维立方体等),检索所有位于该立方体内的对象作为“候选对象”,然后只对候选对象进行实际计算。

例如,假设“差异”是三个属性(a1、a2和a3)的差的绝对值之和。您可以创建一个三维数组,并将数组的每个节点的值设置为具有这些值的对象(如果有)。然后,如果您想要查找所有与对象o的差异小于d的对象,则可以编写以下代码:
for (x1=o.a1-d;x1<o.a1+d;++x1)
{
  for (x2=o.a2-d;x1<o.a2+d;++x2)
  {
    for (x3=o.a3-d;x1<o.a3+d;++x3)
    {
      if (array[x1][x2][x3]!=null
        && (abs(x1-o.a1)+abs(x2-o.a2)+abs(x3-o.a3)<=d)
        {
          ... found a match ...
        }
    }
  }
}

我怀疑差异规则比那更复杂,但没关系,只需增加算法的复杂性以匹配规则的复杂性。重点是使用数组来限制您必须检查的对象集。

  1. 再次谈到计算的性质:如果构成差异的元素中的某个或某些小子集比其他元素更重要,则创建一个数据结构,使您可以快速在范围内进行比较。如果在范围内,则进行完整比较。如果不在范围内,则根本不需要查看它。

@1:是的,我需要多次查找邻居。@2:是的,这样的假设会简化问题,但你在这里提出的那些假设并不适用。我将发布一个后续问题,更具体地阐述我的问题。 - reinierpost

1

对象示例: 图像,文档。当然,使用这些对象的原始表示通常是没有用的。通常,人们会对原始形式进行预处理,并将其转换为某种规范化形式(例如对于文档,可以使用向量表示,其中每个条目表示某个单词出现的次数/百分比,对于图像,则可以是表示在图像中找到的视觉特征)。

如果d是固定的,n^2的预计算是可行的,你可以使用一个链表来表示每个对象的图形表示。 您可以使用近似最近邻算法获得更高效的解决方案,但代价是准确性。


这是我迄今为止找到的最佳方法。谢谢。 - reinierpost

1
如果您的相似度度量是可传递的,那么您不必为所有对象对计算相似度,因为对于对象a、b、c:
similarity(a,c) = similarity(a,b) op similarity(b,c)

其中op是二元运算符,例如乘法或加法。


Op需要澄清,但当他说“metric”时,我想到的是http://en.wikipedia.org/wiki/Metric_%28mathematics%29,通常由于三角不等式而不具有传递性。 - Dan Hook
根据所述,(对象,相似度)是一个度量空间,因此您可以说的关于相似性的一切就是相似性(a,c)<=(相似性(a,b)+ 相似性(b,c))。 - Tordek
@Dan:是的,我的“metric”实际上是指向相同URL的链接。 - reinierpost
@Dan:...嗯...它在编辑框中,但由于一些疯狂的错误没有出现在文本中-已修复。 - reinierpost

1

没有更多度量细节,很难说。我没有任何关于消除O(n^2)方面的想法,但可能有一种方法可以减少涉及的某些常数。例如,如果您拥有欧几里得度量d(p,q) = sqrt( (p_1-q_1)^2 + ..+ (p_n-q_n)^2),则可以将距离d平方并将其与(p_i-q_i)^2的部分和进行比较,在超过d^2时停止。

这是否会真正为您节省时间取决于比较的成本与仅计算求和项以及通过执行此操作可以避免多少个求和计算(显然,d越小,越好)。


好主意。事实上,我有一些关于“近似”节点值的想法,这些想法大致尊重距离度量,同时使计算速度更快,可以用来加速计算,但我认为问题本身已经足够复杂了。 - reinierpost

1

不能使用 kd-tree 吗?

如果可能的话,可能需要对维度进行归一化。之后,您只需要填充树,使用“最近的 N 个邻居”搜索,并尝试在某个范围内找到任何对象。


kd-tree需要一个带有轴的度量空间(以及分割它的能力),遗憾的是OP没有告诉我们是否具备这个属性。 - akuhn
它并不会,这是使它变得困难的事情之一。 - reinierpost

0

我们可以假设相似性是可传递的,即 diff(a,c) == diff(a,b) + diff(b,c) 吗?如果是这样,您可以尝试以下方法:

  1. 对对象集合进行排序。如果对象相似度指标没有一个合理的绝对值,您可以任意选择一个对象作为“零点”,并按照它们与该对象的相似度对所有其他对象进行排序。
  2. 要查找与 o 相似度为 s 的对象,请在排序列表中查找 o,并向左和向右搜索,直到差异增大到大于 s 为止。

这样做的好处是可以一次性完成排序,并且随后的集合构建与将在集合中的成员数量成比例。


1
不,指标不具有传递性。 - Tordek
2
它不是传递的。考虑如果a和c相同会发生什么。你的公式会产生2 * diff(a,b)的值,而实际上应该是零。 - Jerry Coffin
这项工作是否依赖于传递性,问题没有提供足够的信息来说明。如果“差异”是例如人员之间高度的有符号差异,则它将是传递的。如果更像是从相关功能列表中选择的两个产品共享的功能数量,则根本不是可传递的。 - Jay
@Jay:是的,这个问题提供了足够的信息来说明它不是传递性的:“给定两个对象,我可以计算它们的不相似度作为一个数字,一种度量方式——更高的值意味着更少的相似性,而0表示对象具有相同的内容。” - Jerry Coffin
询问问题的任何其他属性都是很好的,特别是当它们对于一个好的解决方案可能是必要的。但是不行,我的差异不能以这种方式添加。(考虑一下如果你交换b和c会发生什么。) - reinierpost

0
听起来像BK-Tree。这里有一个小例子。你基本上创建一棵树,检查哪个分支应该用于相似对象搜索,哪个不应该,这样你就可以避免O(n2)的情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接