慢速的scipy双重积分

3 投票
1 回答
2239 浏览
提问于 2025-04-18 16:54

我正在尝试得到一个叫做 expected_W 或 H 的函数,这个函数是通过积分得到的结果:

IMG

这里面有:

  • 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 个回答

2

如果你的函数不是特别复杂,那么整合这个函数所花的时间听起来就有点长了。

我建议你先分析一下时间都花在哪里了。是花在 dblquad 上,还是其他地方?在整合过程中,w_B 被调用了多少次?如果时间主要花在 dblquad 上,而且调用次数非常多,那你能不能在整合时使用更宽松的容忍度呢?

看起来,乘以高斯函数实际上可以让你大大缩小整合的范围,因为高斯函数的大部分能量都集中在一个很小的区域里。你可以尝试计算一下合理的更紧的边界。你已经把范围限制在 -10 到 10 之间了;那么在 -100 到 100、-10 到 10 和 -1 到 1 之间,性能有没有显著变化呢?


如果你知道你的函数相对平滑,那么有一种简单的整合方法:

  • 确定两个轴上的合理上下限(通过高斯函数来确定)
  • 计算一个合理的网格密度(比如每个方向上100个点)
  • 计算每个点的 w_B(如果能要求 w_B 的向量化版本,这样会快很多)
  • 把所有结果加起来

这个方法虽然技术含量不高,但速度非常快。至于它是否能给你提供足够好的结果以用于外部迭代,这个问题就有点意思了,可能会有帮助。

撰写回答