慢速的scipy双重积分
我正在尝试得到一个叫做 expected_W 或 H 的函数,这个函数是通过积分得到的结果:
这里面有:
- theta 是一个包含两个元素的向量:theta_0 和 theta_1
- f(beta | theta) 是一个关于 beta 的正态分布,均值是 theta_0,方差是 theta_1
- q(epsilon) 是一个关于 epsilon 的正态分布,均值为零,方差为 sigma_epsilon(默认设置为1)。
- w(p, theta, eps, beta) 是我输入的一个函数,所以我不能准确预测它的样子。它可能是非线性的,但应该不会特别复杂。
这是我实现这个问题的方法。我知道我写的包装函数可能很乱,所以如果有人能帮我改进一下,我会很感激。
from __future__ import division
from scipy import integrate
from scipy.stats import norm
import math
import numpy as np
def exp_w(w_B, sigma_eps = 1, **kwargs):
'''
Integrates the w_B function
Input:
+ w_B : the function to be integrated.
+ sigma_eps : variance of the epsilon term. Set to 1 by default
'''
#The integrand function gives everything under the integral:
# w(B(p, \theta, \epsilon, \beta)) f(\beta | \theta ) q(\epsilon)
def integrand(eps, beta, p, theta_0, theta_1, sigma_eps=sigma_eps):
q_e = norm.pdf(eps, loc=0, scale=math.sqrt(sigma_eps))
f_beta = norm.pdf(beta, loc=theta_0, scale=math.sqrt(theta_1))
return w_B(p = p,
theta_0 = theta_0, theta_1 = theta_1,
eps = eps, beta=beta)* q_e *f_beta
#limits of integration. Using limited support for now.
eps_inf = lambda beta : -10 # otherwise: -np.inf
eps_sup = lambda beta : 10 # otherwise: np.inf
beta_inf = -10
beta_sup = 10
def integrated_f(p, theta_0, theta_1):
return integrate.dblquad(integrand, beta_inf, beta_sup,
eps_inf, eps_sup,
args = (p, theta_0, theta_1))
# this integrated_f is the H referenced at the top of the question
return integrated_f
我用一个简单的 w 函数测试了这个函数,对于这个函数我知道它的解析解(但通常情况下不会这样)。
def test_exp_w():
def w_B(p, theta_0, theta_1, eps, beta):
return 3*(p*eps + p*(theta_0 + theta_1) - beta)
# Function that I get
integrated = exp_w(w_B, sigma_eps = 1)
# Function that I should get
def exp_result(p, theta_0, theta_1):
return 3*p*(theta_0 + theta_1) - 3*theta_0
args = np.random.rand(3)
d_args = {'p' : args[0], 'theta_0' : args[1], 'theta_1' : args[2]}
if not (np.allclose(
integrated(**d_args)[0], exp_result(**d_args)) ):
raise Exception("Integration procedure isn't working!")
因此,我的实现似乎是有效的,但对于我的目的来说速度非常慢。我需要重复这个过程成千上万次(这是价值函数迭代中的一步。如果有人觉得相关,我可以提供更多信息)。
在 scipy
版本 0.14.0 和 numpy
版本 1.8.1 下,这个积分计算需要15秒。
有没有人有什么建议可以帮助我解决这个问题?首先,可能会有助于获取有限的积分域,但我还没弄明白怎么做,或者 SciPy 中的高斯求积法是否能很好地处理这个问题(它是否使用了高斯-赫尔米特?)。
谢谢你的时间。
---- 编辑:添加性能分析时间 -----
%lprun 的结果显示,大部分时间花在了
_distn_infraestructure.py:1529(pdf)
和
_continuous_distns.py:97(_norm_pdf)
每个函数调用次数高达83244次。
1 个回答
如果你的函数不是特别复杂,那么整合这个函数所花的时间听起来就有点长了。
我建议你先分析一下时间都花在哪里了。是花在 dblquad
上,还是其他地方?在整合过程中,w_B
被调用了多少次?如果时间主要花在 dblquad
上,而且调用次数非常多,那你能不能在整合时使用更宽松的容忍度呢?
看起来,乘以高斯函数实际上可以让你大大缩小整合的范围,因为高斯函数的大部分能量都集中在一个很小的区域里。你可以尝试计算一下合理的更紧的边界。你已经把范围限制在 -10 到 10 之间了;那么在 -100 到 100、-10 到 10 和 -1 到 1 之间,性能有没有显著变化呢?
如果你知道你的函数相对平滑,那么有一种简单的整合方法:
- 确定两个轴上的合理上下限(通过高斯函数来确定)
- 计算一个合理的网格密度(比如每个方向上100个点)
- 计算每个点的
w_B
(如果能要求w_B
的向量化版本,这样会快很多) - 把所有结果加起来
这个方法虽然技术含量不高,但速度非常快。至于它是否能给你提供足够好的结果以用于外部迭代,这个问题就有点意思了,可能会有帮助。