矩阵求逆(3,3)python-硬编码vs numpy.linalg.inv

矩阵求逆(3,3)python-硬编码vs numpy.linalg.inv,python,performance,numpy,matrix,Python,Performance,Numpy,Matrix,对于大量矩阵,我需要计算一个距离度量,定义为: 虽然我知道强烈反对矩阵求逆,但我看不到解决方法。因此,我试图通过硬编码矩阵求逆来提高性能,因为所有矩阵的大小都是3,3 我希望这至少是一个小小的进步,但事实并非如此 为什么numpy.linalg.inv比这种硬编码矩阵求逆更快/性能更好 此外,我还有什么办法来改善这个瓶颈 def inversion(m): m1, m2, m3, m4, m5, m6, m7, m8, m9 = m.flatten() determina

对于大量矩阵,我需要计算一个距离度量,定义为:

虽然我知道强烈反对矩阵求逆,但我看不到解决方法。因此,我试图通过硬编码矩阵求逆来提高性能,因为所有矩阵的大小都是3,3

我希望这至少是一个小小的进步,但事实并非如此

为什么numpy.linalg.inv比这种硬编码矩阵求逆更快/性能更好

此外,我还有什么办法来改善这个瓶颈

def inversion(m):    
    m1, m2, m3, m4, m5, m6, m7, m8, m9 = m.flatten()
    determinant = m1*m5*m9 + m4*m8*m3 + m7*m2*m6 - m1*m6*m8 - m3*m5*m7 - m2*m4*m9  
    return np.array([[m5*m9-m6*m8, m3*m8-m2*m9, m2*m6-m3*m5],
                     [m6*m7-m4*m9, m1*m9-m3*m7, m3*m4-m1*m6],
                     [m4*m8-m5*m7, m2*m7-m1*m8, m1*m5-m2*m4]])/determinant
对于随机3*3矩阵的定时比较:

%timeit np.linalg.inv(a)
100000个回路,最好为3个:每个回路12.5µs

%timeit inversion(a)
100000个回路,最好为3个:每个回路13.9µs

%timeit inversion(a)
与此密切相关但完全不重复的是代码复查,它解释了背景和整个功能

编辑:正如@Divakar在评论中所建议的,m.ravel而不是m.flatte稍微改进了反转,因此时间比较现在产生:

numpy-100000个环路,每个环路的最佳值为3:12.6µs

%timeit inversion(a)
硬编码-100000个环路,每个环路最好3:12.8µs

%timeit inversion(a)

虽然差距正在缩小,但硬编码的差距仍然较慢。这是怎么回事?

这里是一个简单的优化,节省了9次乘法和3次减法

def inversion(m):    
    m1, m2, m3, m4, m5, m6, m7, m8, m9 = m.ravel()
    inv = np.array([[m5*m9-m6*m8, m3*m8-m2*m9, m2*m6-m3*m5],
                    [m6*m7-m4*m9, m1*m9-m3*m7, m3*m4-m1*m6],
                    [m4*m8-m5*m7, m2*m7-m1*m8, m1*m5-m2*m4]])
    return inv / np.dot(inv[0], m[:, 0])
如果我通过一次完成整个跟踪来正确计数,你可以挤出更多的运算,再进行24次乘法:

def det(m):
   m1, m2, m3, m4, m5, m6, m7, m8, m9 = m.ravel()
   return np.dot(m[:, 0], [m5*m9-m6*m8, m3*m8-m2*m9, m2*m6-m3*m5])
   # or try m1*(m5*m9-m6*m8) + m4*(m3*m8-m2*m9) + m7*(m2*m6-m3*m5)
   # probably the fastest would be to inline the two calls to det
   # I'm not doing it here because of readability but you should try it

def dist(m, n):
   m1, m2, m3, m4, m5, m6, m7, m8, m9 = m.ravel()
   n1, n2, n3, n4, n5, n6, n7, n8, n9 = n.ravel()
   return 0.5 * np.dot(
       m.ravel()/det(m) + n.ravel()/det(n),
       [m5*n9-m6*n8, m6*n7-m4*n9, m4*n8-m5*n7, n3*m8-n2*m9, n1*m9-n3*m7,
        n2*m7-n1*m8, m2*n6-m3*n5, m3*n4-m1*n6, m1*n5-m2*n4])
好的,这是内联版本:

import numpy as np
from timeit import timeit

def dist(m, n):
   m1, m2, m3, m4, m5, m6, m7, m8, m9 = m.ravel()
   n1, n2, n3, n4, n5, n6, n7, n8, n9 = n.ravel()
   return 0.5 * np.dot(
       m.ravel()/(m1*(m5*m9-m6*m8) + m4*(m3*m8-m2*m9) + m7*(m2*m6-m3*m5))
       + n.ravel()/(n1*(n5*n9-n6*n8) + n4*(n3*n8-n2*n9) + n7*(n2*n6-n3*n5)),
       [m5*n9-m6*n8, m6*n7-m4*n9, m4*n8-m5*n7, n3*m8-n2*m9, n1*m9-n3*m7,
        n2*m7-n1*m8, m2*n6-m3*n5, m3*n4-m1*n6, m1*n5-m2*n4])

def dist_np(m, n):
    return 0.5 * np.diag(np.linalg.inv(m)@n + np.linalg.inv(n)@m).sum()

for i in range(3):
    A, B = np.random.random((2,3,3))
    print(dist(A, B), dist_np(A, B))
    print('pp     ', timeit('f(A,B)', number=10000, globals={'f':dist, 'A':A, 'B':B}))
    print('numpy  ', timeit('f(A,B)', number=10000, globals={'f':dist_np, 'A':A, 'B':B}))
印刷品:

2.20109953156 2.20109953156
pp      0.13215381593909115
numpy   0.4334693900309503
7.50799877993 7.50799877993
pp      0.13934064202476293
numpy   0.32861811900511384
-0.780284449609 -0.780284449609
pp      0.1258618349675089
numpy   0.3110764700686559
True
pp      0.14652886800467968
numpy   1.5294789629988372
True
pp      0.1482033939100802
numpy   1.6455406049499288
True
pp      0.1279512889450416
numpy   1.370200254023075
请注意,通过使用该函数的矢量化版本进行批处理,您可以节省大量资源。该测试计算两批100个矩阵之间的所有10000个成对距离:

def dist(m, n):
    m = np.moveaxis(np.reshape(m, m.shape[:-2] + (-1,)), -1, 0)
    n = np.moveaxis(np.reshape(n, n.shape[:-2] + (-1,)), -1, 0)
    m1, m2, m3, m4, m5, m6, m7, m8, m9 = m
    n1, n2, n3, n4, n5, n6, n7, n8, n9 = n
    return 0.5 * np.einsum("i...,i...->...",
        m/(m1*(m5*m9-m6*m8) + m4*(m3*m8-m2*m9) + m7*(m2*m6-m3*m5))
        + n/(n1*(n5*n9-n6*n8) + n4*(n3*n8-n2*n9) + n7*(n2*n6-n3*n5)),
        [m5*n9-m6*n8, m6*n7-m4*n9, m4*n8-m5*n7, n3*m8-n2*m9, n1*m9-n3*m7,
         n2*m7-n1*m8, m2*n6-m3*n5, m3*n4-m1*n6, m1*n5-m2*n4])

def dist_np(m, n):
    return 0.5 * (np.linalg.inv(m)@n + np.linalg.inv(n)@m)[..., np.arange(3), np.arange(3)].sum(axis=-1)

for i in range(3):
    A = np.random.random((100,1,3,3))
    B = np.random.random((1,100,3,3))
    print(np.allclose(dist(A, B), dist_np(A, B)))
    print('pp     ', timeit('f(A,B)', number=100, globals={'f':dist, 'A':A, 'B':B}))
    print('numpy  ', timeit('f(A,B)', number=100, globals={'f':dist_np, 'A':A, 'B':B}))
印刷品:

2.20109953156 2.20109953156
pp      0.13215381593909115
numpy   0.4334693900309503
7.50799877993 7.50799877993
pp      0.13934064202476293
numpy   0.32861811900511384
-0.780284449609 -0.780284449609
pp      0.1258618349675089
numpy   0.3110764700686559
True
pp      0.14652886800467968
numpy   1.5294789629988372
True
pp      0.1482033939100802
numpy   1.6455406049499288
True
pp      0.1279512889450416
numpy   1.370200254023075

我想在调用np.array时,创建四个Python对象和四个列表的开销很小

我创建了以下文件test.py:

一号和二号都在做同样的事情。但是,该过程中的一个创建了四个Python列表,两个没有。现在:

$ python -m timeit -s 'from test import one' 'one()'
100000 loops, best of 3: 3.13 usec per loop
$ python -m timeit -s 'from test import one' 'one()'
100000 loops, best of 3: 2.95 usec per loop
$ python -m timeit -s 'from test import one' 'one()'
100000 loops, best of 3: 3 usec per loop
$ python -m timeit -s 'from test import two' 'two()'
1000000 loops, best of 3: 1.61 usec per loop
$ python -m timeit -s 'from test import two' 'two()'
1000000 loops, best of 3: 1.76 usec per loop
$ python -m timeit -s 'from test import two' 'two()'
1000000 loops, best of 3: 1.69 usec per loop
我还尝试使用元组而不是列表,结果正如预期的那样,比没有新的Python对象要慢,但比列表要快,因为元组是不可修改的,并且这些元组的开销可能更小

def three():
    return np.array(((0.1, 0.2, 0.3),(0.4,0.5,0.6),(0.7,0.8,0.9)))

$ python -m timeit -s 'from test import three' 'three()'
100000 loops, best of 3: 2.11 usec per loop
$ python -m timeit -s 'from test import three' 'three()'
100000 loops, best of 3: 2.03 usec per loop
$ python -m timeit -s 'from test import three' 'three()'
100000 loops, best of 3: 2.08 usec per loop

使用m.ravel进行查看,从而获得一些增强?更新了问题。为什么?C、 这就是为什么,还有一些矢量化的魔力。你会因此得出结论,硬编码python不能比numpy.linalg.inv快吗?存储行列式的倒数,然后用它与3x3数组相乘?不确定你是否会在这里看到任何提升。虽然这是一个不错的举动,但它的表现比问题中的版本更差timeit np.linalg.inva-100000个循环,最佳3:17.2µs/循环%timeit反转\u paula-10000个循环,最佳3:21.2µs/循环%timeit反转\u nikoa-100000个循环,最佳3:19.2µs/循环loop@NikolasRieble看看最新的更新。在我的基准测试中,它比numpy快2-3倍。@NicolasRIeble矢量化版本在批处理时比numpy快10倍。@NikolasRieble假设您有两个3x3数组列表。1将它们转换为阵列A mx3x3 B nx3x3 2只需执行A=A[:,无,…];B=B[None,…]3将这些数据传递给矢量化的dist应该返回一个距离的mxn数组。-将修补程序提取到列表中,或者进行列表理解,或者分配一个mx3x3空数组,并使用高级索引在9个像素上循环,坐标列表将0、1、2的所有组合添加到两个坐标。成为np.moveaxis aye的粉丝:虽然与另一个答案相比有一点改进,但它增加了另一个视角!