【问题标题】:Find the closest group of vectors, one vector from each set?找到最近的一组向量,每组一个向量?
【发布时间】:2022-08-14 09:19:31
【问题描述】:

我有ķ向量集。向量的长度都相同.这些集合的长度不完全相同,但假设它们的平均长度为n每个向量中。我需要找到一组向量,每组一个,彼此之间具有最小距离(L2 范数)。这类似于“最近对”问题,但那只是 2 组,而我有ķ套。

天真的方法是交叉连接所有值并搜索所有值O(n^k)距离。有没有更好的方法/算法?

Example  
Set A [[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]  
Set B [[0.5, 0.9], [0.1, 0.3], [0.9, 0.1]]  
Set C [[0.2, 0.2], [0.8, 0.4], [0.5, 0.1]]  
Result - A [0.1, 0.2], B [0.1, 0.3], C [0.2, 0.2] with L2 distance 0.14  

  • 通过“彼此之间的最小距离”,您是指该组中所有向量对之间距离的最小总和吗?
  • @Cadeyrn 我愿意接受建议。我正在考虑计算总欧几里得距离,它(我相信)分别取每个维度的最小值和最大值之间的差,将这些差平方,然后对这些平方求和,然后取平方和的平方根。但也许这不是最好的方法。
  • 对于这类问题,给出kmn 的值很重要。根据它们的不同,可以使用精确的算法,或者只能使用启发式算法。
  • 你的向量中每个坐标的范围是多少?样品建议<0,1> 范围
  • 我为我的答案添加了工作 C++ 代码

标签: algorithm mathematical-optimization euclidean-distance


【解决方案1】:

Curse of Dimensionality 不是你的朋友。如果m 相当大,则很有可能所有向量对彼此之间的距离相似。因此,没有一大堆好的向量可以找到。因此,您的搜索——即使是通过合理的候选人——将不得不考虑很多选择。

也就是说,我将描述一个更有效的算法。但请注意,它也会消耗大量内存。所以它一开始会比你当前的算法更好地扩展,但它也会在内存限制下达到顶峰。希望它足以满足您的需求。

首先,我将描述一个假设的有向图。 (我们将尽量避免实际构建它的大部分,但从概念上讲它是存在的。)它将是分层的。 l'th 层将是所有l 点集合的集合,每个集合来自第一个l 矩阵。如果它们在第一个l 点上达成一致,我们将l'th 层连接到l+1th,并且连接它们的边的权重是从最后一个到所有其他点的距离之和。 0th 节点是空集。我们将kth 层加入到我们的目标中,边的权重为 1。

验证以下内容。从空集到目标的路径的总权重为其k'th 级别的总 L2 范数。因此,最低权重路径为您提供了您正在寻找的答案。

因此,这是A* search 的一个很好的候选者。用于路径其余部分的启发式方法是:

c = sum(points) / len(points)
s = sum(distance(p, c) for p in points)
heuristic s * (k - len(points))

换句话说,剩下的点只能位于质心,如果这是真的,启发式是剩余距离的总和。

如果有一个自然的丛可以找到,这可以找到答案,只需O(m * n * (n + k) * log(n*k))) 工作。如果没有自然团块,则可能存在任意接近最坏情况界限的示例,例如O(n^k * m * k^2 * log(n))。而且,当然,它可能需要O(n^k) 内存,此时您只会崩溃。

所以这可能有效,也可能无效,但值得一试。

【讨论】:

    【解决方案2】:

    所以你有k 集合,每个集合都有~n 向量,每个向量都有m 维度。

    对于这样的任务,我通常使用所有网格对齐“方向”的地图并仅在它们上进行计算,而不是使用输入数据中存在的方向 这通常会显着减少问题组合的数量,也可以通过提高精度递归地完成(接近最后找到的较粗网格的解决方案)。我会这样攻击你的案子:

    1. 创建主要矢量“方向”的地图O(m*(s^m))

      如果您有坐标范围 <0,1> 然后创建一个包含一些 s 步骤的所有矢量组合的地图,例如 11 个步骤将在 2D 中导致

      (0.0,0),(0.0,0.1)...(1.0,0.9),(1.0,1.0)
      

      将它们保存在m 维向量数组中,以便再次访问 2D 示例:

       vec2 dir[s][s] = 
          { 
          { (0.0,0.0),(0.0,0.1)...(0.0,1.0) },
          { (0.1,0.0),(0.1,0.1)...(0.1,1.0) },
          ...
          { (1.0,0.0),(1.0,0.1)...(1.0,1.0) }
          };
      
    2. 对于dir 的每个向量,计算到每组O(n*k*(s^m)) 的最大距离

      所以只需循环遍历dir 的所有向量,并为每个计算每个集合的最小距离并只记住最大的一个。将结果保存在m 维数组dis 中,因此:

      dis[i1][i2]...[im] = max( |dir[i1][i2]...[im],set[1]| ,
                                |dir[i1][i1]...[im],set[2]| ,
                                ... ,
                                |dir[i1][i2]...[im],set[k]| )
      
    3. dis O(s^m) 中找到最小距离

    4. 在每个集合中找到与dir 中找到的向量最近的元素,对应于dis O(m*n*k*m) 中的最小距离

      这是你想要的输出。如果nO(m*log(n)*k*m) 来说真的很大,这可能会通过二分搜索来加速。

      注意s 应该选得足够大,这样才不会设置有效的解决方案。假设m 不是太大的数字,由此产生的复杂性约为O(m*n*k*(s^m))。只有在以下情况下才会更快:

      O(m*(n^k)) > O(n*k*(s^m))
      

      使用非常相似的技术(“方向”图)查看这些 QA:

      [Edit1] 小 C++/VCL 例子

      //---------------------------------------------------------------------------
      const int K=3; // sets
      const int N=3; // max set size
      const int M=2; // dimensions
      float set[K][N][M]=
          {
          {{0.1, 0.2}, {0.3, 0.4}, {0.5, 0.6}},
          {{0.5, 0.9}, {0.1, 0.3}, {0.9, 0.1}},
          {{0.2, 0.2}, {0.8, 0.4}, {0.5, 0.1}}
          };
      //---------------------------------------------------------------------------
      float distance2(float *a,float *b)  // |a-b|^2
          {
          int m;
          float x,dd;
          for (dd=0.0,m=0;m<M;m++)
              {
              x=a[m]-b[m];
              dd+=x*x;
              }
          return dd;
          }
      //---------------------------------------------------------------------------
      float get_closest(int *ix)  // returns min distance between sets
          {                       // ix[K] will hold selected closest vectors from sets
          const int S=11;         // grid steps
          const float dx=1.0/float(S-1); // grid step
          int s1,s2,k,n,m,s,ss1,ss2;
          float x1,x2,d,d1,d2;
          float dir[S][S][M],dis[S][S];
      
          // create dir[][]
          for (s1=0,x1=0.0;s1<S;s1++,x1+=dx)
           for (s2=0,x2=0.0;s2<S;s2++,x2+=dx)
              {
              m=0;
              dir[s1][s2][m]=x1; m++;
              dir[s1][s2][m]=x2; m++;
              }
      
          // compute dis[][] and its minimum dis[ss1][ss2]
          ss1=0; ss2=0;
          for (s1=0;s1<S;s1++)
           for (s2=0;s2<S;s2++)
              {
              // max distance between set[] and dir[s1][s2]
              for (d2=0.0,k=0;k<K;k++)
                  {
                  // min distance between set[k][] and dir[s1][s2]
                  for (d1=0.0,n=0;n<N;n++)
                      {
                      d=distance2(dir[s1][s2],set[k][n]);
                      if ((n==0)||(d1>d)) d1=d;
                      }
                  if (d2<d1) d2=d1;
                  }
              dis[s1][s2]=d2;
              // remember smallest one
              if (dis[ss1][ss2]>dis[s1][s2]){ ss1=s1; ss2=s2; }
              }
      
          // find corresponding indexes from set[][]
          for (k=0;k<K;k++)
           for (d1=0.0,ix[k]=0,n=0;n<N;n++)
              {
              d=distance2(dir[ss1][ss2],set[k][n]);
              if ((n==0)||(d1>d)){ d1=d; ix[k]=n; }
              }
      
          // compute real distance
          for (d1=0.0,m=0;m<M;m++)
              {
              for (k=0;k<K;k++)
                  {
                  d=set[k][ix[k]][m];
                  if (k==0){ x1=d; x2=d; }
                  if (x1>d) x1=d;
                  if (x2<d) x2=d;
                  }
              d=x2-x1; d1+=d*d;
              }
          return sqrt(d1);
          }
      //---------------------------------------------------------------------------
      __fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
          {
          AnsiString s;
          int k,n,m,ix[K];
          mm_log->Lines->Add(get_closest(ix));
          for (k=0;k<K;k++)
              {
              for (n=ix[k],s="",m=0;m<M;m++) s+=AnsiString().sprintf("%3.1f ",set[k][n][m]);
              mm_log->Lines->Add(s);
              }
          }
      //-------------------------------------------------------------------------
      

      只需忽略 VCL 的东西(最后一个使用 get_closest 并打印其结果的函数),重要的东西是函数 get_closest,它返回每个集合中最近向量的索引数组。这里的结果:

      0.141421367827692
      0.1 0.2 
      0.1 0.3 
      0.2 0.2 
      

      代码要求所有集合的长度相同,对于变量一,您只需稍微调整一下(n 的所有循环)

      如果内存是一个问题请注意,如果所有迭代都移到相同的循环中,则数组dir,dis 可能会被删除...

      如果您想为任何维度做好准备,请参阅:

      寻找nested_for,您可以使用它轻松实现s^m 循环,而无需任何奇怪的黑客攻击......

    【讨论】:

      【解决方案3】:

      我想我对此有一个有趣的启发式方法。

      将每个O(n*k) 向量的第一个元素放入数组v[1]

      将每个O(n*k) 向量的第二个元素放入数组v[2]

      ...

      将每个O(n k) 向量的m-th 元素放入数组v[m] 中。

      此步骤的总时间复杂度:O(m n k)


      v[1] 的所有元素对放入数组p[1] 中,并根据O((n k)^2 log(n k)) 中的元素对的平方差对其进行排序。启动计数器cnt: = 0。遍历p[1] 并查看每对的第二个元素。每当你在i 位置看到一个新元素x 时,保存一个映射map[1][x]: = i,将其放入数组men[1],递增cnt 并将1 放入cnt-th 位置数组women[x]。最后,men[1] 应该有 k 元素。

      v[2] 的所有元素对放入数组p[2] 中,并按O((n k)^2 log(n k)) 中的元素对的平方差对其进行排序。启动计数器cnt: = 0。遍历p[2] 并查看每对的第二个元素。每当你在位置 i 看到一个新元素 x 时,保存一个映射 map[2][x]: = i,将其放入数组 men[2],递增 cnt 并将 2 放入数组 cnt-th 的位置 @987654354 @。最后,men[2] 应该有 k 元素。

      ...

      v[m] 的所有元素对放入数组p[m] 中,并按O((n k)^2 log(n k)) 中的元素对的平方差对其进行排序。启动计数器cnt: = 0。遍历p[m] 并查看每对的第二个元素。每当你在i 位置看到一个新元素x 时,保存一个映射map[m][x]: = i,将其放入数组men[m],递增cnt 并将m 放入cnt-th 位置数组women[x]。最后,men[m] 应该有 k 元素。

      此步骤的总时间复杂度:O(m (n k)^2 log(n k) )


      考虑一个具有m 男性和k 女性的稳定婚姻问题,其中男性i 的偏好由先前计算的men[i] 数组描述,而女性“j”的偏好由先前计算的数组描述women[j] 数组。使用 Gale-Shapley 算法求解,并使用之前计算的 mapp 数组中找到每个匹配项关联的对。将这些对中的每一个放入数组match

      您可以使用match 在线性时间内对其大小进行良好的矢量选择。

      此步骤的总时间复杂度:O(m k)


      最终时间复杂度:O(m (n k)^2 log(n k) )

      【讨论】:

        猜你喜欢
        • 2016-02-13
        • 1970-01-01
        • 1970-01-01
        • 2011-02-22
        • 1970-01-01
        • 2014-01-13
        • 2020-02-05
        • 1970-01-01
        相关资源
        最近更新 更多