scipy 需要帮助在python中优化三重嵌套二项式CDF

z4iuyo4d  于 10个月前  发布在  Python
关注(0)|答案(2)|浏览(79)

我必须计算三重求和(嵌套二项式CDF),公式如下:x1c 0d1x
它是一个二项式求和,首先从k = 0到C,然后m = 0到k,f = 0到C-k,这里s是一个函数,它的输入在0和1之间,输出在(0,1)之间。
我需要帮助找到一个有效的方法来在python中执行此操作。现在我使用的是三重循环,它工作得很好,但对于大型C来说效率不高。我目前使用的三重循环如下:
这里的s基本上是线性的,但它可以采取任何需要的形状。r也取0.1

import numpy as np
from math import comb

def s(d):
  return d * (0.99 - 0.01) + 0.01

S = 0
C = 100

for k in range(C):
  for m in range(k):
    for f in range(C-k):
      S += comb(C, k) * (((0.1)**(k))*((0.9)**(C-k))) * comb(k, m) * comb(C-k, f) * (2**(-C)) * s((f+m)/C)

字符串
有没有更有效的方法可以使用?
Edit:s只是一个以下形式的函数,它接受0到1之间的输入,并根据其形状给出0.01到0.99之间的输出。在示例代码中,它是线性的,但它可以是指数的或其他的。
Edit 2:函数's'不能从第一个求和中出来,在我提供的公式中,它可能是由于打字错误。它现在是固定的沿着随着输入chrslg的建议。

tp5buhyn

tp5buhyn1#

所以s只是一个仿射函数。这允许一些优化

S=0
for k in range(C):
  for m in range(k):
    for f in range(C-k):
      S += comb(C, k) * (((0.1)**(k))*((0.9)**(C-k))) * comb(k, m) * comb(C-k, f) * (2**(-C)) * s((f+m)/C)

字符串
请注意,此代码中有一个错误:数学符号中的Σ使用包含的边界(在这种公式中,通常有from 0 to n included,这意味着n+1次迭代。因为当你抽屉里有n只袜子,然后随机取一个数字,你可以取0只袜子,1只袜子,2只袜子,…最多n只袜子,包括:D)。
代码应该是

S=0
for k in range(C+1):
  for m in range(k+1):
    for f in range(C-k+1):
      S += comb(C, k) * (((0.1)**(k))*((0.9)**(C-k))) * comb(k, m) * comb(C-k, f) * (2**(-C)) * s((f+m)/C)


我们可以从循环中得到所有不依赖于循环索引的内容,正如我在注解中提到的

S=0; r=0.1
for k in range(C+1):
    Sm=0
    for m in range(k+1):
        Sf=0
        for f in range(C-k+1):
            Sf += comb(C-k, f) * s((f+m)/C)
        Sm += Sf * comb(k,m) * 2**(-C)
    S += Sm*comb(C,k) * r**k * (1-r)**(C-k)


在这里,我只是模仿数学方程而不是代码。有些操作已经被这样分解了。
当然,我不能像这样直接删除内部循环,因为现在你已经用不独立于fs((m+f)/C)替换了s(m/C)
但由于s是仿射的,我们可以很容易地将其替换为

S=0
for k in range(C+1):
    Sm=0
    for m in range(k+1):
        Sf=0
        for f in range(C-k+1):
            Sf += comb(C-k, f) * (0.01 + 0.98*m/C + 0.98/C*f)
        Sm += Sf * comb(k,m) * 2**(-C)
    S += Sm*comb(C,k) * r**k * (1-r)**(C-k)


一个常数项,一个与f成比例的项
由于Σcomb(n,k)是2,Σk.comb(n,k)是n×2 ¹,我们可以用

S=0
for k in range(C+1):
    Sm=0
    for m in range(k+1):
        Sf= (0.01+0.98*m/C)*2**(C-k) + 0.98/C * (C-k)*2**(C-k-1)
        Sm += Sf * comb(k,m) * 2**(-C)
    S += Sm*comb(C,k) * r**k * (1-r)**(C-k)


所以这是一个循环!现在我们有2个循环,而不是3个。但还没结束!
合并Sf=Sm +=

S=0
for k in range(C+1):
    Sm=0
    for m in range(k+1):
        Sm += comb(k,m) * ((0.01+0.98*m/C)*2**(-k) + 0.98/C * (C-k)*2**(-k-1))
    S += Sm*comb(C,k) * r**k * (1-r)**(C-k)


请注意,在这里,我们乘以comb(k,m)是由常数项组成的(0.01*2**(-k)0.98/C*(C-k)*2**(-k-1)是常数项,就m而言),并且与m项成比例。因此,使用相同的Σcomb(n,k) = 2ᵏΣk.comb(n,k) = k.2ᵏ⁻¹公式,我们可以再次重写

S=0
for k in range(C+1):
    Sm=(0.01*2**(-k) + 0.98/C * (C-k)*2**(-k-1))*2**k + (0.98/C*2**(-k))*k*2**(k-1)
    S += Sm*comb(C,k) * r**k * (1-r)**(C-k)


第二圈开始了!只剩一个了。还没结束!
可以简化为

S=0
for k in range(C+1):
    Sm=0.01 + 0.49/C*(C-k) + (0.49/C)*k
    S += Sm*comb(C,k) * r**k * (1-r)**(C-k)


甚至

S=0
for k in range(C+1):
    Sm=0.01 + 0.49 - 0.49/C*k + (0.49/C)*k 
    S += Sm*comb(C,k) * r**k * (1-r)**(C-k)


所以Sm=0.5明显

S=0
for k in range(C+1):
    S += 0.5*comb(C,k) * r**k * (1-r)**(C-k)


但是Σ comb(n,k) aᵏbⁿ⁻ᵏ是(a+b)的众所周知的发展。因此,0.5×(r +(1-r))= 0.5×1 = 0.5
因此,您的代码的最终简化(对不起,不能做得比这更好!)

S=0.5


你可以很容易地检查,一旦边界问题得到纠正,你的代码也返回0.5,不管参数(C和r)是多少。

一些 numpy 把戏

由于这应该是一个numpy问题,而不是一个数学简化问题,下面是如何使用numpy加速这种计算。
假设您正在尝试计算内部循环

for f in range(C-k+1):
     Sf += comb(C-k, f) * s((f+m)/C)


(the更有趣的一个。因为这个我想知道s,特别是如果它是向量化的。我最初的计划是给予我现在给出的答案,如果s是可向量的,也就是说s是否可以同时使用标量和数组作为参数。但是我不得不改变我的计划,当我看到s甚至是仿射,这是更好的。但是,好吧,让我们假设s更复杂,例如包含sinlog,甚至只是一些高阶多项式,这些多项式使我的形式计算变得不可能)
如果,我们可以使用向量化函数(同样,如果被要求,可以在整个数组上工作的函数),一次计算一个C-k+1值的向量,那么我们可以跳过for循环,并使用numpy的sum方法求和。它在现实中仍然是一个for循环。但是一个隐藏的,更重要的是,发生在numpy优化的C代码中的一个,在我们的慢python节点中。
我们可以从f的值向量开始
f=np.arange(C-k+1.0)。请注意1.0,lazy方式来确保它是浮动的
好消息是你的s(可能还有你能想到的所有s,甚至是我提到的sinlog或更高次多项式)都是矢量化的。因此,我们可以很容易地计算出s((f+m)/C)的向量。简单地说,就是这样做:如果f是向量,那么(f+m)/C也是,因为s是向量化的。
所以,最难的部分是梳子部分。
有了numpy,你可以用cumprod来完成

f=np.arange(C-k+1.0)
np.seterr(divide='ignore') # Just because the 1st term I am about to compute is 1/0
combRatio=((C-k-f)/f) # That is also a vector, since f is
combRatio[0]=1 # neutral cumprod
mycomb=combRatio.cumprod() #  All ΠcombRatio aka comb(f,C-k)


现在我们有一个向量comb(f,C-k)和一个向量s((f+m)/C)。要得到所有项的向量,我们只需要将两者相乘

f=np.arange(C-k+1.0)
np.seterr(divide='ignore') # Just because the 1st term I am about to compute is 1/0
combRatio=((C-k-f)/f) # That is also a vector, since f is
combRatio[0]=1 # neutral cumprod
mycomb=combRatio.cumprod() #  All ΠcombRatio aka comb(f,C-k)
terms = mycomb * s((f+m)/C)

# And the Σ result is simply
terms.sum()


请注意,如果您愿意使用scipy,它甚至更简单,因为scipy包含一个向量化的comb函数

f=np.arange(C-k+1.0)
Sf=(scipy.specials.comb(C-k,f) * s((f+m)/C)).sum()


这一次,我没有数学技巧。我没有简化任何东西。这只是编码技巧,使循环消失。它仍然存在(scipy的comb,numpy的+-*.sum())。现在甚至有5个(非嵌套的,比纯Python循环快5倍多)

jfgube3f

jfgube3f2#

你可以每k只计算一次comb(C, k) * (((0.1)**(k))*((0.9)**(C-k)))comb(k, m)只计算一次m,如下所示:

for k in range(C):
    step_1 = comb(C, k) * (((0.1) ** (k)) * ((0.9) ** (C - k)))
    for m in range(k):
        step_2 = step_1 * comb(k, m)
        for f in range(C - k):
            S += step_2 * comb(C - k, f) * (2 ** (-C)) * s((f + m) / C)

字符串

相关问题