Быстрый Python / Numpy Frequency-моделирование распределения серьезности

#python #performance #numpy #vectorization

#python #Производительность #numpy #векторизация

Вопрос:

Я ищу способ моделирования классического распределения частотной серьезности, что-то вроде: 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] 
  

Идеи, которые у меня есть, — это «умное нарезание» или «умножение матрицы с = [[1, 0, 0, 0]],[0,1,1,1],[0,0,0,0]]. Но я не мог придумать что-то умное из этих идей.

Я ищу максимально быстрое вычисление X.

Ответ №1:

Мы можем использовать np.bincount , что довольно эффективно для таких операций суммирования на основе интервала / идентификатора, особенно при работе с 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 on arrays вместо гораздо более медленного Python sum .

Если это быстрее, чем другой ответ, зависит от среднего значения вашего распределения Пуассона.

Комментарии:

1. Я использую SIM_NUM = 5 , и ваш код часто приводит к исключению ValueError: NumPy boolean array indexing assignment cannot assign 2 input values to the 3 output values where the mask is true в строке x[N>0] = ...

2. @Warren — Спасибо, должен был запустить код перед публикацией.

3. Спасибо за ваш ответ, я тоже попробовал, но для моего варианта использования bincount был быстрее.