【问题标题】:Python: two-curve gaussian fitting with non-linear least-squaresPython:具有非线性最小二乘的双曲线高斯拟合
【发布时间】:2012-04-25 23:58:43
【问题描述】:

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

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

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()

【问题讨论】:

  • 在这种情况下这将非常困难,因为两个峰值非常接近 - 较小的“高斯”没有明确的峰值。通常(我认为)会识别所有感兴趣的峰,然后遍历每个峰,屏蔽所有其他峰并拟合每个峰。总拟合是所有这些拟合的总和。您需要做的是识别大峰及其范围,然后在拟合到较小的峰之前从数据中屏蔽它

标签: python scipy gaussian least-squares


【解决方案1】:

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

我刚刚做了一个残差函数,它添加了两个高斯函数,然后从真实数据中减去它们。

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

import numpy as np
from scipy.optimize import leastsq
import matplotlib.pyplot as plt

######################################
# Setting up test data
def norm(x, mean, sd):
  norm = []
  for i in range(x.size):
    norm += [1.0/(sd*np.sqrt(2*np.pi))*np.exp(-(x[i] - mean)**2/(2*sd**2))]
  return np.array(norm)

mean1, mean2 = 0, -2
std1, std2 = 0.5, 1 

x = np.linspace(-20, 20, 500)
y_real = norm(x, mean1, std1) + norm(x, mean2, std2)

######################################
# Solving
m, dm, sd1, sd2 = [5, 10, 1, 1]
p = [m, dm, sd1, sd2] # Initial guesses for leastsq
y_init = norm(x, m, sd1) + norm(x, m + dm, sd2) # For final comparison plot

def res(p, y, x):
  m, dm, sd1, sd2 = p
  m1 = m
  m2 = m1 + dm
  y_fit = norm(x, m1, sd1) + norm(x, m2, sd2)
  err = y - y_fit
  return err

plsq = leastsq(res, p, args = (y_real, x))

y_est = norm(x, plsq[0][0], plsq[0][2]) + norm(x, plsq[0][0] + plsq[0][1], plsq[0][3])

plt.plot(x, y_real, label='Real Data')
plt.plot(x, y_init, 'r.', label='Starting Guess')
plt.plot(x, y_est, 'g.', label='Fitted')
plt.legend()
plt.show()

【讨论】:

  • 所以我假设对于 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][2]) + norm(x, plsq[0][0] + plsq[0][1], plsq[ 0][3]);在您的示例中并不明显,因为其中一种方法为零。编辑了这个。否则,很好的解决方案:)
【解决方案2】:

您可以使用来自scikit-learn的高斯混合模型:

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])

你也可以使用下面的函数,通过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

【讨论】:

  • 这将适合数据的直方图,而不是数据本身。
【解决方案3】:

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

(可能不清楚我为什么要建议这个,但正在发生的事情是系数 0 和 4 可以相互抵消。它们都可以是零,或者一个可能是 100,另一个可能是 -100 - 无论哪种方式, 拟合一样好。这会“混淆”拟合例程,当没有一个正确答案时,它会花时间试图找出它们应该是什么,因为无论一个是什么值,另一个可能只是否定的,并且合身将是相同的)。

其实从剧情上看,可能根本不需要零级。我会尝试放弃这两个,看看合身的样子。

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

【讨论】:

  • 尽管有刺痛,但这对我来说实际上是有道理的。如果您可以一次安装整个模型,那将有无数的优势。赞成。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-03-05
  • 2017-02-24
  • 1970-01-01
  • 1970-01-01
  • 2012-08-29
  • 2018-12-10
  • 2021-09-15
相关资源
最近更新 更多