【问题标题】:Efficient construction of cosine similarity matrix from corpus in Chapel从 Chapel 语料库高效构建余弦相似度矩阵
【发布时间】:2017-09-05 02:39:03
【问题描述】:

我有一个 TF/IDF 向量的语料库 V,所以它们非常稀疏。
这是一个大约 2,500 x 150,000 的数组。
我想计算语料库中每个文档之间的余弦相似度。

这几乎是我能想到的最天真的方法。我已经知道三四个优化,但我不想假设答案。我想知道在此计算中使用 Chapel 的计算效率最高的方法。目标是获得Xdiag(X) = 0 的对称矩阵

use Norm,
    LinearAlgebra;

var ndocs = 2500,
    nftrs = 150000,
    docs = 1..ndocs,
    ftrs = 1..nftrs,
    V: [docs, ftrs] real,
    X: [docs, docs] real;

for i in docs {
  var n1 = norm(V[i,..]);
  for j in (i+1)..ndocs {
    var n2 = norm(V[j,..]);
    var c = dot(V[i,..], V[j,..]) / (n1*n2);
    X[i,j] = c;
    X[j,i] = c;
  }
}

编译使用

chpl -I/usr/local/Cellar/openblas/0.2.20/include -L/usr/local/Cellar/openblas/0.2.20/lib -lblas cosim.chpl

== 更新 ==

这实际上应该可以编译和运行。原始代码有以下@bradcray 建议的错误

【问题讨论】:

  • 给定一个数学任务(函数最大化),什么是标准函数,什么是度量?缺少对此的明确声明。 (cit.)计算效率最高的方式(在此计算中使用 Chapel)”的定量衡量标准是什么?感谢 Brian 添加了如此清晰而合理的声明,以使此类优化工作不会遇到移动的沙子。
  • @brian-dolan:上面的代码中有许多错误我试图编辑,但被拒绝说我需要将编辑传达给你。问题是 ndocs 和 nftrs 被声明为域,但随后用于声明数组(需要范围)和内部 for 循环中的边界(需要整数)。这是我建议的解决方法:使用 Norm、LinearAlgebra; var ndocs = 2500, nftrs = 150000, docs = 1..ndocs, ftrs = 1..nftrs, V: [docs, ftrs] 真实, X: [docs, docs] 真实;文档中的 i {
  • @Brad 请不要感到被拒绝!好的,我会看看,可能会寻求更多帮助。啊,很久以前,当我对编程一无所知时......

标签: sparse-matrix cosine-similarity chapel


【解决方案1】:

以下是对原始实现的一些改进:

  • 将所有idot(V[i, ..], V[i, ..]) 预先计算并缓存到一个数组中,以减少重复计算。
  • 使用1..V.sizeV.domain 而不是1..V.shape[1]
    • V.shape 是根据域大小计算得出的,而不是存储为字段。
  • 通过并行计算X,利用此程序的令人尴尬的并行特性

如需了解更多详情,请参阅 GitHub issue,其中探讨了这些变化及其对时间的影响。

【讨论】:

    【解决方案2】:

    [Meta:这个问题一直困扰着我,因为它长期以来一直没有答案。由于它使用了“计算效率最高”的短语,我个人一直回避它。在实践中,很难保证任何解决方案都满足该描述,因为从一台目标机器或数据集到下一台目标机器或数据集可能会发生变化。但是由于没有其他人加强,我将制作一些 cmet,希望它们可能有用。]

    您的代码中有几件事对我来说很突出:

    1) 除非我遗漏了什么,否则您在计算过程中会多次重复计算 norm(V[r, ..])。渐近地说,这表明您正在做只需要线性工作的二次工作。我建议为每一行计算一次范数并将其存储在一个数组中以避免这种冗余计算:

    var normVrow: [docs] real = [r in docs] norm(V[r,..]);
    

    然后,在内部循环中,您可以只引用normVrow[i]normVrow[j]

    2) 由于这是 Chapel,并且您的循环似乎没有交叉循环依赖性,而不是使用串行 for 循环,您可能应该使用并行 forall 循环进行此计算。有一个问题是:

    (a) 将外部循环更改为forall(这会导致负载不平衡,因为整个迭代空间是三角形的),

    (b) 将两个循环都更改为forall 循环(这将通过过度分解来解决负载不平衡问题,但也可能会增加开销),或者

    (c) 使外循环成为动态调度循环,以解决负载不平衡问题。

    我的直觉是使用 Chapel 的 dynamic 迭代器执行选项 c:

    use DynamicIters;
    
    forall i in dynamic(ndocs) {
      ...
    }
    

    3) 最后要考虑的是避免三角迭代空间,而只是冗余计算X[i,j]X[j,i],即使它们具有相同的值。这在共享内存运行中可能没有意义,但如果您在分布式数组X 上进行计算,您可能会减少通信,因为这些矩阵值将由不同的处理器存储。在这种方法中,您可以在 X.domain 上使用单个 forall 循环进行迭代,默认情况下结果将得到很好的负载平衡,而无需动态迭代器。

    【讨论】:

    • 糟糕,我的浏览器中有这个问题的缓存副本,但没有注意到 @bencray 几周前已经回答了这个问题。我的错。
    猜你喜欢
    • 2017-06-13
    • 2015-07-17
    • 2019-12-23
    • 1970-01-01
    • 2021-08-20
    • 2012-07-09
    • 2021-12-04
    • 1970-01-01
    • 2013-08-09
    相关资源
    最近更新 更多