scipy beta分布与numpy clip的数值问题

文章讨论了Beta分布的不同形态,包括在参数α和β取不同值时的表现,特别关注了0、1附近的数值问题。作者通过实例展示了如何使用scipy.stats.beta.pdf函数处理这些边界情况,并指出在编程中应限制x值在[ε,1-ε]之间以避免无限大值。此外,还通过numpy.clip函数验证了数值稳定性。

[1] 用到混合 Beta 分布,估计参数的方法见 [2]。由 [3] 可见 Beta 分布在其参数 α , β \alpha,\beta α,β 在不同取值范围时存在几种形态:

  • α , β < 0 \alpha,\beta < 0 α,β<0:不合法;
  • α = β = 1 \alpha=\beta=1 α=β=1:常数, B ( x ; 1 , 1 ) ≡ 1 \Beta(x;1,1)\equiv 1 B(x;1,1)1
  • α , β > 1 \alpha, \beta > 1 α,β>1:钟形(bell shape),即单峰(unimodal);
  • 0 < α < 1 ≤ β 0<\alpha<1\leq\beta 0<α<1β:L 形;
  • 0 < β < 1 ≤ α 0<\beta<1\leq\alpha 0<β<1α:J 形;
  • 0 < α , β < 1 0<\alpha,\beta<1 0<α,β<1:U 形。

其中后三种在 0、1 处会取到正无穷,可能在编程时引起问题,如:

invalid value encountered in divide

此处给出各种形状( α , β \alpha,\beta α,β 组合)下,变量 x 在各种取值时, B ( x ; α , β ) \Beta(x;\alpha,\beta) B(x;α,β) 的值(尤其是变量 x 在 0、1 附近时)作为参考:

import scipy.stats as stats
import numpy as np

# 临界 epsilon
eps1 = 1e-7
eps2 = 1e-8

# 变量
x = np.array([
    -1, # <<0
    - eps1, - eps2, 0, eps2, eps1, # near 0
    1 - eps1, 1 - eps2, 1, 1 + eps2, 1 + eps1, # near 1
    2, # >>1
], dtype=np.float32)
print(x)

print("\tinvalid: alpha, beta < 0")
print("alpha < 0:", stats.beta.pdf(x, -0.5, 1))
print("beta < 0:", stats.beta.pdf(x, 1, -0.5))

print("\tU-shape: 0 < alpha, beta < 1")
print(stats.beta.pdf(x, 0.5, 0.5))

print("\tL-shape: 0 < alpha < 1 <= beta")
print(stats.beta.pdf(x, 0.5, 1))

print("\tJ-shape: 0 < beta < 1 <= alpha")
print(stats.beta.pdf(x, 1, 0.5))

print("\tconstant: alpha = beta = 1")
print(stats.beta.pdf(x, 1, 1))

print("\tbell-shape (unimodal): 1 < alpha, beta")
print(stats.beta.pdf(x, 2, 2))

输出:

[-1, -1e-7, -1e-8, 0, 1e-8, 1e-7, 0.99999988, 1.0000000e+00, 1, 1.0000000e+00, 1.0000001, 2]

        invalid: alpha, beta < 0
alpha < 0: [nan nan nan nan nan nan nan nan nan nan nan nan]
beta < 0: [nan nan nan nan nan nan nan nan nan nan nan nan]

        U-shape: 0 < alpha, beta < 1
[0, 0, 0, inf, 5.1460, 4.0876, 4.0164, inf, inf, inf, 0, 0]

        L-shape: 0 < alpha < 1 <= beta
[0, 0, 0, inf, 6.2062, 4.9298, 0.1997, 0, 0, 0, 0, 0]

        J-shape: 0 < beta < 1 <= alpha
[0, 0, 0, 0, 0.1558, 0.1962, 4.8439, inf, inf, inf, 0, 0]

        constant: alpha = beta = 1
[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0]

        bell-shape (unimodal): 1 < alpha, beta
[0, 0, 0, 0, 5.99999990e-08, 5.99999947e-07, 7.15255652e-07, 0, 0, 0, 0, 0]

考察其中出现 inf 的位置,可以考虑在调用 scipy.stats.beta.pdf 时将 x 的值限定在 [ ϵ , 1 − ϵ ] [\epsilon, 1 - \epsilon] [ϵ,1ϵ] 之间,其中 ϵ \epsilon ϵ = 1e-7

除了上面的测试,此 ϵ \epsilon ϵ 还能如此验证:用 numpy.clip 重复实验,将 0/1 截断到 [ ϵ , 1 − ϵ ] [\epsilon, 1 - \epsilon] [ϵ,1ϵ] 之间,看从哪个精度开始数值开始不稳定。代码:

import numpy as np

zero = np.zeros([500], dtype=np.float32)
one = np.ones([500], dtype=np.float32)
# 有 0 有 1 的数据
x = np.concatenate([zero, one], axis=0)

# 测试 numpy.clip 对各 epsilon 的稳定性
for eps in (1e-7, 1e-8):
    print(eps)
    for _ in range(100):
        y = np.clip(x.copy(), eps, 1 - eps) # deep copy, then clip
        # 若成功截断,则不应再有 0/1
        assert (0 != y).all() and (1 != y).all()

实验表明,1e-7 能让 numpy.clip 稳定截断,而 1e-8 却不能。

(2024.12.2)本文所讲的数值问题在 [5] 早已发现(cvpr’24 deadline 早于本文首发时间),而在方法中只是一笔带过。

References

  1. (CVPR 2023) BiCro: Noisy Correspondence Rectification for Multi-modality Data via Bi-directional Cross-modal Similarity Consistency - paper, code
  2. EM算法估计beta混合模型参数
  3. 贝塔分布
  4. Beta Distribution | MIT Mathlets
  5. (cvpr’24) Learning to Rematch Mismatched Pairs for Robust Cross-Modal Retrieval - paper, code
<think>我们注意到用户要求生成0到1之间的正态分布样本。然而,正态分布(高斯分布)是定义在全体实数上的,理论上可以取任意实数值。如果我们强行限制在[0,1]区间内,那么就不是严格的正态分布了。但是,我们可以通过生成正态分布然后截断到[0,1]区间来得到近似值,或者通过缩放和移位将正态分布的大部分区域放在[0,1]内。 另一种思路是:用户可能想要一个在0到1之间的随机数,并且要求这些随机数服从正态分布(尽管严格来说不可能,但我们可以让均值在0.5,标准差较小,使得大部分值落在0和1之间)。因此,我们可以生成一个均值为0.5,标准差为某个适当值(比如0.15)的正态分布,然后截断超出[0,1]区间的值。 但是,用户可能并不希望截断,而是希望得到严格在[0,1]区间内的正态分布样本(尽管这样会改变分布形状)。或者,用户可能只是想要一个在0到1之间的随机数,但分布形状是正态的(钟形曲线),那么截断是必要的。 我们还可以考虑使用其他分布,比如Beta分布,它可以产生[0,1]区间上的随机数,并且通过调整参数可以呈现类似正态的钟形曲线。 然而,用户明确要求“正态分布”。因此,我们将采用截断正态分布。 在Python中,我们可以使用`scipy.stats.truncnorm`来生成截断的正态分布。另外,我们也可以使用`numpy.random.normal`生成正态分布,然后手动截断,但这样会导致边界处有堆积(即小于0的值都变成0,大于1的值都变成1),这可能会影响分布的形状。 使用`truncnorm`可以指定截断的范围,并生成在该范围内的正态分布样本。 步骤: 1. 确定正态分布的均值和标准差(例如,均值为0.5,标准差为0.2)。 2. 确定截断区间[a, b],这里我们取[0,1]。 3. 使用`truncnorm`时,需要将区间[0,1]转换为标准正态分布下的对应区间。因为`truncnorm`使用标准正态分布的截断,然后通过缩放和平移得到一般正态分布的截断。 具体来说,`truncnorm`的参数是`(a, b, loc, scale)`,其中a和b是截断点相对于标准正态分布的位置。例如,如果我们想要一个均值为loc,标准差为scale的正态分布在区间[low, high]上截断,那么: a = (low - loc) / scale b = (high - loc) / scale 然后调用`truncnorm.rvs(a, b, loc, scale, size)`。 例如,我们设置均值为0.5,标准差为0.2,截断区间为[0,1]: a = (0 - 0.5) / 0.2 = -2.5 b = (1 - 0.5) / 0.2 = 2.5 这样,我们就在标准正态分布上截取了[-2.5, 2.5]区间,然后变换到均值为0.5,标准差为0.2的分布上,即落在[0,1]区间内。 但是,这样生成的样本不会超出[0,1],并且保持了正态分布的形状(在截断区间内)。 如果用户不想使用`scipy`,我们也可以先生成正态分布,然后截断,但这样会失去截断区间外的尾部,并且边界处会有堆积(即小于0的设为0,大于1的设为1),这样会使得分布不再是连续的正态分布,而是两端有脉冲。因此,推荐使用`truncnorm`。 另外,如果用户只是想要一个类似钟形曲线的分布,并且定义在[0,1]上,也可以使用Beta分布Beta分布由两个参数α和&beta;控制,当α和&beta;都大于1时,分布是单峰的;当α=&beta;且大于1时,分布对称。我们可以设置α=&beta;>1,比如α=&beta;=2,则分布是抛物线形;增大α和&beta;(比如α=&beta;=5)则更接近正态分布。但这里用户要求正态分布,所以我们还是用截断正态。 下面给出两种方法: 方法一:使用`scipy.stats.truncnorm`(推荐) 方法二:使用`numpy.random.normal`然后截断(不推荐,因为会改变分布形状,两端有堆积) 我们假设用户安装了scipy,如果没有,我们可以提供第二种方法作为备选。 另外,我们还可以通过调整参数,使得正态分布的大部分值都落在[0,1]内,这样截断的概率很小,对分布的影响也小。例如,均值为0.5,标准差为1/6,这样大约99.7%的值落在[0,1]内(因为3个标准差覆盖99.7%,所以3*1/6=0.5,即从0到1)。这样我们生成正态分布,然后截断,影响很小。 因此,我们也可以这样: samples = np.random.normal(0.5, 1/6, size) samples = np.clip(samples, 0, 1) 下面我们分别展示。 由于用户要求生成0到1之间的正态分布样本,我们主要采用截断正态(使用scipy)和调整参数后截断(使用numpy)两种方法。 注意:使用截断正态(scipy)可以保证在[0,1]内且没有边界堆积(因为它是直接生成区间内的样本,不会产生超出区间的值然后再截断,而是从条件分布中抽取),所以更准确。 但是,如果用户没有scipy,我们可以用numpy方法。 我们首先给出使用scipy的方法,然后给出使用numpy的方法。 代码示例: """</think>在Python中生成0到1之间的正态分布样本需要特殊处理,因为标准正态分布理论上会生成所有实数,而我们需要将结果限制在[0,1]区间内。以下是几种实现方法: ### 方法1:截断正态分布(推荐) 使用`scipy.stats.truncnorm`生成严格在[0,1]区间的正态分布样本: ```python import numpy as np from scipy.stats import truncnorm # 设置参数 mu = 0.5 # 均值(中心点) sigma = 0.2 # 标准差(控制离散程度) size = 1000 # 样本数量 # 生成截断正态分布样本 a, b = (0 - mu)/sigma, (1 - mu)/sigma # 标准化截断范围 samples = truncnorm.rvs(a, b, loc=mu, scale=sigma, size=size) print(samples[:10]) # 打印前10个样本 ``` ### 方法2:正态分布+裁剪 使用`numpy`生成后裁剪超出范围的数值: ```python import numpy as np mu, sigma = 0.5, 0.15 # 需确保mu±3sigma在[0,1]内 samples = np.random.normal(mu, sigma, 1000) samples = np.clip(samples, 0, 1) # 裁剪到[0,1]区间 ``` ### 关键参数说明 1. **均值($\mu$)** 建议设为0.5(区间中心),满足: $$0 < \mu - 3\sigma < \mu + 3\sigma < 1$$ 2. **标准差($\sigma$)** 决定数据离散程度,需满足: $$\sigma \leq \min(\mu/3, (1-\mu)/3)$$ 例如$\mu=0.5$时,$\sigma \leq 0.1667$ ### 概率分布对比 | 方法 | 优点 | 缺点 | |--------------|-----------------------|--------------------------| | 截断正态分布 | 严格满足概率密度特性 | 需安装scipy | | 裁剪法 | 实现简单 | 边界处概率密度不连续[^4] | ### 可视化验证 ```python import matplotlib.pyplot as plt plt.hist(samples, bins=30, density=True, alpha=0.6) plt.title('截断正态分布样本直方图') plt.xlabel('值域') plt.ylabel('概率密度') plt.show() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值