如何加速多次调用scipy.stats.multivariate_normal.cdf()?二元协方差可变场景
问题描述
需要多次计算二元正态分布累积分布函数(CDF),但当前嵌套循环的实现速度极慢。原代码如下:
from scipy.stats import multivariate_normal res = np.zeros((horizon,nb_groups,nb_ratings)) mean = [0,0] mu = np.zeros((horizon,nb_groups,nb_ratings)) sigma = np.ones((horizon,nb_groups,nb_ratings)) rho = np.ones((horizon,nb_groups,nb_ratings)) #实际代码中rho、mu、sigma并非全为1或0 horizon,nb_groups,nb_ratings = 50,3,8 for t in range(horizon): for g in range(nb_groups): for i in range(nb_ratings): if thresh[t,g,i]==float('-inf'): res[t,g,i]=0 else: res[t,g,i] = multivariate_normal(mean=mean,cov=[[1,rho[t,g,i]],[rho[t,g,i],1]]).cdf([mu[t,g,i]/np.sqrt(1+sigma[t,g,i]**2),thresh[t,g,i]])
测试发现向量化计算可大幅提升速度,但由于每次调用的协方差矩阵不同,无法直接应用该方法。参考测试示例:
循环版本耗时约3分钟,而向量化版本仅耗时不足9秒:
import random import numpy as np from scipy.stats import norm, multivariate_normal as mvn n = 1000000 inputs1 = np.array([random.uniform(-100, 100) for _ in range(n)]) inputs2 = np.array([random.uniform(-100, 100) for _ in range(n)]) inputs3 = np.array([np.array([random.uniform(-100, 100),random.uniform(-100, 100)]) for _ in range(n)]) mean = [0,0] cov = [[1,0.5],[0.5,1]] bivariate = mvn(mean, cov) # 循环版本(慢) for i in range(n): a = bivariate.cdf([inputs1[i],inputs2[i]]) # 向量化版本(快) a = bivariate.cdf(inputs3)
加速方案:利用二元正态CDF的向量化专用函数
由于你计算的是二元标准正态分布(均值固定为[0,0],协方差矩阵仅由相关系数ρ决定),可以直接使用scipy.special.bvn_cdf这个专门的向量化函数,它支持批量输入x1、x2和ρ数组,完全避免Python嵌套循环,效率和测试中的向量化版本一致。
具体实现
- 预处理所有输入数组,计算每个位置的x1值:
x1 = mu / np.sqrt(1 + sigma**2) - 用掩码处理阈值为
-inf的情况,直接设结果为0 - 对其余位置,调用
bvn_cdf批量计算所有CDF值
完整代码
import numpy as np from scipy.special import bvn_cdf # 初始化参数(替换为你的实际数据) horizon, nb_groups, nb_ratings = 50, 3, 8 mu = np.random.randn(horizon, nb_groups, nb_ratings) sigma = np.random.uniform(0.1, 2, (horizon, nb_groups, nb_ratings)) rho = np.random.uniform(-0.99, 0.99, (horizon, nb_groups, nb_ratings)) thresh = np.random.randn(horizon, nb_groups, nb_ratings) thresh[0, 0, 0] = float('-inf') # 模拟阈值为-inf的情况 # 向量化计算核心逻辑 res = np.zeros_like(thresh) x1 = mu / np.sqrt(1 + sigma**2) # 筛选非-inf的位置 valid_mask = thresh != float('-inf') # 批量计算CDF res[valid_mask] = bvn_cdf(x1[valid_mask], thresh[valid_mask], rho[valid_mask])
方案优势
- 无循环开销:避免了原代码中反复创建
multivariate_normal对象的冗余操作 - 全向量化计算:利用numpy数组广播和底层优化的
bvn_cdf函数,计算效率比嵌套循环提升几个数量级 - 结果一致:和原循环代码的计算结果在浮点误差范围内完全一致
正确性验证
可以用原循环代码对比小批量数据的结果,确认误差在可接受范围内:
from scipy.stats import multivariate_normal # 原循环实现(用于验证) res_loop = np.zeros_like(res) mean = [0, 0] for t in range(horizon): for g in range(nb_groups): for i in range(nb_ratings): if thresh[t, g, i] == float('-inf'): res_loop[t, g, i] = 0 else: cov = [[1, rho[t, g, i]], [rho[t, g, i], 1]] res_loop[t, g, i] = multivariate_normal(mean=mean, cov=cov).cdf([x1[t, g, i], thresh[t, g, i]]) # 检查最大误差 print(f"最大绝对误差:{np.max(np.abs(res - res_loop))}") # 应接近1e-10级别,符合浮点精度
内容的提问来源于stack exchange,提问作者Eliot Deneux
相关产品推荐
相关产品推荐

