Python:非线性最小二乘的双曲线高斯拟合

Python:非线性最小二乘的双曲线高斯拟合,python,scipy,gaussian,least-squares,Python,Scipy,Gaussian,Least Squares,我的数学知识有限,这就是我可能被困的原因。我有一个光谱,我试图拟合两个高斯峰。我能适应最大的山峰,但我不能适应最小的山峰。我知道我需要对两个峰值的高斯函数求和,但我不知道哪里出错了。我当前输出的图像如下所示: 蓝线是我的数据,绿线是我当前的适合度。在我的数据中,主峰左侧有一个肩部,我正在使用以下代码尝试拟合该肩部: import matplotlib.pyplot as pt import numpy as np from scipy.optimize import leastsq from

我的数学知识有限,这就是我可能被困的原因。我有一个光谱,我试图拟合两个高斯峰。我能适应最大的山峰,但我不能适应最小的山峰。我知道我需要对两个峰值的高斯函数求和,但我不知道哪里出错了。我当前输出的图像如下所示:

蓝线是我的数据,绿线是我当前的适合度。在我的数据中,主峰左侧有一个肩部,我正在使用以下代码尝试拟合该肩部:

import matplotlib.pyplot as pt
import numpy as np
from scipy.optimize import leastsq
from pylab import *

time = []
counts = []


for i in open('/some/folder/to/file.txt', 'r'):
    segs = i.split()
    time.append(float(segs[0]))
    counts.append(segs[1])

time_array = arange(len(time), dtype=float)
counts_array = arange(len(counts))
time_array[0:] = time
counts_array[0:] = counts


def model(time_array0, coeffs0):
    a = coeffs0[0] + coeffs0[1] * np.exp( - ((time_array0-coeffs0[2])/coeffs0[3])**2 )
    b = coeffs0[4] + coeffs0[5] * np.exp( - ((time_array0-coeffs0[6])/coeffs0[7])**2 ) 
    c = a+b
    return c


def residuals(coeffs, counts_array, time_array):
    return counts_array - model(time_array, coeffs)

# 0 = baseline, 1 = amplitude, 2 = centre, 3 = width
peak1 = np.array([0,6337,16.2,4.47,0,2300,13.5,2], dtype=float)
#peak2 = np.array([0,2300,13.5,2], dtype=float)

x, flag = leastsq(residuals, peak1, args=(counts_array, time_array))
#z, flag = leastsq(residuals, peak2, args=(counts_array, time_array))

plt.plot(time_array, counts_array)
plt.plot(time_array, model(time_array, x), color = 'g') 
#plt.plot(time_array, model(time_array, z), color = 'r')
plt.show()

如果您只拟合两个高斯分布组合的函数,则此代码对我有效

我只是做了一个残差函数,把两个高斯函数相加,然后从实际数据中减去它们

我传递给Numpy最小二乘函数的参数(p)包括:第一个高斯函数的平均值(m)、第一个和第二个高斯函数的平均值差(dm,即水平位移)、第一个高斯函数的标准偏差(sd1)和第二个高斯函数的标准偏差(sd2)


系数0和4是退化的-数据中绝对没有任何东西可以决定它们之间的关系。您应该使用一个零级参数而不是两个(即从代码中删除其中一个)。这可能是阻止你适应的原因(忽略这里的评论,说这是不可能的——数据中显然至少有两个峰值,你肯定能够适应)

(我提出这个建议的原因可能还不清楚,但实际情况是系数0和4可以相互抵消。它们都可以是零,或者一个可以是100,另一个可以是-100——无论哪种方式,拟合都一样好。这“让人困惑”当没有一个正确答案时,拟合程序会花费时间试图找出它们应该是什么,因为无论一个值是什么,另一个值可能只是它的负值,拟合将是相同的)

事实上,从图上看,似乎根本不需要零级。我会试着把这两个都扔掉,看看合身的样子


此外,无需在最小二乘法中拟合系数1和5(或零点)。相反,因为模型是线性的,所以可以在每个循环中计算它们的值。这将使事情变得更快,但并不重要。我刚刚注意到你说你的数学不太好,所以可能忽略这一点。

你可以从以下方面使用高斯混合模型:

您也可以使用下面的函数来拟合ncomp参数所需的高斯数:

from sklearn import mixture
%pylab

def fit_mixture(data, ncomp=2, doplot=False):
    clf = mixture.GMM(n_components=ncomp, covariance_type='full')
    clf.fit(data)
    ml = clf.means_
    wl = clf.weights_
    cl = clf.covars_
    ms = [m[0] for m in ml]
    cs = [numpy.sqrt(c[0][0]) for c in cl]
    ws = [w for w in wl]
    if doplot == True:
        histo = hist(data, 200, normed=True)
        for w, m, c in zip(ws, ms, cs):
            plot(histo[1],w*matplotlib.mlab.normpdf(histo[1],m,np.sqrt(c)), linewidth=3)
    return ms, cs, ws

在这种情况下,这将是相当困难的,因为两个峰值非常接近——对于较小的“高斯”来说,没有确定的峰值。通常,人们(我认为)会识别出所有感兴趣的峰,然后在每个峰上迭代,屏蔽掉所有其他峰并拟合到每个峰。总拟合就是所有这些拟合的总和。看起来你需要做的是确定大的峰值和它的范围,然后在拟合到较小的峰值之前,从数据中屏蔽它。尽管如此,这在我看来是合理的。如果你能一次完成整个模型,那将有无数的优势。向上投票。所以我假设对于n个高斯函数,我需要将n个高斯函数相加,然后从数据中减去它们?@Harpal-是的。您可以修改代码以使用n条曲线。我只想确保以一种没有两条曲线具有相同平均值的方式对算法进行编码。直线y_est=norm(x,plsq[0][0],plsq[0][2])+norm(x,plsq[0][1],plsq[0][3])应该是y_est=norm(x,plsq[0][0]+plsq 0][1],plsq 3]);在您的示例中不明显,因为其中一个平均值为零。在中编辑此。否则,很好的解决方案:)这将适合数据的直方图,而不是数据本身。
from sklearn import mixture
import matplotlib.pyplot
import matplotlib.mlab
import numpy as np
clf = mixture.GMM(n_components=2, covariance_type='full')
clf.fit(yourdata)
m1, m2 = clf.means_
w1, w2 = clf.weights_
c1, c2 = clf.covars_
histdist = matplotlib.pyplot.hist(yourdata, 100, normed=True)
plotgauss1 = lambda x: plot(x,w1*matplotlib.mlab.normpdf(x,m1,np.sqrt(c1))[0], linewidth=3)
plotgauss2 = lambda x: plot(x,w2*matplotlib.mlab.normpdf(x,m2,np.sqrt(c2))[0], linewidth=3)
plotgauss1(histdist[1])
plotgauss2(histdist[1])
from sklearn import mixture
%pylab

def fit_mixture(data, ncomp=2, doplot=False):
    clf = mixture.GMM(n_components=ncomp, covariance_type='full')
    clf.fit(data)
    ml = clf.means_
    wl = clf.weights_
    cl = clf.covars_
    ms = [m[0] for m in ml]
    cs = [numpy.sqrt(c[0][0]) for c in cl]
    ws = [w for w in wl]
    if doplot == True:
        histo = hist(data, 200, normed=True)
        for w, m, c in zip(ws, ms, cs):
            plot(histo[1],w*matplotlib.mlab.normpdf(histo[1],m,np.sqrt(c)), linewidth=3)
    return ms, cs, ws