Более быстрая свертка функций плотности вероятности в Python

Предположим, что необходимо вычислить свертку общего числа дискретных функций плотности вероятности. Для приведенного ниже примера есть четыре распределения, которые принимают значения 0,1,2 с указанными вероятностями:

import numpy as np
pdfs = np.array([[0.6,0.3,0.1],[0.5,0.4,0.1],[0.3,0.7,0.0],[1.0,0.0,0.0]])

Свертку можно найти так:

pdf = pdfs[0]        
for i in range(1,pdfs.shape[0]):
    pdf = np.convolve(pdfs[i], pdf)

Тогда вероятности увидеть 0,1,...,8 равны

array([ 0.09 ,  0.327,  0.342,  0.182,  0.052,  0.007,  0.   ,  0.   ,  0.   ])

Эта часть является узким местом в моем коде, и кажется, что должно быть что-то доступное для векторизации этой операции. Есть ли у кого-нибудь предложения по ускорению?

В качестве альтернативы решение, в котором вы могли бы использовать

pdf1 = np.array([[0.6,0.3,0.1],[0.5,0.4,0.1]])
pdf2 = np.array([[0.3,0.7,0.0],[1.0,0.0,0.0]])
convolve(pd1,pd2) 

и получить попарные свертки

 array([[ 0.18,  0.51,  0.24,  0.07,  0.  ], 
        [ 0.5,  0.4,  0.1,  0. ,  0. ]])

тоже бы здорово помог.


person Forzaa    schedule 06.03.2015    source источник
comment
Согласно документам numpy, аргументы np.convolve могут быть только одномерными. Так что, я думаю, здесь не так много векторизации. Но, может быть, стоит использовать другую свертку, например, основанную на scipy fft? docs.scipy.org/doc/scipy/reference/ сгенерировано/   -  person SmCaterpillar    schedule 06.03.2015
comment
@SmCaterpillar Я немного поиграл с этим, но мои знания о извилинах слишком ограничены, чтобы понять, что там происходит. Версия здесь я понимаю, но я понятия не имею, как указать веса для версии fft.   -  person Forzaa    schedule 06.03.2015
comment
Что вы имеете в виду под весом? Я пробовал оба, и обе свертки дают одинаковый результат для вашего вопроса. Однако fft был намного медленнее (из-за накладных расходов ваша игрушечная проблема слишком мала, возможно, когда сами PDF-файлы содержат больше значений, вы действительно получаете увеличение скорости).   -  person SmCaterpillar    schedule 06.03.2015
comment
@SmCaterpillar Я полагаю, вы снова используете цикл for для версии scipy и сворачиваете одну за другой. Я хотел бы избежать цикла for и немедленно применить операцию ко всем строкам PDF-файлов.   -  person Forzaa    schedule 06.03.2015
comment
Я просматривал эту версию свертки для записи docs.scipy.org/doc/scipy/reference/generated/   -  person Forzaa    schedule 06.03.2015
comment
Вы можете распараллелить его с помощью многопроцессорной обработки. Также у вас много нулевых значений. Пропустить свертки для них.   -  person User    schedule 06.03.2015
comment
Можете ли вы прокомментировать типичные размеры? Какова типичная форма массива pdfs?   -  person Mark Dickinson    schedule 24.03.2015
comment
@Mark Может быть 10 таких PDF-файлов по 100 записей в каждом.   -  person Forzaa    schedule 24.03.2015


Ответы (1)


Вы можете эффективно вычислить свертку всех ваших PDF-файлов, используя быстрые преобразования Фурье (БПФ): ключевым фактом является то, что БПФ свертки является произведением БПФ отдельных функций плотности вероятности. Поэтому преобразуйте каждый PDF-файл, умножьте преобразованные PDF-файлы вместе, а затем выполните обратное преобразование. Вам нужно будет дополнить каждый входной PDF-файл нулями до соответствующей длины, чтобы избежать эффектов зацикливания.

Это должно быть достаточно эффективным: если у вас есть m PDF-файлов, каждый из которых содержит n записей, то время вычисления свертки с использованием этого метода должно возрасти как (m^2)n log(mn). Во времени преобладают БПФ, и мы эффективно вычисляем m + 1 независимых БПФ (m прямых преобразований и одно обратное преобразование), каждое из массива длиной не более mn. Но, как всегда, если вам нужны реальные тайминги, вам следует профилировать.

Вот код:

import numpy.fft

def convolve_many(arrays):
    """
    Convolve a list of 1d float arrays together, using FFTs.
    The arrays need not have the same length, but each array should
    have length at least 1.

    """
    result_length = 1 + sum((len(array) - 1) for array in arrays)

    # Copy each array into a 2d array of the appropriate shape.
    rows = numpy.zeros((len(arrays), result_length))
    for i, array in enumerate(arrays):
        rows[i, :len(array)] = array

    # Transform, take the product, and do the inverse transform
    # to get the convolution.
    fft_of_rows = numpy.fft.fft(rows)
    fft_of_convolution = fft_of_rows.prod(axis=0)
    convolution = numpy.fft.ifft(fft_of_convolution)

    # Assuming real inputs, the imaginary part of the output can
    # be ignored.
    return convolution.real

Применяя это к вашему примеру, вот что я получаю:

>>> convolve_many([[0.6, 0.3, 0.1], [0.5, 0.4, 0.1], [0.3, 0.7], [1.0]])
array([ 0.09 ,  0.327,  0.342,  0.182,  0.052,  0.007])

Это основная идея. Если вы хотите изменить это, вы также можете посмотреть numpy.fft.rfft (и наоборот, numpy.fft.irfft), которые используют тот факт, что входные данные являются реальными, для создания более компактных преобразованных массивов. Вы также можете увеличить скорость, дополнив массив rows нулями, чтобы общее количество столбцов было оптимальным для выполнения БПФ. Определение «оптимального» здесь будет зависеть от реализации БПФ, но, например, степень двойки будет хорошей целью. Наконец, есть некоторые очевидные упрощения, которые можно сделать при создании rows, если все входные массивы имеют одинаковую длину. Но я оставлю эти потенциальные улучшения вам.

person Mark Dickinson    schedule 24.03.2015
comment
Почему бы не использовать scipy.signal.fftconvolve() (docs.scipy.org/doc /scipy/reference/generated/)? - person Dietrich; 24.03.2015
comment
@Dietrich: Потому что (если я что-то не упустил), который сворачивает только два массива за раз, и его повторное использование потребует большого количества ненужных преобразований и непреобразований. - person Mark Dickinson; 24.03.2015
comment
@MarkDickinson Не могли бы вы уточнить, как мы можем сопоставить вывод (вероятность плотности) с фактическими результатами? Вот как мы вычисляем исходы, которым принадлежат эти вероятности? Какова цель result_length? Почему мы добавляем несколько нулей в каждый массив, который мы все равно никогда не заполняем, так как мы заполняем массив строк только до :len(array)?. - person user2974951; 18.08.2020
comment
@ user2974951 Это немного зависит от того, что вы делаете. Как правило, вы сворачиваете PDF-файлы случайных величин X_1, X_2, ..., X_n, чтобы получить PDF-файл X_1 + X_2 + ... + X_n. В этом случае каждый X_i должен быть дискретным с возможными значениями, равномерно распределенными с использованием, скажем, некоторого интервала s, и этот интервал должен совпадать для всех X_i. Затем результаты соответствуют равномерно распределенным значениям с интервалом s, начиная с суммы минимумов каждого X_i и заканчивая суммой максимумов каждого X_i. - person Mark Dickinson; 21.08.2020
comment
@user2974951 user2974951 result_length, как следует из названия, представляет собой длину результирующего массива свертки. Нам нужно дополнить каждый входной массив до этой длины, чтобы БПФ были совместимы. - person Mark Dickinson; 21.08.2020