【问题标题】:Fast replace in numpy array在 numpy 数组中快速替换
【发布时间】:2012-11-15 15:29:14
【问题描述】:

我一直在尝试进行一些修改以加速这个伪代码:

>>> A=np.array([1,1,1,2,2,2,3,3,3])
>>> B=np.array([np.power(A,n) for n in [3,4,5]])
>>> B
array([[  1,   1,   1,   8,   8,   8,  27,  27,  27],
       [  1,   1,   1,  16,  16,  16,  81,  81,  81],
       [  1,   1,   1,  32,  32,  32, 243, 243, 243]])

其中 A 的元素经常重复 10-20 次,需要保留 B 的形状,因为它稍后会乘以另一个相同形状的数组。

我的第一个想法是使用以下代码:

uA=np.unique(A)
uB=np.array([np.power(uA,n) for n in [3,4,5]])
B=[]
for num in range(uB.shape[0]):
    Temp=np.copy(A)
    for k,v in zip(uA,uB[num]): Temp[A==k] = v
    B.append(Temp)
B=np.array(B)
### Also any better way to create the numpy array B?

这看起来相当糟糕,可能有更好的方法。任何关于如何加快速度的想法将不胜感激。

感谢您的宝贵时间。

这是一个更新。我意识到我的函数编码很糟糕。非常感谢大家的建议。以后我会尝试更好地改写我的问题,以便它们显示所需的一切。

Normal='''
import numpy as np
import scipy
def func(value,n):
    if n==0: return 1
    else: return np.power(value,n)/scipy.factorial(n,exact=0)+func(value,n-1)
A=np.random.randint(10,size=250)
A=np.unique(A)
B=np.array([func(A,n) for n in [6,8,10]])
'''

Me='''
import numpy as np
import scipy
def func(value,n):
    if n==0: return 1
    else: return np.power(value,n)/scipy.factorial(n,exact=0)+func(value,n-1)
A=np.random.randint(10,size=250)
uA=np.unique(A)
uB=np.array([func(A,n) for n in [6,8,10]])
B=[]
for num in range(uB.shape[0]):
    Temp=np.copy(A)
    for k,v in zip(uA,uB[num]): Temp[A==k] = v
    B.append(Temp)
B=np.array(B)
'''


Alex='''
import numpy as np
import scipy
A=np.random.randint(10,size=250)
power=np.arange(11)
fact=scipy.factorial(np.arange(11),exact=0).reshape(-1,1)
power=np.power(A,np.arange(11).reshape(-1,1))
value=power/fact
six=np.sum(value[:6],axis=0)
eight=six+np.sum(value[6:8],axis=0)
ten=eight+np.sum(value[8:],axis=0)
B=np.vstack((six,eight,ten))
'''
Alex='''
import numpy as np
import scipy
A=np.random.randint(10,size=250)
power=np.arange(11)
fact=scipy.factorial(np.arange(11),exact=0).reshape(-1,1)
power=np.power(A,np.arange(11).reshape(-1,1))
value=power/fact
six=np.sum(value[:6],axis=0)
eight=six+np.sum(value[6:8],axis=0)
ten=eight+np.sum(value[8:],axis=0)
B=np.vstack((six,eight,ten))
'''

Alex2='''
import numpy as np
import scipy
def find_count(the_list):
    count = list(the_list).count
    result = [count(item) for item in set(the_list)]
    return result
A=np.random.randint(10,size=250)
A_unique=np.unique(A)
A_counts = np.array(find_count(A_unique))
fact=scipy.factorial(np.arange(11),exact=0).reshape(-1,1)
power=np.power(A_unique,np.arange(11).reshape(-1,1))
value=power/fact
six=np.sum(value[:6],axis=0)
eight=six+np.sum(value[6:8],axis=0)
ten=eight+np.sum(value[8:],axis=0)
B_nodup=np.vstack((six,eight,ten))
B_list = [ np.transpose( np.tile( B_nodup[:,i], (A_counts[i], 1) ) ) for i in range(A_unique.shape[0]) ]
B = np.hstack( B_list )
'''


print timeit.timeit(Normal, number=10000)
print timeit.timeit(Me, number=10000)
print timeit.timeit(Alex, number=10000)
print timeit.timeit(Alex2, number=10000)

Normal: 10.7544178963
Me:     23.2039361
Alex:    4.85648703575
Alex2:   4.18024992943

【问题讨论】:

  • Append 很慢,与 ndarray 相比,python 列表也是如此。你不知道B的大小吗?在循环之前声明它并插入值。
  • 你能扩展一下吗。你的意思是 np.empty(shape) 然后填写值吗?
  • 是的。如果您在示例中将每个元素的幂取为 3 个不同的幂(3,4,5),那么您知道您得到了 A * 3 个元素的长度。
  • 更新了伪代码,使其可以正常工作。

标签: python arrays performance numpy replace


【解决方案1】:

如果您将np.power 的形状更改为列向量的形状,您可以在A 上广播np.power

>>> np.power(A.reshape(-1,1), [3,4,5]).T
array([[  1,   1,   1,   8,   8,   8,  27,  27,  27],
       [  1,   1,   1,  16,  16,  16,  81,  81,  81],
       [  1,   1,   1,  32,  32,  32, 243, 243, 243]])

【讨论】:

  • (-1,1) 是一个 coulm 而 (1,-1) 是一个行的任何直观原因?只是试图理解其中的逻辑。还是“仅仅因为”?
  • @user948652:尝试打印它们。或者,如果您正在寻找第一个索引是行索引的原因:这只是一个约定。
  • 啊应该先用谷歌搜索一下,无论如何谢谢 :) newshape : int or tuple of ints 新形状应该与原始形状兼容。如果是整数,则结果将是该长度的一维数组。一个形状维度可以是-1。在这种情况下,该值是从数组的长度和剩余维度推断出来的。
  • 这实际上需要更多的时间,然后根据 timeit 列出理解大约 5%。
  • @Ophion:在我的机器上,您的示例数据大约快 20%。当A.size == 21(7 个不同的值)时,它的速度大约是原来的两倍。
【解决方案2】:

结合使用numpy.tile()和numpy.hstack(),如下:

A = np.array([1,2,3])
A_counts = np.array([3,3,3])
A_powers = np.array([[3],[4],[5]])
B_nodup = np.power(A, A_powers)
B_list = [ np.transpose( np.tile( B_nodup[:,i], (A_counts[i], 1) ) ) for i in range(A.shape[0]) ]
B = np.hstack( B_list )

转置和堆栈可以颠倒,这样可能更快:

B_list = [ np.tile( B_nodup[:,i], (A_counts[i], 1) ) for i in range(A.shape[0]) ]
B = np.transpose( np.vstack( B_list ) )

这可能只有在您计算的函数非常昂贵或重复很多次(超过 10 次)时才值得这样做;做一个平铺和堆叠以防止额外计算 10 次幂函数可能不值得。请进行基准测试并告诉我们。

编辑:或者,您可以使用广播来摆脱列表理解:

>>> A=np.array([1,1,1,2,2,2,3,3,3])
>>> B = np.power(A,[[3],[4],[5]])
>>> B
array([[  1,   1,   1,   8,   8,   8,  27,  27,  27],
       [  1,   1,   1,  16,  16,  16,  81,  81,  81],
       [  1,   1,   1,  32,  32,  32, 243, 243, 243]])

这可能很快,但实际上并没有按照您的要求进行。

【讨论】:

  • 我不确定为什么人们如此反对列表理解。在这种情况下,它与其他选项大致相同/略好。
  • @Ophion:你确定吗?请与上面的广播方法进行比较,而不是下面 larsmans 建议的方法(需要额外的 reshape 和 transpose)。我怀疑广播对于大型阵列来说会赢得很多(尝试 1k x 1k 左右)。
  • @AlexI:你的版本有效地重塑了[3,4,5]。当我在一个更大的例子上运行它时,它比我的版本慢一点,尽管可能不是很明显。 reshape 和转置都很便宜。
  • @Alex:是的,当行数很多时,上面的广播方法要快得多。这通常只有大约三行,因此差异可以忽略不计。它当然很有趣,我将来会使用它。对于 np.tile 我会试一试;但是,顺序很重要,并不总是排序的。
  • @Ophion:np.tile 用于在结果中为每列制作所需数量的副本(由 A_counts 给出);它不需要任何东西进行排序。
【解决方案3】:

我尝试了 200k 次迭代,第一种方法是我的。

import numpy as np
import time

N = 200000
start = time.time()
for j in range(N):

    x = np.array([1,1,1,2,2,2,3,3,3])
    powers = np.array([3,4,5])
    result = np.zeros((powers.size,x.size)).astype(np.int32)
    for i in range(powers.size):
        result[i,:] = x**powers[i]
print time.time()-start, "seconds"

start = time.time()
for j in range(N):
    A=np.array([1,1,1,2,2,2,3,3,3])
    B = np.power(A,[[3],[4],[5]])
print time.time()-start, "seconds"

start = time.time()
for j in range(N):
    np.power(A.reshape(-1,1), [3,4,5]).T
print time.time()-start, "seconds"

start = time.time()
for j in range(N):
    A=np.array([1,1,1,2,2,2,3,3,3])
    B=np.array([np.power(x,n) for n in [3,4,5]])
print time.time()-start, "seconds"

生产

8.88000011444 seconds
9.25099992752 seconds
3.95399999619 seconds
7.43799996376 seconds

larsmans 方法显然是最快的。

(ps 你如何在没有明确的 url 的情况下链接到答案或用户 @larsman 不起作用)

【讨论】:

  • 您忘记为 larsmans 答案的每个循环初始化 A。对此进行更新,使用 timeit 和 A=np.random.randint(20, size=500) 在开始时进行初始化,您会发现它们非常接近。感谢您发布此内容。
猜你喜欢
  • 2011-03-25
  • 2019-12-25
  • 2011-12-21
  • 2016-12-29
  • 2010-12-25
  • 1970-01-01
  • 2015-12-12
  • 2019-10-19
相关资源
最近更新 更多