矩阵求逆(3,3)python-硬编码vs numpy.linalg.inv
对于大量矩阵,我需要计算一个距离度量,定义为: 虽然我知道强烈反对矩阵求逆,但我看不到解决方法。因此,我试图通过硬编码矩阵求逆来提高性能,因为所有矩阵的大小都是3,3 我希望这至少是一个小小的进步,但事实并非如此 为什么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
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的粉丝:虽然与另一个答案相比有一点改进,但它增加了另一个视角!