Split-Pseudo-Voigt拟合求助:自定义函数curve_fit不收敛
Split-Pseudo-Voigt 峰拟合不收敛问题的解决思路
一、有没有现成的内置实现?
- SciPy本身没有封装好的Split-Pseudo-Voigt函数,但lmfit库的
models模块里直接有SplitPseudoVoigtModel,这个库专门做曲线拟合,自带参数初始化、边界约束,比自己写函数用curve_fit省心太多,强烈推荐试试。 - 你嫌弃PyMca文档烂,但它源码里其实有现成的实现——去
PyMca5/PyMca/FitPlugins/SplitPseudoVoigtFitPlugin.py里扒代码就行,直接复用它的拟合逻辑,省得自己踩坑。
二、自定义函数不收敛的常见坑
如果非要自己写函数用curve_fit,大概率是这几个问题:
- 初始值给得太离谱:
curve_fit对初始值极度敏感,Split-Pseudo-Voigt有峰位、半高宽、高斯占比(η)、峰高、背景好几个参数,初始值和真实值差太远的话,直接就发散了。先手动用scipy.signal.find_peaks找峰,把峰位、峰高、半高宽的初始值设得贴近实际,背景用基线估计的结果。 - 没加参数约束:有些参数有物理意义,比如η必须在0到1之间,半高宽得是正数。
curve_fit默认不管这些,参数可能跑到负数或者大于1的范围,直接导致拟合崩了。用bounds参数给每个参数加范围,比如bounds=([-np.inf, 0, 0, 0], [np.inf, np.inf, 1, np.inf])(对应峰位、半高宽、η、峰高的约束)。 - 函数写错了:Split-Pseudo-Voigt是左右半峰用不同的高斯/洛伦兹权重,很多人写成普通Pseudo-Voigt(整个峰用同一个η),这肯定不对。正确的实现大概是这样:
def split_pseudo_voigt(x, x0, dx, eta, height, bg): # x0: 峰位, dx: 半高宽, eta: 左半峰高斯占比(0-1) left_mask = x <= x0 right_mask = x > x0 # 左半峰:高斯权重eta,洛伦兹权重1-eta gauss_left = np.exp(-np.log(2) * ((x[left_mask] - x0)/dx)**2) lorentz_left = 1 / (1 + ((x[left_mask] - x0)/dx)**2) peak_left = height * (eta * gauss_left + (1 - eta) * lorentz_left) # 右半峰:高斯权重1-eta,洛伦兹权重eta gauss_right = np.exp(-np.log(2) * ((x[right_mask] - x0)/dx)**2) lorentz_right = 1 / (1 + ((x[right_mask] - x0)/dx)**2) peak_right = height * ((1 - eta) * gauss_right + eta * lorentz_right) return np.concatenate([peak_left, peak_right]) + bg - 背景没处理好:如果谱线有倾斜或弯曲的基线,只加常数背景会拖垮拟合效果。要么把背景改成线性(加个斜率参数),要么先做基线扣除再拟合峰型。
三、快速调试方法
- 先单独画自定义函数的曲线,用你觉得合理的参数值,确认形状是对的,排除函数写错的问题。
- 用
lmfit替代curve_fit,它会输出参数迭代的详细过程,能直观看到哪个参数在乱跳,方便针对性调整。 - 给参数加约束,别让它们跑到不合理的范围里。
内容的提问来源于stack exchange,提问作者Philipp J
相关产品推荐
相关产品推荐

