scipy.optimize.curve_fit添加bounds时触发x0维度报错排查
问题原因
给scipy.optimize.curve_fit传bounds参数时触发ValueError: x0 must have at most 1 dimension,本质是参数写法不符合curve_fit的接口要求:
- 传入的初始猜测
p0_array是形状为(8,3)的二维数组:不传bounds时curve_fit默认使用的无约束优化器不会严格校验初始值维度,靠原写法里params + *args的参数处理逻辑刚好能凑出多峰参数结构;一旦传入bounds,就会切换到带边界约束的优化器,强制要求初始值x0必须是一维结构,直接触发维度报错 - 自定义的
multi_lorentzian函数参数签名不规范:curve_fit会将拟合参数按顺序逐个传入模型函数,原写法的参数处理逻辑在边界校验流程中无法正确解析参数 - 传入的
bounds_array仅包含3组上下限,对应不上8个洛伦兹峰合计24个待拟合参数(每个峰含振幅、中心、半宽3个参数),维度完全不匹配
修复方法
- 重写
multi_lorentzian函数,直接接收展平的一维参数,在函数内部完成参数reshape,去掉冗余的*args分支处理逻辑 - 将初始猜测
p0_array展平为长度24的一维数组再传入curve_fit - 按24个待拟合参数的顺序,生成对应长度的边界数组:每个峰的参数边界一致,直接将单组边界重复8次即可
- 拟合完成后,再将返回的一维
popt结果reshape为(8,3)的结构,方便后续拆分单峰、计算误差
修复后可运行代码
import numpy as np import scipy as scipy # 洛伦兹单峰模型 def lorentzian(x, amp, cen, wid): return amp*wid**2/((x-cen)**2+wid**2) # 多洛伦兹峰模型,直接接收展平的一维参数 def multi_lorentzian(x, *params): # 将一维参数重构为 (峰数, 3) 的结构,3对应amp/cen/wid params = np.array(params).reshape(-1, 3) total_curve = 0 for amp, cen, wid in params: total_curve += lorentzian(x, amp, cen, wid) return total_curve ############################################################################## # 生成测试数据 samples = 200 start = 2.75 stop = 3 x_array = np.linspace(start, stop, samples) # 频率,单位GHz amp_array = np.random.uniform(0.03, 0.1, 8) # 对比度范围3%~10% cen_array = [2.81, 2.829, 2.831, 2.848, 2.897, 2.914, 2.9165, 2.932] wid_array = [0.003]*8 y_array = 1 - multi_lorentzian(x_array, *np.array([amp_array, cen_array, wid_array]).T.flatten()) y_noise = y_array + np.random.normal(0, 1, samples)*1e-3 # 反转y轴,将谷值转为峰值方便拟合 y_noise_inv = -y_noise + 1 ############################################################################## # 生成拟合初始值 heights= np.random.uniform(0.03, 0.1, 8) widths = np.random.uniform(0.002, 0.004, 8) center_guess = cen_array + np.random.normal(0, 1, 8)*1e-3 p0_array = np.array([heights, center_guess, widths]).T.flatten() # 生成对应24个参数的边界:每个峰的amp∈[0,1], cen∈[2.75,3], wid∈[0,0.5] bounds_array = ( [0., 2.75, 0.]*8, [1., 3., 0.5]*8 ) popt_y, pcov_y = scipy.optimize.curve_fit( multi_lorentzian, x_array, y_noise_inv, p0=p0_array, bounds=bounds_array ) # 重构拟合结果为(8,3)结构 popt_y = popt_y.reshape(len(popt_y)//3, 3) single_peaks = [lorentzian(x_array, i, j, k) for i,j,k in popt_y] perr_y = np.sqrt(np.diag(pcov_y)) residual_y = y_noise_inv - multi_lorentzian(x_array, *popt_y.flatten()) ss_res = np.sum(residual_y**2) ss_tot = np.sum((y_noise_inv-np.mean(y_noise_inv))**2) r_squared = 1 - (ss_res / ss_tot)
注:如果初始值随机偏移过大可能出现收敛不佳的问题,可根据实际峰值检测结果调整初始猜测即可。
内容的提问来源于stack exchange,提问作者Bennissimo17
相关产品推荐
相关产品推荐

