Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/358.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 根据条件对numpy数组元素进行数学运算的有效方法_Python_Numpy_Optimization - Fatal编程技术网

Python 根据条件对numpy数组元素进行数学运算的有效方法

Python 根据条件对numpy数组元素进行数学运算的有效方法,python,numpy,optimization,Python,Numpy,Optimization,我正在尝试优化我的python代码。瓶颈之一出现在 我尝试根据每个元素的值将函数应用于numpy数组。例如,我有一个包含千个元素的数组,我对大于公差的值应用一个函数,对其余的值应用另一个函数(泰勒级数)。我做了掩蔽,但仍然很慢,至少我调用了以下函数6400万次 EPSILONZETA = 1.0e-6 ZETA1_12 = 1.0/12.0 ZETA1_720 = 1.0/720.0 def masked_condition_zero(array, tolerance): """ Re

我正在尝试优化我的python代码。瓶颈之一出现在 我尝试根据每个元素的值将函数应用于numpy数组。例如,我有一个包含千个元素的数组,我对大于公差的值应用一个函数,对其余的值应用另一个函数(泰勒级数)。我做了掩蔽,但仍然很慢,至少我调用了以下函数6400万次

EPSILONZETA = 1.0e-6
ZETA1_12 = 1.0/12.0
ZETA1_720 = 1.0/720.0

def masked_condition_zero(array, tolerance):
    """ Return the indices where values are lesser (and greater) than tolerance
    """
    # search indices where array values < tolerance
    indzeros_ = np.where(np.abs(array) < tolerance)[0]

    # create mask
    mask_ = np.ones(np.shape(array), dtype=bool)

    mask_[[indzeros_]] = False

    return (~mask_, mask_) 

def bernoulli_function1(zeta):
    """ Returns the Bernoulli function of zeta, vector version
    """
    # get the indices according to condition
    zeros_, others_ = masked_condition_zero(zeta, EPSILONZETA)

    # create an array filled with zeros
    fb_ = np.zeros(np.shape(zeta))

    # Apply the original function to the values greater than EPSILONZETA
    fb_[others_] = zeta[others_]/(np.exp(zeta[others_])-1.0)  

    # computes series for zeta < eps
    zeta0_ = zeta[zeros_]
    zeta2_ = zeta0_ *  zeta0_
    zeta4_ =  zeta2_ * zeta2_
    fb_[zeros_] = 1.0 - 0.5*zeta0_ + ZETA1_12 * zeta2_ - ZETA1_720 * zeta4_
    return fb_
EPSILONZETA=1.0e-6
ZETA1_12=1.0/12.0
ZETA1_720=1.0/720.0
def屏蔽_条件_零(阵列,公差):
“”“返回值小于(或大于)公差的索引
"""
#搜索数组值<容差的索引
indzeros=np.where(np.abs(数组)<公差)[0]
#创建遮罩
掩码=np.one(np.shape(数组),dtype=bool)
掩码_[[indzeros_]]=False
返回(~mask,mask)
def伯努利函数1(zeta):
“”“返回zeta的Bernoulli函数,向量版本
"""
#根据条件得到指标
零,其他=屏蔽条件零(zeta,ε)
#创建一个用零填充的数组
fb_u2;=np.零(np.形状(zeta))
#将原始函数应用于大于ε的值
fb[others]=zeta[others]/(np.exp(zeta[others]-1.0)
#计算zeta
现在假设你有一个数组zeta,它的正负浮动在每个循环中都会发生变化,每次迭代次数为2^26次,你想计算fbernoulli_函数1(zeta)


有更好的解决方案吗?

where命令在索引到数组中时速度非常慢。这可能会更快

fb_ = np.zeros_like(zeta)
nonZero= zeta > ZETA_TOLERANCE
zero = ~nonZero
fb_[zero] = function1(zeta[zero])
fb_[nonZero] = function2(zeta[nonZero])
编辑
我意识到我的原始版本是制作同一阵列的两个副本。这个新版本应该快一点。

问题的基本结构是:

def foo(zeta):
    result = np.empty_like(zeta)
    I = condition(zeta)
    nI = ~I
    result[I] = func1(zeta[I])
    result[nI] = func2(zeta[nI])
看起来多项式表达式完全可以计算
zeta
,但这是“例外情况”,即当
zeta
太接近0时的回退计算

如果两个函数都可以对zeta进行评估,则可以在以下位置使用:

np.where(condition(zeta), func1(zeta), func2(zeta))
这是简化版的:

def foo(zeta):
    result = np.empty_like(zeta)
    I = condition(zeta)
    nI = ~I
    v1 = func1(zeta)
    v2 = func2(zeta)
    result[I] = v1[I]
    result[nI] = v2[nI]
另一个选项是将一个函数应用于所有值,另一个仅应用于“异常”

def foo(zeta):
    result = func2(zeta)
    I = condition(zeta)
    result[I] = func1[zeta[I]]
当然相反-
result=func1(zeta);结果[nI]=func2[zeta]

在我简短的时间测试中,
func1
func2
所用的时间大致相同


masked\u condition\u zero
也需要这段时间,但更简单的
np.abs(array)
(它是
~J
)将其减半

让我们比较一下分配策略

def foo(zeta, J, nJ):
    result = np.empty_like(zeta)
    result[J] = fun1(zeta[J])
    result[nJ] = fun2(zeta[nJ])
    return result
对于
zeta[J]
为完整
zeta
的10%的样本,一些样本时间为:

In [127]: timeit foo(zeta, J, nJ)
10000 loops, best of 3: 55.7 µs per loop

In [128]: timeit result=fun2(zeta); result[J]=fun1(zeta[J])
10000 loops, best of 3: 49.2 µs per loop

In [129]: timeit np.where(J, fun1(zeta),fun2(zeta))
10000 loops, best of 3: 73.4 µs per loop

In [130]: timeit result=fun1(zeta); result[nJ]=fun2(zeta[nJ])
10000 loops, best of 3: 60.7 µs per loop
第二种情况是最快的,因为在较少的值上运行
fun1
可以补偿索引
zeta[J]
所增加的成本。索引成本和功能评估成本之间存在权衡。像这样的布尔索引比切片更昂贵。对于其他值的混合,计时可能会走向另一个方向

这看起来像是一个问题,你可以减少时间,但我看不到任何突破性的策略可以将时间缩短一个数量级。

你可以使用numba[](如果你使用anaconda或类似的python发行版,应该安装),这是一个旨在使用numpy的jit编译器

from numba import jit
@jit
def bernoulli_function_fill(zeta, fb_):
    for i in xrange(len(zeta)):
        if np.abs(zeta[i])>EPSILONZETA:
            fb_[i] = zeta[i]/(np.exp(zeta[i])-1.0)
        else:
            zeta0_ = zeta[i]
            zeta2_ = zeta0_ *  zeta0_
            zeta4_ =  zeta2_ * zeta2_
            fb_[i] = 1.0 - 0.5*zeta0_ + ZETA1_12 * zeta2_ - ZETA1_720 * zeta4_
def bernoulli_function_fast(zeta):
    fb_ = np.zeros_like(zeta)
    bernoulli_function_fill(zeta, fb_)
    return fb_
注意:如果您使用新版本的numba,则可以将两者合并为同一个函数

在我的机器上:

#create some test data
zeta = random.uniform(-1,1, size=2**24)
zeta[random.choice(len(zeta),size=2**23,replace=False )] = EPSILONZETA/2
>>> alltrue(bernoulli_function_fast(zeta)==bernoulli_function1(zeta))
True
>>> %timeit bernoulli_function1(zeta) # your function
1 loops, best of 3: 1.49 s per loop
>>> %timeit bernoulli_function_fast(zeta) #numba function
1 loops, best of 3: 347 ms per loop

它的速度快了约4倍,而且更容易阅读。

使用非标准模块(如Numba或Numexpr)可以吗?如果这样做了,我可以尝试一下,但首先我想确定这样做是否是最好的。此外,在我运行代码的地方,他们没有安装这些模块。@moarningsun顺便说一句,建议不错。如果使用
np.expm1
()计算函数,您可能不需要泰勒级数。您仍然需要单独处理0,因为
0/expm1(0)
给出
nan
,但值(在限制内)应该是1。目前,scipy上有一个打开的pull请求,用于添加函数
exprel(x)=(exp(x)-1)/x
:;如果您愿意,请随时在此处发表评论。任何高级索引,无论是数字还是布尔值,都会生成一个副本,并且与切片(生成视图)相比成本高昂。
masked\u condition\u zero
返回两个类似您的布尔数组。所以他也在做同样的索引。在该函数中使用
where
可能是多余的,但我怀疑它是否是时间杀手。但这很容易测试。使用这个建议和np.expm1(@WarrenWeckesser),它的运行速度快了25%:
fb_u=np.one-like(zeta)idx=np.abs(zeta)>EPSILONZETA fb_x=zeta[idx]/np.expm1(zeta[idx])
我很好奇,我的建议和@WarrenWeckesser的建议在多大程度上加快了速度s@Fergal在速度方面,使用exp-1.0稍微好一些(14%),但是假设np.expm1在零附近提供更好的结果(如文档所述),我更喜欢使用第二个。事实上,对于z=2.0E-16,fb(z)=0.9007。。和0.999。。。分别地最后,公差的值
EPSILONZETA=1.0e-250
效果很好。@Fergal我忘了提到@WarrenWeckesser指出的,使用expm1唯一剩下的就是捕捉“零”(由公差调整)。我正在使用您的方法来实现这一点,但是如果您定义了
fb\uu=np.ones\u like(zeta)
,则非零
zero=~nonZero
将变得无用,那么您可以确定,对于非常小的数字,您将有1(否定或肯定
idx=np.abs(zeta)>E)