You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Scipy实现独立正态分布比分布遇收敛与迭代失败问题求助

解决方案:自定义正态比分布的_rvs、_ppf和_fit方法

先修正现有代码的核心错误

你的_cdf方法存在逻辑错误:当前积分区间是(-inf, inf),返回值始终为1,完全不符合CDF的定义(CDF应为从负无穷到当前z的积分)。先修正这个基础问题:

def _cdf(self, z, mu_x, mu_y, sigma_x, sigma_y):
    # CDF是从负无穷到z的积分,而非到正无穷
    cdf_z, _ = integrate.quad(self._pdf, -np.inf, z, args=(mu_x, mu_y, sigma_x, sigma_y))
    return cdf_z

即使修正CDF,默认的rvs和fit效率依然极低,且容易出现收敛问题。以下是针对性的自定义实现:


1. 自定义_rvs方法:直接抽样法

由于z = x/y(x~N(mu_x, sigma_x²),y~N(mu_y, sigma_y²)),最高效的抽样方式是先生成x和y的样本再做除法,完全避开数值积分的低效问题:

def _rvs(self, mu_x, mu_y, sigma_x, sigma_y, size=1, random_state=None):
    rng = random_state or np.random.default_rng()
    x_samples = rng.normal(mu_x, sigma_x, size=size)
    y_samples = rng.normal(mu_y, sigma_y, size=size)
    # 避免除以0,给y添加极小扰动
    y_samples = np.where(y_samples == 0, 1e-10, y_samples)
    return x_samples / y_samples

2. 自定义_ppf方法:数值求逆CDF

如果需要分位数函数(PPF),可以基于修正后的CDF用二分法数值求解,避免默认方法的收敛问题:

def _ppf(self, q, mu_x, mu_y, sigma_x, sigma_y):
    # 先通过抽样确定z的大致范围,缩小二分法区间
    rvs_sample = self._rvs(mu_x, mu_y, sigma_x, sigma_y, size=1000)
    z_min, z_max = np.percentile(rvs_sample, [0.1, 99.9])
    
    # 二分法求解单个分位数
    def find_z(target_q):
        low, high = z_min, z_max
        for _ in range(100):
            mid = (low + high) / 2
            current_cdf = self._cdf(mid, mu_x, mu_y, sigma_x, sigma_y)
            if current_cdf < target_q:
                low = mid
            else:
                high = mid
        return (low + high) / 2
    
    # 处理批量q值
    if np.isscalar(q):
        return find_z(q)
    else:
        return np.array([find_z(q_i) for q_i in q])

3. 自定义_fit方法:极大似然估计+合理初始值

默认拟合方法不适合这种复合分布,我们基于极大似然估计,结合样本矩给出合理初始值,解决收敛问题:

def _fit(self, data, **kwargs):
    # 定义负对数似然函数,用于极小化
    def neg_log_likelihood(params):
        mu_x, mu_y, sigma_x, sigma_y = params
        # 避免标准差为负
        if sigma_x <=0 or sigma_y <=0:
            return np.inf
        pdf_vals = self._pdf(data, mu_x, mu_y, sigma_x, sigma_y)
        # 避免log(0)的情况
        pdf_vals = np.where(pdf_vals < 1e-20, 1e-20, pdf_vals)
        return -np.sum(np.log(pdf_vals))
    
    # 基于样本矩给出初始值猜测
    mu_z = np.mean(data)
    sigma_z = np.std(data)
    init_mu_y = kwargs.get('init_mu_y', 300)
    init_mu_x = mu_z * init_mu_y
    init_sigma_y = kwargs.get('init_sigma_y', 60)
    init_sigma_x = np.sqrt( sigma_z**2 * init_mu_y**2 + (init_mu_x**2 * init_sigma_y**2) / init_mu_y**2 )
    # 确保初始标准差为正
    init_sigma_x = max(init_sigma_x, 10)
    
    # 调用优化器求解
    from scipy.optimize import minimize
    result = minimize(neg_log_likelihood, x0=[init_mu_x, init_mu_y, init_sigma_x, init_sigma_y],
                      bounds=[(None, None), (None, None), (1e-5, None), (1e-5, None)])
    
    return result.x

完整修正后的代码

import numpy as np
from scipy.stats import norm, rv_continuous
import scipy.integrate as integrate
from scipy.optimize import minimize

class normal_ratio_wiki(rv_continuous):
    def _pdf(self, z, mu_x, mu_y, sigma_x, sigma_y):
        a_z = np.sqrt(((1/(sigma_x**2))*(np.power(z,2))) + (1/(sigma_y**2)))
        b_z = ((mu_x/(sigma_x**2)) * z) + (mu_y/sigma_y**2)
        c = ((mu_x**2)/(sigma_x**2)) + ((mu_y**2)/(sigma_y**2))
        d_z = np.exp(((b_z**2)-((c*a_z**2))) / (2*(a_z**2)))
        pdf_z = ((b_z * d_z) / (a_z**3)) * (1/(np.sqrt(2*np.pi)*sigma_x*sigma_y)) * \
        (norm.cdf(b_z/a_z) - norm.cdf(-b_z/a_z)) + ((1/((a_z**2) * np.pi * sigma_x * sigma_y))*np.exp(-c/2))

        return pdf_z

    def _cdf(self, z, mu_x, mu_y, sigma_x, sigma_y):
        cdf_z, _ = integrate.quad(self._pdf, -np.inf, z, args=(mu_x, mu_y, sigma_x, sigma_y))
        return cdf_z
    
    def _rvs(self, mu_x, mu_y, sigma_x, sigma_y, size=1, random_state=None):
        rng = random_state or np.random.default_rng()
        x_samples = rng.normal(mu_x, sigma_x, size=size)
        y_samples = rng.normal(mu_y, sigma_y, size=size)
        y_samples = np.where(y_samples == 0, 1e-10, y_samples)
        return x_samples / y_samples
    
    def _ppf(self, q, mu_x, mu_y, sigma_x, sigma_y):
        rvs_sample = self._rvs(mu_x, mu_y, sigma_x, sigma_y, size=1000)
        z_min, z_max = np.percentile(rvs_sample, [0.1, 99.9])
        
        def find_z(target_q):
            low, high = z_min, z_max
            for _ in range(100):
                mid = (low + high) / 2
                current_cdf = self._cdf(mid, mu_x, mu_y, sigma_x, sigma_y)
                if current_cdf < target_q:
                    low = mid
                else:
                    high = mid
            return (low + high) / 2
        
        if np.isscalar(q):
            return find_z(q)
        else:
            return np.array([find_z(q_i) for q_i in q])
    
    def _fit(self, data, **kwargs):
        def neg_log_likelihood(params):
            mu_x, mu_y, sigma_x, sigma_y = params
            if sigma_x <=0 or sigma_y <=0:
                return np.inf
            pdf_vals = self._pdf(data, mu_x, mu_y, sigma_x, sigma_y)
            pdf_vals = np.where(pdf_vals < 1e-20, 1e-20, pdf_vals)
            return -np.sum(np.log(pdf_vals))
        
        mu_z = np.mean(data)
        sigma_z = np.std(data)
        init_mu_y = kwargs.get('init_mu_y', np.mean(y) if 'y' in kwargs else 300)
        init_mu_x = mu_z * init_mu_y
        init_sigma_y = kwargs.get('init_sigma_y', np.std(y) if 'y' in kwargs else 60)
        init_sigma_x = np.sqrt( sigma_z**2 * init_mu_y**2 + (init_mu_x**2 * init_sigma_y**2) / init_mu_y**2 )
        init_sigma_x = max(init_sigma_x, 10)
        
        result = minimize(neg_log_likelihood, x0=[init_mu_x, init_mu_y, init_sigma_x, init_sigma_y],
                          bounds=[(None, None), (None, None), (1e-5, None), (1e-5, None)])
        
        return result.x

# 测试代码
rng1 = np.random.default_rng(99)
rng2 = np.random.default_rng(88)

# Sample Data 1
x = rng1.normal(141739.951, 1.223808e+06, 1000)
y = rng2.normal(333.91, 64.494571, 1000)
z = x / y

mu_x = x.mean()
mu_y = y.mean()
sigma_x = x.std()
sigma_y = y.std()

rng3 = np.random.default_rng(11)
nr_wiki_inst = normal_ratio_wiki(name='normal_ratio_wiki', seed=rng3)

# 测试rvs
nr_wiki_vars = nr_wiki_inst.rvs(mu_x, mu_y, sigma_x, sigma_y, size=100)
print("rvs样本生成成功,前5个样本:", nr_wiki_vars[:5])

# 测试fit
nr_wiki_params = nr_wiki_inst.fit(z, init_mu_y=mu_y, init_sigma_y=sigma_y)
print("拟合参数:", nr_wiki_params)

关键说明

  • _rvs直接基于原始正态分布抽样,效率远高于数值积分逆变换,彻底解决收敛问题。
  • _ppf用二分法结合抽样确定的区间,避免了默认方法的发散问题。
  • _fit通过极大似然估计+合理初始值,适配了复合分布的拟合需求,解决了默认方法的不适用性。

内容的提问来源于stack exchange,提问作者Smith

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 02:06:06