我正在实现隐马尔可夫模型的前向递归:必要的步骤(a-b)如下所示
x1c 0d1x的数据
下面是我的实现
from scipy.stats import norm
import numpy as np
import random
def gmmdensity(obs, w, mu, sd):
# compute probability density function of gaussian mixture
gauss_mixt = norm.pdf(obs, mu, sd)[:,None]*w
return gauss_mixt
def alpha(obs, states, A, pi, w, mu, sigma):
dens = np.sum(gmmdensity(obs, w, mu, sigma), axis = 2)
# scaling factor is used to renormalize probabilities in order to
# avoid numerical underflow
scaling_factor = np.ones(len(obs))
alpha_matrix = np.zeros((len(states), len(obs)))
# for t = 0
alpha_matrix[:,0] = pi*dens[0]
scaling_factor[0] = 1/np.sum(alpha_matrix[:,0], axis = 0)
alpha_matrix[:,0] *= scaling_factor[0]
# for t == 1:T
for t in range(1, len(obs)):
alpha_matrix[:,t] = np.matmul(alpha_matrix[:,t-1], A)*dens[t]
scaling_factor[t] = 1/np.sum(alpha_matrix[:,t], axis = 0)
alpha_matrix[:,t] *= scaling_factor[t]
return alpha_matrix, scaling_factor
字符串
让我们生成一些数据来运行算法
obs = np.concatenate((np.random.normal(0, 1, size = 500),
np.random.normal(1.5, 1, size = 500))).reshape(-1,1)
N = 2 # number of hidden states
M = 3 # number of mixture components
states = list(range(N))
pi = np.array([0.5, 0.5]) # initial probabilities
A = np.array([[0.8, 0.2], [0.3, 0.7]]) # transition matrix
mu = np.array([np.min(obs), np.median(obs), np.max(obs)]) # means of mixture components
sigma = np.array([1, 1, 1]) # variances of mixture components
w = np.array([[0.2, 0.3, 0.5], [0.6, 0.2, 0.2]]) # weights of mixture components
型
让我们看看算法有多快
%timeit alpha(obs, states, A, pi, w, mu, sigma)
13.6 ms ± 1.24 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
型
有没有可能使这段代码更快?我想过使用numba或cython,但它从来没有在这种情况下完全工作。
3条答案
按热度按时间s2j5cfk01#
我分析了这段代码,发现下面的想法有助于加速它。
alpha_matrix[:,t]
向量是很慢的。将正在操作的向量作为局部变量保存直到完成操作会更快。scaling_factor[t]
也是如此。alpha_matrix[:,t]
的内存局部性很差。它访问一个元素,跳过几个元素,访问一个元素,等等。因为你到处都在做alpha_matrix[:,t]
,我发现如果你交换两个索引,索引会更容易。一旦我们完成了操作,我们就可以把它转回到它的原始状态,这样你就不必修改代码的其余部分。np.sum(..., axis=0)
与不使用轴参数的np.sum()
是一样的,但是使用轴参数会更慢。w
数组相乘会创建一个大的(1000,2,3)数组,然后将其最后一个维度求和。您可以使用einsum()
将这两个步骤合并结合起来。这更快,因为它避免了创建大的中间数组。我从Using Python numpy einsum to obtain dot product between 2 Matrices复制了此代码。scipy.stats.norm.pdf()
在这里慢得出奇。我认为SciPy是使用CDF的有限差分来定义PDF?我用formula for the normal PDF重新编码,发现快了3倍。这在提供的基准测试上加起来大约有40倍的加速:
字符串
完整代码:
型
3mpgtkmj2#
TL; DR:Numba可以大大加快这段代码的速度,特别是在基本循环的时候。这个答案相对于其他答案来说是比较晚的,但是它提供了迄今为止最快的实现。
这确实是一种典型的情况,Numba可以大大加快计算速度。
np.matmul
在我的机器上将一个只有2个元素的向量与一个2x2的矩阵相乘需要超过1 µs的时间。在我的机器上,这显然不需要超过0.01 µs的时间(假设数据在L1缓存中)。大部分时间都在Numpy调用开销中丢失。np.sum
需要> 3 µs,而它应该需要大约几纳秒(出于同样的原因):它只是两个数字的总和!为了在Numba中有效地解决这个问题,你需要使用基本循环并避免创建新的临时数组(在这样的基本循环中分配是昂贵的)。注意,
gmmdensity
不能很容易地转换为Numba,因为它调用了Scipy的norm.pdf
,这是Numba还不支持的AFAIK。下面是一个快速实现:字符串
请注意,
sigma
的类型已经明确提供,所以它是正确的。fast_normal_pdf
和fast_normal_pdf
是受@NickODell的好答案启发而优化的实现。性能指标评测
下面是我的机器上的i5- 9600 KF CPU的性能结果:
型
虽然这个实现比其他实现更大,但它显然是最快的实现。它比我机器上的初始实现快193倍,比目前为止最快的实现快6倍多。
fast_gmmdensitysum
是最昂贵的部分(>60%),特别是np.exp
函数(~40%),很难进一步优化。rkkpypqq3#
下面是函数的numba版本(注意:numba版本会引发
NumbaPerformanceWarning: '@' is faster on contiguous arrays
,它可能需要重新排列输入矩阵(?)):字符串
打印在我的机器(AMD 5700 x):
型
这是~ 17倍加速。