我如何在Python中矢量化多元正态分布的CDF(累积密度函数)?
当查看this帖子时,我发现有一个Fortran实现的多元CDF已经“移植”到Python中。这意味着我可以轻松地评估一种特定情况下的CDF。
然而,我遇到了很多问题,无法高效地将此函数应用于多个条目。
具体而言,我需要“矢量化”的函数需要4个参数:
- 积分下限(向量)
- 积分上限(向量)
- 正态随机变量的均值(向量)
- 正态随机变量的协方差矩阵(矩阵)
但是,我正在尝试在1000多个元素的列表上高效地评估此函数许多次。
以下是一些代码,以说明我的问题。在下面的示例中,我只是使用随机数据来说明我的观点。
import time
import numpy as np
from scipy.stats.mvn import mvnun # library that calculates MVN CDF
np.random.seed(666)
iters = 1000 # number of times the whole dataset will be evaluated
obs = 1500 # number of elements in the dataset
dim = 2 # dimension of multivariate normal distribution
# Creates a random correlation matrix
def gen_random_corr_mtx(d,k):
# Source: https://stats.stackexchange.com/a/125020/215330
#d = number of dimensions
#k = number of factors. more factors -> smaller correlations
W = np.random.randn(d*k).reshape((d,k))
S_temp = np.dot(W,W.T) + np.diag(np.random.rand(d))
S_norm = np.diag(1/np.sqrt(np.diag(S_temp)))
S = np.dot(np.dot(S_norm,S_temp),S_norm)
return(S)
# Creates a covariance matrix from a correlation matrix and
# an array of std devs
def from_cor_to_cov(cor_mtx,st_devs):
cor_mtx = np.array(cor_mtx)
st_devs = np.array(st_devs)
st_devs_diag = np.diag(st_devs)
cov_mtx = st_devs_diag.dot(cor_mtx).dot(st_devs_diag)
return(cov_mtx)
# Creating an array with the lower bounds of the integration
lower = np.random.rand(obs,dim)
# Creating an array with the upper bounds of the integration
upper = lower + np.random.rand(obs,dim)
# Creating an array with the means of the distributions
means = np.random.rand(obs,dim)
# Generating the random covariance matrices
covs = []
for i in range(obs):
# Making sure the covariance matrix is positive semi-definite
while True:
cor_mtx = gen_random_corr_mtx(dim,2)
st_devs = np.abs(np.random.randn(dim))
cov_mtx = from_cor_to_cov(cor_mtx,st_devs)
if np.all(np.linalg.eigvals(cov_mtx) > 0):
break
covs.append(cov_mtx)
covs = np.array(covs)
# Here is where the trouble starts.
time_start = time.time()
for i in range(iters):
results = []
for j in range(obs):
this_p, this_i = mvnun(lower[j],upper[j],means[j],covs[j])
results.append(this_p)
time_end = time.time()
print(time_end-time_start)
# > 4.090040922164917
我有一个包含1500个观测值的数据集,需要进行1000次评估。在我的机器上,这需要4.090040922164917秒来计算。
请注意,我并不试图摆脱外部for循环(i循环)。我只是为了模拟我的真实问题而创建了它。我真正想要消除的是内部循环(j循环)。
如果我找到一种有效地评估整个数据集的CDF的方法,执行时间可以大大缩短。
我知道mvnun函数最初是用Fortran编写的(原始代码here),并使用f2pye“移植”到Python中,如here所示。
有人能帮我吗?我已经开始研究theano,但似乎我唯一的选择是使用scan函数,这可能也不是很好的改进。
谢谢!!!
更新(2023年4月14日)
scipy
库最近进行了更新,现在用户可以确定multivariate_normal.cdf()
方法中积分下限,而不总是默认的n维数组-inf
值。
以下是使用新方法更新后的代码部分:
import scipy
time_start = time.time()
for i in range(iters):
results = []
for j in range(obs):
this_p = scipy.stats.multivariate_normal.cdf(lower_limit=lower[j],
x=upper[j],
mean=means[j],
cov=covs[j])
results.append(this_p)
time_end = time.time()
print(time_end-time_start)
# > 268.0528531074524
上述方法比我最初提出的方法慢得多,需要超过4分钟才能计算完成。但由于大多数scipy的类和方法已经设置为矢量化操作,我猜测/希望我在这里发布的代码只需要轻微调整即可真正实现矢量化。
mvnun
似乎并不关心第四个参数covs[j]
是否作为协方差矩阵有意义,这真的很奇怪。 - josephmure