Лучший способ написать функцию Python, которая интегрирует гауссиан?

При попытке использовать четырехъядерный метод scipy для интеграции гаусса (скажем, есть гауссовский метод с именем gauss), у меня возникли проблемы с передачей необходимых параметров в gauss и оставлением quad для выполнения интеграции по правильной переменной. У кого-нибудь есть хороший пример использования quad с многомерной функцией?

Но это привело меня к более серьезному вопросу о том, как лучше всего интегрировать гауссову функцию. Я не нашел интеграцию по Гауссу в scipy (к моему удивлению). Мой план состоял в том, чтобы написать простую функцию Гаусса и передать ее в квадроцикл (или, может быть, теперь интегратор с фиксированной шириной). Что бы ты сделал?

Редактировать: фиксированная ширина означает что-то вроде trapz, который использует фиксированный dx для расчета областей под кривой.

До сих пор я пришел к методу make___gauss, который возвращает лямбда-функцию, которая затем может перейти в quad. Таким образом, я могу сделать нормальную функцию со средним значением и дисперсией, которые мне нужны, прежде чем интегрировать.

def make_gauss(N, sigma, mu):
    return (lambda x: N/(sigma * (2*numpy.pi)**.5) *
            numpy.e ** (-(x-mu)**2/(2 * sigma**2)))

quad(make_gauss(N=10, sigma=2, mu=0), -inf, inf)

Когда я попытался передать общую функцию Гаусса (которую нужно вызывать с помощью x, N, mu и sigma) и заполнить некоторые значения, используя quad, например

quad(gen_gauss, -inf, inf, (10,2,0))

параметры 10, 2 и 0 НЕ обязательно соответствовали N = 10, сигма = 2, мю = 0, что потребовало более расширенного определения.

erf(z) в scipy.special потребовал бы, чтобы я точно определил, что такое t изначально, но приятно знать, что он есть.


person physicsmichael    schedule 04.02.2009    source источник
comment
Гауссово распределение чисел или данных. Если нанести на график, это выглядит как выпуклая или колоколообразная кривая.   -  person physicsmichael    schedule 07.02.2009
comment
В просторечии gaussian используется как существительное для представления гауссовой кривой или распределения (например, это несколько распространено в статье Википедии). Я полагаю, нам тоже следует писать с заглавной буквы, но SO — довольно разговорное место, не так ли?   -  person physicsmichael    schedule 08.02.2009
comment
Гауссиан - это гауссиан, это гауссиан, независимо от того, какое существительное оно модифицирует. Откажитесь от глупых семантических аргументов, которые ничего не добавляют.   -  person temp2290    schedule 12.06.2009
comment
Функция scipy.stats.norm.cdf вычисляет ваш интеграл.   -  person John D. Cook    schedule 25.07.2009


Ответы (5)


Хорошо, вы, кажется, довольно запутались в нескольких вещах. Давайте начнем с самого начала: вы упомянули «многомерную функцию», но затем перейдем к обсуждению обычной кривой Гаусса с одной переменной. Это не многомерная функция: при ее интегрировании вы интегрируете только одну переменную (x). Различие важно проводить, потому что существует существует чудовище, называемое "многомерным гауссовым распределением", которое представляет собой настоящую многомерную функцию и, если ее интегрировать, требует интегрирования по двум или более переменным (для чего используется дорогостоящий метод Монте-Карло). техника Карло, о которой я упоминал ранее). Но вы, кажется, просто говорите об обычном гауссиане с одной переменной, с которым гораздо проще работать, интегрировать и все такое.

Распределение Гаусса с одной переменной имеет два параметра, sigma и mu, и является функцией одной переменной, которую мы обозначим x. Вы также, кажется, носите с собой параметр нормализации n (который полезен в нескольких приложениях). Параметры нормализации обычно не включаются в расчеты, так как вы можете просто добавить их обратно в конце (помните, что интегрирование — это линейный оператор: int(n*f(x), x) = n*int(f(x), x) ). Но мы можем носить его с собой, если хотите; обозначение, которое мне нравится для нормального распределения, тогда

N(x | mu, sigma, n) := (n/(sigma*sqrt(2*pi))) * exp((-(x-mu)^2)/(2*sigma^2))

(читай, что "нормальное распределение x при заданных sigma, mu и n определяется...") Пока все хорошо; это соответствует функции, которую вы получили. Обратите внимание, что единственной истинной переменной здесь является x: остальные три параметра фиксированы для любого конкретного гауссова уравнения.

Теперь о математическом факте: доказуемо, что все кривые Гаусса имеют одинаковую форму, просто они немного смещены. Таким образом, мы можем работать с N(x|0,1,1), называемым «стандартным нормальным распределением», и просто переводить наши результаты обратно в общую кривую Гаусса. Итак, если у вас есть интеграл от N(x|0,1,1), вы можете тривиально вычислить интеграл от любого гауссиана. Этот интеграл появляется так часто, что у него есть специальное название: функция ошибок erf. Из-за некоторых старых соглашений это не точно erf; также существует пара аддитивных и мультипликативных факторов.

Если Phi(z) = integral(N(x|0,1,1), -inf, z); то есть Phi(z) является интегралом стандартного нормального распределения от минус бесконечности до z, то по определению функции ошибок верно, что

Phi(z) = 0.5 + 0.5 * erf(z / sqrt(2)).

Аналогично, если Phi(z | mu, sigma, n) = integral( N(x|sigma, mu, n), -inf, z); то есть Phi(z | mu, sigma, n) является интегралом нормального распределения с заданными параметрами mu, sigma и n от минус бесконечности до z, то по определению функции ошибок верно, что

Phi(z | mu, sigma, n) = (n/2) * (1 + erf((x - mu) / (sigma * sqrt(2)))).

Взгляните на статью Википедии об обычном CDF, если вам нужны подробности или доказательства. этого факта.

Хорошо, этого должно быть достаточно. Вернемся к вашему (отредактированному) сообщению. Вы говорите: «erf (z) в scipy.special потребует, чтобы я точно определил, что такое t изначально». Я понятия не имею, что вы имеете в виду под этим; причем здесь вообще t (время?)? Надеюсь, приведенное выше объяснение немного демистифицирует функцию ошибки, и теперь становится яснее, почему функция ошибки является правильной функцией для этой работы.

Ваш код Python в порядке, но я бы предпочел замыкание лямбда:

def make_gauss(N, sigma, mu):
    k = N / (sigma * math.sqrt(2*math.pi))
    s = -1.0 / (2 * sigma * sigma)
    def f(x):
        return k * math.exp(s * (x - mu)*(x - mu))
    return f

Использование замыкания позволяет выполнять предварительное вычисление констант k и s, поэтому возвращаемой функции нужно будет выполнять меньше работы при каждом вызове (что может быть важно, если вы ее интегрируете, а это означает, что она будет вызываться много раз). Кроме того, я избегал использования оператора возведения в степень **, который медленнее, чем просто запись возведения в квадрат, и поднял деление из внутреннего цикла и заменил его умножением. Я вообще не смотрел на их реализацию в Python, но из моей последней настройки внутреннего цикла для чистой скорости с использованием необработанной сборки x87 я, кажется, помню, что сложение, вычитание или умножение занимает около 4 циклов процессора каждое, деление около 36, а возведение в степень около 200. Это было пару лет назад, так что относитесь к этим числам с долей скептицизма; тем не менее, это иллюстрирует их относительную сложность. Кроме того, вычисление exp(x) методом грубой силы — очень плохая идея; есть уловки, которые вы можете использовать при написании хорошей реализации exp(x), которые сделают ее значительно быстрее и точнее, чем обычное возведение в степень в стиле a**b.

Я никогда не использовал пустую версию констант pi и e; Я всегда придерживался старых версий математических модулей. Я не знаю, почему вы можете предпочесть любой из них.

Я не уверен, что вы собираетесь делать со звонком quad(). quad(gen_gauss, -inf, inf, (10,2,0)) должен интегрировать ренормализованную гауссиану от минус бесконечности до плюс бесконечности и всегда должен выдавать 10 (ваш коэффициент нормализации), поскольку гауссиана интегрируется до 1 по реальной линии. Любой ответ, далекий от 10 (я бы не стал ожидать точно 10, поскольку quad() в конце концов — это только приблизительное значение) означает, что где-то что-то накосячило... трудно сказать, что накосячило, не зная фактического результата ценность и, возможно, внутреннюю работу quad().

Надеюсь, это развеяло некоторую путаницу и объяснило, почему функция ошибок является правильным решением вашей проблемы, а также как сделать все это самостоятельно, если вам любопытно. Если какое-либо из моих объяснений было неясным, я предлагаю сначала взглянуть на Википедию; если у вас еще остались вопросы, не стесняйтесь спрашивать.

person kquinn    schedule 04.02.2009
comment
Хороший ответ. Кстати, я предполагаю, что в вашей функции make_gauss вы хотели присвоить k и s в теле make_gauss, а не в теле f. - person Mr Fooz; 04.02.2009
comment
Спасибо, что нашли время, чтобы сделать такой полный ответ. Когда я сказал многомерный, я имел в виду передачу quad метода, который принимает несколько аргументов, один из которых должен быть интегрирующей переменной x. Я интегрирую конечные ширины вокруг mu, поэтому erf не подойдет, но в будущем я буду использовать замыкание. - person physicsmichael; 05.02.2009
comment
@Мистер Фуз: исправлено. Не знаю, как я пропустил это. - person kquinn; 05.02.2009
comment
@ vgm64: На самом деле, erf отлично подходит для этого: допустим, вы хотите интегрироваться с mu - delta на mu + delta. Тогда интеграл равен просто Phi(mu + delta | mu, sigma, n) - Phi(mu - delta | mu, sigma, n): функция Phi, которую я определил выше в терминах erf(), является первообразной гауссовой функции. - person kquinn; 05.02.2009
comment
Поэтому я мог бы создать новый метод, который использует Phi(...) - Phi(...), как указано выше, просто чтобы сделать его чище. Как вы думаете, как это соотносится с передачей функции Гаусса числовому интегратору (скорость/точность/техника)? - person physicsmichael; 07.02.2009
comment
Скорость и точность: erf() намного быстрее и точнее, чем обычная интеграция. Чтобы написать erf(), математик или численный аналитик вроде меня создает собственное приближение к интегралу и настраивает его как для скорости, так и для точности. Не могли бы вы написать свой собственный метод cos()? Тогда зачем писать свой собственный erf()? - person kquinn; 07.02.2009
comment
Техника: гораздо лучше, erf() легко распознается как первообразная гауссовой функции. Для нематематиков вы можете включить комментарий, например, найти определение функции ошибки, если вы не понимаете, почему это работает. Использование erf() или Phi() является правильным ответом на эту проблему. - person kquinn; 07.02.2009
comment
Пожалуйста, исправьте отступ последней строки make_gauss. - person Cristian Ciupitu; 07.05.2011
comment
на самом деле для вычисления quad нужно сначала вызвать функцию, а потом вызвать quad без третьего параметра. так и есть: print quad(make_gauss(1,1,0),-np.inf,np.inf) - person Chris; 17.10.2015

scipy поставляется с «функцией ошибок», также известной как интеграл Гаусса:

import scipy.special
help(scipy.special.erf)
person Mr Fooz    schedule 04.02.2009
comment
Вам нужно небольшое изменение переменных, чтобы преобразовать erf в гауссовский CDF. См. примечания здесь: johndcook.com/erf_and_normal_cdf.pdf - person John D. Cook; 25.07.2009

Распределение Гаусса также называют нормальным распределением. Функция cdf в модуле норм scipy делает то, что вы хотите.

from scipy.stats import norm
print norm.cdf(0.0)
>>>0.5

http://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.norm.html#scipy.stats.norm

person Chuck    schedule 18.01.2011

Я предполагаю, что вы имеете дело с многомерными гауссианами; если это так, то в SciPy уже есть функция, которую вы ищете: она называется MVNDIST ("MultiVariate Normal DISTribution"). Документация SciPy, как всегда, ужасна, поэтому я даже не могу найти, где эта функция похоронена, но это где-то здесь Документация — худшая часть SciPy, и в прошлом она меня безмерно расстраивала.

Гауссианы с одной переменной просто используют старую добрую функцию ошибок, для которой доступно множество реализаций.

Что касается решения проблемы в целом, да, как упоминает Джеймс Томпсон, вы просто хотите написать свою собственную функцию распределения Гаусса и передать ее в quad(). Тем не менее, если вы можете избежать обобщенной интеграции, это будет хорошей идеей - специализированные методы интеграции для конкретной функции (например, использование MVNDIST) будут намного быстрее, чем стандартная многомерная интеграция Монте-Карло, которая может быть чрезвычайно медленной. для высокой точности.

person kquinn    schedule 04.02.2009

Почему бы просто не выполнять интегрирование от -бесконечности до +бесконечности, чтобы всегда знать ответ? (шутка!)

Я предполагаю, что единственная причина, по которой в SciPy еще нет готовой гауссовой функции, заключается в том, что эту функцию написать тривиально. Ваше предложение о написании собственной функции и передаче ее в quad для интеграции звучит превосходно. Для этого используется общепринятый инструмент SciPy, для вас требуется минимальное количество кода, и он очень удобочитаем для других людей, даже если они никогда не видели SciPy.

Что именно вы подразумеваете под интегратором фиксированной ширины? Вы имеете в виду использование алгоритма, отличного от того, что использует QUADPACK?

Изменить: для полноты вот что-то вроде того, что я бы попробовал для гауссова со средним значением 0 и стандартным отклонением 1 от 0 до + бесконечности:

from scipy.integrate import quad
from math import pi, exp
mean = 0
sd   = 1
quad(lambda x: 1 / ( sd * ( 2 * pi ) ** 0.5 ) * exp( x ** 2 / (-2 * sd ** 2) ), 0, inf )

Это немного некрасиво, потому что функция Гаусса немного длинная, но все же довольно тривиальная для написания.

person James Thompson    schedule 04.02.2009