【问题标题】:Fast differences of all row pairs with NumPy所有行对的快速差异与 NumPy
【发布时间】:2014-12-23 20:58:16
【问题描述】:

我使用的算法要求每个示例都有一个矩阵,比如Xi,即ai x b,对于每个O(n^2) 示例对,我发现每一行之间的差异Xiu - Xjv ,然后对外积sum_u sum_v np.outer(Xiu - Xjv, Xiu - Xjv)求和。

不幸的是,这个内部双和相当慢,并导致运行时间在大型数据集上失控。现在我只是使用 for 循环来做到这一点。有没有一些pythonic方法来加速这个内部操作?我一直在想一个,但没有运气。

为了澄清,对于每个n 示例,都有一个矩阵Xi,其维度为ai x b,其中ai 对于每个示例都不同。对于每一对(Xi, Xj),我想遍历两个矩阵之间的所有O(ai * bi)对行并找到Xiu - Xjv,将其外积与自身np.outer(Xiu - Xjv, Xiu - Xjv)相乘,最后将所有这些外积相加。

例如:假设 D 是 [[1,2],[3,4]],我们只是对两个矩阵都使用它。

然后,即一步可能是np.outer(D[0] - D[1], D[0] - D[1]),这将是矩阵[4,4],[4,4]

很简单,(0,0) 和 (1,1) 只是 0 个矩阵,而 (0,1) 和 (1,0) 都是 4 个矩阵,所以对所有四个外积的最终总和应该是[[8,8],[8,8]]

【问题讨论】:

  • 我建议添加一个具体的例子——它将帮助更多的人理解这个问题。
  • 你真的想要所有向量对之间的平方欧几里得距离吗?因为有捷径。
  • 不,与平方欧几里得距离相反——外积。例子来了。
  • 我明白了。我不禁想到必须在代数而不是实现层面对此进行简化。
  • 请注意a^T (b c^T) d = (a^T b) (c^T d)sum_i v_i = 1^T v。你应该可以从那里拿走它。

标签: python optimization numpy


【解决方案1】:

好的,这很有趣。我仍然忍不住想,这一切都可以通过对numpy.tensordot 的一次巧妙调用来完成,但无论如何这似乎已经消除了所有 Python 级循环:

import numpy

def slow( a, b=None ):

    if b is None: b = a
    a = numpy.asmatrix( a )
    b = numpy.asmatrix( b )

    out = 0.0
    for ai in a:
        for bj in b:
            dij = bj - ai
            out += numpy.outer( dij, dij )
    return out

def opsum( a, b=None ):

    if b is None: b = a
    a = numpy.asmatrix( a )
    b = numpy.asmatrix( b )

    RA, CA = a.shape
    RB, CB = b.shape    
    if CA != CB: raise ValueError( "input matrices should have the same number of columns" )

    out = -numpy.outer( a.sum( axis=0 ), b.sum( axis=0 ) );
    out += out.T
    out += RB * ( a.T * a )
    out += RA * ( b.T * b )
    return out

def test( a, b=None ):
    print( "ground truth:" )
    print( slow( a, b ) )
    print( "optimized:" )
    print( opsum( a, b ) )  
    print( "max abs discrepancy:" )
    print( numpy.abs( opsum( a, b ) - slow( a, b ) ).max() )
    print( "" )

# OP example
test( [[1,2], [3,4]] )

# non-symmetric example
a = [ [ 1, 2, 3 ], [-4, 5, 6 ], [7, -8, 9 ], [ 10, 11, -12 ] ]
a = numpy.matrix( a, dtype=float )
b = a[ ::2, ::-1 ] + 15
test( a, b )

# non-integer example
test( numpy.random.randn( *a.shape ), numpy.random.randn( *b.shape ) )

使用该(相当随意的)示例输入,opsum 的计时(使用 IPython 中的 timeit opsum(a,b) 测量)看起来仅比 slow 好 3 到 5 倍。但当然它的扩展性要好得多:将数据点的数量扩大 100 倍,将特征的数量扩大 10 倍,然后我们已经看到了速度提高 10,000 倍。

【讨论】:

  • 太棒了。谢谢!
  • 顺便问一下,你是怎么想到这个的?你使用过一些代数定律吗?
  • 如果我能声称自己完全了解代数,那就太好了。事实上,在从第一原理重新组装之前,我将它分解成各个部分。 IE。将整个问题写成一个四重嵌套循环,其核心是一个 scalar 项:out[rOut,cOut] += (a[rA,rOut] - b[rB,rOut]) * (a[rA,cOut] - b[rB,cOut]) 然后将其相乘,分别处理产生的四个项。弄清楚(通过在纸上画出)每个矩阵乘法的含义。将它们重写为numpy 产品。然后继续抓紧代码以优化性能。
  • @AndrewLatham 我已经更改了公式,使其在代数上看起来更整洁/更对称。也许这将有助于直觉了解正在发生的事情。令我惊讶的是,性能甚至有很小的提升。
  • 嘿,天哪,后来我遇到了一个类似的算法(这些来自费舍尔判别分析的手动修改),它需要一个非常不同的计算,你介意看一下吗? stackoverflow.com/questions/31145918/…
猜你喜欢
  • 2021-11-08
  • 2019-01-30
  • 1970-01-01
  • 1970-01-01
  • 2021-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多