【发布时间】:2017-11-22 03:16:41
【问题描述】:
我创建了一个scipy.stats.rv_continuous 子类,它似乎在做我想做的事,但速度非常慢。代码和测试结果如下。
我正在使用的分布函数(破幂律)很容易集成和计算属性,那么是否有另一种内部方法我应该使用解析值进行子类化以使其更快?文档不清楚rvs 的实际绘制方式,但大概是找到了cdf 的倒数。
class Broken_Power_Law(sp.stats.rv_continuous):
def __init__(self, slopes, breaks, name='Broken_Power_Law'):
"""
Here `slopes` are the power-law indices for each section, and
`breaks` are the edges of each section such that `slopes[0]` applies
between `breaks[0]` and `breaks[1]`, etc.
"""
super().__init__(a=np.min(breaks), b=np.max(breaks), name=name)
nums = len(slopes)
# Calculate the proper normalization of the PDF semi-analytically
pdf_norms = np.array([np.power(breaks[ii], slopes[ii-1] - slopes[ii]) if ii > 0 else 1.0
for ii in range(nums)])
pdf_norms = np.cumprod(pdf_norms)
# The additive offsets to calculate CDF values
cdf_offsets = np.array([(an/(alp+1))*(np.power(breaks[ii+1], alp+1) -
np.power(breaks[ii], alp+1))
for ii, (alp, an) in enumerate(zip(slopes, pdf_norms))])
off_sum = cdf_offsets.sum()
cdf_offsets = np.cumsum(cdf_offsets)
pdf_norms /= off_sum
cdf_offsets /= off_sum
self.breaks = breaks
self.slopes = slopes
self.pdf_norms = pdf_norms
self.cdf_offsets = cdf_offsets
self.num_segments = nums
return
def _pdf(self, xx):
mm = np.atleast_1d(xx)
yy = np.zeros_like(mm)
# For each power-law, calculate the distribution in that region
for ii in range(self.num_segments):
idx = (self.breaks[ii] < mm) & (mm <= self.breaks[ii+1])
aa = self.slopes[ii]
an = self.pdf_norms[ii]
yy[idx] = an * np.power(mm[idx], aa)
return yy
def _cdf(self, xx):
mm = np.atleast_1d(xx)
yy = np.zeros_like(mm)
off = 0.0
# For each power-law, calculate the cumulative dist in that region
for ii in range(self.num_segments):
# incorporate the cumulative offset from previous segments
off = self.cdf_offsets[ii-1] if ii > 0 else 0.0
idx = (self.breaks[ii] < mm) & (mm <= self.breaks[ii+1])
aa = self.slopes[ii]
an = self.pdf_norms[ii]
ap1 = aa + 1
yy[idx] = (an/(ap1)) * (np.power(mm[idx], ap1) - np.power(self.breaks[ii], ap1)) + off
return yy
测试时:
> test1 = sp.stats.norm()
> %timeit rvs = test1.rvs(size=100)
46.3 µs ± 1.87 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
> test2 = Broken_Power_Law([-1.3, -2.2, -2.7], [0.08, 0.5, 1.0, 150.0])
> %timeit rvs = test2.rvs(size=100)
200 ms ± 8.57 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
即慢 5000 倍!!!
【问题讨论】:
-
大部分 scipy 是用 C 语言编写的,经过了很好的优化并带有 Python 前端。您编写的许多代码都需要在 Python 和 C 代码之间移动数据,这会占用大量开销。这与 Python 自然比 C 慢的事实导致了您所看到的差异。
-
@James 我不认为这真的是答案的症结所在。许多(大多数?)内置函数都有其分位数函数的封闭形式表达式,可以在不对 CDF 进行数值反转的情况下从中提取——这非常慢(显然)。使用一个很好的、易于计算的表达式会导致 1000 倍的加速(在这种情况下)。当然,您完全正确,许多用于评估这些函数的表达式(例如
sp.special.ndtri与sp.stats.norm一起使用)都是高度优化的 C 代码,这也产生了重要的区别(至少在 5 倍的水平上)。
标签: python numpy optimization random scipy