优化Python:大数组,内存问题
Optimize Python: Large arrays, memory problems
我遇到了速度问题 运行 python / numypy 代码。我不知道如何让它更快,也许是其他人?
假设有一个表面有两个三角剖分,一个精细 (..._fine) 有 M 个点,一个粗糙有 N 个点。此外,每个点(N 浮点数)都有关于粗网格的数据。我正在尝试执行以下操作:
对于细网格上的每个点,在粗网格上找到k个最接近的点并取平均值。短:从粗到细插值数据。
我现在的代码就是这样。对于大数据(在我的例子中 M = 2e6,N = 1e4),代码运行大约 25 分钟,猜测是由于显式 for 循环没有进入 numpy。有什么想法可以通过智能索引解决这个问题吗? M x N 阵列炸毁 RAM..
import numpy as np
p_fine.shape => m x 3
p.shape => n x 3
data_fine = np.empty((m,))
for i, ps in enumerate(p_fine):
data_fine[i] = np.mean(data_coarse[np.argsort(np.linalg.norm(ps-p,axis=1))[:k]])
干杯!
方法 #1
我们正在处理大型数据集,内存是个问题,因此我将尝试优化循环内的计算。现在,我们可以使用 np.einsum
to replace np.linalg.norm
part and np.argpartition
代替 np.argsort
的实际排序,就像这样 -
out = np.empty((m,))
for i, ps in enumerate(p_fine):
subs = ps-p
sq_dists = np.einsum('ij,ij->i',subs,subs)
out[i] = data_coarse[np.argpartition(sq_dists,k)[:k]].sum()
out = out/k
方法 #2
现在,作为另一种方法,我们还可以使用 Scipy's cdist
作为完全矢量化的解决方案,就像这样 -
from scipy.spatial.distance import cdist
out = data_coarse[np.argpartition(cdist(p_fine,p),k,axis=1)[:,:k]].mean(1)
但是,由于我们在这里受内存限制,我们可以分块执行这些操作。基本上,我们将从具有数百万行的 tall 数组 p_fine
中获取行块并使用 cdist
,因此在每次迭代时获取输出元素块,而不仅仅是一个标量。有了这个,我们将通过该块的长度减少循环计数。
所以,最终我们会有这样的实现 -
out = np.empty((m,))
L = 10 # Length of chunk (to be used as a param)
num_iter = m//L
for j in range(num_iter):
p_fine_slice = p_fine[L*j:L*j+L]
out[L*j:L*j+L] = data_coarse[np.argpartition(cdist\
(p_fine_slice,p),k,axis=1)[:,:k]].mean(1)
运行时测试
设置-
# Setup inputs
m,n = 20000,100
p_fine = np.random.rand(m,3)
p = np.random.rand(n,3)
data_coarse = np.random.rand(n)
k = 5
def original_approach(p,p_fine,m,n,k):
data_fine = np.empty((m,))
for i, ps in enumerate(p_fine):
data_fine[i] = np.mean(data_coarse[np.argsort(np.linalg.norm\
(ps-p,axis=1))[:k]])
return data_fine
def proposed_approach(p,p_fine,m,n,k):
out = np.empty((m,))
for i, ps in enumerate(p_fine):
subs = ps-p
sq_dists = np.einsum('ij,ij->i',subs,subs)
out[i] = data_coarse[np.argpartition(sq_dists,k)[:k]].sum()
return out/k
def proposed_approach_v2(p,p_fine,m,n,k,len_per_iter):
L = len_per_iter
out = np.empty((m,))
num_iter = m//L
for j in range(num_iter):
p_fine_slice = p_fine[L*j:L*j+L]
out[L*j:L*j+L] = data_coarse[np.argpartition(cdist\
(p_fine_slice,p),k,axis=1)[:,:k]].sum(1)
return out/k
计时 -
In [134]: %timeit original_approach(p,p_fine,m,n,k)
1 loops, best of 3: 1.1 s per loop
In [135]: %timeit proposed_approach(p,p_fine,m,n,k)
1 loops, best of 3: 539 ms per loop
In [136]: %timeit proposed_approach_v2(p,p_fine,m,n,k,len_per_iter=100)
10 loops, best of 3: 63.2 ms per loop
In [137]: %timeit proposed_approach_v2(p,p_fine,m,n,k,len_per_iter=1000)
10 loops, best of 3: 53.1 ms per loop
In [138]: %timeit proposed_approach_v2(p,p_fine,m,n,k,len_per_iter=2000)
10 loops, best of 3: 63.8 ms per loop
因此,第一个提议的方法大约有 2x
的改进,并且比原始方法有 20x
第二个在最佳位置,len_per_iter
参数设置为 1000
。希望这会将您的 25 分钟运行时间缩短到一分钟多一点。我猜还不错!
首先感谢您的详细帮助。
首先,Divakar,您的解决方案大大提高了速度。使用我的数据,代码 运行 仅需不到 2 分钟,具体取决于块大小。
我也尝试了 sklearn 并以
结束
def sklearnSearch_v3(p, p_fine, k):
neigh = NearestNeighbors(k)
neigh.fit(p)
return data_coarse[neigh.kneighbors(p_fine)[1]].mean(axis=1)
最终速度非常快,对于我的数据大小,我得到以下结果
import numpy as np
from sklearn.neighbors import NearestNeighbors
m,n = 2000000,20000
p_fine = np.random.rand(m,3)
p = np.random.rand(n,3)
data_coarse = np.random.rand(n)
k = 3
产量
%timeit sklearv3(p, p_fine, k)
1 loop, best of 3: 7.46 s per loop
我遇到了速度问题 运行 python / numypy 代码。我不知道如何让它更快,也许是其他人?
假设有一个表面有两个三角剖分,一个精细 (..._fine) 有 M 个点,一个粗糙有 N 个点。此外,每个点(N 浮点数)都有关于粗网格的数据。我正在尝试执行以下操作:
对于细网格上的每个点,在粗网格上找到k个最接近的点并取平均值。短:从粗到细插值数据。
我现在的代码就是这样。对于大数据(在我的例子中 M = 2e6,N = 1e4),代码运行大约 25 分钟,猜测是由于显式 for 循环没有进入 numpy。有什么想法可以通过智能索引解决这个问题吗? M x N 阵列炸毁 RAM..
import numpy as np
p_fine.shape => m x 3
p.shape => n x 3
data_fine = np.empty((m,))
for i, ps in enumerate(p_fine):
data_fine[i] = np.mean(data_coarse[np.argsort(np.linalg.norm(ps-p,axis=1))[:k]])
干杯!
方法 #1
我们正在处理大型数据集,内存是个问题,因此我将尝试优化循环内的计算。现在,我们可以使用 np.einsum
to replace np.linalg.norm
part and np.argpartition
代替 np.argsort
的实际排序,就像这样 -
out = np.empty((m,))
for i, ps in enumerate(p_fine):
subs = ps-p
sq_dists = np.einsum('ij,ij->i',subs,subs)
out[i] = data_coarse[np.argpartition(sq_dists,k)[:k]].sum()
out = out/k
方法 #2
现在,作为另一种方法,我们还可以使用 Scipy's cdist
作为完全矢量化的解决方案,就像这样 -
from scipy.spatial.distance import cdist
out = data_coarse[np.argpartition(cdist(p_fine,p),k,axis=1)[:,:k]].mean(1)
但是,由于我们在这里受内存限制,我们可以分块执行这些操作。基本上,我们将从具有数百万行的 tall 数组 p_fine
中获取行块并使用 cdist
,因此在每次迭代时获取输出元素块,而不仅仅是一个标量。有了这个,我们将通过该块的长度减少循环计数。
所以,最终我们会有这样的实现 -
out = np.empty((m,))
L = 10 # Length of chunk (to be used as a param)
num_iter = m//L
for j in range(num_iter):
p_fine_slice = p_fine[L*j:L*j+L]
out[L*j:L*j+L] = data_coarse[np.argpartition(cdist\
(p_fine_slice,p),k,axis=1)[:,:k]].mean(1)
运行时测试
设置-
# Setup inputs
m,n = 20000,100
p_fine = np.random.rand(m,3)
p = np.random.rand(n,3)
data_coarse = np.random.rand(n)
k = 5
def original_approach(p,p_fine,m,n,k):
data_fine = np.empty((m,))
for i, ps in enumerate(p_fine):
data_fine[i] = np.mean(data_coarse[np.argsort(np.linalg.norm\
(ps-p,axis=1))[:k]])
return data_fine
def proposed_approach(p,p_fine,m,n,k):
out = np.empty((m,))
for i, ps in enumerate(p_fine):
subs = ps-p
sq_dists = np.einsum('ij,ij->i',subs,subs)
out[i] = data_coarse[np.argpartition(sq_dists,k)[:k]].sum()
return out/k
def proposed_approach_v2(p,p_fine,m,n,k,len_per_iter):
L = len_per_iter
out = np.empty((m,))
num_iter = m//L
for j in range(num_iter):
p_fine_slice = p_fine[L*j:L*j+L]
out[L*j:L*j+L] = data_coarse[np.argpartition(cdist\
(p_fine_slice,p),k,axis=1)[:,:k]].sum(1)
return out/k
计时 -
In [134]: %timeit original_approach(p,p_fine,m,n,k)
1 loops, best of 3: 1.1 s per loop
In [135]: %timeit proposed_approach(p,p_fine,m,n,k)
1 loops, best of 3: 539 ms per loop
In [136]: %timeit proposed_approach_v2(p,p_fine,m,n,k,len_per_iter=100)
10 loops, best of 3: 63.2 ms per loop
In [137]: %timeit proposed_approach_v2(p,p_fine,m,n,k,len_per_iter=1000)
10 loops, best of 3: 53.1 ms per loop
In [138]: %timeit proposed_approach_v2(p,p_fine,m,n,k,len_per_iter=2000)
10 loops, best of 3: 63.8 ms per loop
因此,第一个提议的方法大约有 2x
的改进,并且比原始方法有 20x
第二个在最佳位置,len_per_iter
参数设置为 1000
。希望这会将您的 25 分钟运行时间缩短到一分钟多一点。我猜还不错!
首先感谢您的详细帮助。
首先,Divakar,您的解决方案大大提高了速度。使用我的数据,代码 运行 仅需不到 2 分钟,具体取决于块大小。
我也尝试了 sklearn 并以
结束def sklearnSearch_v3(p, p_fine, k):
neigh = NearestNeighbors(k)
neigh.fit(p)
return data_coarse[neigh.kneighbors(p_fine)[1]].mean(axis=1)
最终速度非常快,对于我的数据大小,我得到以下结果
import numpy as np
from sklearn.neighbors import NearestNeighbors
m,n = 2000000,20000
p_fine = np.random.rand(m,3)
p = np.random.rand(n,3)
data_coarse = np.random.rand(n)
k = 3
产量
%timeit sklearv3(p, p_fine, k)
1 loop, best of 3: 7.46 s per loop