【问题标题】:Fit a non-linear function to data/observations with pyMCMC/pyMC使用 pyMCMC/pyMC 将非线性函数拟合到数据/观察值
【发布时间】:2025-12-31 19:15:01
【问题描述】:

我正在尝试用高斯(和更复杂的)函数拟合一些数据。我在下面创建了一个小例子。

我的第一个问题是,我做得对吗?

我的第二个问题是,如何在 x 方向(即观察/数据的 x 位置)添加错误?

很难找到关于如何在 pyMC 中进行这种回归的好的指南。也许是因为使用一些最小二乘法或类似方法更容易,但我最终有很多参数,需要看看我们能如何约束它们并比较不同的模型,pyMC 似乎是个不错的选择。

import pymc
import numpy as np
import matplotlib.pyplot as plt; plt.ion()

x = np.arange(5,400,10)*1e3

# Parameters for gaussian
amp_true = 0.2
size_true = 1.8
ps_true = 0.1

# Gaussian function
gauss = lambda x,amp,size,ps: amp*np.exp(-1*(np.pi**2/(3600.*180.)*size*x)**2/(4.*np.log(2.)))+ps
f_true = gauss(x=x,amp=amp_true, size=size_true, ps=ps_true )

# add noise to the data points
noise = np.random.normal(size=len(x)) * .02 
f = f_true + noise 
f_error = np.ones_like(f_true)*0.05*f.max()

# define the model/function to be fitted.
def model(x, f): 
    amp = pymc.Uniform('amp', 0.05, 0.4, value= 0.15)
    size = pymc.Uniform('size', 0.5, 2.5, value= 1.0)
    ps = pymc.Normal('ps', 0.13, 40, value=0.15)

    @pymc.deterministic(plot=False)
    def gauss(x=x, amp=amp, size=size, ps=ps):
        e = -1*(np.pi**2*size*x/(3600.*180.))**2/(4.*np.log(2.))
        return amp*np.exp(e)+ps
    y = pymc.Normal('y', mu=gauss, tau=1.0/f_error**2, value=f, observed=True)
    return locals()

MDL = pymc.MCMC(model(x,f))
MDL.sample(1e4)

# extract and plot results
y_min = MDL.stats()['gauss']['quantiles'][2.5]
y_max = MDL.stats()['gauss']['quantiles'][97.5]
y_fit = MDL.stats()['gauss']['mean']
plt.plot(x,f_true,'b', marker='None', ls='-', lw=1, label='True')
plt.errorbar(x,f,yerr=f_error, color='r', marker='.', ls='None', label='Observed')
plt.plot(x,y_fit,'k', marker='+', ls='None', ms=5, mew=2, label='Fit')
plt.fill_between(x, y_min, y_max, color='0.5', alpha=0.5)
plt.legend()

我意识到我可能需要运行更多的迭代,最后使用老化和细化。绘制数据和拟合的图如下所示。

pymc.Matplot.plot(MDL) 图形看起来像这样,显示了很好的峰值分布。这个不错吧?

【问题讨论】:

  • 考虑将标题改为“适合非线性函数...”,我认为这将有助于在未来引导其他感兴趣的读者。

标签: python regression pymc probabilistic-programming


【解决方案1】:

编辑:重要提示 这已经困扰我一段时间了。我和亚伯拉罕在这里给出的答案是正确的,因为它们增加了 x 的可变性。但是:请注意,您不能简单地以这种方式添加不确定性来消除您在 x 值中的错误,以便您回归到“真 x”。如果您拥有真正的 x,则此答案中的方法可以向您展示向 x 添加错误如何影响您的回归。如果您的 x 测量错误,这些答案对您没有帮助。 x 值中存在错误是一个非常棘手的问题,因为它会导致“衰减”和“变量中的错误效应”。简短的版本是:x 中的无偏随机误差会导致回归估计中的偏差。如果您有这个问题,请查看 Carroll, R.J., Ruppert, D., Crainiceanu, C.M.和 Stefanski, L.A.,2006 年。非线性模型中的测量误差:现代视角。 Chapman and Hall/CRC.,或贝叶斯方法,Gustafson, P.,2003。统计和流行病学中的测量误差和错误分类:影响和贝叶斯调整。 CRC出版社。我最终使用 Carroll 等人的 SIMEX 方法和 PyMC3 解决了我的具体问题。详情参见 Carstens, H.、Xia, X. 和 Yadavalli, S., 2017。用于测量和验证的低成本电能表校准方法。 Applied energy, 188, pp.563-575 .它也可以在 ArXiv 上获得


我将上面 Abraham Flaxman 的答案转换为 PyMC3,以防有人需要。一些非常小的变化,但仍然可能令人困惑。

首先是确定性装饰器@Deterministic被类似分布的调用函数var=pymc3.Deterministic()所取代。二、在生成正态分布随机变量的向量时,

rvs = pymc2.rnormal(mu=mu, tau=tau)

替换为

rvs = pymc3.Normal('var_name', mu=mu, tau=tau,shape=size(var)).random()

完整代码如下:

import numpy as np
from pymc3 import *
import matplotlib.pyplot as plt

# set random seed for reproducibility
np.random.seed(12345)

x = np.arange(5,400,10)*1e3

# Parameters for gaussian
amp_true = 0.2
size_true = 1.8
ps_true = 0.1

#Gaussian function
gauss = lambda x,amp,size,ps: amp*np.exp(-1*(np.pi**2/(3600.*180.)*size*x)**2/(4.*np.log(2.)))+ps
f_true = gauss(x=x,amp=amp_true, size=size_true, ps=ps_true )

# add noise to the data points
noise = np.random.normal(size=len(x)) * .02 
f = f_true + noise 
f_error = np.ones_like(f_true)*0.05*f.max()

with Model() as model3:
    amp = Uniform('amp', 0.05, 0.4, testval= 0.15)
    size = Uniform('size', 0.5, 2.5, testval= 1.0)
    ps = Normal('ps', 0.13, 40, testval=0.15)

    gauss=Deterministic('gauss',amp*np.exp(-1*(np.pi**2*size*x/(3600.*180.))**2/(4.*np.log(2.)))+ps)

    y =Normal('y', mu=gauss, tau=1.0/f_error**2, observed=f)

    start=find_MAP()
    step=NUTS()
    trace=sample(2000,start=start)

# extract and plot results
y_min = np.percentile(trace.gauss,2.5,axis=0)
y_max = np.percentile(trace.gauss,97.5,axis=0)
y_fit = np.percentile(trace.gauss,50,axis=0)
plt.plot(x,f_true,'b', marker='None', ls='-', lw=1, label='True')
plt.errorbar(x,f,yerr=f_error, color='r', marker='.', ls='None', label='Observed')
plt.plot(x,y_fit,'k', marker='+', ls='None', ms=5, mew=1, label='Fit')
plt.fill_between(x, y_min, y_max, color='0.5', alpha=0.5)
plt.legend()

结果

y_error

对于 x 中的错误(注意变量的“x”后缀):

# define the model/function to be fitted in PyMC3:
with Model() as modelx:

    x_obsx = pm3.Normal('x_obsx',mu=x, tau=(1e4)**-2, shape=40)

    ampx = Uniform('ampx', 0.05, 0.4, testval=0.15)
    sizex = Uniform('sizex', 0.5, 2.5, testval=1.0)
    psx = Normal('psx', 0.13, 40, testval=0.15)

    x_pred = Normal('x_pred', mu=x_obsx, tau=(1e4)**-2*np.ones_like(x_obsx),testval=5*np.ones_like(x_obsx),shape=40) # this allows error in x_obs

    gauss=Deterministic('gauss',ampx*np.exp(-1*(np.pi**2*sizex*x_pred/(3600.*180.))**2/(4.*np.log(2.)))+psx)

    y = Normal('y', mu=gauss, tau=1.0/f_error**2, observed=f)

    start=find_MAP()
    step=NUTS()
    tracex=sample(20000,start=start)

结果:

x_error_graph

最后的观察是在做的时候

traceplot(tracex[100:])
plt.tight_layout();

(结果未显示),我们可以看到sizex 似乎由于x 的测量误差而遭受“衰减”或“回归稀释”。

【讨论】:

  • 仅链接的答案不是 SO 的方式。链接可能有一天会过时。在您的答案中加入基本信息(例如来自链接页面)!
  • 非常感谢您分享您的代码。可以问一个问题:当你定义 x_obsx 时,“.random()”方法的目的是什么?
  • 我不确定 - 我怀疑我还在学习 PyMC3 的基本原理。这可能是不必要的(尽管我目前无法测试)。
  • 其实没必要。我删除了它 - 谢谢。另外,我在顶部添加了一条关于变量错误效应的注释,这对于该问题非常重要。
  • 我同意find_MAP 实际上并不是一个好主意。我有一段时间没有使用 PyMC,但如果你给我一些更新的代码,我会替换旧代码。不过,过拟合问题很奇怪。它以前没有过拟合。
【解决方案2】:

我的第一个问题是,我做得对吗?

是的!你需要包括一个你知道的老化期。我喜欢扔掉我的前半部分样品。您不需要进行任何细化,但有时它会使您的 MCMC 后处理更快,存储更小。

我建议的唯一另一件事是设置一个随机种子,以便您的结果是“可重复的”:np.random.seed(12345) 可以解决问题。

哦,如果我真的给出了太多建议,我会说 import seaborn 以使 matplotlib 结果更漂亮。

我的第二个问题是,如何在 x 方向(即观察/数据的 x 位置)添加错误?

一种方法是为每个错误包含一个潜在变量。这在您的示例中有效,但如果您有更多观察结果,则不可行。我将举一个小例子来帮助您开始这条道路:

# add noise to observed x values
x_obs = pm.rnormal(mu=x, tau=(1e4)**-2)

# define the model/function to be fitted.
def model(x_obs, f): 
    amp = pm.Uniform('amp', 0.05, 0.4, value= 0.15)
    size = pm.Uniform('size', 0.5, 2.5, value= 1.0)
    ps = pm.Normal('ps', 0.13, 40, value=0.15)

    x_pred = pm.Normal('x', mu=x_obs, tau=(1e4)**-2) # this allows error in x_obs

    @pm.deterministic(plot=False)
    def gauss(x=x_pred, amp=amp, size=size, ps=ps):
        e = -1*(np.pi**2*size*x/(3600.*180.))**2/(4.*np.log(2.))
        return amp*np.exp(e)+ps
    y = pm.Normal('y', mu=gauss, tau=1.0/f_error**2, value=f, observed=True)
    return locals()

MDL = pm.MCMC(model(x_obs, f))
MDL.use_step_method(pm.AdaptiveMetropolis, MDL.x_pred) # use AdaptiveMetropolis to "learn" how to step
MDL.sample(200000, 100000, 10)  # run chain longer since there are more dimensions

如果您在xy 中有噪音,似乎很难得到好的答案:

这里是a notebook collecting this all up

【讨论】:

  • 这一切似乎都按预期工作,但有时,将y_fit = MDL.stats()['gauss']['mean'] 作为“最佳拟合”值并不对应于采用amp=MDL.stats()['amp']['mean'](同样适用于sizeps)和从这些计算最佳拟合。这是为什么呢?
  • E[f(x)] 不一定等于 f(E[x])。我想你会更喜欢E[f(x)]对应的点估计,也就是你上面的y_fit
  • 但我想得到最合适的参数,并从中计算一些值。那么计算出来的值会不会对应行(y_fit)?