基于 Hermite 插值的 CDF 逆变换 (HINV)#

  • 必需:CDF

  • 可选:PDF,dPDF

  • 速度

    • 设置:(非常)慢

    • 抽样:(非常)快

HINV 是数值逆变换的一种变体,其中逆 CDF 使用 Hermite 插值进行近似,即,区间 [0,1] 被分成几个区间,并且在每个区间中,逆 CDF 通过使用 CDF 和 PDF 在区间边界处的值构造的多项式来近似。 这使得可以通过拆分特定区间来提高精度,而无需在未受影响的区间中进行重新计算。 实现了三种类型的样条曲线:线性、三次和五次插值。 对于线性插值,只需要 CDF。 三次插值还需要 PDF,五次插值需要 PDF 及其导数。

这些样条曲线必须在设置步骤中计算。 但是,它仅适用于具有有界域的分布; 对于具有无界域的分布,尾部被截断,使得尾部区域的概率与给定的 u 分辨率相比很小。

该方法不是精确的,因为它仅生成近似分布的随机变量。 然而,“u 方向”中的最大数值误差(即 |U - CDF(X)|,其中 X 是对应于分位数 U 的近似百分位数,即 X = approx_ppf(U))可以设置为所需的分辨率(在机器精度范围内)。 请注意,非常小的 u 分辨率值是可能的,但可能会增加设置步骤的成本。

NumericalInverseHermite 使用 Hermite 样条近似连续统计分布 CDF 的逆。 可以通过传递 order 参数来指定 Hermite 样条的阶数。

[1] 中所述,它首先在分布支持范围内的分位数网格 x 处评估分布的 PDF 和 CDF。 它使用结果来拟合 Hermite 样条 H,使得 H(p) == x,其中 p 是与分位数 x 对应的百分位数数组。 因此,样条曲线以机器精度近似分布的 CDF 的逆,在百分位数 p 处,但通常,样条曲线在百分位点之间的中点处不会那么准确

p_mid = (p[:-1] + p[1:])/2

因此,需要细化分位数网格以减少最大“u 误差”

u_error = np.max(np.abs(dist.cdf(H(p_mid)) - p_mid))

低于指定的容差 u_resolution。 当达到所需的容差或下次细化后的网格区间数可能超过允许的最大区间数 (100000) 时,细化停止。

>>> import numpy as np
>>> from scipy.stats.sampling import NumericalInverseHermite
>>> from scipy.stats import norm, genexpon
>>> from scipy.special import ndtr

要创建从标准正态分布采样的生成器,请执行

>>> class StandardNormal:
...     def pdf(self, x):
...        return 1/np.sqrt(2*np.pi) * np.exp(-x**2 / 2)
...     def cdf(self, x):
...        return ndtr(x)
...
>>> dist = StandardNormal()
>>> urng = np.random.default_rng()
>>> rng = NumericalInverseHermite(dist, random_state=urng)

NumericalInverseHermite 有一种方法可以近似分布的 PPF。

>>> rng = NumericalInverseHermite(dist)
>>> p = np.linspace(0.01, 0.99, 99) # percentiles from 1% to 99%
>>> np.allclose(rng.ppf(p), norm.ppf(p))
True

根据分布的随机抽样方法的实现,给定相同的随机状态,生成的随机变量可能几乎相同。

>>> dist = genexpon(9, 16, 3)
>>> rng = NumericalInverseHermite(dist)
>>> # `seed` ensures identical random streams are used by each `rvs` method
>>> seed = 500072020
>>> rvs1 = dist.rvs(size=100, random_state=np.random.default_rng(seed))
>>> rvs2 = rng.rvs(size=100, random_state=np.random.default_rng(seed))
>>> np.allclose(rvs1, rvs2)
True

为了检查随机变量是否紧密遵循给定的分布,我们可以查看其直方图

>>> import matplotlib.pyplot as plt
>>> dist = StandardNormal()
>>> urng = np.random.default_rng()
>>> rng = NumericalInverseHermite(dist, random_state=urng)
>>> rvs = rng.rvs(10000)
>>> x = np.linspace(rvs.min()-0.1, rvs.max()+0.1, 1000)
>>> fx = norm.pdf(x)
>>> plt.plot(x, fx, 'r-', lw=2, label='true distribution')
>>> plt.hist(rvs, bins=20, density=True, alpha=0.8, label='random variates')
>>> plt.xlabel('x')
>>> plt.ylabel('PDF(x)')
>>> plt.title('Numerical Inverse Hermite Samples')
>>> plt.legend()
>>> plt.show()
" "

给定 PDF 相对于变量 (即 x) 的导数,我们可以使用五次 Hermite 插值通过传递 order 参数来近似 PPF

>>> class StandardNormal:
...     def pdf(self, x):
...        return 1/np.sqrt(2*np.pi) * np.exp(-x**2 / 2)
...     def dpdf(self, x):
...        return -1/np.sqrt(2*np.pi) * x * np.exp(-x**2 / 2)
...     def cdf(self, x):
...        return ndtr(x)
...
>>> dist = StandardNormal()
>>> urng = np.random.default_rng()
>>> rng = NumericalInverseHermite(dist, order=5, random_state=urng)

较高的阶数会导致较少的区间数

>>> rng3 = NumericalInverseHermite(dist, order=3)
>>> rng5 = NumericalInverseHermite(dist, order=5)
>>> rng3.intervals, rng5.intervals
(3000, 522)

可以通过调用 u_error 方法来估计 u 误差。 它运行一个小的蒙特卡罗模拟来估计 u 误差。 默认情况下,使用 100,000 个样本。 这可以通过传递 sample_size 参数来更改

>>> rng1 = NumericalInverseHermite(dist, u_resolution=1e-10)
>>> rng1.u_error(sample_size=1000000)  # uses one million samples
UError(max_error=9.53167544892608e-11, mean_absolute_error=2.2450136432146864e-11)

这将返回一个命名的元组,其中包含最大 u 误差和平均绝对 u 误差。

可以通过降低 u 分辨率(允许的最大 u 误差)来减少 u 误差

>>> rng2 = NumericalInverseHermite(dist, u_resolution=1e-13)
>>> rng2.u_error(sample_size=1000000)
UError(max_error=9.32027892364129e-14, mean_absolute_error=1.5194172675685075e-14)

请注意,这会增加计算时间,因为设置时间和区间数增加。

>>> rng1.intervals
1022
>>> rng2.intervals
5687
>>> from timeit import timeit
>>> f = lambda: NumericalInverseHermite(dist, u_resolution=1e-10)
>>> timeit(f, number=1)
0.017409582000254886  # may vary
>>> f = lambda: NumericalInverseHermite(dist, u_resolution=1e-13)
>>> timeit(f, number=1)
0.08671202100003939  # may vary

有关此方法的更多详细信息,请参见 [1][2]

参考文献#