使用itertools更快地循环

时间:2014-05-15 15:05:49

标签: python performance numpy itertools

我有一个功能

def getSamples():
    p = lambda x : mlab.normpdf(x,3,2) + mlab.normpdf(x,-5,1)
    q = lambda x : mlab.normpdf(x,5,14)
    k=30
    goodSamples = []
    rightCount = 0
    totalCount = 0
    while(rightCount < 100000):
        z0 = np.random.normal(5, 14)
        u0 = np.random.uniform(0,k*q(z0))
        if(p(z0) > u0):
            goodSamples.append(z0)
            rightCount += 1
        totalCount += 1
    return np.array(goodSamples)

我生成100000个样本的实现需要很长时间。如何使用itertools或类似内容快速完成?

3 个答案:

答案 0 :(得分:3)

我想说让代码更快的秘诀不在于改变循环语法。以下是几点:

  1. np.random.normal有一个额外的参数size,可让您一次获得多个值。我建议使用一组说1E09元素然后检查你的条件,看看有多少是好的。然后,您可以估计出这种可能性。
  2. 要创建统一样本,为什么不使用sympy对pdf进行符号评估? (我不知道这是否更快,但可能因为你已经知道了均值和方差。)
  3. 同样,p可以使用符号函数吗?

答案 1 :(得分:1)

一般来说,性能问题是由“错误的方式”做事造成的。 Numpy在使用时可以非常快,因为它被设计为使用,即通过利用其向量处理将这些向量化操作传递给编译代码。来自其他编程语言/方法的两个不良做法是

  1. 循环:每当你认为你需要一个循环停止并思考。大多数时候你没有,事实上甚至不想要一个。编写和运行没有循环的代码要快得多。
  2. 内存分配:只要知道对象的大小,就为它预先分配空间。与替代方案相比,内存不断增长,尤其是Python列表中的内存增长非常缓慢。
  3. 在这种情况下,很容易获得(大约)两个数量级的加速;权衡是更多的内存使用。

    以下是一些代表性代码,不一定要盲目使用。我甚至没有验证它会产生正确的结果。它或多或少是您日常工作的直接翻译。您似乎使用拒绝方法从概率分布中绘制随机数。对于概率分布,可能有更有效的算法来执行此操作。

    def getSamples2() :
        p = lambda x : mlab.normpdf(x,3,2) + mlab.normpdf(x,-5,1)
        q = lambda x : mlab.normpdf(x,5,14)
        k=30
        N = 100000 # Total number of samples we want
        Ngood = 0 # Current number of good samples
        goodSamples = np.zeros(N) # Storage for the good samples
        while Ngood < N : # Unfortunately a loop, ....
           z0 = np.random.normal(5, 14, size=N)
           u0 = np.random.uniform(size=N)*k*q(z0)
           ind, = np.where(p(z0) > u0)
           n = min(len(ind), N-Ngood)
           goodSamples[Ngood:Ngood+n] = z0[ind[:n]]
           Ngood += n
        return goodSamples
    

    这会以块的形式生成随机数并保存好的数字。我没有尝试优化块大小(这里我只使用N,我们想要的总数,原则上这可能/应该是不同的,甚至可以根据我们留下的数量进行调整)。不幸的是,这仍然使用循环,但现在这将运行“数十次”而不是100,000次。这也使用where函数和数组切片;这些都是很好的通用工具。

    在我的机器上使用%timeit进行的一次测试中,我找到了

    In [27]: %timeit getSamples() # Original routine
    1 loops, best of 3: 49.3 s per loop
    In [28]: %timeit getSamples2()
    1 loops, best of 3: 505 ms per loop
    

答案 2 :(得分:0)

这里有点像“魔法”,但我不确定它可以提供帮助。对于性能来说,准备一个numpy数组(使用零)并填充它而不创建python自动增长列表可能要好得多。这是itertools和零准备。 (请原谅我未经测试的代码)

from itertools import count, ifilter, imap, takewhile
import operator


def getSamples():
    p = lambda x : mlab.normpdf(x, 3, 2) + mlab.normpdf(x, -5, 1)
    q = lambda x : mlab.normpdf(x, 5, 14)

    k = 30
    n = 100000
    samples_iter = imap(
        operator.itemgetter(1),
        takewhile(
            lambda i, s: i < n,
            enumerate(
                ifilter(lambda z: p(z) > np.random.uniform(0,k*q(z)),
                        (np.random.normal(5, 14) for _ in count()))
    )))

    goodSamples = numpy.zeros(n)
    # set values from iterator, probably there is a better way for that
    for i, sample in enumerate(samples_iter):
        goodSamples[i] = sample
    return goodSamples