使用np.einsum 和numexpr 模块的技巧可以实现三个阶段的边际改进。
第 1 阶段: 使用 sum-reduction 和 einsum 计算归一化输出,然后利用 numexpr 执行平方根 -
import numexpr as ne
def normalize_einsum_numexpr(X):
sq_sums = np.einsum('ij,ij->i',X,X)[:,None]
return ne.evaluate('X/sqrt(sq_sums)')
这相当于normalize(X)。
第 2 阶段: 使用 cross-product 的定义获取与切片等效的 numpy.cross -
def numpy_cross_slicing(X,Y):
c0 = X[:,1]*Y[:,2] - X[:,2]*Y[:,1]
c1 = X[:,2]*Y[:,0] - X[:,0]*Y[:,2]
c2 = X[:,0]*Y[:,1] - X[:,1]*Y[:,0]
return np.column_stack((c0,c1,c2))
第 3 阶段: 通过切片和利用 numexpr 获得标准化叉积 -
def numpy_cross_norm_slicing(X,Y):
c0 = X[:,1]*Y[:,2] - X[:,2]*Y[:,1]
c1 = X[:,2]*Y[:,0] - X[:,0]*Y[:,2]
c2 = X[:,0]*Y[:,1] - X[:,1]*Y[:,0]
s = ne.evaluate('sqrt(c0**2 + c1**2 + c2**2)')
c0 /= s
c1 /= s
c2 /= s
return np.column_stack((c0,c1,c2))
这将替换normalize(np.cross(X,Y))。
综上所述,我们可以替换 vectorToMatrix,就像这样 -
def vectorToMatrix1(X,Y):
X = normalize_einsum_numexpr(X) # make sure X is normalized
Y = normalize_einsum_numexpr(Y) # make sure Y is normalized
Z = numpy_cross_norm_slicing(X,Y) # Z is the normal ...
Y = numpy_cross_slicing(Z,X)
return np.dstack((X,Y,Z)).swapaxes(2,1)
运行时测试
输入设置:
In [271]: X = np.random.random((10**6,3))
...: Y = np.random.random((10**6,3))
...:
第一阶段:
In [272]: np.allclose(normalize(X), normalize_einsum_numexpr(X))
Out[272]: True
In [273]: %timeit normalize(X)
...: %timeit normalize_einsum_numexpr(X)
...:
100 loops, best of 3: 11.4 ms per loop
100 loops, best of 3: 10.6 ms per loop
第二阶段:
In [274]: np.allclose(np.cross(X,Y), numpy_cross_slicing(X,Y) )
Out[274]: True
In [275]: %timeit np.cross(X,Y)
...: %timeit numpy_cross_slicing(X,Y)
...:
10 loops, best of 3: 29.8 ms per loop
10 loops, best of 3: 27.9 ms per loop
第三阶段:
In [276]: np.allclose(normalize(np.cross(X,Y)), numpy_cross_norm_slicing(X,Y))
Out[276]: True
In [277]: %timeit normalize(np.cross(X,Y))
...: %timeit numpy_cross_norm_slicing(X,Y)
...:
10 loops, best of 3: 44.5 ms per loop
10 loops, best of 3: 34.9 ms per loop
完整代码:
In [395]: np.allclose(vectorToMatrix(X,Y), vectorToMatrix1(X,Y))
Out[395]: True
In [396]: %timeit vectorToMatrix(X,Y)
10 loops, best of 3: 130 ms per loop
In [397]: %timeit vectorToMatrix1(X,Y)
10 loops, best of 3: 122 ms per loop
因此,只有一些微小的改进。
不放弃!
查看瓶颈,许多堆叠步骤没有帮助。所以,在这些基础上进行改进,一个修改后的版本就这样结束了 -
def vectorToMatrix2(X,Y):
X = normalize_einsum_numexpr(X) # make sure X is normalized
Y = normalize_einsum_numexpr(Y) # make sure Y is normalized
c0 = X[:,1]*Y[:,2] - X[:,2]*Y[:,1]
c1 = X[:,2]*Y[:,0] - X[:,0]*Y[:,2]
c2 = X[:,0]*Y[:,1] - X[:,1]*Y[:,0]
s = ne.evaluate('sqrt(c0**2 + c1**2 + c2**2)')
c0 /= s
c1 /= s
c2 /= s
d0 = c1*X[:,2] - c2*X[:,1]
d1 = c2*X[:,0] - c0*X[:,2]
d2 = c0*X[:,1] - c1*X[:,0]
c = [c0,c1,c2]
d = [d0,d1,d2]
return np.concatenate((X.T, d, c)).reshape(3,3,-1).transpose(2,0,1)
具有相同百万点设置的新计时 -
In [505]: %timeit vectorToMatrix(X,Y) # original code
...: %timeit vectorToMatrix1(X,Y)
...: %timeit vectorToMatrix2(X,Y)
...:
10 loops, best of 3: 130 ms per loop
10 loops, best of 3: 117 ms per loop
10 loops, best of 3: 101 ms per loop
20%+加速,还不错!