Как часть пакета статистического программирования, мне нужно добавить преобразованные в лог значения вместе с LogSumExp Function . Это значительно менее эффективно, чем сложение незарегистрированных значений.
Кроме того, мне нужно добавить значения вместе, используя функциональность numpy .ufun c .reduecat .
Существуют различные варианты, которые я рассмотрел, с код ниже:
- (для сравнения в не лог-пространстве) используйте numpy .add.reduceat
- Numpy ufun c для добавления зарегистрированных значений вместе: np.logaddexp.reduceat
- Рукописная функция сокращения со следующими функциями logsumexp:
def logsumexp_reduceat(arr, indices, logsum_exp_func):
res = list()
i_start = indices[0]
for cur_index, i in enumerate(indices[1:]):
res.append(logsum_exp_func(arr[i_start:i]))
i_start = i
res.append(logsum_exp_func(arr[i:]))
return res
@numba.jit(nopython=True)
def logsumexp(X):
r = 0.0
for x in X:
r += np.exp(x)
return np.log(r)
@numba.jit(nopython=True)
def logsumexp_stream(X):
alpha = -np.Inf
r = 0.0
for x in X:
if x != -np.Inf:
if x <= alpha:
r += np.exp(x - alpha)
else:
r *= np.exp(alpha - x)
r += 1.0
alpha = x
return np.log(r) + alpha
arr = np.random.uniform(0,0.1, 10000)
log_arr = np.log(arr)
indices = sorted(np.random.randint(0, 10000, 100))
# approach 1
%timeit np.add.reduceat(arr, indices)
12.7 µs ± 503 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
# approach 2
%timeit np.logaddexp.reduceat(log_arr, indices)
462 µs ± 17.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
# approach 3, scipy function
%timeit logsum_exp_reduceat(arr, indices, scipy.special.logsumexp)
3.69 ms ± 273 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
# approach 3 handwritten logsumexp
%timeit logsumexp_reduceat(log_arr, indices, logsumexp)
139 µs ± 7.1 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
# approach 3 streaming logsumexp
%timeit logsumexp_reduceat(log_arr, indices, logsumexp_stream)
164 µs ± 10.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Результаты timeit показывают, что рукописные функции logsumexp с numba являются самыми быстрыми вариантами, но все же в 10 раз медленнее, чем numpy .add.reduceat.
Несколько вопросов:
- Существуют ли другие подходы (или настройки для вариантов, которые я представил? d) что быстрее? Например, есть ли способ использовать таблицу поиска для вычисления функции logsumexp?
- Почему функция Себастьяна Новозина «streaming logsumexp» не быстрее, чем наивный подход?