【问题标题】:Fast Python/Numpy Frequency-Severity Distribution Simulation快速 Python/Numpy 频率严重性分布模拟
【发布时间】:2016-10-04 19:38:00
【问题描述】:

我正在寻找模拟经典频率严重性分布的方法,例如: X = sum(i = 1..N, Y_i),其中 N 是例如泊松分布和 Y 对数正态分布。

简单的 numpy 脚本是:

import numpy as np
SIM_NUM = 3

X = []
for _i in range(SIM_NUM):
    nr_claims = np.random.poisson(1)
    temp = []
    for _j in range(nr_claims):
         temp.append(np.random.lognormal(0, 1))
    X.append(sum(temp))

现在我尝试将其矢量化以提高性能。更好一点的是:

N = np.random.poisson(1, SIM_NUM)
X = []
for n in N:
    X.append(sum(np.random.lognormal(0, 1, n)))

我的问题是我是否仍然可以矢量化第二个循环?例如,通过以下方式模拟所有损失:

N = np.random.poisson(1, SIM_NUM)
# print(N) would lead to something like [1 3 0]
losses = np.random.lognormal(0,1, sum(N))
# print(N) would lead to something like 
#[ 0.56750244  0.84161871  0.41567216  1.04311742]

# X should now be [ 0.56750244, 0.84161871 + 0.41567216 + 1.04311742, 0] 

我的想法是“智能切片”或“A = [[1, 0, 0, 0]],[0,1,1,1],[0,0,0,0] 的矩阵乘法]。但我无法从这些想法中做出一些聪明的事情。

我正在寻找最快的 X 计算。

【问题讨论】:

    标签: python performance numpy vectorization


    【解决方案1】:

    我们可以使用 np.bincount ,这对于这种基于间隔/ID 的求和操作非常有效,特别是在使用 1D 数组时。实现看起来像这样 -

    # Generate all poisson distribution values in one go
    pv = np.random.poisson(1,SIM_NUM)
    
    # Use poisson values to get count of total for random lognormal needed.
    # Generate all those random numbers again in vectorized way 
    rand_arr = np.random.lognormal(0, 1, pv.sum())
    
    # Finally create IDs using pv as extents for use with bincount to do
    # ID based and thus effectively interval-based summing
    out = np.bincount(np.arange(pv.size).repeat(pv),rand_arr,minlength=SIM_NUM)
    

    运行时测试-

    函数定义:

    def original_app1(SIM_NUM):
        X = []
        for _i in range(SIM_NUM):
            nr_claims = np.random.poisson(1)
            temp = []
            for _j in range(nr_claims):
                 temp.append(np.random.lognormal(0, 1))
            X.append(sum(temp))
        return X
    
    def original_app2(SIM_NUM):
        N = np.random.poisson(1, SIM_NUM)
        X = []
        for n in N:
            X.append(sum(np.random.lognormal(0, 1, n)))
        return X
    
    def vectorized_app1(SIM_NUM):
        pv = np.random.poisson(1,SIM_NUM)
        r = np.random.lognormal(0, 1,pv.sum())
        return np.bincount(np.arange(pv.size).repeat(pv),r,minlength=SIM_NUM)
    

    大型数据集的时间安排:

    In [199]: SIM_NUM = 1000
    
    In [200]: %timeit original_app1(SIM_NUM)
    100 loops, best of 3: 2.6 ms per loop
    
    In [201]: %timeit original_app2(SIM_NUM)
    100 loops, best of 3: 6.65 ms per loop
    
    In [202]: %timeit vectorized_app1(SIM_NUM)
    1000 loops, best of 3: 252 µs per loop
    
    In [203]: SIM_NUM = 10000
    
    In [204]: %timeit original_app1(SIM_NUM)
    10 loops, best of 3: 26.1 ms per loop
    
    In [205]: %timeit original_app2(SIM_NUM)
    10 loops, best of 3: 77.5 ms per loop
    
    In [206]: %timeit vectorized_app1(SIM_NUM)
    100 loops, best of 3: 2.46 ms per loop
    

    所以,我们正在那里寻找一些 10x+ 加速。

    【讨论】:

      【解决方案2】:

      你正在寻找numpy.add.reduceat:

      N = np.random.poisson(1, SIM_NUM)
      losses = np.random.lognormal(0,1, np.sum(N))
      
      x = np.zeros(SIM_NUM)
      offsets = np.r_[0, np.cumsum(N[N>0])]
      x[N>0] = np.add.reduceat(losses, offsets[:-1])
      

      n == 0 单独处理的情况,因为reduceat 的工作方式。另外,一定要在数组上使用numpy.sum,而不是慢得多的Python sum

      这是否比其他答案更快取决于您的泊松分布的平均值。

      【讨论】:

      • 我使用SIM_NUM = 5,你的代码经常在x[N>0] = ...这一行导致异常ValueError: NumPy boolean array indexing assignment cannot assign 2 input values to the 3 output values where the mask is true
      • @Warren - 谢谢,应该在发布之前运行代码。
      • 感谢您的回答,我也试过了,但对于我的用例 bincount 更快。
      猜你喜欢
      • 1970-01-01
      • 2015-05-08
      • 2015-11-09
      • 2019-11-08
      • 2013-03-19
      • 1970-01-01
      • 1970-01-01
      • 2018-03-24
      • 1970-01-01
      相关资源
      最近更新 更多